Matthew Jones

In this part, we're going to further improve our drawing canvas by adding an undo/redo functionality. To accomplish this, we have to implement a state manager system which keeps track of the various states of the canvas and the objects on it. It's not as bad as it sounds, I promise. Let's go!

The State Manager

In order to accomplish this undo/redo functionality, we need a class that will keep track of the various states of the drawing canvas. Said class will need to keep a representation of the canvas in JSON, so that said representation can be easily restored.

Lucky for us, FabricJS already provides a way to get the JSON for the canvas: the method toDatalessJSON(). By using this method, we can get the complete state of the canvas at any given time.

The state manager itself will need to keep a stack of states, so that we can pop off the top state to undo. It will also need to keep a separate stack of popped states, so that we can redo.

The method saveState() is used as a shortcut method to allow other methods to a) save the current state and b) render all objects on the canvas again. saveState() needs to be used in quite a few places, most notably whenever a canvas object is modified, created, or deleted.

class DrawingEditor {
//...Properties and constructor
private initializeCanvasEvents() {
//...Other events
this.canvas.on('mouse:up', (o) => {
this.isDown = false;
switch (this.cursorMode) {
//If the cursor mode is currently Draw when a mouseup
//event occurs, we have just finished dragging to
//create that object. Hence, we need to add the new
//state of the canvas to the state manager.
case CursorMode.Draw:
this.isObjectSelected = false;
this.saveState();
}
});
//If an object has been modified at all, save the new state
this.canvas.on("object:modified", (e) => {
this.saveState();
});
}
//This method is called by the DeleteComponent from Part 5.
deleteSelected(): void {
this.canvas.remove(this.canvas.getActiveObject());
this.canvas.renderAll();
this.saveState();
}
//...Other methods
}

Our DrawingEditor will now save the state of the canvas whenever objects are changed, created, or deleted. But we still need toolbar items for undo/redo. Guess what that means? We need some new display components!