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>
157 lines
3.1 KiB
TypeScript
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();
|
|
}
|
|
}
|