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<any>; | |
/** | |
* Optionally called when the command is continued for multi-step commands | |
* @param args arguments to pass to the command | |
*/ | |
continue?: (...args: any) => any | Promise<any>; | |
/** | |
* Optionally called when the command is stopped for multi-step commands | |
* @param args arguments to pass to the command | |
*/ | |
stop?: (...args: any) => any | Promise<any>; | |
/** | |
* 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<any>; | |
/** | |
* 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<any>; | |
} | |
/** | |
* 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<boolean>; | |
/** | |
* Whether or not there are commands that can be redone | |
* Observable store that you can subscribe to for updates | |
*/ | |
readonly can_redo: Writable<boolean>; | |
/** | |
* Resets the history | |
*/ | |
reset(): void; | |
/** | |
* The current history node | |
* Observable store that you can subscribe to for updates | |
*/ | |
readonly current_history: Writable<CommandNode>; | |
} | |
/** | |
* 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); | |
} | |
}; | |
} | |