Files
audio-ui/lib/history/history-manager.ts
Sebastian Krüger 159da29082 feat: implement Phase 5 - undo/redo system with command pattern
Added comprehensive undo/redo functionality:
- Command pattern interface and base classes
- HistoryManager with 50-operation stack
- EditCommand for all edit operations (cut, delete, paste, trim)
- Full keyboard shortcuts (Ctrl+Z undo, Ctrl+Y/Ctrl+Shift+Z redo)
- HistoryControls UI component with visual feedback
- Integrated history system with all edit operations
- Toast notifications for undo/redo actions
- History state tracking and display

New files:
- lib/history/command.ts - Command interface and BaseCommand
- lib/history/history-manager.ts - HistoryManager class
- lib/history/commands/edit-command.ts - EditCommand and factory functions
- lib/hooks/useHistory.ts - React hook for history management
- components/editor/HistoryControls.tsx - History UI component

Modified files:
- components/editor/AudioEditor.tsx - Integrated history system
- components/editor/EditControls.tsx - Updated keyboard shortcuts display

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 17:08:31 +01:00

157 lines
3.1 KiB
TypeScript

/**
* 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();
}
}