js-draw 0.1.11 → 0.2.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.
Files changed (220) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.firebaserc +5 -0
  3. package/.github/workflows/firebase-hosting-merge.yml +25 -0
  4. package/.github/workflows/firebase-hosting-pull-request.yml +22 -0
  5. package/.github/workflows/github-pages.yml +52 -0
  6. package/CHANGELOG.md +13 -0
  7. package/README.md +11 -6
  8. package/dist/bundle.js +1 -1
  9. package/dist/src/Color4.d.ts +19 -0
  10. package/dist/src/Color4.js +24 -3
  11. package/dist/src/Editor.d.ts +133 -4
  12. package/dist/src/Editor.js +124 -27
  13. package/dist/src/EditorImage.d.ts +8 -3
  14. package/dist/src/EditorImage.js +42 -26
  15. package/dist/src/EventDispatcher.d.ts +18 -0
  16. package/dist/src/EventDispatcher.js +19 -4
  17. package/dist/src/Pointer.d.ts +1 -1
  18. package/dist/src/Pointer.js +4 -3
  19. package/dist/src/SVGLoader.d.ts +1 -1
  20. package/dist/src/SVGLoader.js +14 -6
  21. package/dist/src/UndoRedoHistory.js +15 -2
  22. package/dist/src/Viewport.d.ts +8 -25
  23. package/dist/src/Viewport.js +18 -10
  24. package/dist/src/bundle/bundled.d.ts +1 -2
  25. package/dist/src/bundle/bundled.js +1 -2
  26. package/dist/src/commands/Command.d.ts +2 -2
  27. package/dist/src/commands/Command.js +4 -4
  28. package/dist/src/commands/Duplicate.d.ts +2 -2
  29. package/dist/src/commands/Duplicate.js +4 -5
  30. package/dist/src/commands/Erase.d.ts +2 -2
  31. package/dist/src/commands/Erase.js +7 -6
  32. package/dist/src/commands/SerializableCommand.d.ts +4 -5
  33. package/dist/src/commands/SerializableCommand.js +12 -4
  34. package/dist/src/commands/invertCommand.d.ts +4 -0
  35. package/dist/src/commands/invertCommand.js +44 -0
  36. package/dist/src/commands/lib.d.ts +6 -0
  37. package/dist/src/commands/lib.js +6 -0
  38. package/dist/src/commands/localization.d.ts +2 -1
  39. package/dist/src/commands/localization.js +1 -0
  40. package/dist/src/components/AbstractComponent.d.ts +16 -11
  41. package/dist/src/components/AbstractComponent.js +28 -17
  42. package/dist/src/components/SVGGlobalAttributesObject.d.ts +4 -4
  43. package/dist/src/components/SVGGlobalAttributesObject.js +8 -2
  44. package/dist/src/components/Stroke.d.ts +16 -6
  45. package/dist/src/components/Stroke.js +12 -9
  46. package/dist/src/components/Text.d.ts +5 -5
  47. package/dist/src/components/Text.js +9 -9
  48. package/dist/src/components/UnknownSVGObject.d.ts +4 -4
  49. package/dist/src/components/UnknownSVGObject.js +7 -2
  50. package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
  51. package/dist/src/components/builders/ArrowBuilder.js +1 -1
  52. package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
  53. package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
  54. package/dist/src/components/builders/LineBuilder.d.ts +1 -1
  55. package/dist/src/components/builders/LineBuilder.js +1 -1
  56. package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
  57. package/dist/src/components/builders/RectangleBuilder.js +3 -3
  58. package/dist/src/components/builders/types.d.ts +1 -1
  59. package/dist/src/components/lib.d.ts +4 -0
  60. package/dist/src/components/lib.js +4 -0
  61. package/dist/src/lib.d.ts +25 -0
  62. package/dist/src/lib.js +25 -0
  63. package/dist/src/localization.d.ts +1 -0
  64. package/dist/src/localization.js +5 -1
  65. package/dist/src/localizations/es.js +1 -1
  66. package/dist/src/{geometry → math}/LineSegment2.d.ts +0 -0
  67. package/dist/src/{geometry → math}/LineSegment2.js +0 -0
  68. package/dist/src/math/Mat33.d.ts +78 -0
  69. package/dist/src/{geometry → math}/Mat33.js +48 -20
  70. package/dist/src/{geometry → math}/Path.d.ts +2 -1
  71. package/dist/src/{geometry → math}/Path.js +59 -52
  72. package/dist/src/{geometry → math}/Rect2.d.ts +2 -2
  73. package/dist/src/{geometry → math}/Rect2.js +0 -0
  74. package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
  75. package/dist/src/{geometry → math}/Vec2.js +0 -0
  76. package/dist/src/math/Vec3.d.ts +96 -0
  77. package/dist/src/{geometry → math}/Vec3.js +63 -15
  78. package/dist/src/math/lib.d.ts +7 -0
  79. package/dist/src/math/lib.js +7 -0
  80. package/dist/src/math/rounding.d.ts +3 -0
  81. package/dist/src/math/rounding.js +121 -0
  82. package/dist/src/rendering/Display.d.ts +47 -1
  83. package/dist/src/rendering/Display.js +60 -15
  84. package/dist/src/rendering/caching/CacheRecord.d.ts +3 -2
  85. package/dist/src/rendering/caching/CacheRecord.js +4 -1
  86. package/dist/src/rendering/caching/CacheRecordManager.d.ts +5 -4
  87. package/dist/src/rendering/caching/CacheRecordManager.js +16 -4
  88. package/dist/src/rendering/caching/RenderingCache.d.ts +2 -3
  89. package/dist/src/rendering/caching/RenderingCache.js +10 -11
  90. package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
  91. package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
  92. package/dist/src/rendering/caching/testUtils.js +1 -1
  93. package/dist/src/rendering/caching/types.d.ts +2 -4
  94. package/dist/src/rendering/localization.d.ts +2 -0
  95. package/dist/src/rendering/localization.js +2 -0
  96. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
  97. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
  98. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
  99. package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
  100. package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
  101. package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
  102. package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
  103. package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
  104. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
  105. package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
  106. package/dist/src/toolbar/HTMLToolbar.js +1 -0
  107. package/dist/src/toolbar/icons.d.ts +3 -0
  108. package/dist/src/toolbar/icons.js +142 -132
  109. package/dist/src/toolbar/localization.d.ts +2 -1
  110. package/dist/src/toolbar/localization.js +2 -1
  111. package/dist/src/toolbar/makeColorInput.js +3 -2
  112. package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
  113. package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
  114. package/dist/src/toolbar/widgets/BaseWidget.js +2 -0
  115. package/dist/src/toolbar/widgets/HandToolWidget.js +3 -3
  116. package/dist/src/toolbar/widgets/PenWidget.js +1 -0
  117. package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
  118. package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
  119. package/dist/src/tools/Eraser.js +1 -1
  120. package/dist/src/tools/PanZoom.d.ts +1 -1
  121. package/dist/src/tools/PanZoom.js +24 -14
  122. package/dist/src/tools/Pen.d.ts +1 -2
  123. package/dist/src/tools/Pen.js +8 -1
  124. package/dist/src/tools/PipetteTool.js +1 -0
  125. package/dist/src/tools/SelectionTool.d.ts +3 -3
  126. package/dist/src/tools/SelectionTool.js +51 -28
  127. package/dist/src/tools/TextTool.js +1 -1
  128. package/dist/src/types.d.ts +21 -10
  129. package/dist/src/types.js +7 -5
  130. package/firebase.json +16 -0
  131. package/package.json +118 -101
  132. package/src/Color4.ts +23 -2
  133. package/src/Editor.ts +181 -37
  134. package/src/EditorImage.test.ts +2 -4
  135. package/src/EditorImage.ts +46 -28
  136. package/src/EventDispatcher.ts +21 -6
  137. package/src/Pointer.ts +4 -3
  138. package/src/SVGLoader.ts +14 -6
  139. package/src/UndoRedoHistory.ts +18 -2
  140. package/src/Viewport.ts +23 -18
  141. package/src/bundle/bundled.ts +1 -2
  142. package/src/commands/Command.ts +5 -5
  143. package/src/commands/Duplicate.ts +4 -5
  144. package/src/commands/Erase.ts +7 -6
  145. package/src/commands/SerializableCommand.ts +17 -9
  146. package/src/commands/invertCommand.ts +51 -0
  147. package/src/commands/lib.ts +14 -0
  148. package/src/commands/localization.ts +3 -1
  149. package/src/components/AbstractComponent.ts +35 -24
  150. package/src/components/SVGGlobalAttributesObject.ts +11 -4
  151. package/src/components/Stroke.test.ts +4 -6
  152. package/src/components/Stroke.ts +15 -11
  153. package/src/components/Text.test.ts +2 -2
  154. package/src/components/Text.ts +9 -10
  155. package/src/components/UnknownSVGObject.ts +10 -4
  156. package/src/components/builders/ArrowBuilder.ts +2 -2
  157. package/src/components/builders/FreehandLineBuilder.ts +190 -80
  158. package/src/components/builders/LineBuilder.ts +2 -2
  159. package/src/components/builders/RectangleBuilder.ts +3 -3
  160. package/src/components/builders/types.ts +1 -1
  161. package/src/components/lib.ts +9 -0
  162. package/src/lib.ts +28 -0
  163. package/src/localization.ts +6 -0
  164. package/src/localizations/es.ts +2 -1
  165. package/src/{geometry → math}/LineSegment2.test.ts +0 -0
  166. package/src/{geometry → math}/LineSegment2.ts +0 -0
  167. package/src/{geometry → math}/Mat33.test.ts +0 -0
  168. package/src/{geometry → math}/Mat33.ts +48 -20
  169. package/src/{geometry → math}/Path.fromString.test.ts +0 -0
  170. package/src/{geometry → math}/Path.test.ts +0 -0
  171. package/src/{geometry → math}/Path.toString.test.ts +11 -2
  172. package/src/{geometry → math}/Path.ts +61 -58
  173. package/src/{geometry → math}/Rect2.test.ts +0 -0
  174. package/src/{geometry → math}/Rect2.ts +2 -2
  175. package/src/{geometry → math}/Vec2.test.ts +0 -0
  176. package/src/{geometry → math}/Vec2.ts +0 -0
  177. package/src/{geometry → math}/Vec3.test.ts +0 -0
  178. package/src/{geometry → math}/Vec3.ts +64 -16
  179. package/src/math/lib.ts +15 -0
  180. package/src/math/rounding.test.ts +40 -0
  181. package/src/math/rounding.ts +147 -0
  182. package/src/rendering/Display.ts +63 -15
  183. package/src/rendering/caching/CacheRecord.test.ts +3 -3
  184. package/src/rendering/caching/CacheRecord.ts +6 -2
  185. package/src/rendering/caching/CacheRecordManager.ts +34 -8
  186. package/src/rendering/caching/RenderingCache.test.ts +3 -3
  187. package/src/rendering/caching/RenderingCache.ts +11 -16
  188. package/src/rendering/caching/RenderingCacheNode.ts +23 -7
  189. package/src/rendering/caching/testUtils.ts +1 -1
  190. package/src/rendering/caching/types.ts +2 -7
  191. package/src/rendering/localization.ts +4 -0
  192. package/src/rendering/renderers/AbstractRenderer.ts +4 -4
  193. package/src/rendering/renderers/CanvasRenderer.ts +5 -5
  194. package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
  195. package/src/rendering/renderers/DummyRenderer.ts +4 -4
  196. package/src/rendering/renderers/SVGRenderer.ts +10 -4
  197. package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
  198. package/src/toolbar/HTMLToolbar.ts +1 -0
  199. package/src/toolbar/icons.ts +157 -137
  200. package/src/toolbar/localization.ts +4 -2
  201. package/src/toolbar/makeColorInput.ts +3 -2
  202. package/src/toolbar/toolbar.css +1 -1
  203. package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
  204. package/src/toolbar/widgets/BaseWidget.ts +2 -0
  205. package/src/toolbar/widgets/HandToolWidget.ts +3 -3
  206. package/src/toolbar/widgets/PenWidget.ts +2 -0
  207. package/src/toolbar/widgets/SelectionWidget.ts +46 -41
  208. package/src/tools/Eraser.ts +2 -2
  209. package/src/tools/PanZoom.ts +28 -17
  210. package/src/tools/Pen.ts +11 -2
  211. package/src/tools/PipetteTool.ts +2 -0
  212. package/src/tools/SelectionTool.test.ts +2 -4
  213. package/src/tools/SelectionTool.ts +52 -24
  214. package/src/tools/TextTool.ts +2 -2
  215. package/src/tools/UndoRedoShortcut.test.ts +1 -1
  216. package/src/types.ts +23 -7
  217. package/tsconfig.json +4 -1
  218. package/typedoc.json +20 -0
  219. package/dist/src/geometry/Mat33.d.ts +0 -32
  220. package/dist/src/geometry/Vec3.d.ts +0 -34
