feat: press enter to confirm selection
also clean up useEffect(s) and useRef(s)
This commit is contained in:
parent
f17b2b9f42
commit
4b59e9b4be
3 changed files with 97 additions and 85 deletions
|
|
@ -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,8 +382,15 @@ 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(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
|
case "Escape":
|
||||||
|
setSelectionLayerBlocks([]);
|
||||||
|
break;
|
||||||
|
case "Enter":
|
||||||
|
confirmSelection(blocks, selectionLayerBlocks, setBlocks, setSelectionLayerBlocks);
|
||||||
|
break;
|
||||||
case " ": // Space
|
case " ": // Space
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
oldToolRef.current = tool;
|
oldToolRef.current = tool;
|
||||||
|
|
@ -399,7 +405,7 @@ function Canvas() {
|
||||||
if (tool === "zoom") setCssCursor("zoom-out");
|
if (tool === "zoom") setCssCursor("zoom-out");
|
||||||
break;
|
break;
|
||||||
case "Delete": {
|
case "Delete": {
|
||||||
setBlocks((prev) => prev.filter((b) => !selectionCoordsRef.current.some(([x2, y2]) => x2 === b.x && y2 === b.y)));
|
setBlocks((prev) => prev.filter((b) => !selectionCoords.some(([x2, y2]) => x2 === b.x && y2 === b.y)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "1":
|
case "1":
|
||||||
|
|
@ -430,9 +436,12 @@ function Canvas() {
|
||||||
setTool("zoom");
|
setTool("zoom");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[tool, blocks, selectionCoords, selectionLayerBlocks, setBlocks, setCssCursor, setSelectionLayerBlocks, setTool]
|
||||||
|
);
|
||||||
|
|
||||||
const onKeyUp = (e: KeyboardEvent) => {
|
const onKeyUp = useCallback(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case " ": // Space
|
case " ": // Space
|
||||||
if (!oldToolRef.current) return;
|
if (!oldToolRef.current) return;
|
||||||
|
|
@ -448,11 +457,9 @@ function Canvas() {
|
||||||
setCssCursor("zoom-in");
|
setCssCursor("zoom-in");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[setCssCursor, setTool]
|
||||||
useEffect(() => {
|
);
|
||||||
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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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([]);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue