Open anzhel-milenov opened 3 months ago
OK I've found the issue it is because of the 6.0 Promises instead of function callback, so
this:
this.canvas.loadFromJSON(redoHistoryEvent.json, this.canvas.renderAll.bind(this.canvas));
becomes this:
this.canvas.loadFromJSON(redoHistoryEvent.json).then(() => {
this.canvas.renderAll();
});
But remains the issue of identifying objects after they are loaded from JSON
Here's my implementation of a history
saveCanvasState(fabricCanvas = this.fabricCanvas, history = this.history) {
const jsonCanvas = fabricCanvas.toObject();
history.push(jsonCanvas);
}
clear(fabricCanvas = this.fabricCanvas) {
fabricCanvas.remove(...fabricCanvas.getObjects());
}
undo(fabricCanvas = this.fabricCanvas, history = this.history) {
if (history.length === 1) return;
this.clear();
history.pop();
fabricCanvas.off("object:added");
fabric.util.enlivenObjects(history[history.length - 1].objects)
.then((objs) => {
objs.forEach((obj) => fabricCanvas.add(obj));
fabricCanvas.on("object:added", () => this.saveCanvasState(fabricCanvas));
});
}
Here's my implementation of a history
saveCanvasState(fabricCanvas = this.fabricCanvas, history = this.history) { const jsonCanvas = fabricCanvas.toObject(); history.push(jsonCanvas); } clear(fabricCanvas = this.fabricCanvas) { fabricCanvas.remove(...fabricCanvas.getObjects()); } undo(fabricCanvas = this.fabricCanvas, history = this.history) { if (history.length === 1) return; this.clear(); history.pop(); fabricCanvas.off("object:added"); fabric.util.enlivenObjects(history[history.length - 1].objects) .then((objs) => { objs.forEach((obj) => fabricCanvas.add(obj)); fabricCanvas.on("object:added", () => this.saveCanvasState(fabricCanvas)); }); }
Oh my god, thank you lol. This really helped me. I have modified the code to work with "modified" and "removed" events as well and if everything works out and i remember to do it, i will post the completed code here.
Here is what i have ended up with, its a modified version of @AlvesJorge, ironing out any quirks i could find. Kinda inefficient but it is what it is:
import * as fabric from "fabric";
class CanvasHistory {
constructor(canvas) {
this.canvas = canvas;
this.history = [];
this.historyRedo = [];
this._isClearingCanvas = false; // Flag to avoid tracking during canvas clearing
this._init();
}
_init() {
this._saveCanvasState(); // Save initial state
// Automatically save canvas state on object addition
this.canvas.on("custom:added", () => this._saveCanvasState());
this.canvas.on("object:modified", () => this._saveCanvasState());
this.canvas.on("object:removed", () => {
if (!this._isClearingCanvas) {
this._saveCanvasState();
}
});
}
_saveCanvasState() {
const jsonCanvas = structuredClone(this.canvas.toObject().objects)
this.history.push(jsonCanvas);
}
_clearCanvas() {
this._isClearingCanvas = true;
this.canvas.remove(...this.canvas.getObjects());
this._isClearingCanvas = false;
}
async undo() {
if (this.history.length <= 1) return; // Prevent undoing beyond the initial state
this._clearCanvas();
this.historyRedo.push(this.history.pop()); // Remove the current state
const lastState = this.history[this.history.length - 1];
const objects = await fabric.util.enlivenObjects(lastState);
this._applyState(objects)
}
async redo() {
if (this.historyRedo.length === 0) return; // Prevent undoing beyond the initial state
console.log(this.historyRedo)
this._clearCanvas();
const lastState = this.historyRedo.pop();
this.history.push(lastState)
const objects = await fabric.util.enlivenObjects(lastState);
this._applyState(objects)
}
_applyState(objects) {
this.canvas.off("custom:added");
this.canvas.off("object:modified");
this.canvas.off("object:removed");
objects.forEach((obj) => {
this.canvas.add(obj)
});
// Re-enable event listeners
this.canvas.on("custom:added", () => this._saveCanvasState());
this.canvas.on("object:modified", () => this._saveCanvasState());
this.canvas.on("object:removed", () => {
if (!this._isClearingCanvas) {
this._saveCanvasState();
}
});
this.canvas.renderAll()
}
}
export default CanvasHistory;
NOTE: I am using a custom event here, instead of "object:added", i have "custom:added". This is useful for drawing apps where the drawing's shape should only be registered when the mouse up event has taken place.
CheckList
Description
We are trying to implement some "history" events like (Undo/Redo) into our fabricjs 6.0 project I haven't found any native solutions, so we are using fabric-history 2.0 npm package.
They seem to not have fabricjs 6.0 support, and I found this comment in here (https://github.com/alimozdemir/fabric-history/issues/48) where user "StringKe" provides some solution (maybe custom reworked the code to fit into the 6.0 ts logic).
Unfortunately, it is just not working properly somehow.
Do you think is possible to have some easier way to make this happen, no matter with package or custom? Maybe there are changes of loadFromJSON, that are working differently?
Current State
Here I can put a simple code how I am trying to use the fabric-history. I am using Vuejs3, but I think the code is readable
And this is the fabric-history.ts code:
Additional Context
Here are a few screenshots of the behavior.
1. Initial state:
2. Moved object
3. Clicked button Undo(object is on the previous position but disappeared. I see it by the mouse changing the pointer when I move around the previous position on the center;
4. Clicked on the invisible object rect and is shown again (Feels like is rendered, again, have no clue what this behavior is).