js-draw 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -503,11 +503,12 @@ export class Editor {
503
503
  * ```
504
504
  */
505
505
  dispatchNoAnnounce(command, addToHistory = false) {
506
+ const result = command.apply(this);
506
507
  if (addToHistory) {
507
508
  const apply = false; // Don't double-apply
508
509
  this.history.push(command, apply);
509
510
  }
510
- return command.apply(this);
511
+ return result;
511
512
  }
512
513
  /**
513
514
  * Apply a large transformation in chunks.
@@ -172,9 +172,13 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
172
172
  super('add-element');
173
173
  this.element = element;
174
174
  this.applyByFlattening = applyByFlattening;
175
- // Store the element's serialization --- .serializeToJSON may be called on this
176
- // even when this is not at the top of the undo/redo stack.
177
- this.serializedElem = element.serialize();
175
+ this.serializedElem = null;
176
+ // FIXME: The serialized version of this command may be inaccurate if this is
177
+ // serialized when this command is not on the top of the undo stack.
178
+ //
179
+ // Caching the element's serialized data leads to additional memory usage *and*
180
+ // sometimes incorrect behavior in collaborative editing.
181
+ this.serializedElem = null;
178
182
  if (isNaN(element.getBBox().area)) {
179
183
  throw new Error('Elements in the image cannot have NaN bounding boxes');
180
184
  }
@@ -197,8 +201,9 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
197
201
  return localization.addElementAction(this.element.description(localization));
198
202
  }
199
203
  serializeToJSON() {
204
+ var _a;
200
205
  return {
201
- elemData: this.serializedElem,
206
+ elemData: (_a = this.serializedElem) !== null && _a !== void 0 ? _a : this.element.serialize(),
202
207
  };
203
208
  }
204
209
  },
@@ -207,7 +212,9 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
207
212
  const id = json.elemData.id;
208
213
  const foundElem = editor.image.lookupElement(id);
209
214
  const elem = foundElem !== null && foundElem !== void 0 ? foundElem : AbstractComponent.deserialize(json.elemData);
210
- return new EditorImage.AddElementCommand(elem);
215
+ const result = new EditorImage.AddElementCommand(elem);
216
+ result.serializedElem = json.elemData;
217
+ return result;
211
218
  });
212
219
  })(),
213
220
  _a);
@@ -215,6 +215,12 @@ export default class SVGLoader {
215
215
  // In some environments, computedStyles.fontSize can be increased by the system.
216
216
  // Thus, to prevent text from growing on load/save, prefer .style.fontSize.
217
217
  let fontSizeMatch = fontSizeExp.exec(elem.style.fontSize);
218
+ if (!fontSizeMatch && elem.tagName.toLowerCase() === 'tspan' && elem.parentElement) {
219
+ // Try to inherit the font size of the parent text element.
220
+ fontSizeMatch = fontSizeExp.exec(elem.parentElement.style.fontSize);
221
+ }
222
+ // If we still couldn't find a font size, try to use computedStyles (which can be
223
+ // wrong).
218
224
  if (!fontSizeMatch) {
219
225
  fontSizeMatch = fontSizeExp.exec(computedStyles.fontSize);
220
226
  }
@@ -3,12 +3,11 @@ import Command from './commands/Command';
3
3
  type AnnounceRedoCallback = (command: Command) => void;
4
4
  type AnnounceUndoCallback = (command: Command) => void;
5
5
  declare class UndoRedoHistory {
6
+ #private;
6
7
  private readonly editor;
7
8
  private announceRedoCallback;
8
9
  private announceUndoCallback;
9
- private undoStack;
10
- private redoStack;
11
- private maxUndoRedoStackSize;
10
+ private readonly maxUndoRedoStackSize;
12
11
  constructor(editor: Editor, announceRedoCallback: AnnounceRedoCallback, announceUndoCallback: AnnounceUndoCallback);
13
12
  private fireUpdateEvent;
14
13
  push(command: Command, apply?: boolean): void;
@@ -1,19 +1,35 @@
1
- import { EditorEventType } from './types';
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _UndoRedoHistory_undoStack, _UndoRedoHistory_redoStack;
13
+ import { EditorEventType, UndoEventType } from './types';
2
14
  class UndoRedoHistory {
3
15
  // @internal
4
16
  constructor(editor, announceRedoCallback, announceUndoCallback) {
5
17
  this.editor = editor;
6
18
  this.announceRedoCallback = announceRedoCallback;
7
19
  this.announceUndoCallback = announceUndoCallback;
20
+ _UndoRedoHistory_undoStack.set(this, void 0);
21
+ _UndoRedoHistory_redoStack.set(this, void 0);
8
22
  this.maxUndoRedoStackSize = 700;
9
- this.undoStack = [];
10
- this.redoStack = [];
23
+ __classPrivateFieldSet(this, _UndoRedoHistory_undoStack, [], "f");
24
+ __classPrivateFieldSet(this, _UndoRedoHistory_redoStack, [], "f");
11
25
  }
12
- fireUpdateEvent() {
26
+ fireUpdateEvent(stackUpdateType, triggeringCommand) {
13
27
  this.editor.notifier.dispatch(EditorEventType.UndoRedoStackUpdated, {
14
28
  kind: EditorEventType.UndoRedoStackUpdated,
15
- undoStackSize: this.undoStack.length,
16
- redoStackSize: this.redoStack.length,
29
+ undoStackSize: __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").length,
30
+ redoStackSize: __classPrivateFieldGet(this, _UndoRedoHistory_redoStack, "f").length,
31
+ command: triggeringCommand,
32
+ stackUpdateType,
17
33
  });
18
34
  }
19
35
  // Adds the given command to this and applies it to the editor.
@@ -21,17 +37,17 @@ class UndoRedoHistory {
21
37
  if (apply) {
22
38
  command.apply(this.editor);
23
39
  }
24
- this.undoStack.push(command);
25
- for (const elem of this.redoStack) {
40
+ __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").push(command);
41
+ for (const elem of __classPrivateFieldGet(this, _UndoRedoHistory_redoStack, "f")) {
26
42
  elem.onDrop(this.editor);
27
43
  }
28
- this.redoStack = [];
29
- if (this.undoStack.length > this.maxUndoRedoStackSize) {
44
+ __classPrivateFieldSet(this, _UndoRedoHistory_redoStack, [], "f");
45
+ if (__classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").length > this.maxUndoRedoStackSize) {
30
46
  const removeAtOnceCount = 10;
31
- const removedElements = this.undoStack.splice(0, removeAtOnceCount);
47
+ const removedElements = __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").splice(0, removeAtOnceCount);
32
48
  removedElements.forEach(elem => elem.onDrop(this.editor));
33
49
  }
34
- this.fireUpdateEvent();
50
+ this.fireUpdateEvent(UndoEventType.CommandDone, command);
35
51
  this.editor.notifier.dispatch(EditorEventType.CommandDone, {
36
52
  kind: EditorEventType.CommandDone,
37
53
  command,
@@ -39,12 +55,12 @@ class UndoRedoHistory {
39
55
  }
40
56
  // Remove the last command from this' undo stack and apply it.
41
57
  undo() {
42
- const command = this.undoStack.pop();
58
+ const command = __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").pop();
43
59
  if (command) {
44
- this.redoStack.push(command);
60
+ __classPrivateFieldGet(this, _UndoRedoHistory_redoStack, "f").push(command);
45
61
  command.unapply(this.editor);
46
62
  this.announceUndoCallback(command);
47
- this.fireUpdateEvent();
63
+ this.fireUpdateEvent(UndoEventType.CommandUndone, command);
48
64
  this.editor.notifier.dispatch(EditorEventType.CommandUndone, {
49
65
  kind: EditorEventType.CommandUndone,
50
66
  command,
@@ -52,12 +68,12 @@ class UndoRedoHistory {
52
68
  }
53
69
  }
54
70
  redo() {
55
- const command = this.redoStack.pop();
71
+ const command = __classPrivateFieldGet(this, _UndoRedoHistory_redoStack, "f").pop();
56
72
  if (command) {
57
- this.undoStack.push(command);
73
+ __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").push(command);
58
74
  command.apply(this.editor);
59
75
  this.announceRedoCallback(command);
60
- this.fireUpdateEvent();
76
+ this.fireUpdateEvent(UndoEventType.CommandRedone, command);
61
77
  this.editor.notifier.dispatch(EditorEventType.CommandDone, {
62
78
  kind: EditorEventType.CommandDone,
63
79
  command,
@@ -65,10 +81,11 @@ class UndoRedoHistory {
65
81
  }
66
82
  }
67
83
  get undoStackSize() {
68
- return this.undoStack.length;
84
+ return __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").length;
69
85
  }
70
86
  get redoStackSize() {
71
- return this.redoStack.length;
87
+ return __classPrivateFieldGet(this, _UndoRedoHistory_redoStack, "f").length;
72
88
  }
73
89
  }
90
+ _UndoRedoHistory_undoStack = new WeakMap(), _UndoRedoHistory_redoStack = new WeakMap();
74
91
  export default UndoRedoHistory;
@@ -56,8 +56,8 @@ class DefaultRestyleComponentCommand extends UnresolvedSerializableCommand {
56
56
  unapply(editor) {
57
57
  this.getComponent(editor).forceStyle(this.originalStyle, editor);
58
58
  }
59
- description(_editor, localizationTable) {
60
- return localizationTable.restyledElements;
59
+ description(editor, localizationTable) {
60
+ return localizationTable.restyledElement(this.getComponent(editor).description(localizationTable));
61
61
  }
62
62
  serializeToJSON() {
63
63
  return {
@@ -6,6 +6,6 @@ export interface ImageComponentLocalization {
6
6
  svgObject: string;
7
7
  emptyBackground: string;
8
8
  filledBackgroundWithColor: (color: string) => string;
9
- restyledElements: string;
9
+ restyledElement: (elementDescription: string) => string;
10
10
  }
11
11
  export declare const defaultComponentLocalization: ImageComponentLocalization;
@@ -2,9 +2,9 @@ export const defaultComponentLocalization = {
2
2
  unlabeledImageNode: 'Unlabeled image node',
3
3
  stroke: 'Stroke',
4
4
  svgObject: 'SVG Object',
5
- restyledElements: 'Restyled elements',
6
5
  emptyBackground: 'Empty background',
7
6
  filledBackgroundWithColor: (color) => `Filled background (${color})`,
8
7
  text: (text) => `Text object: ${text}`,
9
8
  imageNode: (description) => `Image: ${description}`,
9
+ restyledElement: (elementDescription) => `Restyled ${elementDescription}`,
10
10
  };
package/dist/src/lib.d.ts CHANGED
@@ -28,5 +28,6 @@ export * from './toolbar/lib';
28
28
  export * from './rendering/lib';
29
29
  export { default as Pointer, PointerDevice } from './Pointer';
30
30
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
31
+ export { default as UndoRedoHistory } from './UndoRedoHistory';
31
32
  export { Editor, EditorSettings };
32
33
  export default Editor;
package/dist/src/lib.js CHANGED
@@ -28,5 +28,6 @@ export * from './toolbar/lib';
28
28
  export * from './rendering/lib';
29
29
  export { default as Pointer, PointerDevice } from './Pointer';
30
30
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
31
+ export { default as UndoRedoHistory } from './UndoRedoHistory';
31
32
  export { Editor };
32
33
  export default Editor;
@@ -14,6 +14,7 @@ export default class HTMLToolbar {
14
14
  private container;
15
15
  private resizeObserver;
16
16
  private listeners;
17
+ private widgetOrderCounter;
17
18
  private widgetsById;
18
19
  private widgetList;
19
20
  private overflowWidget;
@@ -23,6 +23,8 @@ export default class HTMLToolbar {
23
23
  this.editor = editor;
24
24
  this.localizationTable = localizationTable;
25
25
  this.listeners = [];
26
+ // Flex-order of the next widget to be added.
27
+ this.widgetOrderCounter = 0;
26
28
  this.widgetsById = {};
27
29
  this.widgetList = [];
28
30
  // Widget to toggle overflow menu.
@@ -197,7 +199,7 @@ export default class HTMLToolbar {
197
199
  this.setupColorPickers();
198
200
  // Ensure that the widget gets displayed in the correct
199
201
  // place in the toolbar, even if it's removed and re-added.
200
- container.style.order = `${this.widgetList.length}`;
202
+ container.style.order = `${this.widgetOrderCounter++}`;
201
203
  this.queueReLayout();
202
204
  }
203
205
  /**
@@ -233,6 +235,7 @@ export default class HTMLToolbar {
233
235
  if (options.maxSize) {
234
236
  spacer.style.maxWidth = options.maxSize;
235
237
  }
238
+ spacer.style.order = `${this.widgetOrderCounter++}`;
236
239
  this.container.appendChild(spacer);
237
240
  }
238
241
  serializeState() {
@@ -4,6 +4,7 @@ export default class OverflowWidget extends BaseWidget {
4
4
  var _a;
5
5
  super(editor, 'overflow-widget', localizationTable);
6
6
  this.overflowChildren = [];
7
+ this.container.classList.add('toolbar-overflow-widget');
7
8
  // Make the dropdown openable
8
9
  this.container.classList.add('dropdownShowable');
9
10
  (_a = this.overflowContainer) !== null && _a !== void 0 ? _a : (this.overflowContainer = document.createElement('div'));
@@ -32,6 +33,7 @@ export default class OverflowWidget extends BaseWidget {
32
33
  */
33
34
  clearChildren() {
34
35
  this.overflowContainer.replaceChildren();
36
+ this.container.classList.remove('horizontal');
35
37
  const overflowChildren = this.overflowChildren;
36
38
  this.overflowChildren = [];
37
39
  return overflowChildren;
@@ -56,6 +58,10 @@ export default class OverflowWidget extends BaseWidget {
56
58
  this.overflowChildren.push(widget);
57
59
  widget.addTo(this.overflowContainer);
58
60
  widget.setIsToplevel(false);
61
+ // Switch to a horizontal layout if enough children
62
+ if (this.overflowChildren.length > 2) {
63
+ this.container.classList.add('horizontal');
64
+ }
59
65
  }
60
66
  // This always returns false.
61
67
  // Don't try to move the overflow menu to itself.
@@ -284,7 +284,7 @@ export default class SelectionTool extends BaseTool {
284
284
  });
285
285
  return true;
286
286
  }
287
- if (evt.key === 'a') {
287
+ if (evt.key === 'a' || evt.key === 'r') {
288
288
  // Selected all in onKeyDown. Don't finalizeTransform.
289
289
  return true;
290
290
  }
@@ -85,6 +85,11 @@ export declare enum EditorEventType {
85
85
  ColorPickerColorSelected = 10,
86
86
  ToolbarDropdownShown = 11
87
87
  }
88
+ export declare enum UndoEventType {
89
+ CommandDone = 0,
90
+ CommandUndone = 1,
91
+ CommandRedone = 2
92
+ }
88
93
  type EditorToolEventType = EditorEventType.ToolEnabled | EditorEventType.ToolDisabled | EditorEventType.ToolUpdated;
89
94
  export interface EditorToolEvent {
90
95
  readonly kind: EditorToolEventType;
@@ -107,6 +112,8 @@ export interface EditorUndoStackUpdated {
107
112
  readonly kind: EditorEventType.UndoRedoStackUpdated;
108
113
  readonly undoStackSize: number;
109
114
  readonly redoStackSize: number;
115
+ readonly command?: Command;
116
+ readonly stackUpdateType: UndoEventType;
110
117
  }
111
118
  export interface CommandDoneEvent {
112
119
  readonly kind: EditorEventType.CommandDone;
package/dist/src/types.js CHANGED
@@ -26,3 +26,10 @@ export var EditorEventType;
26
26
  EditorEventType[EditorEventType["ColorPickerColorSelected"] = 10] = "ColorPickerColorSelected";
27
27
  EditorEventType[EditorEventType["ToolbarDropdownShown"] = 11] = "ToolbarDropdownShown";
28
28
  })(EditorEventType || (EditorEventType = {}));
29
+ // Types of `EditorUndoStackUpdated` events.
30
+ export var UndoEventType;
31
+ (function (UndoEventType) {
32
+ UndoEventType[UndoEventType["CommandDone"] = 0] = "CommandDone";
33
+ UndoEventType[UndoEventType["CommandUndone"] = 1] = "CommandUndone";
34
+ UndoEventType[UndoEventType["CommandRedone"] = 2] = "CommandRedone";
35
+ })(UndoEventType || (UndoEventType = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "./dist/src/lib.d.ts",
6
6
  "types": "./dist/src/lib.js",
package/src/Editor.css CHANGED
@@ -51,6 +51,10 @@
51
51
  box-sizing: border-box;
52
52
  width: 100%;
53
53
  height: 100%;
54
+
55
+ /* Allow the canvas to shrink (needed in Chrome) */
56
+ min-height: 0px;
57
+ max-height: inherit;
54
58
  }
55
59
 
56
60
  .imageEditorContainer .loadingMessage {
package/src/Editor.ts CHANGED
@@ -663,12 +663,14 @@ export class Editor {
663
663
  * ```
664
664
  */
665
665
  public dispatchNoAnnounce(command: Command, addToHistory: boolean = false) {
666
+ const result = command.apply(this);
667
+
666
668
  if (addToHistory) {
667
669
  const apply = false; // Don't double-apply
668
670
  this.history.push(command, apply);
669
671
  }
670
672
 
671
- return command.apply(this);
673
+ return result;
672
674
  }
673
675
 
674
676
  /**
@@ -211,7 +211,7 @@ export default class EditorImage {
211
211
 
212
212
  // A Command that can access private [EditorImage] functionality
213
213
  private static AddElementCommand = class extends SerializableCommand {
214
- private serializedElem: any;
214
+ private serializedElem: any|null = null;
215
215
 
216
216
  // If [applyByFlattening], then the rendered content of this element
217
217
  // is present on the display's wet ink canvas. As such, no re-render is necessary
@@ -222,9 +222,12 @@ export default class EditorImage {
222
222
  ) {
223
223
  super('add-element');
224
224
 
225
- // Store the element's serialization --- .serializeToJSON may be called on this
226
- // even when this is not at the top of the undo/redo stack.
227
- this.serializedElem = element.serialize();
225
+ // FIXME: The serialized version of this command may be inaccurate if this is
226
+ // serialized when this command is not on the top of the undo stack.
227
+ //
228
+ // Caching the element's serialized data leads to additional memory usage *and*
229
+ // sometimes incorrect behavior in collaborative editing.
230
+ this.serializedElem = null;
228
231
 
229
232
  if (isNaN(element.getBBox().area)) {
230
233
  throw new Error('Elements in the image cannot have NaN bounding boxes');
@@ -253,7 +256,7 @@ export default class EditorImage {
253
256
 
254
257
  protected serializeToJSON() {
255
258
  return {
256
- elemData: this.serializedElem,
259
+ elemData: this.serializedElem ?? this.element.serialize(),
257
260
  };
258
261
  }
259
262
 
@@ -262,7 +265,9 @@ export default class EditorImage {
262
265
  const id = json.elemData.id;
263
266
  const foundElem = editor.image.lookupElement(id);
264
267
  const elem = foundElem ?? AbstractComponent.deserialize(json.elemData);
265
- return new EditorImage.AddElementCommand(elem);
268
+ const result = new EditorImage.AddElementCommand(elem);
269
+ result.serializedElem = json.elemData;
270
+ return result;
266
271
  });
267
272
  }
268
273
  };
@@ -54,4 +54,61 @@ describe('SVGLoader', () => {
54
54
  expect(elem.getBBox().topLeft.x).toBe(0);
55
55
  expect(elem.getBBox().h).toBeGreaterThan(200);
56
56
  });
57
+
58
+ it('tspans without specified font-sizes should inherit their font size from their parent element', async () => {
59
+ const editor = createEditor();
60
+ await editor.loadFrom(SVGLoader.fromString(`
61
+ <svg>
62
+ <text style='font-size: 22px;'>
63
+ Testing...
64
+ <tspan>Test 2...</tspan>
65
+ <tspan>Test 3...</tspan>
66
+ <tspan style='font-size: 3px;'>Test 4...</tspan>
67
+ </text>
68
+ </svg>
69
+ `, true));
70
+ const elem = editor.image
71
+ .getAllElements()
72
+ .filter(elem => elem instanceof TextComponent)[0] as TextComponent;
73
+ expect(elem).not.toBeNull();
74
+
75
+ // Ensure each child object has the correct size
76
+ expect(elem.serialize().data).toMatchObject({
77
+ 'textObjects': [
78
+ { },
79
+ {
80
+ 'json':
81
+ {
82
+ 'textObjects': [{ 'text': 'Test 2...' }],
83
+ 'style': {
84
+ 'size': 22,
85
+ }
86
+ }
87
+ },
88
+ { },
89
+ {
90
+ 'json': {
91
+ 'textObjects': [{ 'text': 'Test 3...' }],
92
+ 'style': {
93
+ 'size': 22
94
+ }
95
+ }
96
+ },
97
+ { },
98
+ {
99
+ 'json': {
100
+ 'textObjects': [{ 'text': 'Test 4...' }],
101
+ 'style': {
102
+ 'size': 3,
103
+ }
104
+ }
105
+ },
106
+ { }
107
+ ],
108
+
109
+ 'style': {
110
+ 'size': 22,
111
+ }
112
+ });
113
+ });
57
114
  });
package/src/SVGLoader.ts CHANGED
@@ -257,6 +257,13 @@ export default class SVGLoader implements ImageLoader {
257
257
  // In some environments, computedStyles.fontSize can be increased by the system.
258
258
  // Thus, to prevent text from growing on load/save, prefer .style.fontSize.
259
259
  let fontSizeMatch = fontSizeExp.exec(elem.style.fontSize);
260
+ if (!fontSizeMatch && elem.tagName.toLowerCase() === 'tspan' && elem.parentElement) {
261
+ // Try to inherit the font size of the parent text element.
262
+ fontSizeMatch = fontSizeExp.exec(elem.parentElement.style.fontSize);
263
+ }
264
+
265
+ // If we still couldn't find a font size, try to use computedStyles (which can be
266
+ // wrong).
260
267
  if (!fontSizeMatch) {
261
268
  fontSizeMatch = fontSizeExp.exec(computedStyles.fontSize);
262
269
  }
@@ -1,15 +1,15 @@
1
1
  import Editor from './Editor';
2
2
  import Command from './commands/Command';
3
- import { EditorEventType } from './types';
3
+ import { EditorEventType, UndoEventType } from './types';
4
4
 
5
5
  type AnnounceRedoCallback = (command: Command)=>void;
6
6
  type AnnounceUndoCallback = (command: Command)=>void;
7
7
 
8
8
  class UndoRedoHistory {
9
- private undoStack: Command[];
10
- private redoStack: Command[];
9
+ #undoStack: Command[];
10
+ #redoStack: Command[];
11
11
 
12
- private maxUndoRedoStackSize: number = 700;
12
+ private readonly maxUndoRedoStackSize: number = 700;
13
13
 
14
14
  // @internal
15
15
  public constructor(
@@ -17,15 +17,20 @@ class UndoRedoHistory {
17
17
  private announceRedoCallback: AnnounceRedoCallback,
18
18
  private announceUndoCallback: AnnounceUndoCallback,
19
19
  ) {
20
- this.undoStack = [];
21
- this.redoStack = [];
20
+ this.#undoStack = [];
21
+ this.#redoStack = [];
22
22
  }
23
23
 
24
- private fireUpdateEvent() {
24
+ private fireUpdateEvent(
25
+ stackUpdateType: UndoEventType, triggeringCommand: Command
26
+ ) {
25
27
  this.editor.notifier.dispatch(EditorEventType.UndoRedoStackUpdated, {
26
28
  kind: EditorEventType.UndoRedoStackUpdated,
27
- undoStackSize: this.undoStack.length,
28
- redoStackSize: this.redoStack.length,
29
+ undoStackSize: this.#undoStack.length,
30
+ redoStackSize: this.#redoStack.length,
31
+
32
+ command: triggeringCommand,
33
+ stackUpdateType,
29
34
  });
30
35
  }
31
36
 
@@ -34,20 +39,20 @@ class UndoRedoHistory {
34
39
  if (apply) {
35
40
  command.apply(this.editor);
36
41
  }
37
- this.undoStack.push(command);
42
+ this.#undoStack.push(command);
38
43
 
39
- for (const elem of this.redoStack) {
44
+ for (const elem of this.#redoStack) {
40
45
  elem.onDrop(this.editor);
41
46
  }
42
- this.redoStack = [];
47
+ this.#redoStack = [];
43
48
 
44
- if (this.undoStack.length > this.maxUndoRedoStackSize) {
49
+ if (this.#undoStack.length > this.maxUndoRedoStackSize) {
45
50
  const removeAtOnceCount = 10;
46
- const removedElements = this.undoStack.splice(0, removeAtOnceCount);
51
+ const removedElements = this.#undoStack.splice(0, removeAtOnceCount);
47
52
  removedElements.forEach(elem => elem.onDrop(this.editor));
48
53
  }
49
54
 
50
- this.fireUpdateEvent();
55
+ this.fireUpdateEvent(UndoEventType.CommandDone, command);
51
56
  this.editor.notifier.dispatch(EditorEventType.CommandDone, {
52
57
  kind: EditorEventType.CommandDone,
53
58
  command,
@@ -56,13 +61,13 @@ class UndoRedoHistory {
56
61
 
57
62
  // Remove the last command from this' undo stack and apply it.
58
63
  public undo() {
59
- const command = this.undoStack.pop();
64
+ const command = this.#undoStack.pop();
60
65
  if (command) {
61
- this.redoStack.push(command);
66
+ this.#redoStack.push(command);
62
67
  command.unapply(this.editor);
63
68
  this.announceUndoCallback(command);
64
69
 
65
- this.fireUpdateEvent();
70
+ this.fireUpdateEvent(UndoEventType.CommandUndone, command);
66
71
  this.editor.notifier.dispatch(EditorEventType.CommandUndone, {
67
72
  kind: EditorEventType.CommandUndone,
68
73
  command,
@@ -71,13 +76,13 @@ class UndoRedoHistory {
71
76
  }
72
77
 
73
78
  public redo() {
74
- const command = this.redoStack.pop();
79
+ const command = this.#redoStack.pop();
75
80
  if (command) {
76
- this.undoStack.push(command);
81
+ this.#undoStack.push(command);
77
82
  command.apply(this.editor);
78
83
  this.announceRedoCallback(command);
79
84
 
80
- this.fireUpdateEvent();
85
+ this.fireUpdateEvent(UndoEventType.CommandRedone, command);
81
86
  this.editor.notifier.dispatch(EditorEventType.CommandDone, {
82
87
  kind: EditorEventType.CommandDone,
83
88
  command,
@@ -86,11 +91,11 @@ class UndoRedoHistory {
86
91
  }
87
92
 
88
93
  public get undoStackSize(): number {
89
- return this.undoStack.length;
94
+ return this.#undoStack.length;
90
95
  }
91
96
 
92
97
  public get redoStackSize(): number {
93
- return this.redoStack.length;
98
+ return this.#redoStack.length;
94
99
  }
95
100
  }
96
101
 
@@ -111,8 +111,8 @@ class DefaultRestyleComponentCommand extends UnresolvedSerializableCommand {
111
111
  this.getComponent(editor).forceStyle(this.originalStyle, editor);
112
112
  }
113
113
 
114
- public description(_editor: Editor, localizationTable: EditorLocalization): string {
115
- return localizationTable.restyledElements;
114
+ public description(editor: Editor, localizationTable: EditorLocalization): string {
115
+ return localizationTable.restyledElement(this.getComponent(editor).description(localizationTable));
116
116
  }
117
117
 
118
118
  protected serializeToJSON() {