/** * History Manager for Undo/Redo functionality */ import type { Command } from './command'; export interface HistoryState { canUndo: boolean; canRedo: boolean; undoDescription: string | null; redoDescription: string | null; historySize: number; } export class HistoryManager { private undoStack: Command[] = []; private redoStack: Command[] = []; private maxHistorySize: number; private listeners: Set<() => void> = new Set(); constructor(maxHistorySize: number = 50) { this.maxHistorySize = maxHistorySize; } /** * Execute a command and add it to history */ execute(command: Command): void { command.execute(); this.undoStack.push(command); // Limit history size if (this.undoStack.length > this.maxHistorySize) { this.undoStack.shift(); } // Clear redo stack when new command is executed this.redoStack = []; this.notifyListeners(); } /** * Undo the last command */ undo(): boolean { if (!this.canUndo()) return false; const command = this.undoStack.pop()!; command.undo(); this.redoStack.push(command); this.notifyListeners(); return true; } /** * Redo the last undone command */ redo(): boolean { if (!this.canRedo()) return false; const command = this.redoStack.pop()!; command.redo(); this.undoStack.push(command); this.notifyListeners(); return true; } /** * Check if undo is available */ canUndo(): boolean { return this.undoStack.length > 0; } /** * Check if redo is available */ canRedo(): boolean { return this.redoStack.length > 0; } /** * Get current history state */ getState(): HistoryState { return { canUndo: this.canUndo(), canRedo: this.canRedo(), undoDescription: this.getUndoDescription(), redoDescription: this.getRedoDescription(), historySize: this.undoStack.length, }; } /** * Get description of next undo action */ getUndoDescription(): string | null { if (!this.canUndo()) return null; return this.undoStack[this.undoStack.length - 1].getDescription(); } /** * Get description of next redo action */ getRedoDescription(): string | null { if (!this.canRedo()) return null; return this.redoStack[this.redoStack.length - 1].getDescription(); } /** * Clear all history */ clear(): void { this.undoStack = []; this.redoStack = []; this.notifyListeners(); } /** * Subscribe to history changes */ subscribe(listener: () => void): () => void { this.listeners.add(listener); return () => this.listeners.delete(listener); } /** * Notify all listeners of history changes */ private notifyListeners(): void { this.listeners.forEach((listener) => listener()); } /** * Get current history size */ getHistorySize(): number { return this.undoStack.length; } /** * Set maximum history size */ setMaxHistorySize(size: number): void { this.maxHistorySize = size; // Trim undo stack if needed while (this.undoStack.length > this.maxHistorySize) { this.undoStack.shift(); } this.notifyListeners(); } }