zhengyiming
17 小时以前 745f1e2ee7072731611391b89c5c0020783828bf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import { fabric } from 'fabric';
 
fabric.Canvas.prototype.initialize = (function (originalFn) {
  return function (...args) {
    originalFn.call(this, ...args);
    this._historyInit();
    return this;
  };
})(fabric.Canvas.prototype.initialize);
 
/**
 * Override the dispose function for the _historyDispose();
 */
fabric.Canvas.prototype.dispose = (function (originalFn) {
  return function (...args) {
    originalFn.call(this, ...args);
    this._historyDispose();
    return this;
  };
})(fabric.Canvas.prototype.dispose);
 
/**
 * Returns current state of the string of the canvas
 */
fabric.Canvas.prototype._historyNext = function () {
  return JSON.stringify(this.toDatalessJSON(this.extraProps));
};
 
/**
 * Returns an object with fabricjs event mappings
 */
fabric.Canvas.prototype._historyEvents = function () {
  return {
    'object:added': (e) => this._historySaveAction(e),
    'object:removed': (e) => this._historySaveAction(e),
    'object:modified': (e) => this._historySaveAction(e),
    'object:skewing': (e) => this._historySaveAction(e),
  };
};
 
/**
 * Initialization of the plugin
 */
fabric.Canvas.prototype._historyInit = function () {
  this.historyStack = [];
  this.historyIndex = 0;
  this.historyMaxLength = 100;
  this.extraProps = [
    'id',
    'gradientAngle',
    'selectable',
    'hasControls',
    'linkData',
    'editable',
    'extensionType',
    'extension',
  ];
  this.historyNextState = this._historyNext();
  // 需要两次操作的标记,为true时表示当前操作记录为最新记录,需要撤销两步,因为最顶层的是当前的最新记录,undo一次后后置为false
  this.isLatestHistoryState = true;
  // 正在读取历史记录的标记,为 true 时不允许 undo/redo
  this.isLoadingHistory = false;
 
  this.on(this._historyEvents());
};
 
/**
 * Remove the custom event listeners
 */
fabric.Canvas.prototype._historyDispose = function () {
  this.off(this._historyEvents());
};
 
/**
 * It pushes the state of the canvas into history stack
 */
fabric.Canvas.prototype._historySaveAction = function (e) {
  if (this.historyProcessing) return;
  if (!e || (e.target && !e.target.excludeFromExport)) {
    const json = this._historyNext();
    // 当前操作记录非最新记录,更新记录前需要校正历史索引,不然会丢失一个记录(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 });
  }
};
 
/**
 * Undo to latest history.
 * Pop the latest state of the history. Re-render.
 * 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;
 
  // 当前操作记录为最新记录,需要撤销两步,因为最顶层的是当前的最新记录
  this.isLatestHistoryState && this.historyIndex-- && (this.isLatestHistoryState = false);
  const history = this.historyStack[--this.historyIndex];
  if (history) {
    // Push the current state to the redo history
    this.historyNextState = history;
    this._loadHistory(history, 'history:undo', callback);
  } else {
    console.log(1111);
    this.historyIndex < 0 && (this.historyIndex = 0);
    this.historyProcessing = false;
  }
};
 
/**
 * 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;
  // 当前操作记录不是最新记录(被撤销过),需要恢复两步,抵消最初撤销时撤销两步的操作
  !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.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;
  const that = this;
 
  // 需要把历史记录中的 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();
  });
};
 
/**
 * Clear undo and redo history stacks
 */
fabric.Canvas.prototype.clearHistory = function (type) {
  const one = this.historyStack.pop();
  if (!type || !one) {
    this.historyStack = [];
    this.historyIndex = 0;
    this.fire('history:clear');
  } else {
    this.historyStack = [one];
    this.historyIndex = 1;
    this.fire('history:clear');
  }
  this.isLatestHistoryState = true;
};
 
fabric.Canvas.prototype.clearUndo = function () {
  this.historyStack.splice(this.historyIndex);
};
 
// 如果在做一些操作之后,需要撤销上一步的操作并刷新历史记录(想在监听modified事件后做些额外的操作并记录操作后的历史),可以调用这个方法
fabric.Canvas.prototype.refreshHistory = function () {
  this.historyProcessing = false;
  this.historyStack.splice(--this.historyIndex);
  this._historySaveAction();
};
 
/**
 * On the history
 */
fabric.Canvas.prototype.onHistory = function () {
  this.historyProcessing = false;
 
  this._historySaveAction();
};
 
/**
 * Check if there are actions that can be undone
 */
 
fabric.Canvas.prototype.canUndo = function () {
  return this.historyIndex > 0;
};
 
/**
 * Check if there are actions that can be redone
 */
fabric.Canvas.prototype.canRedo = function () {
  return this.historyStack.length > this.historyIndex;
};
 
/**
 * Off the history
 */
fabric.Canvas.prototype.offHistory = function () {
  this.historyProcessing = true;
};