From b432b9c8f55ef3be4a7aad529e1b43a5252cfd25 Mon Sep 17 00:00:00 2001 From: heruiwoniou Date: Mon, 9 Sep 2024 12:52:11 +0000 Subject: [PATCH 1/9] fix:improve history behavior Close #170 --- .../src/ReactSketchCanvas/index.tsx | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index 6095e34..5193f25 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -50,8 +50,8 @@ export const ReactSketchCanvas = React.forwardRef< const svgCanvas = React.createRef(); const [drawMode, setDrawMode] = React.useState(true); const [isDrawing, setIsDrawing] = React.useState(false); - const [resetStack, setResetStack] = React.useState([]); - const [undoStack, setUndoStack] = React.useState([]); + const [history, setHistory] = React.useState([]); + const [historyPos, setHistoryPos] = React.useState(-1); const [currentPaths, setCurrentPaths] = React.useState([]); const liftStrokeUp = React.useCallback((): void => { @@ -80,27 +80,21 @@ export const ReactSketchCanvas = React.forwardRef< setDrawMode(!erase); }, clearCanvas: (): void => { - setResetStack([...currentPaths]); setCurrentPaths([]); + setHistory(his => [...his.slice(0, historyPos + 1), []]); + setHistoryPos(pos => pos + 1); }, undo: (): void => { - // If there was a last reset then - if (resetStack.length !== 0) { - setCurrentPaths([...resetStack]); - setResetStack([]); - - return; + if (historyPos > 0) { + setCurrentPaths(history[historyPos - 1]); + setHistoryPos(pos => pos - 1); } - - setUndoStack((paths) => [...paths, ...currentPaths.slice(-1)]); - setCurrentPaths((paths) => paths.slice(0, -1)); }, redo: (): void => { - // Nothing to Redo - if (undoStack.length === 0) return; - - setCurrentPaths((paths) => [...paths, ...undoStack.slice(-1)]); - setUndoStack((paths) => paths.slice(0, -1)); + if (historyPos < history.length - 1) { + setCurrentPaths(history[historyPos + 1]); + setHistoryPos(pos => pos + 1); + } }, exportImage: ( imageType: ExportImageType, @@ -140,6 +134,8 @@ export const ReactSketchCanvas = React.forwardRef< }), loadPaths: (paths: CanvasPath[]): void => { setCurrentPaths((path) => [...path, ...paths]); + setHistory(his => [...his.slice(0, historyPos + 1), [...currentPaths.slice(0, -1), ...paths]]); + setHistoryPos(pos => pos + 1); }, getSketchingTime: (): Promise => new Promise((resolve, reject) => { @@ -164,15 +160,14 @@ export const ReactSketchCanvas = React.forwardRef< } }), resetCanvas: (): void => { - setResetStack([]); - setUndoStack([]); + setHistory([]); + setHistoryPos(-1); setCurrentPaths([]); }, - })); + }), [currentPaths, history, historyPos, svgCanvas, withTimestamp]); const handlePointerDown = (point: Point, isEraser = false): void => { setIsDrawing(true); - setUndoStack([]); const isDraw = !isEraser && drawMode; @@ -210,14 +205,16 @@ export const ReactSketchCanvas = React.forwardRef< return; } + const currentStroke = currentPaths.slice(-1)?.[0] ?? null; + + setHistory(his => [...his.slice(0, historyPos + 1), [...currentPaths.slice(0, -1), currentStroke]]); + setHistoryPos(pos => pos + 1); setIsDrawing(false); if (!withTimestamp) { return; } - const currentStroke = currentPaths.slice(-1)?.[0] ?? null; - if (currentStroke === null) { return; } From f22a27d09dda480bf27ffc13e1868420666c92c8 Mon Sep 17 00:00:00 2001 From: heruiwoniou Date: Tue, 17 Sep 2024 13:56:32 +0000 Subject: [PATCH 2/9] fix:fixed cannot undo to empty state bug --- packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index 5193f25..bdb65fc 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -50,8 +50,8 @@ export const ReactSketchCanvas = React.forwardRef< const svgCanvas = React.createRef(); const [drawMode, setDrawMode] = React.useState(true); const [isDrawing, setIsDrawing] = React.useState(false); - const [history, setHistory] = React.useState([]); - const [historyPos, setHistoryPos] = React.useState(-1); + const [history, setHistory] = React.useState([[]]); + const [historyPos, setHistoryPos] = React.useState(0); const [currentPaths, setCurrentPaths] = React.useState([]); const liftStrokeUp = React.useCallback((): void => { From 69e0b1ba98a301a15519c816656047a5f6d0a261 Mon Sep 17 00:00:00 2001 From: heruiwoniou Date: Tue, 17 Sep 2024 13:59:09 +0000 Subject: [PATCH 3/9] fix:improve loadPath function When using loadPath, set to the initial value of history --- packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index bdb65fc..1f31b4a 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -134,8 +134,7 @@ export const ReactSketchCanvas = React.forwardRef< }), loadPaths: (paths: CanvasPath[]): void => { setCurrentPaths((path) => [...path, ...paths]); - setHistory(his => [...his.slice(0, historyPos + 1), [...currentPaths.slice(0, -1), ...paths]]); - setHistoryPos(pos => pos + 1); + setHistory([[...currentPaths.slice(0, -1), ...paths]]); }, getSketchingTime: (): Promise => new Promise((resolve, reject) => { From 1d6f383d85d54c44313b7366119d9e5fa2694657 Mon Sep 17 00:00:00 2001 From: Eugene Fairley Date: Sun, 6 Apr 2025 12:59:16 -0700 Subject: [PATCH 4/9] fix timing issue with setting history --- .../src/ReactSketchCanvas/index.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index 1f31b4a..ca36820 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import {useCallback} from "react"; import { Canvas } from "../Canvas"; import { CanvasPath, @@ -52,8 +53,16 @@ export const ReactSketchCanvas = React.forwardRef< const [isDrawing, setIsDrawing] = React.useState(false); const [history, setHistory] = React.useState([[]]); const [historyPos, setHistoryPos] = React.useState(0); + const [historySynced, setHistorySynced] = React.useState(false); const [currentPaths, setCurrentPaths] = React.useState([]); + const addLastStroke = useCallback(():void => { + if (!historySynced) { + setHistory(his => [...his.slice(0, historyPos), [...currentPaths]]); + setHistorySynced(true); + } + }, [currentPaths, historyPos, historySynced]); + const liftStrokeUp = React.useCallback((): void => { const lastStroke = currentPaths.slice(-1)?.[0] ?? null; @@ -80,18 +89,21 @@ export const ReactSketchCanvas = React.forwardRef< setDrawMode(!erase); }, clearCanvas: (): void => { + addLastStroke(); setCurrentPaths([]); setHistory(his => [...his.slice(0, historyPos + 1), []]); setHistoryPos(pos => pos + 1); }, undo: (): void => { if (historyPos > 0) { + addLastStroke(); setCurrentPaths(history[historyPos - 1]); setHistoryPos(pos => pos - 1); } }, redo: (): void => { if (historyPos < history.length - 1) { + addLastStroke(); setCurrentPaths(history[historyPos + 1]); setHistoryPos(pos => pos + 1); } @@ -163,7 +175,7 @@ export const ReactSketchCanvas = React.forwardRef< setHistoryPos(-1); setCurrentPaths([]); }, - }), [currentPaths, history, historyPos, svgCanvas, withTimestamp]); + }), [currentPaths, history, historyPos, svgCanvas, withTimestamp, addLastStroke]); const handlePointerDown = (point: Point, isEraser = false): void => { setIsDrawing(true); @@ -184,7 +196,9 @@ export const ReactSketchCanvas = React.forwardRef< endTimestamp: 0, }; } - + setHistoryPos(pos => pos + 1); + addLastStroke(); + setHistorySynced(false); setCurrentPaths((paths) => [...paths, stroke]); }; @@ -206,8 +220,6 @@ export const ReactSketchCanvas = React.forwardRef< const currentStroke = currentPaths.slice(-1)?.[0] ?? null; - setHistory(his => [...his.slice(0, historyPos + 1), [...currentPaths.slice(0, -1), currentStroke]]); - setHistoryPos(pos => pos + 1); setIsDrawing(false); if (!withTimestamp) { From 03637e447032a1080f8082734f8919779a86efc9 Mon Sep 17 00:00:00 2001 From: Eugene Fairley Date: Sun, 6 Apr 2025 15:46:47 -0700 Subject: [PATCH 5/9] add test for undo after clear --- packages/tests/src/actions/undoRedo.spec.tsx | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/packages/tests/src/actions/undoRedo.spec.tsx b/packages/tests/src/actions/undoRedo.spec.tsx index 5f73ede..e9ce989 100644 --- a/packages/tests/src/actions/undoRedo.spec.tsx +++ b/packages/tests/src/actions/undoRedo.spec.tsx @@ -250,6 +250,60 @@ test("should still keep the stack on clearCanvas", async ({ mount }) => { ).toHaveCount(1); }); +test("should undo a stroke after clear canvas", async ({ mount }) => { + const component = await mount( + , + ); + + const canvas = component.locator(`#${canvasId}`); + const undoButton = component.locator(`#${undoButtonId}`); + const clearCanvasButton = component.locator(`#${clearCanvasButtonId}`); + + await drawLine(canvas, { + length: 50, + originX: 0, + originY: 10, + }); + + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(1); + + // Clear 1 stroke + await clearCanvasButton.click(); + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(0); + + await drawLine(canvas, { + length: 50, + originX: 10, + originY: 10, + }); + await drawLine(canvas, { + length: 50, + originX: 20, + originY: 10, + }); + await drawLine(canvas, { + length: 50, + originX: 30, + originY: 10, + }); + + // Undo 1 of 3 new strokes => 2 left + await undoButton.click(); + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(2); +}); + test("should clear the stack on resetCanvas", async ({ mount }) => { const component = await mount( Date: Sun, 20 Apr 2025 12:58:27 -0700 Subject: [PATCH 6/9] Fix undo behavior with loadPaths and add tests --- .../src/ReactSketchCanvas/index.tsx | 4 +- packages/tests/src/actions/undoRedo.spec.tsx | 91 +++++++++++++++++++ .../tests/src/stories/WithUndoRedoButtons.tsx | 12 +++ 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index ca36820..1ed60ab 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -145,8 +145,10 @@ export const ReactSketchCanvas = React.forwardRef< } }), loadPaths: (paths: CanvasPath[]): void => { + addLastStroke(); setCurrentPaths((path) => [...path, ...paths]); - setHistory([[...currentPaths.slice(0, -1), ...paths]]); + setHistoryPos(pos => pos + 1); + setHistory(his => [...his.slice(0, historyPos+1), [...currentPaths, ...paths]]); }, getSketchingTime: (): Promise => new Promise((resolve, reject) => { diff --git a/packages/tests/src/actions/undoRedo.spec.tsx b/packages/tests/src/actions/undoRedo.spec.tsx index e9ce989..3e514b6 100644 --- a/packages/tests/src/actions/undoRedo.spec.tsx +++ b/packages/tests/src/actions/undoRedo.spec.tsx @@ -2,6 +2,7 @@ import { expect, test } from "@playwright/experimental-ct-react"; import { drawEraserLine, drawLine, getCanvasIds } from "../commands"; import { WithUndoRedoButtons } from "../stories/WithUndoRedoButtons"; +import penStrokes from "../fixtures/penStroke.json"; test.use({ viewport: { width: 500, height: 500 } }); @@ -10,6 +11,7 @@ const undoButtonId = "undo-button"; const redoButtonId = "redo-button"; const clearCanvasButtonId = "clear-canvas-button"; const resetCanvasButtonId = "reset-canvas-button"; +const loadPathsButtonId = "load-paths-button"; const { firstStrokeGroupId, eraserStrokeGroupId } = getCanvasIds(canvasId); @@ -22,6 +24,7 @@ test.describe("undo", () => { redoButtonId={redoButtonId} clearCanvasButtonId={clearCanvasButtonId} resetCanvasButtonId={resetCanvasButtonId} + paths={penStrokes} />, ); @@ -53,6 +56,7 @@ test.describe("undo", () => { redoButtonId={redoButtonId} clearCanvasButtonId={clearCanvasButtonId} resetCanvasButtonId={resetCanvasButtonId} + paths={penStrokes} />, ); @@ -100,6 +104,7 @@ test.describe("redo", () => { redoButtonId={redoButtonId} clearCanvasButtonId={clearCanvasButtonId} resetCanvasButtonId={resetCanvasButtonId} + paths={penStrokes} />, ); @@ -137,6 +142,7 @@ test.describe("redo", () => { redoButtonId={redoButtonId} clearCanvasButtonId={clearCanvasButtonId} resetCanvasButtonId={resetCanvasButtonId} + paths={penStrokes} />, ); @@ -193,6 +199,7 @@ test("should still keep the stack on clearCanvas", async ({ mount }) => { redoButtonId={redoButtonId} clearCanvasButtonId={clearCanvasButtonId} resetCanvasButtonId={resetCanvasButtonId} + paths={penStrokes} />, ); @@ -258,6 +265,7 @@ test("should undo a stroke after clear canvas", async ({ mount }) => { redoButtonId={redoButtonId} clearCanvasButtonId={clearCanvasButtonId} resetCanvasButtonId={resetCanvasButtonId} + paths={penStrokes} />, ); @@ -304,6 +312,88 @@ test("should undo a stroke after clear canvas", async ({ mount }) => { ).toHaveCount(2); }); +test("should undo loaded paths", async ({ mount }) => { + const component = await mount( + , + ); + + const canvas = component.locator(`#${canvasId}`); + const undoButton = component.locator(`#${undoButtonId}`); + const loadPathsButton = component.locator(`#${loadPathsButtonId}`); + + await drawLine(canvas, { + length: 50, + originX: 0, + originY: 10, + }); + + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(1); + + await loadPathsButton.click(); + + // Load 1 stroke + 1 existing = 2 + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(2); + + // Undo load action should reset to 1 original stroke + await undoButton.click(); + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(1); +}); + +test("should undo draw after load", async ({ mount }) => { + const component = await mount( + , + ); + + const canvas = component.locator(`#${canvasId}`); + const undoButton = component.locator(`#${undoButtonId}`); + const loadPathsButton = component.locator(`#${loadPathsButtonId}`); + await loadPathsButton.click(); + + // Load 1 stroke + 1 existing = 2 + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(1); + + await drawLine(canvas, { + length: 50, + originX: 0, + originY: 10, + }); + + // Load 1 stroke + 1 new stroke = 2 + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(2); + + // Undo => 2 total - 1 last stroke = 1 + await undoButton.click(); + await expect( + component.locator(firstStrokeGroupId).locator("path"), + ).toHaveCount(1); +}); + test("should clear the stack on resetCanvas", async ({ mount }) => { const component = await mount( { redoButtonId={redoButtonId} clearCanvasButtonId={clearCanvasButtonId} resetCanvasButtonId={resetCanvasButtonId} + paths={penStrokes} />, ); diff --git a/packages/tests/src/stories/WithUndoRedoButtons.tsx b/packages/tests/src/stories/WithUndoRedoButtons.tsx index 4507cc9..a3f83b8 100644 --- a/packages/tests/src/stories/WithUndoRedoButtons.tsx +++ b/packages/tests/src/stories/WithUndoRedoButtons.tsx @@ -3,6 +3,7 @@ import { ReactSketchCanvas, ReactSketchCanvasProps, ReactSketchCanvasRef, + CanvasPath } from "react-sketch-canvas"; interface WithUndoRedoButtonsProps extends ReactSketchCanvasProps { @@ -10,6 +11,8 @@ interface WithUndoRedoButtonsProps extends ReactSketchCanvasProps { redoButtonId?: string; clearCanvasButtonId?: string; resetCanvasButtonId?: string; + loadPathsButtonId?: string; + paths: CanvasPath[]; } export function WithUndoRedoButtons({ @@ -17,6 +20,8 @@ export function WithUndoRedoButtons({ redoButtonId = "redo-button", clearCanvasButtonId = "clear-canvas-button", resetCanvasButtonId = "reset-canvas-button", + loadPathsButtonId = "load-paths-button", + paths, ...canvasProps }: WithUndoRedoButtonsProps) { const canvasRef = useRef(null); @@ -37,6 +42,10 @@ export function WithUndoRedoButtons({ canvasRef.current?.resetCanvas(); }; + const handleLoadPathsClick = () => { + canvasRef.current?.loadPaths(paths); + }; + return (
{/* eslint-disable-next-line react/jsx-props-no-spreading */} @@ -61,6 +70,9 @@ export function WithUndoRedoButtons({ > Reset Canvas +
); } From efc3b2cda6faaaa6371dfc35ba5887f2639bfdd6 Mon Sep 17 00:00:00 2001 From: Eugene Fairley Date: Sun, 20 Apr 2025 13:34:09 -0700 Subject: [PATCH 7/9] Fix reset canvas history logic --- packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index 1ed60ab..31c94e7 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -174,7 +174,7 @@ export const ReactSketchCanvas = React.forwardRef< }), resetCanvas: (): void => { setHistory([]); - setHistoryPos(-1); + setHistoryPos(0); setCurrentPaths([]); }, }), [currentPaths, history, historyPos, svgCanvas, withTimestamp, addLastStroke]); From 24515b8e08a1579ae44c2adc1f3291b31dc6c829 Mon Sep 17 00:00:00 2001 From: Eugene Fairley Date: Sun, 11 May 2025 14:24:23 -0700 Subject: [PATCH 8/9] Use event queue for undo/redo/etc. --- .../src/ReactSketchCanvas/index.tsx | 83 ++++++++++++++----- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index 31c94e7..94d3b51 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -10,6 +10,11 @@ import { import { CanvasRef } from "../Canvas/types"; import { ReactSketchCanvasProps, ReactSketchCanvasRef } from "./types"; +type Operation = { + type: 'undo' | 'redo' | 'clear' | 'loadPaths'; + payload?: CanvasPath[]; +}; + /** * ReactSketchCanvas is a wrapper around Canvas component to provide a controlled way to manage the canvas paths. * It provides a set of methods to manage the canvas paths, undo, redo, clear and reset the canvas. @@ -55,6 +60,8 @@ export const ReactSketchCanvas = React.forwardRef< const [historyPos, setHistoryPos] = React.useState(0); const [historySynced, setHistorySynced] = React.useState(false); const [currentPaths, setCurrentPaths] = React.useState([]); + const [operationQueue, setOperationQueue] = React.useState([]); + const [isProcessingQueue, setIsProcessingQueue] = React.useState(false); const addLastStroke = useCallback(():void => { if (!historySynced) { @@ -84,29 +91,69 @@ export const ReactSketchCanvas = React.forwardRef< // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentPaths]); + const processQueue = React.useCallback(async () => { + if (isProcessingQueue || operationQueue.length === 0) return; + + setIsProcessingQueue(true); + const operation = operationQueue[0]; + + try { + switch (operation.type) { + case 'undo': + if (historyPos > 0) { + addLastStroke(); + setCurrentPaths(history[historyPos - 1]); + setHistoryPos(pos => pos - 1); + } + break; + case 'redo': + if (historyPos < history.length - 1) { + addLastStroke(); + setCurrentPaths(history[historyPos + 1]); + setHistoryPos(pos => pos + 1); + } + break; + case 'clear': + addLastStroke(); + setCurrentPaths([]); + setHistory(his => [...his.slice(0, historyPos + 1), []]); + setHistoryPos(pos => pos + 1); + break; + case 'loadPaths': + if (operation.payload) { + addLastStroke(); + setCurrentPaths((path) => [...path, ...operation.payload!]); + setHistoryPos(pos => pos + 1); + setHistory(his => [...his.slice(0, historyPos+1), [...currentPaths, ...operation.payload!]]); + } + break; + } + } finally { + setOperationQueue(queue => queue.slice(1)); + setIsProcessingQueue(false); + } + }, [operationQueue, isProcessingQueue, historyPos, history, currentPaths, addLastStroke]); + + React.useEffect(() => { + processQueue(); + }, [processQueue, operationQueue]); + + const enqueueOperation = useCallback((operation: Operation) => { + setOperationQueue(queue => [...queue, operation]); + }, []); + React.useImperativeHandle(ref, () => ({ eraseMode: (erase: boolean): void => { setDrawMode(!erase); }, clearCanvas: (): void => { - addLastStroke(); - setCurrentPaths([]); - setHistory(his => [...his.slice(0, historyPos + 1), []]); - setHistoryPos(pos => pos + 1); + enqueueOperation({ type: 'clear' }); }, undo: (): void => { - if (historyPos > 0) { - addLastStroke(); - setCurrentPaths(history[historyPos - 1]); - setHistoryPos(pos => pos - 1); - } + enqueueOperation({ type: 'undo' }); }, redo: (): void => { - if (historyPos < history.length - 1) { - addLastStroke(); - setCurrentPaths(history[historyPos + 1]); - setHistoryPos(pos => pos + 1); - } + enqueueOperation({ type: 'redo' }); }, exportImage: ( imageType: ExportImageType, @@ -145,10 +192,7 @@ export const ReactSketchCanvas = React.forwardRef< } }), loadPaths: (paths: CanvasPath[]): void => { - addLastStroke(); - setCurrentPaths((path) => [...path, ...paths]); - setHistoryPos(pos => pos + 1); - setHistory(his => [...his.slice(0, historyPos+1), [...currentPaths, ...paths]]); + enqueueOperation({ type: 'loadPaths', payload: paths }); }, getSketchingTime: (): Promise => new Promise((resolve, reject) => { @@ -176,8 +220,9 @@ export const ReactSketchCanvas = React.forwardRef< setHistory([]); setHistoryPos(0); setCurrentPaths([]); + setOperationQueue([]); }, - }), [currentPaths, history, historyPos, svgCanvas, withTimestamp, addLastStroke]); + }), [currentPaths, history, historyPos, svgCanvas, withTimestamp, addLastStroke, enqueueOperation]); const handlePointerDown = (point: Point, isEraser = false): void => { setIsDrawing(true); From 8a85d56704e277ead8cfe8e3b2da70bef274ff07 Mon Sep 17 00:00:00 2001 From: Eugene Fairley Date: Tue, 13 May 2025 12:04:16 -0700 Subject: [PATCH 9/9] Fix order of operations --- .../src/ReactSketchCanvas/index.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index 94d3b51..fc9e4ef 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -122,9 +122,15 @@ export const ReactSketchCanvas = React.forwardRef< case 'loadPaths': if (operation.payload) { addLastStroke(); - setCurrentPaths((path) => [...path, ...operation.payload!]); - setHistoryPos(pos => pos + 1); - setHistory(his => [...his.slice(0, historyPos+1), [...currentPaths, ...operation.payload!]]); + setCurrentPaths((paths) => { + const newPaths = [...paths, ...operation.payload!]; + setHistory(his => { + const newHistoryPos = historyPos + 1; + setHistoryPos(newHistoryPos); + return [...his.slice(0, newHistoryPos), newPaths]; + }); + return newPaths; + }); } break; } @@ -243,8 +249,8 @@ export const ReactSketchCanvas = React.forwardRef< endTimestamp: 0, }; } - setHistoryPos(pos => pos + 1); addLastStroke(); + setHistoryPos(pos => pos + 1); setHistorySynced(false); setCurrentPaths((paths) => [...paths, stroke]); };