From e134fc09fceb540dbe490b005c6de08ad68f09d1 Mon Sep 17 00:00:00 2001 From: GeorgeSmith215 <77789341+GeorgeSmith215@users.noreply.github.com> Date: Sat, 26 Oct 2024 11:44:26 +0800 Subject: [PATCH] refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin (#521) * fix(plugin): fix the DringPlugin.ts that the cursor style grab and grabbing is not so much available * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin * refactor(fabric-history): refactor the fabric-history and tuning the HistoryPlugin --------- Co-authored-by: GeorgeSmith --- packages/core/plugin/HistoryPlugin.ts | 23 ++++---- packages/core/utils/fabric-history.js | 80 +++++++++++++++++++-------- 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/packages/core/plugin/HistoryPlugin.ts b/packages/core/plugin/HistoryPlugin.ts index 8eb18501..0c6acf65 100644 --- a/packages/core/plugin/HistoryPlugin.ts +++ b/packages/core/plugin/HistoryPlugin.ts @@ -2,8 +2,8 @@ /* * @Author: 秦少卫 * @Date: 2023-06-20 13:06:31 - * @LastEditors: 秦少卫 - * @LastEditTime: 2024-07-12 21:35:16 + * @LastEditors: George GeorgeSmith163@163.com + * @LastEditTime: 2024-10-15 09:35:35 * @Description: 历史记录插件 */ import { fabric } from 'fabric'; @@ -17,12 +17,13 @@ declare module '@kuaitu/core' { interface IEditor extends IPlugin {} } +type callback = () => void; type extendCanvas = { - undo: () => void; - redo: () => void; + undo: (callback?: callback) => void; + redo: (callback?: callback) => void; clearHistory: () => void; - historyUndo: any[]; - historyRedo: any[]; + historyStack: any[]; + historyIndex: number; }; class HistoryPlugin implements IPluginTempl { @@ -42,15 +43,15 @@ class HistoryPlugin implements IPluginTempl { this.historyUpdate(); }); window.addEventListener('beforeunload', (e) => { - if (this.canvas.historyUndo.length > 0) { + if (this.canvas.historyStack.length > 0) { (e || window.event).returnValue = '确认离开'; } }); } historyUpdate() { - const { historyUndo, historyRedo } = this.canvas; - this.editor.emit('historyUpdate', historyUndo.length, historyRedo.length); + const { historyStack, historyIndex } = this.canvas; + this.editor.emit('historyUpdate', historyIndex, historyStack.length - historyIndex); } // 导入模板之后,清理 History 缓存 @@ -61,10 +62,6 @@ class HistoryPlugin implements IPluginTempl { } undo() { - // if (this.canvas.historyUndo.length === 1) { - // // this.canvas.clearUndo(); - // // this.editor.clear(); - // } this.canvas.undo(); this.historyUpdate(); } diff --git a/packages/core/utils/fabric-history.js b/packages/core/utils/fabric-history.js index 868542c7..c98e3e00 100644 --- a/packages/core/utils/fabric-history.js +++ b/packages/core/utils/fabric-history.js @@ -1,8 +1,8 @@ /* * @Author: 秦少卫 * @Date: 2024-07-09 13:46:14 - * @LastEditors: 秦少卫 - * @LastEditTime: 2024-07-12 21:36:51 + * @LastEditors: George GeorgeSmith163@163.com + * @LastEditTime: 2024-10-14 16:16:16 * @Description: file content */ /** @@ -52,8 +52,9 @@ fabric.Canvas.prototype._historyEvents = function () { * Initialization of the plugin */ fabric.Canvas.prototype._historyInit = function () { - this.historyUndo = []; - this.historyRedo = []; + this.historyStack = []; + this.historyIndex = 0; + this.historyMaxLength = 100; this.extraProps = [ 'id', 'gradientAngle', @@ -65,6 +66,10 @@ fabric.Canvas.prototype._historyInit = function () { 'extension', ]; this.historyNextState = this._historyNext(); + // 需要两次操作的标记,为true时表示当前操作记录为最新记录,需要撤销两步,因为最顶层的是当前的最新记录,undo一次后后置为false + this.isLatestHistoryState = true; + // 正在读取历史记录的标记,为 true 时不允许 undo/redo + this.isLoadingHistory = false; this.on(this._historyEvents()); }; @@ -83,9 +88,19 @@ fabric.Canvas.prototype._historySaveAction = function (e) { if (this.historyProcessing) return; if (!e || (e.target && !e.target.excludeFromExport)) { const json = this._historyNext(); - this.historyUndo.push(json); + // 当前操作记录非最新记录,更新记录前需要校正历史索引,不然会丢失一个记录(undo时撤销了两次记录)。理论上不会超出历史记录上限,不过还是加了限制 + !this.isLatestHistoryState && + (this.isLatestHistoryState = true) && + this.historyIndex < this.historyMaxLength && + this.historyIndex++; + // 每次的最新操作都要清空历史索引之后的记录,防止redo旧记录,不然可能会redo之前某个阶段的操作记录 + this.historyStack.length > this.historyIndex && this.historyStack.splice(this.historyIndex); + // 最多保存 historyMaxLength 条记录 + if (this.historyIndex >= this.historyMaxLength) this.historyStack.shift(); + this.historyIndex < this.historyMaxLength && this.historyIndex++; + this.historyStack.push(json); this.historyNextState = this._historyNext(); - this.fire('history:append', { json: json }); + this.fire('history:append', { json }); } }; @@ -95,19 +110,23 @@ fabric.Canvas.prototype._historySaveAction = function (e) { * Also, pushes into redo history. */ fabric.Canvas.prototype.undo = function (callback) { + if (this.isLoadingHistory) return; + if (this.historyIndex <= 0) return; // The undo process will render the new states of the objects // Therefore, object:added and object:modified events will triggered again // To ignore those events, we are setting a flag. this.historyProcessing = true; - const history = this.historyUndo.pop(); + // 当前操作记录为最新记录,需要撤销两步,因为最顶层的是当前的最新记录 + this.isLatestHistoryState && this.historyIndex-- && (this.isLatestHistoryState = false); + const history = this.historyStack[--this.historyIndex]; if (history) { // Push the current state to the redo history - this.historyRedo.push(this._historyNext()); this.historyNextState = history; this._loadHistory(history, 'history:undo', callback); } else { console.log(1111); + this.historyIndex < 0 && (this.historyIndex = 0); this.historyProcessing = false; } }; @@ -116,31 +135,40 @@ fabric.Canvas.prototype.undo = function (callback) { * Redo to latest undo history. */ fabric.Canvas.prototype.redo = function (callback) { + if (this.isLoadingHistory) return; + if (this.historyIndex >= this.historyStack.length) return; // The undo process will render the new states of the objects // Therefore, object:added and object:modified events will triggered again // To ignore those events, we are setting a flag. this.historyProcessing = true; - const history = this.historyRedo.pop(); + // 当前操作记录不是最新记录(被撤销过),需要恢复两步,抵消最初撤销时撤销两步的操作 + !this.isLatestHistoryState && ++this.historyIndex && (this.isLatestHistoryState = true); + const history = this.historyStack[this.historyIndex]; if (history) { // Every redo action is actually a new action to the undo history - this.historyUndo.push(this._historyNext()); this.historyNextState = history; this._loadHistory(history, 'history:redo', callback); + this.historyIndex++; } else { this.historyProcessing = false; } }; +// loadFromJSON 是异步操作,所以通过 isLoadingHistory = true 表示历史读取中,不可 undo/redo, +// 不然当页面复杂且快速 undo/redo 多次后,可能会在之前的历史上 redo/undo fabric.Canvas.prototype._loadHistory = function (history, event, callback) { + this.isLoadingHistory = true; var that = this; - history?.objects?.forEach((item) => { - if (item?.id === 'workspace') item.evented = false; - }); + // 需要把历史记录中的 workspace 的 evented 属性设置为 false,否则会导致历史记录恢复后,鼠标悬浮 workspace 出现可操作的样式 + const workspaceHistory = history.objects?.find((item) => item.id === 'workspace'); + workspaceHistory && (workspaceHistory.evented = false); + this.loadFromJSON(history, function () { that.renderAll(); that.fire(event); that.historyProcessing = false; + that.isLoadingHistory = false; if (callback && typeof callback === 'function') callback(); }); @@ -150,20 +178,28 @@ fabric.Canvas.prototype._loadHistory = function (history, event, callback) { * Clear undo and redo history stacks */ fabric.Canvas.prototype.clearHistory = function (type) { - if (!type) { - this.historyUndo = []; - this.historyRedo = []; + const one = this.historyStack.pop(); + if (!type || !one) { + this.historyStack = []; + this.historyIndex = 0; this.fire('history:clear'); } else { - const one = this.historyUndo.pop(); - this.historyUndo = [one]; - this.historyRedo = []; + this.historyStack = [one]; + this.historyIndex = 1; this.fire('history:clear'); } + this.isLatestHistoryState = true; }; fabric.Canvas.prototype.clearUndo = function () { - this.historyUndo = []; + this.historyStack.splice(this.historyIndex); +}; + +// 如果在做一些操作之后,需要撤销上一步的操作并刷新历史记录(想在监听modified事件后做些额外的操作并记录操作后的历史),可以调用这个方法 +fabric.Canvas.prototype.refreshHistory = function () { + this.historyProcessing = false; + this.historyStack.splice(--this.historyIndex); + this._historySaveAction(); }; /** @@ -180,14 +216,14 @@ fabric.Canvas.prototype.onHistory = function () { */ fabric.Canvas.prototype.canUndo = function () { - return this.historyUndo.length > 0; + return this.historyIndex > 0; }; /** * Check if there are actions that can be redone */ fabric.Canvas.prototype.canRedo = function () { - return this.historyRedo.length > 0; + return this.historyStack.length > this.historyIndex; }; /**