From d638c911c8564088f664f018aef6f5f0c3782070 Mon Sep 17 00:00:00 2001 From: clioo Date: Tue, 4 Aug 2026 14:02:14 -0600 Subject: [PATCH] Fix organize undo and viewport restoration --- src/App.tsx | 57 ++++++++++++++++++++++++++++--------- src/index.css | 26 +++++++++++++++-- src/store.test.ts | 7 +++++ src/store.ts | 22 ++++++++++++++ tests/e2e/opengraph.spec.ts | 34 ++++++++++++++++++++-- 5 files changed, 129 insertions(+), 17 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index febd730..c97a3f2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -229,6 +229,7 @@ function App() { commit, setNodesLive, setEdgesLive, + setViewportLive, updateViewport, setSelected, setSettingsOpen, @@ -251,7 +252,7 @@ function App() { archiveGraph, restoreGraph, } = useOpenGraphStore(); - const { screenToFlowPosition, fitView } = useReactFlow(); + const { screenToFlowPosition, fitView, getViewport, setViewport } = useReactFlow(); const canvasRef = useRef(null); const companionRef = useRef(null); const [editingName, setEditingName] = useState(false); @@ -283,6 +284,35 @@ function App() { ? document.edges.find((edge) => edge.id === selected.id) : undefined; + const restoreHistoryViewport = useCallback(() => { + skipViewportCommitUntilRef.current = Date.now() + 500; + window.requestAnimationFrame(() => { + const viewport = useOpenGraphStore.getState().document.viewport; + void setViewport(viewport, { duration: 200 }); + }); + }, [setViewport]); + + const handleUndo = useCallback( + (message = "Change undone") => { + if (!useOpenGraphStore.getState().past.length) return; + undo(); + setOrganizationRevision(null); + setOrganizationToast(null); + setToast(message); + restoreHistoryViewport(); + }, + [restoreHistoryViewport, setToast, undo], + ); + + const handleRedo = useCallback(() => { + if (!useOpenGraphStore.getState().future.length) return; + redo(); + setOrganizationRevision(null); + setOrganizationToast(null); + setToast("Change redone"); + restoreHistoryViewport(); + }, [redo, restoreHistoryViewport, setToast]); + useEffect(() => { ( window as Window & { __opengraphDocument?: typeof document } @@ -360,6 +390,7 @@ function App() { }, [appearance]); useEffect(() => { const fitOnNarrow = () => { + skipViewportCommitUntilRef.current = Date.now() + 250; const frame = window.requestAnimationFrame(() => fitView({ padding: window.innerWidth <= 800 ? 0.18 : 0.14, @@ -397,11 +428,11 @@ function App() { } if (command && event.key.toLowerCase() === "z") { event.preventDefault(); - event.shiftKey ? redo() : undo(); + event.shiftKey ? handleRedo() : handleUndo(); } if (command && event.key.toLowerCase() === "y") { event.preventDefault(); - redo(); + handleRedo(); } if (command && event.key.toLowerCase() === "d") { event.preventDefault(); @@ -422,10 +453,10 @@ function App() { createGraph, deleteSelected, duplicateSelected, - redo, + handleRedo, + handleUndo, setActiveTool, setSelected, - undo, ]); const onNodesChange = useCallback( (changes: NodeChange[]) => { @@ -646,6 +677,7 @@ function App() { }; const fit = () => fitView({ padding: 0.18, duration: 200 }); const organize = useCallback(() => { + setViewportLive(getViewport()); const current = useOpenGraphStore.getState().document; const result = organizeGraphDocument(current); if (result.document === current) { @@ -666,7 +698,7 @@ function App() { window.requestAnimationFrame(() => fitView({ padding: 0.18, duration: 200 }), ); - }, [commit, fitView, setSelected, setToast]); + }, [commit, fitView, getViewport, setSelected, setToast, setViewportLive]); const activeGraphs = useMemo( () => graphs.filter((graph) => !graph.archived), [graphs], @@ -819,10 +851,10 @@ function App() { Organize
- + handleUndo()} disabled={!past.length}> - +
@@ -846,9 +878,10 @@ function App() { className="primary-button" onClick={handleCopy} disabled={copying} + aria-label={copying ? "Preparing graph" : "Copy graph"} > - {copying ? "Preparing…" : "Copy graph"} + {copying ? "Preparing…" : "Copy graph"}