diff --git a/components/editor/index.tsx b/components/editor/index.tsx index e7f3824e3..7c967aba3 100644 --- a/components/editor/index.tsx +++ b/components/editor/index.tsx @@ -207,7 +207,7 @@ export default function Editor({ isDirty, level, setIsDirty, setLevel }: EditorP } } - function onClick(index: number, rightClick: boolean) { + function onClick(index: number, rightClick: boolean, isDragging?: boolean) { setIsDirty(true); setLevel(prevLevel => { if (!prevLevel) { diff --git a/components/level/basicLayout.tsx b/components/level/basicLayout.tsx index 2410f7ebd..d159e2f15 100644 --- a/components/level/basicLayout.tsx +++ b/components/level/basicLayout.tsx @@ -12,7 +12,7 @@ interface BasicLayoutProps { hideText?: boolean; id: string; level: Level; - onClick?: (index: number, rightClick: boolean) => void; + onClick?: (index: number, rightClick: boolean, isDragging?: boolean) => void; } export default function BasicLayout({ cellClassName, cellStyle, controls, hideText, id, level, onClick }: BasicLayoutProps) { @@ -28,7 +28,7 @@ export default function BasicLayout({ cellClassName, cellStyle, controls, hideTe hideText={hideText} id={id} leastMoves={level.leastMoves} - onCellClick={(x, y, rightClick) => onClick ? onClick(y * (level.width + 1) + x, rightClick) : undefined} + onCellClick={(x, y, rightClick, isDragging) => onClick ? onClick(y * (level.width + 1) + x, rightClick, isDragging) : undefined} /> {!controls ? null : } diff --git a/components/level/grid.tsx b/components/level/grid.tsx index 37c083e11..760e78c02 100644 --- a/components/level/grid.tsx +++ b/components/level/grid.tsx @@ -7,7 +7,7 @@ import { teko } from '@root/helpers/getFont'; import Position from '@root/models/position'; import classNames from 'classnames'; import { useTheme } from 'next-themes'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import Theme from '../../constants/theme'; import Tile from './tile/tile'; @@ -20,7 +20,7 @@ interface GridProps { hideText?: boolean; id: string; leastMoves: number; - onCellClick?: (x: number, y: number, rightClick: boolean) => void; + onCellClick?: (x: number, y: number, rightClick: boolean, isDragging?: boolean) => void; optimizeDom?: boolean; themeOverride?: Theme; } @@ -88,7 +88,7 @@ export default function Grid({ cellClassName, cellStyle, disableAnimation, gameO className={cellClassName ? cellClassName(x, y) : undefined} disableAnimation={disableAnimation} game={game} - handleClick={onCellClick ? (rightClick: boolean) => onCellClick(x, y, rightClick) : undefined} + handleClick={onCellClick ? (rightClick: boolean) => onCellClick(x, y, rightClick, isDragging) : undefined} key={`tile-${y}-${x}`} pos={new Position(x, y)} style={cellStyle ? cellStyle(x, y) : undefined} @@ -108,7 +108,7 @@ export default function Grid({ cellClassName, cellStyle, disableAnimation, gameO className={cellClassName ? cellClassName(x, y) : undefined} disableAnimation={disableAnimation} game={game} - handleClick={onCellClick ? (rightClick: boolean) => onCellClick(x, y, rightClick) : undefined} + handleClick={onCellClick ? (rightClick: boolean) => onCellClick(x, y, rightClick, isDragging) : undefined} key={`block-${tileState.block.id}`} onTopOf={tileAtPosition.tileType} pos={new Position(x, y)} @@ -125,7 +125,7 @@ export default function Grid({ cellClassName, cellStyle, disableAnimation, gameO className={cellClassName ? cellClassName(x, y) : undefined} disableAnimation={disableAnimation} game={game} - handleClick={onCellClick ? (rightClick: boolean) => onCellClick(x, y, rightClick) : undefined} + handleClick={onCellClick ? (rightClick: boolean, isDragging?: boolean) => onCellClick(x, y, rightClick, isDragging) : undefined} inHole={true} key={`block-${tileState.blockInHole.id}`} pos={new Position(x, y)} @@ -138,6 +138,8 @@ export default function Grid({ cellClassName, cellStyle, disableAnimation, gameO } } + const [isDragging, setIsDragging] = useState(false); + const getBackground = useCallback(() => { if (!optimizeDom) { return null; @@ -147,7 +149,7 @@ export default function Grid({ cellClassName, cellStyle, disableAnimation, gameO const x = Math.floor(offsetX / tileSize); const y = Math.floor(offsetY / tileSize); - onCellClick && onCellClick(x, y, rightClick); + onCellClick && onCellClick(x, y, rightClick, isDragging); }; const onBgClick = (e: React.MouseEvent) => { @@ -188,7 +190,7 @@ export default function Grid({ cellClassName, cellStyle, disableAnimation, gameO }} /> ); - }, [borderWidth, height, onCellClick, optimizeDom, tileSize, width]); + }, [borderWidth, height, isDragging, onCellClick, optimizeDom, tileSize, width]); // Prevent hydration mismatch by not rendering theme class until mounted const [mounted, setMounted] = useState(false); @@ -197,12 +199,31 @@ export default function Grid({ cellClassName, cellStyle, disableAnimation, gameO setMounted(true); }, []); + const lastTileDragged = useRef(undefined); + const onMouseMove = (e: React.MouseEvent | React.TouchEvent) => { + if (isDragging) { + const rect = e.currentTarget.getBoundingClientRect(); + const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX; + const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY; + const tileX = Math.floor((clientX - rect.left) / tileSize); + const tileY = Math.floor((clientY - rect.top) / tileSize); + + if (tileX === lastTileDragged.current?.x && tileY === lastTileDragged.current?.y) { + return; + } + + lastTileDragged.current = new Position(tileX, tileY); + onCellClick && onCellClick(tileX, tileY, false, isDragging); + } + }; + return ( -
+
{tileSize !== 0 && setIsDragging(true)} + onMouseUp={() => setIsDragging(false)} + onTouchStart={() => setIsDragging(true)} + onTouchEnd={() => setIsDragging(false)} + onTouchMove={onMouseMove} + onMouseMove={(e) => { + onMouseMove(e); + }} > {getBackground()} {tiles}