import { writable, type Writable } from "svelte/store"; /** * Base command interface that is added to the command_managers history */ export interface Command { /** * Optionally called when the command is first executed for multi-step commands * @param args arguments to pass to the command */ start?: (...args: any) => any | Promise; /** * Optionally called when the command is continued for multi-step commands * @param args arguments to pass to the command */ continue?: (...args: any) => any | Promise; /** * Optionally called when the command is stopped for multi-step commands * @param args arguments to pass to the command */ stop?: (...args: any) => any | Promise; /** * Called by the command manager to execute the command, can act as a no-op if the work has already been done * This function must be able to recreate the command if the command is undone and redone (`stop`/`start`/`continue` will not be called again) */ execute(): any | Promise; /** * Called by the command manager to undo the command * This function must be able to undo the work done by the execute function */ undo(): any | Promise; } /** * Command manager interface that handles the undo/redo history */ export interface CommandManager { /** * Undo the last command */ undo(): void; /** * Redo the last undone command */ redo(): void; /** * Execute a command and add it to the history * @param command command to execute */ execute(command: Command): void; /** * Whether or not there are commands that can be undone * Observable store that you can subscribe to for updates */ readonly can_undo: Writable; /** * Whether or not there are commands that can be redone * Observable store that you can subscribe to for updates */ readonly can_redo: Writable; /** * Resets the history */ reset(): void; /** * The current history node * Observable store that you can subscribe to for updates */ readonly current_history: Writable; } /** * Command node interface that is used to create the undo/redo history */ interface CommandNode { /** * Command that the node holds */ command: Command | null; /** * Next command in the history */ next: CommandNode | null; /** * Previous command in the history */ previous: CommandNode | null; /** * Push a command onto the history * @param command command to push onto the history */ push(command: Command): void; } /** * Creates a command node * @param command command to add to the node * @returns a command node */ function command_node(command?: Command): CommandNode { return { command: command || null, next: null, previous: null, push: function (command: Command) { const node = command_node(command); node.previous = this; this.next = node; } }; } /** * Creates a command manager * @returns a command manager */ export function command_manager(): CommandManager { let history: CommandNode = command_node(); const can_undo = writable(false); const can_redo = writable(false); const current_history = writable(history); return { undo: function () { if (history.previous) { history.command?.undo(); history = history.previous; } can_undo.set(!!history.previous); can_redo.set(!!history.next); current_history.set(history); }, redo: function () { if (history.next) { history.next.command?.execute(); history = history.next; } can_undo.set(!!history.previous); can_redo.set(!!history.next); current_history.set(history); }, execute: function (command: Command) { command.execute(); history.push(command); history = history.next!; can_undo.set(!!history.previous); can_redo.set(!!history.next); current_history.set(history); }, can_undo, can_redo, current_history, reset: function () { history = command_node(); can_undo.set(false); can_redo.set(false); current_history.set(history); } }; }