@@ -1,12 +1,14 @@
1
1
  var _a;
2
2
  import AbstractComponent from './components/AbstractComponent';
3
- import Rect2 from './geometry/Rect2';
3
+ import Rect2 from './math/Rect2';
4
4
  import SerializableCommand from './commands/SerializableCommand';
5
+ // @internal
5
6
  export const sortLeavesByZIndex = (leaves) => {
6
7
  leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
7
8
  };
8
9
  // Handles lookup/storage of elements in the image
9
10
  export default class EditorImage {
11
+ // @internal
10
12
  constructor() {
11
13
  this.root = new ImageNode();
12
14
  this.componentsById = {};
@@ -21,13 +23,15 @@ export default class EditorImage {
21
23
  }
22
24
  return null;
23
25
  }
26
+ /** @internal */
24
27
  renderWithCache(screenRenderer, cache, viewport) {
25
28
  cache.render(screenRenderer, this.root, viewport);
26
29
  }
30
+ /** @internal */
27
31
  render(renderer, viewport) {
28
32
  this.root.render(renderer, viewport.visibleRect);
29
33
  }
30
- // Renders all nodes, even ones not within the viewport
34
+ /** Renders all nodes, even ones not within the viewport. @internal */
31
35
  renderAll(renderer) {
32
36
  const leaves = this.root.getLeaves();
33
37
  sortLeavesByZIndex(leaves);
@@ -40,6 +44,7 @@ export default class EditorImage {
40
44
  sortLeavesByZIndex(leaves);
41
45
  return leaves.map(leaf => leaf.getContent());
42
46
  }
47
+ /** @internal */
43
48
  onDestroyElement(elem) {
44
49
  delete this.componentsById[elem.getId()];
45
50
  }
@@ -83,24 +88,25 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
83
88
  container === null || container === void 0 ? void 0 : container.remove();
84
89
  editor.queueRerender();
85
90
  }
86
- description(localization) {
91
+ description(_editor, localization) {
87
92
  return localization.addElementAction(this.element.description(localization));
88
93
  }
89
- serializeToString() {
90
- return JSON.stringify({
94
+ serializeToJSON() {
95
+ return {
91
96
  elemData: this.element.serialize(),
92
- });
97
+ };
93
98
  }
94
99
  },
95
100
  (() => {
96
- SerializableCommand.register('add-element', (data, _editor) => {
97
- const json = JSON.parse(data);
98
- const elem = AbstractComponent.deserialize(json.elemData);
101
+ SerializableCommand.register('add-element', (json, editor) => {
102
+ const id = json.elemData.id;
103
+ const foundElem = editor.image.lookupElement(id);
104
+ const elem = foundElem !== null && foundElem !== void 0 ? foundElem : AbstractComponent.deserialize(json.elemData);
99
105
  return new EditorImage.AddElementCommand(elem);
100
106
  });
101
107
  })(),
102
108
  _a);
103
- // TODO: Assign leaf nodes to CacheNodes. When leaf nodes are modified, the corresponding CacheNodes can be updated.
109
+ /** Part of the Editor's image. @internal */
104
110
  export class ImageNode {
105
111
  constructor(parent = null) {
106
112
  this.parent = parent;
@@ -136,16 +142,22 @@ export class ImageNode {
136
142
  // Returns a list of `ImageNode`s with content (and thus no children).
137
143
  getLeavesIntersectingRegion(region, isTooSmall) {
138
144
  const result = [];
139
- // Don't render if too small
140
- if (isTooSmall === null || isTooSmall === void 0 ? void 0 : isTooSmall(this.bbox)) {
141
- return [];
142
- }
143
- if (this.content !== null && this.getBBox().intersects(region)) {
144
- result.push(this);
145
- }
146
- const children = this.getChildrenIntersectingRegion(region);
147
- for (const child of children) {
148
- result.push(...child.getLeavesIntersectingRegion(region, isTooSmall));
145
+ let current;
146
+ const workList = [];
147
+ workList.push(this);
148
+ const toNext = () => {
149
+ current = undefined;
150
+ const next = workList.pop();
151
+ if (next && !(isTooSmall === null || isTooSmall === void 0 ? void 0 : isTooSmall(next.bbox))) {
152
+ current = next;
153
+ if (current.content !== null && current.getBBox().intersection(region)) {
154
+ result.push(current);
155
+ }
156
+ workList.push(...current.getChildrenIntersectingRegion(region));
157
+ }
158
+ };
159
+ while (workList.length > 0) {
160
+ toNext();
149
161
  }
150
162
  return result;
151
163
  }
@@ -180,13 +192,17 @@ export class ImageNode {
180
192
  // share a parent.
181
193
  const leafBBox = leaf.getBBox();
182
194
  if (leafBBox.containsRect(this.getBBox())) {
183
- // Create a node for this' children and for the new content..
184
195
  const nodeForNewLeaf = new ImageNode(this);
185
- const nodeForChildren = new ImageNode(this);
186
- nodeForChildren.children = this.children;
187
- this.children = [nodeForNewLeaf, nodeForChildren];
188
- nodeForChildren.recomputeBBox(true);
189
- nodeForChildren.updateParents();
196
+ if (this.children.length < this.targetChildCount) {
197
+ this.children.push(nodeForNewLeaf);
198
+ }
199
+ else {
200
+ const nodeForChildren = new ImageNode(this);
201
+ nodeForChildren.children = this.children;
202
+ this.children = [nodeForNewLeaf, nodeForChildren];
203
+ nodeForChildren.recomputeBBox(true);
204
+ nodeForChildren.updateParents();
205
+ }
190
206
  return nodeForNewLeaf.addLeaf(leaf);
191
207
  }
192
208
  const containingNodes = this.children.filter(child => child.getBBox().containsRect(leafBBox));
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Handles notifying listeners of events.
3
+ *
4
+ * `EventKeyType` is used to distinguish events (e.g. a `ClickEvent` vs a `TouchEvent`)
5
+ * while `EventMessageType` is the type of the data sent with an event (can be `void`).
6
+ *
7
+ * @example
8
+ * ```
9
+ * const dispatcher = new EventDispatcher<'event1'|'event2'|'event3', void>();
10
+ * dispatcher.on('event1', () => {
11
+ * console.log('Event 1 triggered.');
12
+ * });
13
+ * dispatcher.dispatch('event1');
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
1
18
  declare type CallbackHandler<EventType> = (data: EventType) => void;
2
19
  export default class EventDispatcher<EventKeyType extends string | symbol | number, EventMessageType> {
3
20
  private listeners;
@@ -6,6 +23,7 @@ export default class EventDispatcher<EventKeyType extends string | symbol | numb
6
23
  on(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): {
7
24
  remove: () => boolean;
8
25
  };
26
+ /** Removes an event listener. This is equivalent to calling `.remove()` on the object returned by `.on`. */
9
27
  off(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): void;
10
28
  }
11
29
  export {};
@@ -1,6 +1,21 @@
1
- // Code shared with Joplin
2
- // EventKeyType is used to distinguish events (e.g. a 'ClickEvent' vs a 'TouchEvent')
3
- // while EventMessageType is the type of the data sent with an event (can be `void`)
1
+ /**
2
+ * Handles notifying listeners of events.
3
+ *
4
+ * `EventKeyType` is used to distinguish events (e.g. a `ClickEvent` vs a `TouchEvent`)
5
+ * while `EventMessageType` is the type of the data sent with an event (can be `void`).
6
+ *
7
+ * @example
8
+ * ```
9
+ * const dispatcher = new EventDispatcher<'event1'|'event2'|'event3', void>();
10
+ * dispatcher.on('event1', () => {
11
+ * console.log('Event 1 triggered.');
12
+ * });
13
+ * dispatcher.dispatch('event1');
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ // { @inheritDoc EventDispatcher! }
4
19
  export default class EventDispatcher {
5
20
  constructor() {
6
21
  this.listeners = {};
@@ -26,7 +41,7 @@ export default class EventDispatcher {
26
41
  },
27
42
  };
28
43
  }
29
- // Equivalent to calling .remove() on the object returned by .on
44
+ /** Removes an event listener. This is equivalent to calling `.remove()` on the object returned by `.on`. */
30
45
  off(eventName, callback) {
31
46
  const listeners = this.listeners[eventName];
32
47
  if (!listeners)
@@ -1,4 +1,4 @@
1
- import { Point2 } from './geometry/Vec2';
1
+ import { Point2 } from './math/Vec2';
2
2
  import Viewport from './Viewport';
3
3
  export declare enum PointerDevice {
4
4
  Pen = 0,
@@ -1,4 +1,4 @@
1
- import { Vec2 } from './geometry/Vec2';
1
+ import { Vec2 } from './math/Vec2';
2
2
  export var PointerDevice;
3
3
  (function (PointerDevice) {
4
4
  PointerDevice[PointerDevice["Pen"] = 0] = "Pen";
@@ -9,7 +9,7 @@ export var PointerDevice;
9
9
  PointerDevice[PointerDevice["Other"] = 5] = "Other";
10
10
  })(PointerDevice || (PointerDevice = {}));
11
11
  // Provides a snapshot containing information about a pointer. A Pointer
12
- // object is immutable --- it will not be updated when the pointer's information changes.
12
+ // object is immutable it will not be updated when the pointer's information changes.
13
13
  export default class Pointer {
14
14
  constructor(
15
15
  // The (x, y) position of the pointer relative to the top-left corner
@@ -20,7 +20,7 @@ export default class Pointer {
20
20
  canvasPos, pressure, isPrimary, down, device,
21
21
  // Unique ID for the pointer
22
22
  id,
23
- // Numeric timestamp (milliseconds, as from (new Date).getTime())
23
+ // Numeric timestamp (milliseconds, as from `(new Date).getTime()`)
24
24
  timeStamp) {
25
25
  this.screenPos = screenPos;
26
26
  this.canvasPos = canvasPos;
@@ -31,6 +31,7 @@ export default class Pointer {
31
31
  this.id = id;
32
32
  this.timeStamp = timeStamp;
33
33
  }
34
+ // Creates a Pointer from a DOM event.
34
35
  static ofEvent(evt, isDown, viewport) {
35
36
  var _a, _b;
36
37
  const screenPos = Vec2.of(evt.offsetX, evt.offsetY);
@@ -1,4 +1,4 @@
1
- import Rect2 from './geometry/Rect2';
1
+ import Rect2 from './math/Rect2';
2
2
  import { ComponentAddedListener, ImageLoader, OnDetermineExportRectListener, OnProgressListener } from './types';
3
3
  export declare const defaultSVGViewRect: Rect2;
4
4
  export declare const svgAttributesDataKey = "svgAttrs";
@@ -12,10 +12,10 @@ import Stroke from './components/Stroke';
12
12
  import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
13
13
  import Text from './components/Text';
14
14
  import UnknownSVGObject from './components/UnknownSVGObject';
15
- import Mat33 from './geometry/Mat33';
16
- import Path from './geometry/Path';
17
- import Rect2 from './geometry/Rect2';
18
- import { Vec2 } from './geometry/Vec2';
15
+ import Mat33 from './math/Mat33';
16
+ import Path from './math/Path';
17
+ import Rect2 from './math/Rect2';
18
+ import { Vec2 } from './math/Vec2';
19
19
  // Size of a loaded image if no size is specified.
20
20
  export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
21
21
  // Key to retrieve unrecognised attributes from an AbstractComponent
@@ -171,8 +171,16 @@ export default class SVGLoader {
171
171
  if (transformProperty === '' || transformProperty === 'none') {
172
172
  transformProperty = elem.style.transform || 'none';
173
173
  }
174
- // Compute transform matrix
175
- let transform = Mat33.fromCSSMatrix(transformProperty);
174
+ // Compute transform matrix. Prefer the actual .style.transform
175
+ // to the computed stylesheet -- in some browsers, the computedStyles version
176
+ // can have lower precision.
177
+ let transform;
178
+ try {
179
+ transform = Mat33.fromCSSMatrix(elem.style.transform);
180
+ }
181
+ catch (_e) {
182
+ transform = Mat33.fromCSSMatrix(transformProperty);
183
+ }
176
184
  const supportedAttrs = [];
177
185
  const elemX = elem.getAttribute('x');
178
186
  const elemY = elem.getAttribute('y');
@@ -1,5 +1,6 @@
1
1
  import { EditorEventType } from './types';
2
2
  class UndoRedoHistory {
3
+ // @internal
3
4
  constructor(editor, announceRedoCallback, announceUndoCallback) {
4
5
  this.editor = editor;
5
6
  this.announceRedoCallback = announceRedoCallback;
@@ -25,6 +26,10 @@ class UndoRedoHistory {
25
26
  }
26
27
  this.redoStack = [];
27
28
  this.fireUpdateEvent();
29
+ this.editor.notifier.dispatch(EditorEventType.CommandDone, {
30
+ kind: EditorEventType.CommandDone,
31
+ command,
32
+ });
28
33
  }
29
34
  // Remove the last command from this' undo stack and apply it.
30
35
  undo() {
@@ -33,8 +38,12 @@ class UndoRedoHistory {
33
38
  this.redoStack.push(command);
34
39
  command.unapply(this.editor);
35
40
  this.announceUndoCallback(command);
41
+ this.fireUpdateEvent();
42
+ this.editor.notifier.dispatch(EditorEventType.CommandUndone, {
43
+ kind: EditorEventType.CommandUndone,
44
+ command,
45
+ });
36
46
  }
37
- this.fireUpdateEvent();
38
47
  }
39
48
  redo() {
40
49
  const command = this.redoStack.pop();
@@ -42,8 +51,12 @@ class UndoRedoHistory {
42
51
  this.undoStack.push(command);
43
52
  command.apply(this.editor);
44
53
  this.announceRedoCallback(command);
54
+ this.fireUpdateEvent();
55
+ this.editor.notifier.dispatch(EditorEventType.CommandDone, {
56
+ kind: EditorEventType.CommandDone,
57
+ command,
58
+ });
45
59
  }
46
- this.fireUpdateEvent();
47
60
  }
48
61
  get undoStackSize() {
49
62
  return this.undoStack.length;
@@ -1,31 +1,16 @@
1
1
  import Command from './commands/Command';
2
- import { CommandLocalization } from './commands/localization';
3
- import Editor from './Editor';
4
- import Mat33 from './geometry/Mat33';
5
- import Rect2 from './geometry/Rect2';
6
- import { Point2, Vec2 } from './geometry/Vec2';
2
+ import Mat33 from './math/Mat33';
3
+ import Rect2 from './math/Rect2';
4
+ import { Point2, Vec2 } from './math/Vec2';
7
5
  import { StrokeDataPoint } from './types';
8
6
  import { EditorNotifier } from './types';
9
7
  declare type PointDataType<T extends Point2 | StrokeDataPoint | number> = T extends Point2 ? Point2 : number;
8
+ export declare abstract class ViewportTransform extends Command {
9
+ abstract readonly transform: Mat33;
10
+ }
10
11
  export declare class Viewport {
11
12
  private notifier;
12
- static ViewportTransform: {
13
- new (transform: Mat33): {
14
- readonly "__#679@#inverseTransform": Mat33;
15
- readonly transform: Mat33;
16
- apply(editor: Editor): void;
17
- unapply(editor: Editor): void;
18
- description(localizationTable: CommandLocalization): string;
19
- onDrop(_editor: Editor): void;
20
- };
21
- union(a: Command, b: Command): Command;
22
- readonly empty: {
23
- description(_localizationTable: import("./localization").EditorLocalization): string;
24
- apply(_editor: Editor): void;
25
- unapply(_editor: Editor): void;
26
- onDrop(_editor: Editor): void;
27
- };
28
- };
13
+ private static ViewportTransform;
29
14
  private transform;
30
15
  private inverseTransform;
31
16
  private screenRect;
@@ -34,6 +19,7 @@ export declare class Viewport {
34
19
  get visibleRect(): Rect2;
35
20
  screenToCanvas(screenPoint: Point2): Point2;
36
21
  canvasToScreen(canvasPoint: Point2): Point2;
22
+ static transformBy(transform: Mat33): ViewportTransform;
37
23
  resetTransform(newTransform?: Mat33): void;
38
24
  get screenToCanvasTransform(): Mat33;
39
25
  get canvasToScreenTransform(): Mat33;
@@ -45,7 +31,4 @@ export declare class Viewport {
45
31
  roundPoint(point: Point2): Point2;
46
32
  zoomTo(toMakeVisible: Rect2, allowZoomIn?: boolean, allowZoomOut?: boolean): Command;
47
33
  }
48
- export declare namespace Viewport {
49
- type ViewportTransform = typeof Viewport.ViewportTransform.prototype;
50
- }
51
34
  export default Viewport;
@@ -11,17 +11,21 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  };
12
12
  var _inverseTransform, _a;
13
13
  import Command from './commands/Command';
14
- import Mat33 from './geometry/Mat33';
15
- import Rect2 from './geometry/Rect2';
16
- import { Vec2 } from './geometry/Vec2';
17
- import Vec3 from './geometry/Vec3';
14
+ import Mat33 from './math/Mat33';
15
+ import Rect2 from './math/Rect2';
16
+ import { Vec2 } from './math/Vec2';
17
+ import Vec3 from './math/Vec3';
18
18
  import { EditorEventType } from './types';
19
+ export class ViewportTransform extends Command {
20
+ }
19
21
  export class Viewport {
22
+ // @internal
20
23
  constructor(notifier) {
21
24
  this.notifier = notifier;
22
25
  this.resetTransform(Mat33.identity);
23
26
  this.screenRect = Rect2.empty;
24
27
  }
28
+ // @internal
25
29
  updateScreenSize(screenSize) {
26
30
  this.screenRect = this.screenRect.resizedTo(screenSize);
27
31
  }
@@ -35,7 +39,10 @@ export class Viewport {
35
39
  canvasToScreen(canvasPoint) {
36
40
  return this.transform.transformVec2(canvasPoint);
37
41
  }
38
- // Updates the transformation directly. Using ViewportTransform is preferred.
42
+ static transformBy(transform) {
43
+ return new Viewport.ViewportTransform(transform);
44
+ }
45
+ // Updates the transformation directly. Using `transformBy` is preferred.
39
46
  // [newTransform] should map from canvas coordinates to screen coordinates.
40
47
  resetTransform(newTransform = Mat33.identity) {
41
48
  const oldTransform = this.transform;
@@ -61,6 +68,7 @@ export class Viewport {
61
68
  // Use transformVec3 to avoid translating the vector
62
69
  return this.transform.transformVec3(Vec3.unitX).magnitude();
63
70
  }
71
+ // Returns the size of one screen pixel in canvas units.
64
72
  getSizeOfPixelOnCanvas() {
65
73
  return 1 / this.getScaleFactor();
66
74
  }
@@ -132,7 +140,7 @@ export class Viewport {
132
140
  }
133
141
  }
134
142
  // Command that translates/scales the viewport.
135
- Viewport.ViewportTransform = (_a = class extends Command {
143
+ Viewport.ViewportTransform = (_a = class extends ViewportTransform {
136
144
  constructor(transform) {
137
145
  super();
138
146
  this.transform = transform;
@@ -149,13 +157,13 @@ Viewport.ViewportTransform = (_a = class extends Command {
149
157
  viewport.resetTransform(viewport.transform.rightMul(__classPrivateFieldGet(this, _inverseTransform, "f")));
150
158
  editor.queueRerender();
151
159
  }
152
- description(localizationTable) {
160
+ description(editor, localizationTable) {
153
161
  const result = [];
154
162
  // Describe the transformation's affect on the viewport (note that transformation transforms
155
163
  // the **elements** within the viewport). Assumes the transformation only does rotation/scale/translation.
156
- const origVec = Vec2.unitX;
164
+ const origVec = editor.viewport.visibleRect.center;
157
165
  const linearTransformedVec = this.transform.transformVec3(Vec2.unitX);
158
- const affineTransformedVec = this.transform.transformVec2(Vec2.unitX);
166
+ const affineTransformedVec = this.transform.transformVec2(origVec);
159
167
  const scale = linearTransformedVec.magnitude();
160
168
  const rotation = 180 / Math.PI * linearTransformedVec.angle();
161
169
  const translation = affineTransformedVec.minus(origVec);
@@ -175,7 +183,7 @@ Viewport.ViewportTransform = (_a = class extends Command {
175
183
  else if (translation.x < -minTranslation) {
176
184
  result.push(localizationTable.movedRight);
177
185
  }
178
- if (translation.y < minTranslation) {
186
+ if (translation.y < -minTranslation) {
179
187
  result.push(localizationTable.movedDown);
180
188
  }
181
189
  else if (translation.y > minTranslation) {
@@ -1,5 +1,4 @@
1
1
  import '../styles';
2
2
  import Editor from '../Editor';
3
- import getLocalizationTable from '../localizations/getLocalizationTable';
3
+ export * from '../lib';
4
4
  export default Editor;
5
- export { Editor, getLocalizationTable };
@@ -1,6 +1,5 @@
1
1
  // Main entrypoint for Webpack when building a bundle for release.
2
2
  import '../styles';
3
3
  import Editor from '../Editor';
4
- import getLocalizationTable from '../localizations/getLocalizationTable';
4
+ export * from '../lib';
5
5
  export default Editor;
6
- export { Editor, getLocalizationTable };
@@ -4,10 +4,10 @@ export declare abstract class Command {
4
4
  abstract apply(editor: Editor): void;
5
5
  abstract unapply(editor: Editor): void;
6
6
  onDrop(_editor: Editor): void;
7
- abstract description(localizationTable: EditorLocalization): string;
7
+ abstract description(editor: Editor, localizationTable: EditorLocalization): string;
8
8
  static union(a: Command, b: Command): Command;
9
9
  static readonly empty: {
10
- description(_localizationTable: EditorLocalization): string;
10
+ description(_editor: Editor, _localizationTable: EditorLocalization): string;
11
11
  apply(_editor: Editor): void;
12
12
  unapply(_editor: Editor): void;
13
13
  onDrop(_editor: Editor): void;
@@ -11,9 +11,9 @@ export class Command {
11
11
  b.unapply(editor);
12
12
  a.unapply(editor);
13
13
  }
14
- description(localizationTable) {
15
- const aDescription = a.description(localizationTable);
16
- const bDescription = b.description(localizationTable);
14
+ description(editor, localizationTable) {
15
+ const aDescription = a.description(editor, localizationTable);
16
+ const bDescription = b.description(editor, localizationTable);
17
17
  if (aDescription === bDescription) {
18
18
  return aDescription;
19
19
  }
@@ -23,7 +23,7 @@ export class Command {
23
23
  }
24
24
  }
25
25
  Command.empty = new class extends Command {
26
- description(_localizationTable) { return ''; }
26
+ description(_editor, _localizationTable) { return ''; }
27
27
  apply(_editor) { }
28
28
  unapply(_editor) { }
29
29
  };
@@ -9,6 +9,6 @@ export default class Duplicate extends SerializableCommand {
9
9
  constructor(toDuplicate: AbstractComponent[]);
10
10
  apply(editor: Editor): void;
11
11
  unapply(editor: Editor): void;
12
- description(localizationTable: EditorLocalization): string;
13
- protected serializeToString(): string;
12
+ description(_editor: Editor, localizationTable: EditorLocalization): string;
13
+ protected serializeToJSON(): string[];
14
14
  }
@@ -14,20 +14,19 @@ export default class Duplicate extends SerializableCommand {
14
14
  unapply(editor) {
15
15
  this.reverse.apply(editor);
16
16
  }
17
- description(localizationTable) {
17
+ description(_editor, localizationTable) {
18
18
  var _a;
19
19
  if (this.duplicates.length === 0) {
20
20
  return localizationTable.duplicatedNoElements;
21
21
  }
22
22
  return localizationTable.duplicateAction((_a = describeComponentList(localizationTable, this.duplicates)) !== null && _a !== void 0 ? _a : localizationTable.elements, this.duplicates.length);
23
23
  }
24
- serializeToString() {
25
- return JSON.stringify(this.toDuplicate.map(elem => elem.getId()));
24
+ serializeToJSON() {
25
+ return this.toDuplicate.map(elem => elem.getId());
26
26
  }
27
27
  }
28
28
  (() => {
29
- SerializableCommand.register('duplicate', (data, editor) => {
30
- const json = JSON.parse(data);
29
+ SerializableCommand.register('duplicate', (json, editor) => {
31
30
  const elems = json.map((id) => editor.image.lookupElement(id));
32
31
  return new Duplicate(elems);
33
32
  });
@@ -9,6 +9,6 @@ export default class Erase extends SerializableCommand {
9
9
  apply(editor: Editor): void;
10
10
  unapply(editor: Editor): void;
11
11
  onDrop(editor: Editor): void;
12
- description(localizationTable: EditorLocalization): string;
13
- protected serializeToString(): string;
12
+ description(_editor: Editor, localizationTable: EditorLocalization): string;
13
+ protected serializeToJSON(): string[];
14
14
  }
@@ -34,7 +34,7 @@ export default class Erase extends SerializableCommand {
34
34
  }
35
35
  }
36
36
  }
37
- description(localizationTable) {
37
+ description(_editor, localizationTable) {
38
38
  var _a;
39
39
  if (this.toRemove.length === 0) {
40
40
  return localizationTable.erasedNoElements;
@@ -42,15 +42,16 @@ export default class Erase extends SerializableCommand {
42
42
  const description = (_a = describeComponentList(localizationTable, this.toRemove)) !== null && _a !== void 0 ? _a : localizationTable.elements;
43
43
  return localizationTable.eraseAction(description, this.toRemove.length);
44
44
  }
45
- serializeToString() {
45
+ serializeToJSON() {
46
46
  const elemIds = this.toRemove.map(elem => elem.getId());
47
- return JSON.stringify(elemIds);
47
+ return elemIds;
48
48
  }
49
49
  }
50
50
  (() => {
51
- SerializableCommand.register('erase', (data, editor) => {
52
- const json = JSON.parse(data);
53
- const elems = json.map((elemId) => editor.image.lookupElement(elemId));
51
+ SerializableCommand.register('erase', (json, editor) => {
52
+ const elems = json
53
+ .map((elemId) => editor.image.lookupElement(elemId))
54
+ .filter((elem) => elem !== null);
54
55
  return new Erase(elems);
55
56
  });
56
57
  })();
@@ -1,13 +1,12 @@
1
1
  import Editor from '../Editor';
2
2
  import Command from './Command';
3
- declare type DeserializationCallback = (data: string, editor: Editor) => SerializableCommand;
3
+ export declare type DeserializationCallback = (data: Record<string, any> | any[], editor: Editor) => SerializableCommand;
4
4
  export default abstract class SerializableCommand extends Command {
5
5
  private commandTypeId;
6
6
  constructor(commandTypeId: string);
7
- protected abstract serializeToString(): string;
7
+ protected abstract serializeToJSON(): string | Record<string, any> | any[];
8
8
  private static deserializationCallbacks;
9
- serialize(): string;
10
- static deserialize(data: string, editor: Editor): SerializableCommand;
9
+ serialize(): Record<string | symbol, any>;
10
+ static deserialize(data: string | Record<string, any>, editor: Editor): SerializableCommand;
11
11
  static register(commandTypeId: string, deserialize: DeserializationCallback): void;
12
12
  }
13
- export {};
@@ -7,20 +7,28 @@ export default class SerializableCommand extends Command {
7
7
  throw new Error(`Command ${commandTypeId} must have a registered deserialization callback. To do this, call SerializableCommand.register.`);
8
8
  }
9
9
  }
10
+ // Convert this command to an object that can be passed to `JSON.stringify`.
11
+ //
12
+ // Do not rely on the stability of the optupt of this function — it can change
13
+ // form without a major version increase.
10
14
  serialize() {
11
- return JSON.stringify({
12
- data: this.serializeToString(),
15
+ return {
16
+ data: this.serializeToJSON(),
13
17
  commandType: this.commandTypeId,
14
- });
18
+ };
15
19
  }
20
+ // Convert a `string` containing JSON data (or the output of `JSON.parse`) into a
21
+ // `Command`.
16
22
  static deserialize(data, editor) {
17
- const json = JSON.parse(data);
23
+ const json = typeof data === 'string' ? JSON.parse(data) : data;
18
24
  const commandType = json.commandType;
19
25
  if (!(commandType in SerializableCommand.deserializationCallbacks)) {
20
26
  throw new Error(`Unrecognised command type ${commandType}!`);
21
27
  }
22
28
  return SerializableCommand.deserializationCallbacks[commandType](json.data, editor);
23
29
  }
30
+ // Register a deserialization callback. This must be called at least once for every subclass of
31
+ // `SerializableCommand`.
24
32
  static register(commandTypeId, deserialize) {
25
33
  SerializableCommand.deserializationCallbacks[commandTypeId] = deserialize;
26
34
  }
@@ -0,0 +1,4 @@
1
+ import Command from './Command';
2
+ import SerializableCommand from './SerializableCommand';
3
+ declare const invertCommand: <T extends Command>(command: T) => T extends SerializableCommand ? SerializableCommand : Command;
4
+ export default invertCommand;