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