From 2d00568ce9dfce7f570dd1d705bbf29c63efc774 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Sat, 27 Jan 2024 17:06:40 -0800 Subject: [PATCH] undo support for trash --- src/components/grid/grid.tsx | 87 ++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/src/components/grid/grid.tsx b/src/components/grid/grid.tsx index e0fd982..990d4bb 100644 --- a/src/components/grid/grid.tsx +++ b/src/components/grid/grid.tsx @@ -35,6 +35,24 @@ interface Line { stroke: string; } +interface AddLineAction { + line: Line; +} + +interface DeleteLinesAction { + lines: Line[]; +} + +type Action = AddLineAction | DeleteLinesAction; + +function isAddLineAction(action: Action): action is AddLineAction { + return action.hasOwnProperty('line'); +} + +function isDeleteLinesAction(action: Action): action is DeleteLinesAction { + return action.hasOwnProperty('lines'); +} + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection // https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function function lineSegmentIntersection(l0: Range, l1: Range): boolean { @@ -145,7 +163,10 @@ const Grid: FC = () => { }, [range, GAP]); const [userLines, setUserLines] = useState([]); - const [userRedoLines, setUserRedoLines] = useState([]); + + const [userActions, setUserActions] = useState([]); + const [userRedoActions, setUserRedoActions] = useState([]); + const [userStartPoint, setUserStartPoint] = useState(null); const userLineColor = useRecoilValue(lineColorState); const userDrawMode = useRecoilValue(drawModeState); @@ -161,8 +182,6 @@ const Grid: FC = () => { const point = snapToGrid(mousePoint, GAP); - setUserRedoLines([]); - if (lodash.isEqual(userStartPoint, point)) { setUserStartPoint(null); return; @@ -174,11 +193,13 @@ const Grid: FC = () => { const line = pointsToRange(userStartPoint, point); + setUserRedoActions([]); + if (userDrawMode == 'line') { - setUserLines([ - ...userLines, - { ...line, stroke: userLineColor }, - ]); + // draw a new line + const userLine = { ...line, stroke: userLineColor }; + setUserLines([...userLines, userLine]); + setUserActions([...userActions, { line: userLine }]); if (event.shiftKey == false) { setUserStartPoint(null); @@ -186,6 +207,7 @@ const Grid: FC = () => { setUserStartPoint(point); } } else if (userDrawMode == 'trash') { + // trash lines const userLinesGood: Line[] = []; const userLinesTrashed: Line[] = []; @@ -201,6 +223,7 @@ const Grid: FC = () => { // TODO: add undo support for userLinesTrashed setUserLines(userLinesGood); + setUserActions([...userActions, { lines: userLinesTrashed }]); setUserStartPoint(null); } else { throw Error('invalid user draw mode'); @@ -211,6 +234,8 @@ const Grid: FC = () => { userStartPoint, setUserStartPoint, setUserLines, + setUserActions, + setUserRedoActions, GAP, mousePoint, ] @@ -225,22 +250,46 @@ const Grid: FC = () => { // ctrl+z/ctrl+y for undo/redo const onUndo = useCallback(() => { - console.log('onUndo', userLines); - const line = userLines.at(-1); - if (!line) return; + console.log('onUndo', userActions); + const action = userActions.at(-1); + if (!action) return; - setUserLines(userLines.slice(0, -1)); - setUserRedoLines([...userRedoLines, line]); - }, [userLines, setUserLines, userRedoLines, setUserRedoLines]); + if (isAddLineAction(action)) { + setUserLines( + userLines.filter((userLine) => userLine != action.line) + ); + } else if (isDeleteLinesAction(action)) { + // NOTE: this does not preserve layering + setUserLines([...userLines, ...action.lines]); + } else { + console.error('invalid action'); + } + setUserActions(userActions.slice(0, -1)); + setUserRedoActions([...userRedoActions, action]); + }, [userLines, setUserLines, userActions, setUserActions]); const onRedo = useCallback(() => { - console.log('onRedo', userRedoLines); - const line = userRedoLines.at(-1); - if (!line) return; + console.log('onRedo', userRedoActions); + const action = userRedoActions.at(-1); + if (!action) return; - setUserRedoLines(userRedoLines.slice(0, -1)); - setUserLines([...userLines, line]); - }, [userLines, setUserLines, userRedoLines, setUserRedoLines]); + if (isAddLineAction(action)) { + setUserLines([...userLines, action.line]); + } else if (isDeleteLinesAction(action)) { + setUserLines( + userLines.filter( + (userLine) => + !action.lines.find( + (actionLine) => userLine === actionLine + ) + ) + ); + } else { + throw new Error('invalid action'); + } + setUserRedoActions(userRedoActions.slice(0, -1)); + setUserActions([...userActions, action]); + }, [userLines, setUserLines, userActions, setUserActions]); useKeyPress(onUndo, 'z', true); useKeyPress(onRedo, 'y', true);