Portal Community

Undo Implementation

undo: () => {
    const { undoStack, redoStack, nodes, edges } = get();

    if (undoStack.length === 0) return;  // Nothing to undo

    // Pop the top snapshot from undo stack
    const [previous, ...remainingUndo] = undoStack;

    // Push current state to redo stack
    const currentSnapshot: HistorySnapshot = {
        nodes: JSON.parse(JSON.stringify(nodes)),
        edges: JSON.parse(JSON.stringify(edges))
    };

    // Restore the graph to the snapshot state
    set({
        nodes    : previous.nodes,
        edges    : previous.edges,
        undoStack: remainingUndo,
        redoStack: [currentSnapshot, ...redoStack]
    });
}

Keyboard Binding

// useKeyboardShortcuts.ts
useEffect(() => {
    function handleKeyDown(e: KeyboardEvent) {
        const isMod = e.metaKey || e.ctrlKey;

        if (isMod && e.key === 'z' && !e.shiftKey) {
            e.preventDefault();
            useWorkflowStore.getState().undo();
        }
    }

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
}, []);

Undo Button in Toolbar

function UndoButton() {
    const { undo, undoStack } = useWorkflowStore(
        state => ({ undo: state.undo, undoStack: state.undoStack }),
        shallow
    );

    return (
        <button onClick={undo} disabled={undoStack.length === 0}>
            <i className="fa-solid fa-rotate-left" />
            Undo
        </button>
    );
}

After Undo

Before UndoAfter Undo
undoStack: [A, B, C]undoStack: [B, C]
redoStack: []redoStack: [current]
Canvas shows: current stateCanvas shows: state A