utilities_history_manager.js
/**
* Command-pattern undo/redo history manager.
* Each command is a plain object:
* { label: string, undo: () => void|Promise, redo: () => void|Promise, dirtyFlags: string[] }
*
* dirtyFlags values: 'transcript' | 'speakers' | 'annotations' | 'projectName' | 'projectFolder'
*/
export class HistoryManager {
/**
* @param {object} [options] - Optional configuration.
* @param {number} [options.maxSize=100] - Maximum number of commands to retain in the undo stack.
*/
constructor({ maxSize = 100 } = {}) {
this._undoStack = [];
this._redoStack = [];
this._maxSize = maxSize;
}
/**
* Pushes a command onto the undo stack and clears the redo stack.
* @param {object} command - The command object with undo, redo, label, and dirtyFlags properties.
*/
push(command) {
this._undoStack.push(command);
if (this._undoStack.length > this._maxSize) {
this._undoStack.shift();
}
this._redoStack = [];
}
/**
* Undoes the most recent command. Returns the command after awaiting undo().
* @returns {Promise<object|null>}
*/
async undo() {
if (!this._undoStack.length) return null;
const command = this._undoStack.pop();
this._redoStack.push(command);
await command.undo();
return command;
}
/**
* Redoes the most recently undone command. Returns the command after awaiting redo().
* @returns {Promise<object|null>}
*/
async redo() {
if (!this._redoStack.length) return null;
const command = this._redoStack.pop();
this._undoStack.push(command);
await command.redo();
return command;
}
/** Clears both stacks. */
clear() {
this._undoStack = [];
this._redoStack = [];
}
/** @returns {boolean} Whether there are commands available to undo. */
get canUndo() { return this._undoStack.length > 0; }
/** @returns {boolean} Whether there are commands available to redo. */
get canRedo() { return this._redoStack.length > 0; }
}