undo support for trash

This commit is contained in:
Michael Peters 2024-01-27 17:06:40 -08:00
parent 0b4a442f5e
commit 2d00568ce9

View File

@ -35,6 +35,24 @@ interface Line {
stroke: string; 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://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
// https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function // https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function
function lineSegmentIntersection(l0: Range, l1: Range): boolean { function lineSegmentIntersection(l0: Range, l1: Range): boolean {
@ -145,7 +163,10 @@ const Grid: FC = () => {
}, [range, GAP]); }, [range, GAP]);
const [userLines, setUserLines] = useState<Line[]>([]); const [userLines, setUserLines] = useState<Line[]>([]);
const [userRedoLines, setUserRedoLines] = useState<Line[]>([]);
const [userActions, setUserActions] = useState<Action[]>([]);
const [userRedoActions, setUserRedoActions] = useState<Action[]>([]);
const [userStartPoint, setUserStartPoint] = useState<Point | null>(null); const [userStartPoint, setUserStartPoint] = useState<Point | null>(null);
const userLineColor = useRecoilValue(lineColorState); const userLineColor = useRecoilValue(lineColorState);
const userDrawMode = useRecoilValue(drawModeState); const userDrawMode = useRecoilValue(drawModeState);
@ -161,8 +182,6 @@ const Grid: FC = () => {
const point = snapToGrid(mousePoint, GAP); const point = snapToGrid(mousePoint, GAP);
setUserRedoLines([]);
if (lodash.isEqual(userStartPoint, point)) { if (lodash.isEqual(userStartPoint, point)) {
setUserStartPoint(null); setUserStartPoint(null);
return; return;
@ -174,11 +193,13 @@ const Grid: FC = () => {
const line = pointsToRange(userStartPoint, point); const line = pointsToRange(userStartPoint, point);
setUserRedoActions([]);
if (userDrawMode == 'line') { if (userDrawMode == 'line') {
setUserLines([ // draw a new line
...userLines, const userLine = { ...line, stroke: userLineColor };
{ ...line, stroke: userLineColor }, setUserLines([...userLines, userLine]);
]); setUserActions([...userActions, { line: userLine }]);
if (event.shiftKey == false) { if (event.shiftKey == false) {
setUserStartPoint(null); setUserStartPoint(null);
@ -186,6 +207,7 @@ const Grid: FC = () => {
setUserStartPoint(point); setUserStartPoint(point);
} }
} else if (userDrawMode == 'trash') { } else if (userDrawMode == 'trash') {
// trash lines
const userLinesGood: Line[] = []; const userLinesGood: Line[] = [];
const userLinesTrashed: Line[] = []; const userLinesTrashed: Line[] = [];
@ -201,6 +223,7 @@ const Grid: FC = () => {
// TODO: add undo support for userLinesTrashed // TODO: add undo support for userLinesTrashed
setUserLines(userLinesGood); setUserLines(userLinesGood);
setUserActions([...userActions, { lines: userLinesTrashed }]);
setUserStartPoint(null); setUserStartPoint(null);
} else { } else {
throw Error('invalid user draw mode'); throw Error('invalid user draw mode');
@ -211,6 +234,8 @@ const Grid: FC = () => {
userStartPoint, userStartPoint,
setUserStartPoint, setUserStartPoint,
setUserLines, setUserLines,
setUserActions,
setUserRedoActions,
GAP, GAP,
mousePoint, mousePoint,
] ]
@ -225,22 +250,46 @@ const Grid: FC = () => {
// ctrl+z/ctrl+y for undo/redo // ctrl+z/ctrl+y for undo/redo
const onUndo = useCallback(() => { const onUndo = useCallback(() => {
console.log('onUndo', userLines); console.log('onUndo', userActions);
const line = userLines.at(-1); const action = userActions.at(-1);
if (!line) return; if (!action) return;
setUserLines(userLines.slice(0, -1)); if (isAddLineAction(action)) {
setUserRedoLines([...userRedoLines, line]); setUserLines(
}, [userLines, setUserLines, userRedoLines, setUserRedoLines]); 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(() => { const onRedo = useCallback(() => {
console.log('onRedo', userRedoLines); console.log('onRedo', userRedoActions);
const line = userRedoLines.at(-1); const action = userRedoActions.at(-1);
if (!line) return; if (!action) return;
setUserRedoLines(userRedoLines.slice(0, -1)); if (isAddLineAction(action)) {
setUserLines([...userLines, line]); setUserLines([...userLines, action.line]);
}, [userLines, setUserLines, userRedoLines, setUserRedoLines]); } 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(onUndo, 'z', true);
useKeyPress(onRedo, 'y', true); useKeyPress(onRedo, 'y', true);