export type HistoryHandler = { undo: (action: HistoryAction) => void; redo: (action: HistoryAction) => void; } interface HistoryEvent { source: string; event: string; actions: HistoryAction[]; } interface HistoryAction { element: any; from?: any; to?: any; } export class History { private handlers: Record, any?: (action: HistoryAction) => any }>; private history: HistoryEvent[]; private position: number; constructor() { this.handlers = {}; this.history = []; this.position = -1; } get last() { return this.history.length > 0 && this.position > -1 ? this.history[this.position] : undefined; } get undoable() { return this.history && this.position !== -1; } get redoable() { return this.history && this.position < this.history.length - 1; } undo() { const last = this.last; if(!last) return; last.actions.forEach(e => { this.handlers[last.source] && this.handlers[last.source].handlers[last.event]?.undo(e) this.handlers[last.source] && this.handlers[last.source].any && this.handlers[last.source].any!(e); }); this.position--; } redo() { if(!this.history || this.history.length - 1 <= this.position) return; this.position++; const last = this.last; if(!last) { this.position--; return; } last.actions.forEach(e => { this.handlers[last.source] && this.handlers[last.source].handlers[last.event]?.redo(e) this.handlers[last.source] && this.handlers[last.source].any && this.handlers[last.source].any!(e); }); } add(source: string, event: string, actions: HistoryAction[], apply: boolean = false) { this.position++; this.history.splice(this.position, history.length - this.position, { source, event, actions }); if(apply) actions.forEach(e => { this.handlers[source] && this.handlers[source].handlers[event]?.redo(e); this.handlers[source] && this.handlers[source].any && this.handlers[source].any(e); }); } register(source: string, handlers: Record, any?: (action: HistoryAction) => any) { this.handlers[source] = { handlers, any }; } unregister(source: string) { delete this.handlers[source]; } }