Skip to content

Instantly share code, notes, and snippets.

@onedayitwillmake
Last active January 24, 2019 20:37
Show Gist options
  • Save onedayitwillmake/59811274042952f069e0699a3160c05e to your computer and use it in GitHub Desktop.
Save onedayitwillmake/59811274042952f069e0699a3160c05e to your computer and use it in GitHub Desktop.
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;
}
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