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,8 +1,9 @@
1
1
  import Editor from '../../Editor';
2
2
  import SelectionTool from '../../tools/SelectionTool';
3
3
  import { EditorEventType } from '../../types';
4
- import { makeSelectionIcon } from '../icons';
4
+ import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
5
5
  import { ToolbarLocalization } from '../localization';
6
+ import ActionButtonWidget from './ActionButtonWidget';
6
7
  import BaseToolWidget from './BaseToolWidget';
7
8
 
8
9
  export class SelectionWidget extends BaseToolWidget {
@@ -10,44 +11,46 @@ export class SelectionWidget extends BaseToolWidget {
10
11
  editor: Editor, private tool: SelectionTool, localization: ToolbarLocalization
11
12
  ) {
12
13
  super(editor, tool, localization);
13
- }
14
-
15
- protected getTitle(): string {
16
- return this.localizationTable.select;
17
- }
18
-
19
- protected createIcon(): Element {
20
- return makeSelectionIcon();
21
- }
22
14
 
23
- protected fillDropdown(dropdown: HTMLElement): boolean {
24
- const container = document.createElement('div');
25
- const resizeButton = document.createElement('button');
26
- const duplicateButton = document.createElement('button');
27
- const deleteButton = document.createElement('button');
28
-
29
- resizeButton.innerText = this.localizationTable.resizeImageToSelection;
30
- resizeButton.disabled = true;
31
- deleteButton.innerText = this.localizationTable.deleteSelection;
32
- deleteButton.disabled = true;
33
- duplicateButton.innerText = this.localizationTable.duplicateSelection;
34
- duplicateButton.disabled = true;
35
-
36
- resizeButton.onclick = () => {
37
- const selection = this.tool.getSelection();
38
- this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
39
- };
15
+ const resizeButton = new ActionButtonWidget(
16
+ editor, localization,
17
+ makeResizeViewportIcon,
18
+ this.localizationTable.resizeImageToSelection,
19
+ () => {
20
+ const selection = this.tool.getSelection();
21
+ this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
22
+ },
23
+ );
24
+ const deleteButton = new ActionButtonWidget(
25
+ editor, localization,
26
+ makeDeleteSelectionIcon,
27
+ this.localizationTable.deleteSelection,
28
+ () => {
29
+ const selection = this.tool.getSelection();
30
+ this.editor.dispatch(selection!.deleteSelectedObjects());
31
+ this.tool.clearSelection();
32
+ },
33
+ );
34
+ const duplicateButton = new ActionButtonWidget(
35
+ editor, localization,
36
+ makeDuplicateSelectionIcon,
37
+ this.localizationTable.duplicateSelection,
38
+ () => {
39
+ const selection = this.tool.getSelection();
40
+ this.editor.dispatch(selection!.duplicateSelectedObjects());
41
+ },
42
+ );
40
43
 
41
- deleteButton.onclick = () => {
42
- const selection = this.tool.getSelection();
43
- this.editor.dispatch(selection!.deleteSelectedObjects());
44
- this.tool.clearSelection();
45
- };
44
+ this.addSubWidget(resizeButton);
45
+ this.addSubWidget(deleteButton);
46
+ this.addSubWidget(duplicateButton);
46
47
 
47
- duplicateButton.onclick = () => {
48
- const selection = this.tool.getSelection();
49
- this.editor.dispatch(selection!.duplicateSelectedObjects());
48
+ const updateDisabled = (disabled: boolean) => {
49
+ resizeButton.setDisabled(disabled);
50
+ deleteButton.setDisabled(disabled);
51
+ duplicateButton.setDisabled(disabled);
50
52
  };
53
+ updateDisabled(true);
51
54
 
52
55
  // Enable/disable actions based on whether items are selected
53
56
  this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
@@ -59,14 +62,16 @@ export class SelectionWidget extends BaseToolWidget {
59
62
  const selection = this.tool.getSelection();
60
63
  const hasSelection = selection && selection.region.area > 0;
61
64
 
62
- resizeButton.disabled = !hasSelection;
63
- deleteButton.disabled = resizeButton.disabled;
64
- duplicateButton.disabled = resizeButton.disabled;
65
+ updateDisabled(!hasSelection);
65
66
  }
66
67
  });
68
+ }
67
69
 
