feat: press enter to confirm selection

also clean up useEffect(s) and useRef(s)
This commit is contained in:
trafficlunar 2025-01-24 15:30:05 +00:00
parent f17b2b9f42
commit 4b59e9b4be
3 changed files with 97 additions and 85 deletions

View file

@ -11,7 +11,7 @@ import { ThemeContext } from "@/context/Theme";
import { ToolContext } from "@/context/Tool"; import { ToolContext } from "@/context/Tool";
import { useTextures } from "@/hooks/useTextures"; import { useTextures } from "@/hooks/useTextures";
import { isInSelection } from "@/utils/selection"; import { confirmSelection, isInSelection } from "@/utils/selection";
import Blocks from "./Blocks"; import Blocks from "./Blocks";
import Cursor from "./Cursor"; import Cursor from "./Cursor";
@ -51,7 +51,6 @@ function Canvas() {
const holdingAltRef = useRef(false); const holdingAltRef = useRef(false);
const holdingShiftRef = useRef(false); const holdingShiftRef = useRef(false);
const oldToolRef = useRef<Tool>(); const oldToolRef = useRef<Tool>();
const selectionCoordsRef = useRef<CoordinateArray>(selectionCoords);
const visibleArea = useMemo(() => { const visibleArea = useMemo(() => {
const blockSize = 16 * scale; const blockSize = 16 * scale;
@ -383,76 +382,84 @@ function Canvas() {
} }
}, [tool, holdingAltRef, scale, mouseCoords, blocks, setSelectionCoords, setSelectedBlock, zoom]); }, [tool, holdingAltRef, scale, mouseCoords, blocks, setSelectionCoords, setSelectedBlock, zoom]);
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = useCallback(
switch (e.key) { (e: KeyboardEvent) => {
case " ": // Space switch (e.key) {
setDragging(true); case "Escape":
oldToolRef.current = tool; setSelectionLayerBlocks([]);
setTool("hand"); break;
setCssCursor("grabbing"); case "Enter":
break; confirmSelection(blocks, selectionLayerBlocks, setBlocks, setSelectionLayerBlocks);
case "Shift": break;
holdingShiftRef.current = true; case " ": // Space
break; setDragging(true);
case "Alt": oldToolRef.current = tool;
holdingAltRef.current = true; setTool("hand");
if (tool === "zoom") setCssCursor("zoom-out"); setCssCursor("grabbing");
break; break;
case "Delete": { case "Shift":
setBlocks((prev) => prev.filter((b) => !selectionCoordsRef.current.some(([x2, y2]) => x2 === b.x && y2 === b.y))); holdingShiftRef.current = true;
break; break;
case "Alt":
holdingAltRef.current = true;
if (tool === "zoom") setCssCursor("zoom-out");
break;
case "Delete": {
setBlocks((prev) => prev.filter((b) => !selectionCoords.some(([x2, y2]) => x2 === b.x && y2 === b.y)));
break;
}
case "1":
setTool("hand");
break;
case "2":
setTool("move");
break;
case "3":
setTool("rectangle-select");
break;
case "4":
setTool("lasso");
break;
case "5":
setTool("magic-wand");
break;
case "6":
setTool("pencil");
break;
case "7":
setTool("eraser");
break;
case "8":
setTool("eyedropper");
break;
case "9":
setTool("zoom");
break;
} }
case "1": },
setTool("hand"); [tool, blocks, selectionCoords, selectionLayerBlocks, setBlocks, setCssCursor, setSelectionLayerBlocks, setTool]
break; );
case "2":
setTool("move");
break;
case "3":
setTool("rectangle-select");
break;
case "4":
setTool("lasso");
break;
case "5":
setTool("magic-wand");
break;
case "6":
setTool("pencil");
break;
case "7":
setTool("eraser");
break;
case "8":
setTool("eyedropper");
break;
case "9":
setTool("zoom");
break;
}
};
const onKeyUp = (e: KeyboardEvent) => { const onKeyUp = useCallback(
switch (e.key) { (e: KeyboardEvent) => {
case " ": // Space switch (e.key) {
if (!oldToolRef.current) return; case " ": // Space
setDragging(false); if (!oldToolRef.current) return;
setCssCursor("grab"); setDragging(false);
setTool(oldToolRef.current); setCssCursor("grab");
break; setTool(oldToolRef.current);
case "Shift": break;
holdingShiftRef.current = false; case "Shift":
break; holdingShiftRef.current = false;
case "Alt": break;
holdingAltRef.current = false; case "Alt":
setCssCursor("zoom-in"); holdingAltRef.current = false;
break; setCssCursor("zoom-in");
} break;
}; }
},
useEffect(() => { [setCssCursor, setTool]
selectionCoordsRef.current = selectionCoords; );
}, [selectionCoords]);
useEffect(() => { useEffect(() => {
const container = stageContainerRef.current; const container = stageContainerRef.current;
@ -470,8 +477,7 @@ function Canvas() {
resizeCanvas(); resizeCanvas();
return () => resizeObserver.disconnect(); return () => resizeObserver.disconnect();
// eslint-disable-next-line react-hooks/exhaustive-deps }, [stageContainerRef, setStageSize]);
}, [stageContainerRef]);
useEffect(() => { useEffect(() => {
window.addEventListener("keydown", onKeyDown); window.addEventListener("keydown", onKeyDown);
@ -484,8 +490,7 @@ function Canvas() {
window.removeEventListener("keydown", onKeyDown); window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("keyup", onKeyUp); window.removeEventListener("keyup", onKeyUp);
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps }, [onKeyDown, onKeyUp]);
}, []);
return ( return (
<div ref={stageContainerRef} style={{ cursor: cssCursor }} className="relative w-full h-full bg-zinc-200 dark:bg-black"> <div ref={stageContainerRef} style={{ cursor: cssCursor }} className="relative w-full h-full bg-zinc-200 dark:bg-black">

View file

@ -4,6 +4,8 @@ import { CheckIcon, XIcon } from "lucide-react";
import { CanvasContext } from "@/context/Canvas"; import { CanvasContext } from "@/context/Canvas";
import { SelectionContext } from "@/context/Selection"; import { SelectionContext } from "@/context/Selection";
import { confirmSelection } from "@/utils/selection";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
function SelectionBar() { function SelectionBar() {
@ -12,14 +14,6 @@ function SelectionBar() {
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
const confirmSelection = () => {
const combinedBlocks = [...blocks, ...layerBlocks];
const uniqueBlocks = Array.from(new Map(combinedBlocks.map((block) => [`${block.x},${block.y}`, block])).values());
setBlocks(uniqueBlocks);
setLayerBlocks([]);
};
useEffect(() => { useEffect(() => {
setIsVisible(layerBlocks.length !== 0); setIsVisible(layerBlocks.length !== 0);
}, [layerBlocks]); }, [layerBlocks]);
@ -35,7 +29,7 @@ function SelectionBar() {
<XIcon /> <XIcon />
</Button> </Button>
<span className="mx-2 text-[0.85rem]">Confirm selection?</span> <span className="mx-2 text-[0.85rem]">Confirm selection?</span>
<Button variant="ghost" className="w-8 h-8" onClick={confirmSelection}> <Button variant="ghost" className="w-8 h-8" onClick={() => confirmSelection(blocks, layerBlocks, setBlocks, setLayerBlocks)}>
<CheckIcon /> <CheckIcon />
</Button> </Button>
</div> </div>

View file

@ -1,7 +1,20 @@
// Check if a block is within the selection // Check if a block is within the selection
export const isInSelection = (selection: CoordinateArray, x: number, y: number): boolean => { export function isInSelection(selection: CoordinateArray, x: number, y: number): boolean {
if (selection.length !== 0) { if (selection.length !== 0) {
return selection.some(([x2, y2]) => x2 === x && y2 === y); return selection.some(([x2, y2]) => x2 === x && y2 === y);
} }
return true; return true;
}; }
export function confirmSelection(
blocks: Block[],
layerBlocks: Block[],
setBlocks: React.Dispatch<React.SetStateAction<Block[]>>,
setLayerBlocks: React.Dispatch<React.SetStateAction<Block[]>>
) {
const combinedBlocks = [...blocks, ...layerBlocks];
const uniqueBlocks = Array.from(new Map(combinedBlocks.map((block) => [`${block.x},${block.y}`, block])).values());
setBlocks(uniqueBlocks);
setLayerBlocks([]);
}