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; } }