All files / src/hooks useUndoRedoStore.ts

59.25% Statements 32/54
42.85% Branches 6/14
62.5% Functions 5/8
59.25% Lines 32/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146              290582x 290582x     290582x             290582x 2974x   2944x 2944x 2944x       30x             290582x   69x         69x         290582x 2x         2x 2x       2x   2x   2x           2x                   290582x 2x         2x 2x       2x   2x   2x           2x                     290582x       290582x       290582x         290582x                                              
import { useSyncExternalStore, useCallback, useEffect, useRef } from "react";
import { undoRedoStore } from "@/utils/UndoRedoStore";
import { commandExecutor } from "@/utils/commandExecutor";
import { Command } from "@/types/commands";
import { useProjectId } from "./project";
 
export function useUndoRedoStore() {
  const flowsheetId = useProjectId();
  const prevFlowsheetIdRef = useRef<number | null>(null);
 
  // Subscribe to external store using React's useSyncExternalStore
  const snapshot = useSyncExternalStore(
    undoRedoStore.subscribe,
    undoRedoStore.getSnapshot,
    undoRedoStore.getSnapshot, // Same for server-side rendering
  );
 
  // Automatically set flowsheet context when available
  useEffect(() => {
    if (flowsheetId && Number.isFinite(flowsheetId) && flowsheetId > 0) {
      // Only update if flowsheet actually changed (prevent unnecessary operations)
      if (prevFlowsheetIdRef.current !== flowsheetId) {
        prevFlowsheetIdRef.current = flowsheetId;
        undoRedoStore.setFlowsheet(String(flowsheetId));
      }
    } else {
      // Clear previous flowsheet reference if invalid
      Iif (prevFlowsheetIdRef.current !== null) {
        prevFlowsheetIdRef.current = null;
      }
    }
  }, [flowsheetId]);
 
  // Command execution methods - memoized to prevent unnecessary re-renders
  const addHistory = useCallback(
    (command: Command) => {
      Iif (!snapshot.isReady) {
        console.warn("[useUndoRedoStore] Cannot add command: Store not ready");
        return;
      }
 
      undoRedoStore.addCommand(command);
    },
    [snapshot.isReady],
  );
 
  const executeUndo = useCallback(async (): Promise<boolean> => {
    Iif (!snapshot.canUndo) {
      console.warn("[useUndoRedoStore] Cannot undo: No commands available");
      return false;
    }
 
    const command = undoRedoStore.undo();
    Iif (!command) {
      return false;
    }
 
    try {
      // Execute the undo command using the command executor
      const success = await commandExecutor.executeCommand(command, true);
 
      Iif (!success) {
        // Restore command on failure
        undoRedoStore.restoreFailedUndo(command);
        return false;
      }
 
      return true;
    } catch (error) {
      console.error("[useUndoRedoStore] Undo execution failed:", error);
 
      // Restore command on failure
      undoRedoStore.restoreFailedUndo(command);
      return false;
    }
  }, [snapshot.canUndo]);
 
  const executeRedo = useCallback(async (): Promise<boolean> => {
    Iif (!snapshot.canRedo) {
      console.warn("[useUndoRedoStore] Cannot redo: No commands available");
      return false;
    }
 
    const command = undoRedoStore.redo();
    Iif (!command) {
      return false;
    }
 
    try {
      // Execute the redo command using the command executor
      const success = await commandExecutor.executeCommand(command, false);
 
      Iif (!success) {
        // Restore command on failure
        undoRedoStore.restoreFailedRedo(command);
        return false;
      }
 
      return true;
    } catch (error) {
      console.error("[useUndoRedoStore] Redo execution failed:", error);
 
      // Restore command on failure
      undoRedoStore.restoreFailedRedo(command);
      return false;
    }
  }, [snapshot.canRedo]);
 
  // Utility methods
  const resetHistory = useCallback(() => {
    undoRedoStore.resetStacks();
  }, []);
 
  const removeFlowsheet = useCallback((id: string) => {
    undoRedoStore.removeFlowsheet(id);
  }, []);
 
  const getDebugInfo = useCallback(() => {
    return undoRedoStore.getDebugInfo();
  }, []);
 
  // Return clean API following React patterns
  return {
    // State (directly from external store snapshot)
    canUndo: snapshot.canUndo,
    canRedo: snapshot.canRedo,
    isReady: snapshot.isReady,
    undoStackSize: snapshot.undoStackSize,
    redoStackSize: snapshot.redoStackSize,
    currentFlowsheetId: snapshot.flowsheetId,
 
    // Actions
    addHistory,
    executeUndo,
    executeRedo,
    resetHistory,
    removeFlowsheet,
    getDebugInfo,
 
    // Legacy compatibility aliases (to ease migration)
    onUndo: executeUndo,
    onRedo: executeRedo,
    resetUndoRedoHistory: resetHistory,
  };
}