import AsyncActionQueue from '../AsyncActionQueue'; const EventEmitter = require('events'); class UndoQueue { private inner_: any[] = []; private size_: number = 20; pop() { return this.inner_.pop(); } push(e: any) { this.inner_.push(e); while (this.length > this.size_) { this.inner_.splice(0,1); } } get length(): number { return this.inner_.length; } at(index: number): any { return this.inner_[index]; } } export default class UndoRedoService { private pushAsyncQueue: AsyncActionQueue = new AsyncActionQueue(700); private undoStates: UndoQueue = new UndoQueue(); private redoStates: UndoQueue = new UndoQueue(); private eventEmitter: any = new EventEmitter(); private isUndoing: boolean = false; constructor() { this.push = this.push.bind(this); } on(eventName: string, callback: Function) { return this.eventEmitter.on(eventName, callback); } off(eventName: string, callback: Function) { return this.eventEmitter.removeListener(eventName, callback); } push(state: any) { this.undoStates.push(state); this.redoStates = new UndoQueue(); this.eventEmitter.emit('stackChange'); } schedulePush(state: any) { this.pushAsyncQueue.push(async () => { this.push(state); }); } async undo(redoState: any) { if (this.isUndoing) return; if (!this.canUndo) throw new Error('Nothing to undo'); this.isUndoing = true; await this.pushAsyncQueue.processAllNow(); const state = this.undoStates.pop(); this.redoStates.push(redoState); this.eventEmitter.emit('stackChange'); this.isUndoing = false; return state; } async redo(undoState: any) { if (this.isUndoing) return; if (!this.canRedo) throw new Error('Nothing to redo'); this.isUndoing = true; await this.pushAsyncQueue.processAllNow(); const state = this.redoStates.pop(); this.undoStates.push(undoState); this.eventEmitter.emit('stackChange'); this.isUndoing = false; return state; } async reset() { this.undoStates = new UndoQueue(); this.redoStates = new UndoQueue(); this.isUndoing = false; const output = this.pushAsyncQueue.reset(); this.eventEmitter.emit('stackChange'); return output; } get canUndo(): boolean { return !!this.undoStates.length; } get canRedo(): boolean { return !!this.redoStates.length; } }