68
- container.replaceChildren(resizeButton, duplicateButton, deleteButton);
69
- dropdown.appendChild(container);
70
- return true;
70
+ protected getTitle(): string {
71
+ return this.localizationTable.select;
72
+ }
73
+
74
+ protected createIcon(): Element {
75
+ return makeSelectionIcon();
71
76
  }
72
77
  }
@@ -1,8 +1,8 @@
1
1
  import { PointerEvt } from '../types';
2
2
  import BaseTool from './BaseTool';
3
3
  import Editor from '../Editor';
4
- import { Point2 } from '../geometry/Vec2';
5
- import LineSegment2 from '../geometry/LineSegment2';
4
+ import { Point2 } from '../math/Vec2';
5
+ import LineSegment2 from '../math/LineSegment2';
6
6
  import Erase from '../commands/Erase';
7
7
  import { ToolType } from './ToolController';
8
8
  import AbstractComponent from '../components/AbstractComponent';
@@ -1,11 +1,11 @@
1
1
 
2
2
  import { Editor } from '../Editor';
3
- import Mat33 from '../geometry/Mat33';
4
- import { Point2, Vec2 } from '../geometry/Vec2';
5
- import Vec3 from '../geometry/Vec3';
3
+ import Mat33 from '../math/Mat33';
4
+ import { Point2, Vec2 } from '../math/Vec2';
5
+ import Vec3 from '../math/Vec3';
6
6
  import Pointer, { PointerDevice } from '../Pointer';
7
7
  import { EditorEventType, KeyPressEvent, PointerEvt, WheelEvt } from '../types';
8
- import { Viewport } from '../Viewport';
8
+ import { Viewport, ViewportTransform } from '../Viewport';
9
9
  import BaseTool from './BaseTool';
10
10
  import { ToolType } from './ToolController';
11
11
 
@@ -16,7 +16,6 @@ interface PinchData {
16
16
  dist: number;
17
17
  }
18
18
 
