All files / src/hooks useUndoRedoStore.ts

41.17% Statements 28/68
100% Branches 13/13
50% Functions 1/2
48.14% Lines 26/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            118365x 118365x 118365x     118365x             118365x       5571x 5571x               242718x   118365x           175x 129052x   118365x           2x             2x           2x                   128727x   118365x           2x             2x           2x                   124501x     118365x       118365x       118365x         20850x                                         306015x    
import { useEffect, useRef, useSyncExternalStore } from "react";
import { Command } from "@/types/commands";
import { commandExecutor } from "@/utils/commandExecutor";
import { undoRedoStore } from "@/utils/UndoRedoStore";
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
      if (prevFlowsheetIdRef.current !== null) {
        prevFlowsheetIdRef.current = null;
      }
    }
  }, [flowsheetId]);
 
  const addHistory = (command: Command) => {
    if (!snapshot.isReady) {
      console.warn("[useUndoRedoStore] Cannot add command: Store not ready");
      return;
    }
 
    undoRedoStore.addCommand(command);
  };
 
  const executeUndo = async (): Promise<boolean> => {
    if (!snapshot.canUndo) {
      console.warn("[useUndoRedoStore] Cannot undo: No commands available");
      return false;
    }
 
    const command = undoRedoStore.undo();
    if (!command) {
      return false;
    }
 
    try {
      // Execute the undo command using the command executor
      const success = await commandExecutor.executeCommand(command, true);
 
      if (!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;
    }
  };
 
  const executeRedo = async (): Promise<boolean> => {
    if (!snapshot.canRedo) {
      console.warn("[useUndoRedoStore] Cannot redo: No commands available");
      return false;
    }
 
    const command = undoRedoStore.redo();
    if (!command) {
      return false;
    }
 
    try {
      // Execute the redo command using the command executor
      const success = await commandExecutor.executeCommand(command, false);
 
      if (!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;
    }
  };
 
  // Utility methods
  const resetHistory = () => {
    undoRedoStore.resetStacks();
  };
 
  const removeFlowsheet = (id: string) => {
    undoRedoStore.removeFlowsheet(id);
  };
 
  const getDebugInfo = () => {
    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,
  };
}