136 lines
3.2 KiB
TypeScript
136 lines
3.2 KiB
TypeScript
|
|
import { create } from 'zustand';
|
||
|
|
import type { Command, HistoryState } from '@/types/history';
|
||
|
|
|
||
|
|
interface HistoryStore extends HistoryState {
|
||
|
|
/** Execute a command and add to history */
|
||
|
|
executeCommand: (command: Command) => void;
|
||
|
|
/** Undo the last command */
|
||
|
|
undo: () => void;
|
||
|
|
/** Redo the last undone command */
|
||
|
|
redo: () => void;
|
||
|
|
/** Clear all history */
|
||
|
|
clearHistory: () => void;
|
||
|
|
/** Check if can undo */
|
||
|
|
canUndo: () => boolean;
|
||
|
|
/** Check if can redo */
|
||
|
|
canRedo: () => boolean;
|
||
|
|
/** Get current state summary */
|
||
|
|
getHistorySummary: () => { undoCount: number; redoCount: number };
|
||
|
|
}
|
||
|
|
|
||
|
|
const MAX_HISTORY_SIZE = 50;
|
||
|
|
|
||
|
|
export const useHistoryStore = create<HistoryStore>((set, get) => ({
|
||
|
|
undoStack: [],
|
||
|
|
redoStack: [],
|
||
|
|
maxHistorySize: MAX_HISTORY_SIZE,
|
||
|
|
isExecuting: false,
|
||
|
|
|
||
|
|
executeCommand: (command) => {
|
||
|
|
const state = get();
|
||
|
|
|
||
|
|
// Prevent recursive execution
|
||
|
|
if (state.isExecuting) return;
|
||
|
|
|
||
|
|
set({ isExecuting: true });
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Try to merge with last command
|
||
|
|
const lastCommand = state.undoStack[state.undoStack.length - 1];
|
||
|
|
if (lastCommand && lastCommand.merge && lastCommand.merge(command)) {
|
||
|
|
// Command was merged, re-execute the merged command
|
||
|
|
lastCommand.execute();
|
||
|
|
set({ isExecuting: false });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Execute the new command
|
||
|
|
command.execute();
|
||
|
|
|
||
|
|
// Add to undo stack
|
||
|
|
const newUndoStack = [...state.undoStack, command];
|
||
|
|
|
||
|
|
// Limit stack size
|
||
|
|
if (newUndoStack.length > state.maxHistorySize) {
|
||
|
|
newUndoStack.shift();
|
||
|
|
}
|
||
|
|
|
||
|
|
set({
|
||
|
|
undoStack: newUndoStack,
|
||
|
|
redoStack: [], // Clear redo stack on new action
|
||
|
|
isExecuting: false,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to execute command:', error);
|
||
|
|
set({ isExecuting: false });
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
undo: () => {
|
||
|
|
const state = get();
|
||
|
|
|
||
|
|
if (state.undoStack.length === 0 || state.isExecuting) return;
|
||
|
|
|
||
|
|
set({ isExecuting: true });
|
||
|
|
|
||
|
|
try {
|
||
|
|
const command = state.undoStack[state.undoStack.length - 1];
|
||
|
|
command.undo();
|
||
|
|
|
||
|
|
set({
|
||
|
|
undoStack: state.undoStack.slice(0, -1),
|
||
|
|
redoStack: [...state.redoStack, command],
|
||
|
|
isExecuting: false,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to undo command:', error);
|
||
|
|
set({ isExecuting: false });
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
redo: () => {
|
||
|
|
const state = get();
|
||
|
|
|
||
|
|
if (state.redoStack.length === 0 || state.isExecuting) return;
|
||
|
|
|
||
|
|
set({ isExecuting: true });
|
||
|
|
|
||
|
|
try {
|
||
|
|
const command = state.redoStack[state.redoStack.length - 1];
|
||
|
|
command.execute();
|
||
|
|
|
||
|
|
set({
|
||
|
|
undoStack: [...state.undoStack, command],
|
||
|
|
redoStack: state.redoStack.slice(0, -1),
|
||
|
|
isExecuting: false,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to redo command:', error);
|
||
|
|
set({ isExecuting: false });
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
clearHistory: () => {
|
||
|
|
set({
|
||
|
|
undoStack: [],
|
||
|
|
redoStack: [],
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
canUndo: () => {
|
||
|
|
return get().undoStack.length > 0;
|
||
|
|
},
|
||
|
|
|
||
|
|
canRedo: () => {
|
||
|
|
return get().redoStack.length > 0;
|
||
|
|
},
|
||
|
|
|
||
|
|
getHistorySummary: () => {
|
||
|
|
const state = get();
|
||
|
|
return {
|
||
|
|
undoCount: state.undoStack.length,
|
||
|
|
redoCount: state.redoStack.length,
|
||
|
|
};
|
||
|
|
},
|
||
|
|
}));
|