19
-
20
19
  export enum PanZoomMode {
21
20
  OneFingerTouchGestures = 0x1,
22
21
  TwoFingerTouchGestures = 0x1 << 1,
@@ -27,7 +26,7 @@ export enum PanZoomMode {
27
26
 
28
27
  export default class PanZoom extends BaseTool {
29
28
  public readonly kind: ToolType.PanZoom = ToolType.PanZoom;
30
- private transform: Viewport.ViewportTransform|null = null;
29
+ private transform: ViewportTransform|null = null;
31
30
 
32
31
  private lastAngle: number;
33
32
  private lastDist: number;
@@ -74,7 +73,7 @@ export default class PanZoom extends BaseTool {
74
73
  }
75
74
 
76
75
  if (handlingGesture) {
77
- this.transform ??= new Viewport.ViewportTransform(Mat33.identity);
76
+ this.transform ??= Viewport.transformBy(Mat33.identity);
78
77
  this.editor.display.setDraftMode(true);
79
78
  }
80
79
 
@@ -100,14 +99,14 @@ export default class PanZoom extends BaseTool {
100
99
  this.lastScreenCenter = screenCenter;
101
100
  this.lastDist = dist;
102
101
  this.lastAngle = angle;
103
- this.transform = new Viewport.ViewportTransform(
102
+ this.transform = Viewport.transformBy(
104
103
  this.transform!.transform.rightMul(transformUpdate)
105
104
  );
106
105
  }
107
106
 
108
107
  private handleOneFingerMove(pointer: Pointer) {
109
108
  const delta = this.getCenterDelta(pointer.screenPos);
110
- this.transform = new Viewport.ViewportTransform(
109
+ this.transform = Viewport.transformBy(
111
110
  this.transform!.transform.rightMul(
112
111
  Mat33.translation(delta)
113
112
  )
@@ -116,7 +115,7 @@ export default class PanZoom extends BaseTool {
116
115
  }
117
116
 
118
117
  public onPointerMove({ allPointers }: PointerEvt): void {
119
- this.transform ??= new Viewport.ViewportTransform(Mat33.identity);
118
+ this.transform ??= Viewport.transformBy(Mat33.identity);
120
119
 
121
120
  const lastTransform = this.transform;
122
121
  if (allPointers.length === 2) {
@@ -146,21 +145,25 @@ export default class PanZoom extends BaseTool {
146
145
 
147
146
  // Applies [transformUpdate] to the editor. This stacks on top of the
148
147
  // current transformation, if it exists.
149
- private updateTransform(transformUpdate: Mat33) {
148
+ private updateTransform(transformUpdate: Mat33, announce: boolean = false) {
150
149
  let newTransform = transformUpdate;
151
150
  if (this.transform) {
152
151
  newTransform = this.transform.transform.rightMul(transformUpdate);
153
152
  }
154
153
 
155
154
  this.transform?.unapply(this.editor);
156
- this.transform = new Viewport.ViewportTransform(newTransform);
155
+ this.transform = Viewport.transformBy(newTransform);
157
156
  this.transform.apply(this.editor);
157
+
158
+ if (announce) {
159
+ this.editor.announceForAccessibility(this.transform.description(this.editor, this.editor.localization));
160
+ }
158
161
  }
159
162
 
160
163
  public onWheel({ delta, screenPos }: WheelEvt): boolean {
161
164
  // Reset the transformation -- wheel events are individual events, so we don't
162
165
  // need to unapply/reapply.
163
- this.transform = new Viewport.ViewportTransform(Mat33.identity);
166
+ this.transform = Viewport.transformBy(Mat33.identity);
164
167
 
165
168
  const canvasPos = this.editor.viewport.screenToCanvas(screenPos);
166
169
  const toCanvas = this.editor.viewport.screenToCanvasTransform;
@@ -176,7 +179,7 @@ export default class PanZoom extends BaseTool {
176
179
  ).rightMul(
177
180
  Mat33.translation(translation)
178
181
  );
179
- this.updateTransform(transformUpdate);
182
+ this.updateTransform(transformUpdate, true);
180
183
 
181
184
  return true;
182
185
  }
@@ -187,7 +190,7 @@ export default class PanZoom extends BaseTool {
187
190
  }
188
191
 
189
192
  // No need to keep the same the transform for keyboard events.
190
- this.transform = new Viewport.ViewportTransform(Mat33.identity);
193
+ this.transform = Viewport.transformBy(Mat33.identity);
191
194
 
192
195
  let translation = Vec2.zero;
193
196
  let scale = 1;
@@ -205,10 +208,12 @@ export default class PanZoom extends BaseTool {
205
208
  case 'ArrowRight':
206
209
  translation = Vec2.of(1, 0);
207
210
  break;
211
+ case 'q':
208
212
  case 'k':
209
213
  case 'ArrowUp':
210
214
  translation = Vec2.of(0, -1);
211
215
  break;
216
+ case 'e':
212
217
  case 'j':
213
218
  case 'ArrowDown':
214
219
  translation = Vec2.of(0, 1);
@@ -231,13 +236,19 @@ export default class PanZoom extends BaseTool {
231
236
 
232
237
  // For each keypress,
233
238
  translation = translation.times(30); // Move at most 30 units
234
- rotation *= Math.PI / 8; // Rotate at most a sixteenth of a rotation
239
+ rotation *= Math.PI / 8; // Rotate at least a sixteenth of a rotation
235
240
 
236
241
  // Transform the canvas, not the viewport:
237
242
  translation = translation.times(-1);
238
243
  rotation = rotation * -1;
239
244
  scale = 1 / scale;
240
245
 
246
+ // Work around an issue that seems to be related to rotation matricies losing precision on inversion.
247
+ // TODO: Figure out why and implement a better solution.
248
+ if (rotation !== 0) {
249
+ rotation += 0.0001;
250
+ }
251
+
241
252
  const toCanvas = this.editor.viewport.screenToCanvasTransform;
242
253
 
243
254
  // Transform without translating (treat toCanvas as a linear instead of
@@ -253,7 +264,7 @@ export default class PanZoom extends BaseTool {
253
264
  )).rightMul(Mat33.translation(
254
265
  translation
255
266
  ));
256
- this.updateTransform(transformUpdate);
267
+ this.updateTransform(transformUpdate, true);
257
268
 
258
269
  return true;
259
270
  }
package/src/tools/Pen.ts CHANGED
@@ -8,7 +8,7 @@ import BaseTool from './BaseTool';
8
8
  import { ToolType } from './ToolController';
9
9
  import { ComponentBuilder, ComponentBuilderFactory } from '../components/builders/types';
10
10
 
11
- interface PenStyle {
11
+ export interface PenStyle {
12
12
  color: Color4;
13
13
  thickness: number;
14
14
  }
@@ -34,7 +34,16 @@ export default class Pen extends BaseTool {
34
34
 
35
35
  private getStrokePoint(pointer: Pointer): StrokeDataPoint {
36
36
  const minPressure = 0.3;
37
- const pressure = Math.max(pointer.pressure ?? 1.0, minPressure);
37
+ let pressure = Math.max(pointer.pressure ?? 1.0, minPressure);
38
+
39
+ if (!isFinite(pressure)) {
40
+ console.warn('Non-finite pressure!', pointer);
41
+ pressure = minPressure;
42
+ }
43
+ console.assert(isFinite(pointer.canvasPos.length()), 'Non-finite canvas position!');
44
+ console.assert(isFinite(pointer.screenPos.length()), 'Non-finite screen position!');
45
+ console.assert(isFinite(pointer.timeStamp), 'Non-finite timeStamp on pointer!');
46
+
38
47
  return {
39
48
  pos: pointer.canvasPos,
40
49
  width: pressure * this.getPressureMultiplier(),
@@ -1,3 +1,5 @@
1
+ // @internal @packageDocumentation
2
+
1
3
  import Color4 from '../Color4';
2
4
  import Editor from '../Editor';
3
5
  import { PointerEvt } from '../types';
@@ -1,11 +1,9 @@
1
- /* @jest-environment jsdom */
2
-
3
1
  import Color4 from '../Color4';
4
2
  import Stroke from '../components/Stroke';
5
3
  import Editor from '../Editor';
6
4
  import EditorImage from '../EditorImage';
7
- import Path from '../geometry/Path';
8
- import { Vec2 } from '../geometry/Vec2';
5
+ import Path from '../math/Path';
6
+ import { Vec2 } from '../math/Vec2';
9
7
  import { InputEvtType } from '../types';
10
8
  import SelectionTool from './SelectionTool';
11
9
  import { ToolType } from './ToolController';
@@ -3,15 +3,16 @@ import Duplicate from '../commands/Duplicate';
3
3
  import Erase from '../commands/Erase';
4
4
  import AbstractComponent from '../components/AbstractComponent';
5
5
  import Editor from '../Editor';
6
- import Mat33 from '../geometry/Mat33';
6
+ import Mat33 from '../math/Mat33';
7
7
  // import Mat33 from "../geometry/Mat33";
8
- import Rect2 from '../geometry/Rect2';
9
- import { Point2, Vec2 } from '../geometry/Vec2';
8
+ import Rect2 from '../math/Rect2';
9
+ import { Point2, Vec2 } from '../math/Vec2';
10
10
  import { EditorLocalization } from '../localization';
11
11
  import { EditorEventType, KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
12
12
  import Viewport from '../Viewport';
13
13
  import BaseTool from './BaseTool';
14
14
  import { ToolType } from './ToolController';
15
+ import SerializableCommand from '../commands/SerializableCommand';
15
16
 
16
17
  const handleScreenSize = 30;
17
18
  const styles = `
@@ -124,6 +125,7 @@ const makeDraggable = (element: HTMLElement, onDrag: DragCallback, onDragEnd: Dr
124
125
  // Maximum number of strokes to transform without a re-render.
125
126
  const updateChunkSize = 100;
126
127
 
128
+ // @internal
127
129
  class Selection {
128
130
  public region: Rect2;
129
131
  private boxRotation: number;
@@ -131,7 +133,7 @@ class Selection {
131
133
  private rotateCircle: HTMLElement;
132
134
  private selectedElems: AbstractComponent[];
133
135
  private transform: Mat33;
134
- private transformationCommands: Command[];
136
+ private transformationCommands: SerializableCommand[];
135
137
 
136
138
  public constructor(
137
139
  public startPoint: Point2, private editor: Editor
@@ -230,7 +232,7 @@ class Selection {
230
232
  this.transformPreview(Mat33.zRotation(deltaRotation, this.region.center));
231
233
  }
232
234
 
233
- private computeTransformCommands() {
235
+ private computeTransformCommands(): SerializableCommand[] {
234
236
  return this.selectedElems.map(elem => {
235
237
  return elem.transformBy(this.transform);
236
238
  });
@@ -275,42 +277,68 @@ class Selection {
275
277
 
276
278
  // Make the commands undo-able
277
279
  this.editor.dispatch(new Selection.ApplyTransformationCommand(
278
- this, currentTransfmCommands, fullTransform, inverseTransform, deltaBoxRotation
280
+ this, currentTransfmCommands, fullTransform, deltaBoxRotation
279
281
  ));
280
282
  }
281
283
 
282
- private static ApplyTransformationCommand = class extends Command {
284
+ static {
285
+ SerializableCommand.register('selection-tool-transform', (json: any, editor) => {
286
+ // The selection box is lost when serializing/deserializing. No need to store box rotation
287
+ const guiBoxRotation = 0;
288
+ const fullTransform: Mat33 = new Mat33(...(json.transform as [
289
+ number, number, number,
290
+ number, number, number,
291
+ number, number, number,
292
+ ]));
293
+ const commands = (json.commands as any[]).map(data => SerializableCommand.deserialize(data, editor));
294
+
295
+ return new this.ApplyTransformationCommand(null, commands, fullTransform, guiBoxRotation);
296
+ });
297
+ }
298
+
299
+ private static ApplyTransformationCommand = class extends SerializableCommand {
283
300
  public constructor(
284
- private selection: Selection,
285
- private currentTransfmCommands: Command[],
286
- private fullTransform: Mat33, private inverseTransform: Mat33,
301
+ private selection: Selection|null,
302
+ private currentTransfmCommands: SerializableCommand[],
303
+ private fullTransform: Mat33,
287
304
  private deltaBoxRotation: number,
288
305
  ) {
289
- super();
306
+ super('selection-tool-transform');
290
307
  }
291
308
 
292
309
  public async apply(editor: Editor) {
293
310
  // Approximate the new selection
294
- this.selection.region = this.selection.region.transformedBoundingBox(this.fullTransform);
295
- this.selection.boxRotation += this.deltaBoxRotation;
296
- this.selection.updateUI();
311
+ if (this.selection) {
312
+ this.selection.region = this.selection.region.transformedBoundingBox(this.fullTransform);
313
+ this.selection.boxRotation += this.deltaBoxRotation;
314
+ this.selection.updateUI();
315
+ }
297
316
 
298
317
  await editor.asyncApplyCommands(this.currentTransfmCommands, updateChunkSize);
299
- this.selection.recomputeRegion();
300
- this.selection.updateUI();
318
+ this.selection?.recomputeRegion();
319
+ this.selection?.updateUI();
301
320
  }
302
321
 
303
322
  public async unapply(editor: Editor) {
304
- this.selection.region = this.selection.region.transformedBoundingBox(this.inverseTransform);
305
- this.selection.boxRotation -= this.deltaBoxRotation;
306
- this.selection.updateUI();
323
+ if (this.selection) {
324
+ this.selection.region = this.selection.region.transformedBoundingBox(this.fullTransform.inverse());
325
+ this.selection.boxRotation -= this.deltaBoxRotation;
326
+ this.selection.updateUI();
327
+ }
307
328
 
308
329
  await editor.asyncUnapplyCommands(this.currentTransfmCommands, updateChunkSize);
309
- this.selection.recomputeRegion();
310
- this.selection.updateUI();
330
+ this.selection?.recomputeRegion();
331
+ this.selection?.updateUI();
332
+ }
333
+
334
+ protected serializeToJSON() {
335
+ return {
336
+ commands: this.currentTransfmCommands.map(command => command.serialize()),
337
+ transform: this.fullTransform.toArray(),
338
+ };
311
339
  }
312
340
 
313
- public description(localizationTable: EditorLocalization) {
341
+ public description(_editor: Editor, localizationTable: EditorLocalization) {
314
342
  return localizationTable.transformedElements(this.currentTransfmCommands.length);
315
343
  }
316
344
  };
@@ -460,7 +488,7 @@ class Selection {
460
488
  const closestPoint = visibleRect.getClosestPointOnBoundaryTo(this.region.center);
461
489
  const delta = this.region.center.minus(closestPoint);
462
490
  this.editor.dispatchNoAnnounce(
463
- new Viewport.ViewportTransform(Mat33.translation(delta.times(-1))), false
491
+ Viewport.transformBy(Mat33.translation(delta.times(-1))), false
464
492
  );
465
493
  }
466
494
  }
@@ -671,7 +699,7 @@ export default class SelectionTool extends BaseTool {
671
699
 
672
700
  if (enabled) {
673
701
  this.handleOverlay.tabIndex = 0;
674
- this.handleOverlay.ariaLabel = this.editor.localization.selectionToolKeyboardShortcuts;
702
+ this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
675
703
  } else {
676
704
  this.handleOverlay.tabIndex = -1;
677
705
  }
@@ -2,8 +2,8 @@ import Color4 from '../Color4';
2
2
  import Text, { TextStyle } from '../components/Text';
3
3
  import Editor from '../Editor';
4
4
  import EditorImage from '../EditorImage';
5
- import Mat33 from '../geometry/Mat33';
6
- import { Vec2 } from '../geometry/Vec2';
5
+ import Mat33 from '../math/Mat33';
6
+ import { Vec2 } from '../math/Vec2';
7
7
  import { PointerDevice } from '../Pointer';
8
8
  import { EditorEventType, PointerEvt } from '../types';
9
9
  import BaseTool from './BaseTool';
@@ -3,7 +3,7 @@
3
3
  import Color4 from '../Color4';
4
4
  import Stroke from '../components/Stroke';
5
5
  import EditorImage from '../EditorImage';
6
- import Path from '../geometry/Path';
6
+ import Path from '../math/Path';
7
7
  import createEditor from '../testing/createEditor';
8
8
  import { InputEvtType } from '../types';
9
9
 
package/src/types.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  // Types related to the image editor
2
2
 
3
3
  import EventDispatcher from './EventDispatcher';
4
- import Mat33 from './geometry/Mat33';
5
- import { Point2, Vec2 } from './geometry/Vec2';
6
- import Vec3 from './geometry/Vec3';
4
+ import Mat33 from './math/Mat33';
5
+ import { Point2, Vec2 } from './math/Vec2';
6
+ import Vec3 from './math/Vec3';
7
7
  import BaseTool from './tools/BaseTool';
8
8
  import AbstractComponent from './components/AbstractComponent';
9
- import Rect2 from './geometry/Rect2';
9
+ import Rect2 from './math/Rect2';
10
10
  import Pointer from './Pointer';
11
11
  import Color4 from './Color4';
12
+ import Command from './commands/Command';
12
13
 
13
14
 
14
15
  export interface PointerEvtListener {
@@ -91,12 +92,17 @@ export enum EditorEventType {
91
92
  ToolEnabled,
92
93
  ToolDisabled,
93
94
  ToolUpdated,
95
+
94
96
  UndoRedoStackUpdated,
97
+ CommandDone,
98
+ CommandUndone,
95
99
  ObjectAdded,
100
+
96
101
  ViewportChanged,
97
102
  DisplayResized,
103
+
98
104
  ColorPickerToggled,
99
- ColorPickerColorSelected
105
+ ColorPickerColorSelected,
100
106
  }
101
107
 
102
108
  type EditorToolEventType = EditorEventType.ToolEnabled
@@ -131,6 +137,16 @@ export interface EditorUndoStackUpdated {
131
137
  readonly redoStackSize: number;
132
138
  }
133
139
 
140
+ export interface CommandDoneEvent {
141
+ readonly kind: EditorEventType.CommandDone;
142
+ readonly command: Command;
143
+ }
144
+
145
+ export interface CommandUndoneEvent {
146
+ readonly kind: EditorEventType.CommandUndone;
147
+ readonly command: Command;
148
+ }
149
+
134
150
  export interface ColorPickerToggled {
135
151
  readonly kind: EditorEventType.ColorPickerToggled;
136
152
  readonly open: boolean;
@@ -143,8 +159,8 @@ export interface ColorPickerColorSelected {
143
159
 
144
160
  export type EditorEventDataType = EditorToolEvent | EditorObjectEvent
145
161
  | EditorViewportChangedEvent | DisplayResizedEvent
146
- | EditorUndoStackUpdated
147
- | ColorPickerToggled| ColorPickerColorSelected;
162
+ | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent
163
+ | ColorPickerToggled | ColorPickerColorSelected;
148
164
 
149
165
 
150
166
  // Returns a Promise to indicate that the event source should pause until the Promise resolves.
package/tsconfig.json CHANGED
@@ -24,6 +24,9 @@
24
24
 
25
25
  // Files that don't need transpilation
26
26
  "**/*.test.ts",
27
- "__mocks__/*"
27
+ "__mocks__/*",
28
+
29
+ // Output files
30
+ "./dist/**"
28
31
  ],
29
32
  }
package/typedoc.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "entryPoints": [
3
+ "./src/"
4
+ ],
5
+ "exclude": [
6
+ "**/*.test.ts"
7
+ ],
8
+ "excludePrivate": true,
9
+ "excludeInternal": true,
10
+ "commentStyle": "all",
11
+ "readme": "README.md",
12
+ "entryPointStrategy": "expand",
13
+ "out": "docs/typedoc/",
14
+
15
+ "validation": {
16
+ "notExported": false,
17
+ "invalidLink": true,
18
+ "notDocumented": false
19
+ }
20
+ }
@@ -1,32 +0,0 @@
1
- import { Point2, Vec2 } from './Vec2';
2
- import Vec3 from './Vec3';
3
- export default class Mat33 {
4
- readonly a1: number;
5
- readonly a2: number;
6
- readonly a3: number;
7
- readonly b1: number;
8
- readonly b2: number;
9
- readonly b3: number;
10
- readonly c1: number;
11
- readonly c2: number;
12
- readonly c3: number;
13
- private readonly rows;
14
- constructor(a1: number, a2: number, a3: number, b1: number, b2: number, b3: number, c1: number, c2: number, c3: number);
15
- static ofRows(r1: Vec3, r2: Vec3, r3: Vec3): Mat33;
16
- static identity: Mat33;
17
- inverse(): Mat33;
18
- invertable(): boolean;
19
- private cachedInverse;
20
- private computeInverse;
21
- transposed(): Mat33;
22
- rightMul(other: Mat33): Mat33;
23
- transformVec2(other: Vec3): Vec2;
24
- transformVec3(other: Vec3): Vec3;
25
- eq(other: Mat33, fuzz?: number): boolean;
26
- toString(): string;
27
- toArray(): number[];
28
- static translation(amount: Vec2): Mat33;
29
- static zRotation(radians: number, center?: Point2): Mat33;
30
- static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
31
- static fromCSSMatrix(cssString: string): Mat33;
32
- }
@@ -1,34 +0,0 @@
1
- export default class Vec3 {
2
- readonly x: number;
3
- readonly y: number;
4
- readonly z: number;
5
- private constructor();
6
- get xy(): {
7
- x: number;
8
- y: number;
9
- };
10
- static of(x: number, y: number, z: number): Vec3;
11
- at(idx: number): number;
12
- length(): number;
13
- magnitude(): number;
14
- magnitudeSquared(): number;
15
- angle(): number;
16
- normalized(): Vec3;
17
- times(c: number): Vec3;
18
- plus(v: Vec3): Vec3;
19
- minus(v: Vec3): Vec3;
20
- dot(other: Vec3): number;
21
- cross(other: Vec3): Vec3;
22
- orthog(): Vec3;
23
- extend(distance: number, direction: Vec3): Vec3;
24
- lerp(target: Vec3, fractionTo: number): Vec3;
25
- zip(other: Vec3, zip: (componentInThis: number, componentInOther: number) => number): Vec3;
26
- map(fn: (component: number) => number): Vec3;
27
- asArray(): number[];
28
- eq(other: Vec3, fuzz: number): boolean;
29
- toString(): string;
30
- static unitX: Vec3;
31
- static unitY: Vec3;
32
- static unitZ: Vec3;
33
- static zero: Vec3;
34
- }