Last active
January 24, 2019 20:37
-
-
Save onedayitwillmake/59811274042952f069e0699a3160c05e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
onInputUp(cursor: CursorController) { | |
if(this.points.length > 2) { | |
let newStrokes = Array.from(this.strokes); | |
newStrokes.push(this.points); | |
this.setStrokes(newStrokes); | |
} | |
this.points = []; | |
} | |
setStrokes(arr:Array<Array<vec2>>) { | |
let oldValue = Array.from(this.strokes); | |
UndoManager.registerUndo({ | |
coalesceMode: CoalesceMode.None, | |
title: 'undo stroke', | |
fn: ()=> this.setStrokes(oldValue) | |
}); | |
this.strokes = arr; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SimpleEmitter from "../../core/SimpleEmitter"; | |
enum UndoManagerState { | |
Collecting = 'Collecting', | |
Undoing = 'Undoing', | |
Redoing = 'Redoing' | |
} | |
// Coalsece aka merge style | |
// None: AAABBB > AAABBB | |
// First: AAABBB > AB | |
enum CoalesceMode { | |
None, | |
First | |
} | |
// Typed object used as registerUndo parameter | |
interface UndoOptions { | |
// Used as the key when checking if we should coalesce the undo or not | |
title:string, | |
coalesceMode:number, | |
fn:Function | |
} | |
class UndoManager extends SimpleEmitter { | |
private _state:UndoManagerState = UndoManagerState.Collecting; | |
undoStack:Array<UndoOptions> = []; | |
redoStack:Array<UndoOptions> = []; | |
maxUndos:number = 100; | |
constructor(){ | |
super(); | |
window.addEventListener('keydown', (e)=>{ | |
if(e.key === "z") { | |
this.undo(); | |
} else if(e.key === 'y') { | |
this.redo(); | |
} | |
}) | |
} | |
registerUndo(options:UndoOptions) { | |
// Currently undoing, register it as a redo | |
if(this._state === UndoManagerState.Undoing) { | |
this.redoStack.push(options); | |
} else { | |
// Collect everything, or if there is nothing in the stack | |
if(options.coalesceMode === CoalesceMode.None || this.undoStack.length === 0) { | |
this.undoStack.push(options); | |
} | |
// Ignore repeated actions, except the first one of its type | |
else if(options.coalesceMode === CoalesceMode.First) { | |
const last = this.undoStack[this.undoStack.length - 1]; | |
if(options.title !== last.title) { | |
this.undoStack.push(options); | |
} | |
} | |
if(this.undoStack.length > this.maxUndos) { | |
this.undoStack.shift(); | |
} | |
} | |
// Not undoing or redoing, clear the redo stack | |
if(this._state === UndoManagerState.Collecting) { | |
this.clearRedo(); | |
} | |
} | |
undo(){ | |
if(!this.canUndo) {return;} | |
this._state = UndoManagerState.Undoing; | |
(this.undoStack.pop() as UndoOptions).fn(); | |
this._state = UndoManagerState.Collecting; | |
} | |
redo(){ | |
if (!this.canRedo) { | |
return | |
} | |
this._state = UndoManagerState.Redoing; | |
(this.redoStack.pop() as UndoOptions).fn(); | |
this._state = UndoManagerState.Collecting; | |
} | |
clearRedo(){ | |
this.redoStack.length = 0; | |
} | |
get canUndo():boolean { | |
return this.undoStack.length > 0; | |
} | |
get canRedo():boolean { | |
return this.redoStack.length > 0; | |
} | |
} | |
// Because this file lives in multiple .js files on the site... | |
// We just want to use the first one loaded as the only one on the page | |
// @ts-ignore | |
const singleton = (window.UndoManager = window.UndoManager || new UndoManager()); | |
export {CoalesceMode}; | |
export {singleton as UndoManager} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment