undo/redo

This commit is contained in:
Michael Peters 2024-01-27 12:40:13 -08:00
parent 8e6abb95db
commit a258592054

View File

@ -1,5 +1,12 @@
import { useMouse, useWindowSize } from '@uidotdev/usehooks';
import { FC, MouseEvent, useCallback, useMemo, useState } from 'react';
import {
FC,
KeyboardEvent,
MouseEvent,
useCallback,
useMemo,
useState,
} from 'react';
import './grid.scss';
@ -51,7 +58,6 @@ function useMouseInRange(range: Range): Point {
const Grid: FC = () => {
const GAP = 20;
// const [offset, setOffset] = useState<Point>({ x: GAP / 2, y: GAP / 2 });
const [offset, setOffset] = useState<Point>({ x: 10, y: 10 });
const range = useRange(offset);
const mousePoint = useMouseInRange(range);
@ -71,8 +77,68 @@ const Grid: FC = () => {
}, [range, GAP]);
const [userLines, setUserLines] = useState<Range[]>([]);
const [userRedoLines, setUserRedoLines] = useState<Range[]>([]);
const [userStartPoint, setUserStartPoint] = useState<Point | null>(null);
// mouse clicks create lines
const onGridClick = useCallback(
(event: MouseEvent<SVGSVGElement>) => {
const point = snapToGrid(mousePoint, GAP);
setUserRedoLines([]);
if (lodash.isEqual(userStartPoint, point)) return;
if (lodash.isNull(userStartPoint)) {
setUserStartPoint(point);
return;
}
const line = pointsToRange(userStartPoint, point);
setUserLines([...userLines, line]);
if (event.shiftKey == false) {
setUserStartPoint(null);
} else {
setUserStartPoint(point);
}
},
[userStartPoint, setUserStartPoint, setUserLines, GAP, mousePoint]
);
const onGridRightClick = useCallback(
(event: MouseEvent<SVGSVGElement>) => {
event.preventDefault(); // disable default context menu
setUserStartPoint(null);
},
[setUserStartPoint]
);
// ctrl+z/ctrl+y for undo/redo
const onUndo = useCallback(() => {
const line = userLines.at(-1);
if (!line) return;
setUserLines(userLines.slice(0, -1));
setUserRedoLines([...userRedoLines, line]);
}, [userLines, setUserLines, userRedoLines, setUserRedoLines]);
const onRedo = useCallback(() => {
const line = userRedoLines.at(-1);
if (!line) return;
setUserRedoLines(userRedoLines.slice(0, -1));
setUserLines([...userLines, line]);
}, [userLines, setUserLines, userRedoLines, setUserRedoLines]);
const onGridKeyDown = useCallback(
(event: KeyboardEvent<SVGSVGElement>) => {
if (event.key == 'z' && event.ctrlKey) onUndo();
if (event.key == 'y' && event.ctrlKey) onRedo();
},
[onUndo, onRedo]
);
const gridLineElements = useMemo(() => {
return gridLines.map((line, idx) => (
<line
@ -130,28 +196,6 @@ const Grid: FC = () => {
));
}, [userLines]);
const onGridClick = useCallback(
(event: MouseEvent<SVGSVGElement>) => {
const point = snapToGrid(mousePoint, GAP);
if (lodash.isEqual(userStartPoint, point)) return;
if (lodash.isNull(userStartPoint)) {
setUserStartPoint(point);
return;
}
const line = pointsToRange(userStartPoint, point);
setUserLines([...userLines, line]);
if (event.shiftKey == false) {
setUserStartPoint(null);
} else {
setUserStartPoint(point);
}
},
[userStartPoint, setUserStartPoint, setUserLines, GAP, mousePoint]
);
return (
<svg
className="grid"
@ -159,7 +203,10 @@ const Grid: FC = () => {
viewBox={`${range.x0} ${range.y0} ${range.x1 - range.x0} ${
range.y1 - range.y0
}`}
onClick={onGridClick}
tabIndex={0} // to support onKeyDown event
onMouseDown={onGridClick}
onContextMenu={onGridRightClick}
onKeyDown={onGridKeyDown}
>
{gridLineElements}
{userLineElements}