js-draw 0.1.12 → 0.2.1

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 (132) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.firebaserc +5 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +34 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. package/.github/ISSUE_TEMPLATE/translation.md +96 -0
  6. package/.github/workflows/firebase-hosting-merge.yml +25 -0
  7. package/.github/workflows/firebase-hosting-pull-request.yml +22 -0
  8. package/.github/workflows/github-pages.yml +52 -0
  9. package/CHANGELOG.md +9 -0
  10. package/README.md +11 -6
  11. package/dist/bundle.js +1 -1
  12. package/dist/src/Color4.d.ts +19 -0
  13. package/dist/src/Color4.js +24 -3
  14. package/dist/src/Editor.d.ts +129 -2
  15. package/dist/src/Editor.js +94 -17
  16. package/dist/src/EditorImage.d.ts +7 -2
  17. package/dist/src/EditorImage.js +41 -25
  18. package/dist/src/EventDispatcher.d.ts +18 -0
  19. package/dist/src/EventDispatcher.js +19 -4
  20. package/dist/src/Pointer.js +3 -2
  21. package/dist/src/UndoRedoHistory.js +15 -2
  22. package/dist/src/Viewport.js +4 -1
  23. package/dist/src/bundle/bundled.d.ts +1 -2
  24. package/dist/src/bundle/bundled.js +1 -2
  25. package/dist/src/commands/Duplicate.d.ts +1 -1
  26. package/dist/src/commands/Duplicate.js +3 -4
  27. package/dist/src/commands/Erase.d.ts +1 -1
  28. package/dist/src/commands/Erase.js +6 -5
  29. package/dist/src/commands/SerializableCommand.d.ts +4 -5
  30. package/dist/src/commands/SerializableCommand.js +12 -4
  31. package/dist/src/commands/invertCommand.d.ts +4 -0
  32. package/dist/src/commands/invertCommand.js +44 -0
  33. package/dist/src/commands/lib.d.ts +6 -0
  34. package/dist/src/commands/lib.js +6 -0
  35. package/dist/src/commands/localization.d.ts +1 -0
  36. package/dist/src/commands/localization.js +1 -0
  37. package/dist/src/components/AbstractComponent.d.ts +13 -8
  38. package/dist/src/components/AbstractComponent.js +26 -15
  39. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
  40. package/dist/src/components/SVGGlobalAttributesObject.js +7 -1
  41. package/dist/src/components/Stroke.d.ts +12 -2
  42. package/dist/src/components/Stroke.js +10 -7
  43. package/dist/src/components/Text.d.ts +2 -2
  44. package/dist/src/components/Text.js +6 -6
  45. package/dist/src/components/UnknownSVGObject.d.ts +1 -1
  46. package/dist/src/components/UnknownSVGObject.js +6 -1
  47. package/dist/src/components/lib.d.ts +4 -0
  48. package/dist/src/components/lib.js +4 -0
  49. package/dist/src/lib.d.ts +25 -0
  50. package/dist/src/lib.js +25 -0
  51. package/dist/src/localizations/de.d.ts +3 -0
  52. package/dist/src/localizations/de.js +4 -0
  53. package/dist/src/localizations/getLocalizationTable.js +2 -0
  54. package/dist/src/math/Mat33.d.ts +47 -1
  55. package/dist/src/math/Mat33.js +48 -20
  56. package/dist/src/math/Path.js +3 -3
  57. package/dist/src/math/Rect2.d.ts +2 -2
  58. package/dist/src/math/Vec3.d.ts +62 -0
  59. package/dist/src/math/Vec3.js +62 -14
  60. package/dist/src/math/lib.d.ts +7 -0
  61. package/dist/src/math/lib.js +7 -0
  62. package/dist/src/math/rounding.js +1 -0
  63. package/dist/src/rendering/Display.d.ts +44 -0
  64. package/dist/src/rendering/Display.js +45 -6
  65. package/dist/src/rendering/caching/CacheRecord.d.ts +1 -0
  66. package/dist/src/rendering/caching/CacheRecord.js +3 -0
  67. package/dist/src/rendering/caching/CacheRecordManager.d.ts +4 -3
  68. package/dist/src/rendering/caching/CacheRecordManager.js +16 -4
  69. package/dist/src/rendering/caching/RenderingCache.d.ts +2 -3
  70. package/dist/src/rendering/caching/RenderingCache.js +9 -10
  71. package/dist/src/rendering/caching/types.d.ts +1 -3
  72. package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
  73. package/dist/src/toolbar/HTMLToolbar.js +1 -0
  74. package/dist/src/toolbar/makeColorInput.js +1 -1
  75. package/dist/src/toolbar/widgets/PenWidget.js +1 -0
  76. package/dist/src/tools/Pen.d.ts +1 -2
  77. package/dist/src/tools/Pen.js +8 -1
  78. package/dist/src/tools/PipetteTool.js +1 -0
  79. package/dist/src/tools/SelectionTool.js +45 -22
  80. package/dist/src/types.d.ts +17 -6
  81. package/dist/src/types.js +7 -5
  82. package/firebase.json +16 -0
  83. package/package.json +118 -101
  84. package/src/Color4.ts +23 -2
  85. package/src/Editor.ts +147 -25
  86. package/src/EditorImage.ts +45 -27
  87. package/src/EventDispatcher.ts +21 -6
  88. package/src/Pointer.ts +3 -2
  89. package/src/UndoRedoHistory.ts +18 -2
  90. package/src/Viewport.ts +5 -2
  91. package/src/bundle/bundled.ts +1 -2
  92. package/src/commands/Duplicate.ts +3 -4
  93. package/src/commands/Erase.ts +6 -5
  94. package/src/commands/SerializableCommand.ts +17 -9
  95. package/src/commands/invertCommand.ts +51 -0
  96. package/src/commands/lib.ts +14 -0
  97. package/src/commands/localization.ts +2 -0
  98. package/src/components/AbstractComponent.ts +31 -20
  99. package/src/components/SVGGlobalAttributesObject.ts +8 -1
  100. package/src/components/Stroke.test.ts +1 -1
  101. package/src/components/Stroke.ts +11 -7
  102. package/src/components/Text.ts +6 -7
  103. package/src/components/UnknownSVGObject.ts +7 -1
  104. package/src/components/lib.ts +9 -0
  105. package/src/lib.ts +28 -0
  106. package/src/localizations/de.ts +98 -0
  107. package/src/localizations/getLocalizationTable.ts +2 -0
  108. package/src/math/Mat33.ts +48 -20
  109. package/src/math/Path.ts +3 -3
  110. package/src/math/Rect2.ts +2 -2
  111. package/src/math/Vec3.ts +62 -14
  112. package/src/math/lib.ts +15 -0
  113. package/src/math/rounding.ts +2 -0
  114. package/src/rendering/Display.ts +46 -6
  115. package/src/rendering/caching/CacheRecord.test.ts +1 -1
  116. package/src/rendering/caching/CacheRecord.ts +4 -0
  117. package/src/rendering/caching/CacheRecordManager.ts +33 -7
  118. package/src/rendering/caching/RenderingCache.ts +10 -15
  119. package/src/rendering/caching/types.ts +1 -6
  120. package/src/rendering/renderers/CanvasRenderer.ts +1 -1
  121. package/src/styles.js +4 -0
  122. package/src/toolbar/HTMLToolbar.ts +1 -0
  123. package/src/toolbar/makeColorInput.ts +1 -1
  124. package/src/toolbar/toolbar.css +8 -2
  125. package/src/toolbar/widgets/PenWidget.ts +2 -0
  126. package/src/tools/PanZoom.ts +0 -1
  127. package/src/tools/Pen.ts +11 -2
  128. package/src/tools/PipetteTool.ts +2 -0
  129. package/src/tools/SelectionTool.ts +46 -18
  130. package/src/types.ts +19 -3
  131. package/tsconfig.json +4 -1
  132. package/typedoc.json +20 -0
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
3
+ *
4
+ * @example
5
+ * ```
6
+ * const editor = new Editor(document.body);
7
+ * const w = editor.display.width;
8
+ * const h = editor.display.height;
9
+ * const center = Vec2.of(w / 2, h / 2);
10
+ * const colorAtCenter = editor.display.getColorAt(center);
11
+ * ```
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
1
16
  import AbstractRenderer from './renderers/AbstractRenderer';
2
17
  import CanvasRenderer from './renderers/CanvasRenderer';
3
18
  import { Editor } from '../Editor';
@@ -23,6 +38,7 @@ export default class Display {
23
38
  private resizeSurfacesCallback?: ()=> void;
24
39
  private flattenCallback?: ()=> void;
25
40
 
41
+ /** @internal */
26
42
  public constructor(
27
43
  private editor: Editor, mode: RenderingMode, private parent: HTMLElement|null
28
44
  ) {
@@ -60,7 +76,7 @@ export default class Display {
60
76
  return this.dryInkRenderer.canRenderFromWithoutDataLoss(renderer);
61
77
  },
62
78
  blockResolution: cacheBlockResolution,
63
- cacheSize: 500 * 500 * 4 * 220,
79
+ cacheSize: 500 * 500 * 4 * 150,
64
80
  maxScale: 1.5,
65
81
  minComponentsPerCache: 45,
66
82
  minComponentsToUseCache: 105,
@@ -75,9 +91,11 @@ export default class Display {
75
91
  });
76
92
  }
77
93
 
78
- // Returns the visible width of the display (e.g. how much
79
- // space the display's element takes up in the x direction
80
- // in the DOM).
94
+ /**
95
+ * @returns the visible width of the display (e.g. how much
96
+ * space the display's element takes up in the x direction
97
+ * in the DOM).
98
+ */
81
99
  public get width(): number {
82
100
  return this.dryInkRenderer.displaySize().x;
83
101
  }
@@ -86,10 +104,15 @@ export default class Display {
86
104
  return this.dryInkRenderer.displaySize().y;
87
105
  }
88
106
 
107
+ /** @internal */
89
108
  public getCache() {
90
109
  return this.cache;
91
110
  }
92
111
 
112
+ /**
113
+ * @returns the color at the given point on the dry ink renderer, or `null` if `screenPos`
114
+ * is not on the display.
115
+ */
93
116
  public getColorAt = (_screenPos: Point2): Color4|null => {
94
117
  return null;
95
118
  };
@@ -170,6 +193,10 @@ export default class Display {
170
193
  this.editor.createHTMLOverlay(textRendererOutputContainer);
171
194
  }
172
195
 
196
+ /**
197
+ * Rerenders the text-based display.
198
+ * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
199
+ */
173
200
  public rerenderAsText() {
174
201
  this.textRenderer.clear();
175
202
  this.editor.image.render(this.textRenderer, this.editor.viewport);
@@ -179,7 +206,11 @@ export default class Display {
179
206
  }
180
207
  }
181
208
 
182
- // Clears the drawing surfaces and otherwise prepares for a rerender.
209
+ /**
210
+ * Clears the drawing surfaces and otherwise prepares for a rerender.
211
+ *
212
+ * @returns the dry ink renderer.
213
+ */
183
214
  public startRerender(): AbstractRenderer {
184
215
  this.resizeSurfacesCallback?.();
185
216
  this.wetInkRenderer.clear();
@@ -188,19 +219,28 @@ export default class Display {
188
219
  return this.dryInkRenderer;
189
220
  }
190
221
 
222
+ /**
223
+ * If `draftMode`, the dry ink renderer is configured to render
224
+ * low-quality output.
225
+ */
191
226
  public setDraftMode(draftMode: boolean) {
192
227
  this.dryInkRenderer.setDraftMode(draftMode);
193
228
  }
194
229
 
230
+ /** @internal */
195
231
  public getDryInkRenderer(): AbstractRenderer {
196
232
  return this.dryInkRenderer;
197
233
  }
198
234
 
235
+ /**
236
+ * @returns The renderer used for showing action previews (e.g. an unfinished stroke).
237
+ * The `wetInkRenderer`'s surface is stacked above the `dryInkRenderer`'s.
238
+ */
199
239
  public getWetInkRenderer(): AbstractRenderer {
200
240
  return this.wetInkRenderer;
201
241
  }
202
242
 
203
- // Re-renders the contents of the wetInkRenderer onto the dryInkRenderer
243
+ /** Re-renders the contents of the wetInkRenderer onto the dryInkRenderer. */
204
244
  public flatten() {
205
245
  this.flattenCallback?.();
206
246
  }
@@ -11,7 +11,7 @@ describe('CacheRecord', () => {
11
11
  const { cache } = createCache(undefined, {
12
12
  blockResolution,
13
13
  });
14
- const state = cache.getSharedState();
14
+ const state = cache['sharedState'];
15
15
 
16
16
  const record = new CacheRecord(() => {}, state);
17
17
 
@@ -11,6 +11,9 @@ export default class CacheRecord {
11
11
  private lastUsedCycle: number;
12
12
  private allocd: boolean = false;
13
13
 
14
+ // For debugging
15
+ public allocCount: number = 0;
16
+
14
17
  public constructor(
15
18
  private onBeforeDeallocCallback: BeforeDeallocCallback|null,
16
19
  private cacheState: CacheState,
@@ -46,6 +49,7 @@ export default class CacheRecord {
46
49
  this.allocd = true;
47
50
  this.onBeforeDeallocCallback = newDeallocCallback;
48
51
  this.lastUsedCycle = this.cacheState.currentRenderingCycle;
52
+ this.allocCount ++;
49
53
  }
50
54
 
51
55
  public getLastUsedCycle(): number {
@@ -1,38 +1,64 @@
1
- import { BeforeDeallocCallback, PartialCacheState } from './types';
1
+ import { BeforeDeallocCallback, CacheProps, CacheState } from './types';
2
2
  import CacheRecord from './CacheRecord';
3
3
  import Rect2 from '../../math/Rect2';
4
4
 
5
+ const debugMode = false;
5
6
 
6
7
  export class CacheRecordManager {
7
8
  // Fixed-size array: Cache blocks are assigned indicies into [cachedCanvases].
8
9
  private cacheRecords: CacheRecord[] = [];
9
10
  private maxCanvases: number;
11
+ private cacheState: CacheState;
10
12
 
11
- public constructor(private readonly cacheState: PartialCacheState) {
12
- const cacheProps = cacheState.props;
13
+ public constructor(cacheProps: CacheProps) {
13
14
  this.maxCanvases = Math.ceil(
14
15
  // Assuming four components per pixel:
15
16
  cacheProps.cacheSize / 4 / cacheProps.blockResolution.x / cacheProps.blockResolution.y
16
17
  );
17
18
  }
18
19
 
20
+ public setSharedState(state: CacheState) {
21
+ this.cacheState = state;
22
+ }
23
+
19
24
  public allocCanvas(drawTo: Rect2, onDealloc: BeforeDeallocCallback): CacheRecord {
20
25
  if (this.cacheRecords.length < this.maxCanvases) {
21
26
  const record: CacheRecord = new CacheRecord(
22
27
  onDealloc,
23
- {
24
- ...this.cacheState,
25
- recordManager: this,
26
- },
28
+ this.cacheState,
27
29
  );
28
30
  record.setRenderingRegion(drawTo);
29
31
  this.cacheRecords.push(record);
30
32
 
33
+ if (debugMode) {
34
+ console.log('[Cache] Cache spaces used: ', this.cacheRecords.length, ' of ', this.maxCanvases);
35
+ }
36
+
31
37
  return record;
32
38
  } else {
33
39
  const lru = this.getLeastRecentlyUsedRecord()!;
40
+
41
+ if (debugMode) {
42
+ console.log(
43
+ '[Cache] Re-alloc. Times allocated: ', lru.allocCount,
44
+ '\nLast used cycle: ', lru.getLastUsedCycle(),
45
+ '\nCurrent cycle: ', this.cacheState.currentRenderingCycle
46
+ );
47
+ }
48
+
34
49
  lru.realloc(onDealloc);
35
50
  lru.setRenderingRegion(drawTo);
51
+
52
+ if (debugMode) {
53
+ console.log(
54
+ '[Cache] Now re-alloc\'d. Last used cycle: ', lru.getLastUsedCycle()
55
+ );
56
+ console.assert(
57
+ lru['cacheState'] === this.cacheState,
58
+ '[Cache] Unequal cache states! cacheState should be a shared object!'
59
+ );
60
+ }
61
+
36
62
  return lru;
37
63
  }
38
64
  }
@@ -4,46 +4,41 @@ import Viewport from '../../Viewport';
4
4
  import AbstractRenderer from '../renderers/AbstractRenderer';
5
5
  import RenderingCacheNode from './RenderingCacheNode';
6
6
  import { CacheRecordManager } from './CacheRecordManager';
7
- import { CacheProps, CacheState, PartialCacheState } from './types';
7
+ import { CacheProps, CacheState } from './types';
8
8
 
9
9
  export default class RenderingCache {
10
- private partialSharedState: PartialCacheState;
10
+ private sharedState: CacheState;
11
11
  private recordManager: CacheRecordManager;
12
12
  private rootNode: RenderingCacheNode|null;
13
13
 
14
14
  public constructor(cacheProps: CacheProps) {
15
- this.partialSharedState = {
15
+ this.recordManager = new CacheRecordManager(cacheProps);
16
+ this.sharedState = {
16
17
  props: cacheProps,
17
18
  currentRenderingCycle: 0,
18
- };
19
- this.recordManager = new CacheRecordManager(this.partialSharedState);
20
- }
21
-
22
- public getSharedState(): CacheState {
23
- return {
24
- ...this.partialSharedState,
25
19
  recordManager: this.recordManager,
26
20
  };
21
+ this.recordManager.setSharedState(this.sharedState);
27
22
  }
28
23
 
29
24
  public render(screenRenderer: AbstractRenderer, image: ImageNode, viewport: Viewport) {
30
25
  const visibleRect = viewport.visibleRect;
31
- this.partialSharedState.currentRenderingCycle ++;
26
+ this.sharedState.currentRenderingCycle ++;
32
27
 
33
28
  // If we can't use the cache,
34
- if (!this.partialSharedState.props.isOfCorrectType(screenRenderer)) {
29
+ if (!this.sharedState.props.isOfCorrectType(screenRenderer)) {
35
30
  image.render(screenRenderer, visibleRect);
36
31
  return;
37
32
  }
38
33
 
39
34
  if (!this.rootNode) {
40
35
  // Adjust the node so that it has the correct aspect ratio
41
- const res = this.partialSharedState.props.blockResolution;
36
+ const res = this.sharedState.props.blockResolution;
42
37
 
43
38
  const topLeft = visibleRect.topLeft;
44
39
  this.rootNode = new RenderingCacheNode(
45
40
  new Rect2(topLeft.x, topLeft.y, res.x, res.y),
46
- this.getSharedState()
41
+ this.sharedState
47
42
  );
48
43
  }
49
44
 
@@ -54,7 +49,7 @@ export default class RenderingCache {
54
49
  this.rootNode = this.rootNode!.smallestChildContaining(visibleRect) ?? this.rootNode;
55
50
 
56
51
  const visibleLeaves = image.getLeavesIntersectingRegion(viewport.visibleRect, rect => screenRenderer.isTooSmallToRender(rect));
57
- if (visibleLeaves.length > this.partialSharedState.props.minComponentsToUseCache) {
52
+ if (visibleLeaves.length > this.sharedState.props.minComponentsToUseCache) {
58
53
  this.rootNode!.renderItems(screenRenderer, [ image ], viewport);
59
54
  } else {
60
55
  image.render(screenRenderer, visibleRect);
@@ -27,13 +27,8 @@ export interface CacheProps {
27
27
  minComponentsToUseCache: number;
28
28
  }
29
29
 
30
- // CacheRecordManager relies on a partial copy of the shared state. Thus,
31
- // we need to separate partial/non-partial state.
32
- export interface PartialCacheState {
30
+ export interface CacheState {
33
31
  currentRenderingCycle: number;
34
32
  props: CacheProps;
35
- }
36
-
37
- export interface CacheState extends PartialCacheState {
38
33
  recordManager: CacheRecordManager;
39
34
  }
@@ -64,7 +64,7 @@ export default class CanvasRenderer extends AbstractRenderer {
64
64
  this.minRenderSizeAnyDimen = 2;
65
65
  } else {
66
66
  this.minSquareCurveApproxDist = 1;
67
- this.minRenderSizeBothDimens = 1;
67
+ this.minRenderSizeBothDimens = 0.5;
68
68
  this.minRenderSizeAnyDimen = 0;
69
69
  }
70
70
  }
package/src/styles.js CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Applies the editor's styles to the document.
3
+ * @packageDocumentation
4
+ */
1
5
 
2
6
  import './Editor.css';
3
7
  import '@melloware/coloris/dist/coloris.css';
@@ -43,6 +43,7 @@ export default class HTMLToolbar {
43
43
  this.setupColorPickers();
44
44
  }
45
45
 
46
+ // @internal
46
47
  public setupColorPickers() {
47
48
  const closePickerOverlay = document.createElement('div');
48
49
  closePickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
@@ -8,7 +8,7 @@ import { makePipetteIcon } from './icons';
8
8
  type OnColorChangeListener = (color: Color4)=>void;
9
9
 
10
10
 
11
- // Returns [ input, container ].
11
+ // Returns [ color input, input container ].
12
12
  export const makeColorInput = (editor: Editor, onColorChange: OnColorChangeListener): [ HTMLInputElement, HTMLElement ] => {
13
13
  const colorInputContainer = document.createElement('span');
14
14
  const colorInput = document.createElement('input');
@@ -24,6 +24,7 @@
24
24
  .toolbar-root > .toolbar-toolContainer > * > button,
25
25
  .toolbar-root > .toolbar-buttonGroup > button {
26
26
  width: min-content;
27
+ white-space: pre;
27
28
  height: min(20vh, 60px);
28
29
  }
29
30
 
@@ -64,8 +65,13 @@
64
65
  margin-right: 3px;
65
66
 
66
67
  min-width: 40px;
67
- max-width: 70px;
68
- font-size: 11pt;
68
+ width: min-content;
69
+ font-size: 1em;
70
+ }
71
+
72
+ .toolbar-dropdown > .toolbar-toolContainer > button,
73
+ .toolbar-dropdown > .toolbar-toolContainer > .toolbar-button {
74
+ width: 6em;
69
75
  }
70
76
 
71
77
  .toolbar-button:hover, .toolbar-root button:not(:disabled):hover {
@@ -1,3 +1,5 @@
1
+ // @internal @packageDocumentation
2
+
1
3
  import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';
2
4
  import { makeFreehandLineBuilder } from '../../components/builders/FreehandLineBuilder';
3
5
  import { makeLineBuilder } from '../../components/builders/LineBuilder';
@@ -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,
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';
@@ -12,6 +12,7 @@ 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,39 +277,65 @@ 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
341
  public description(_editor: Editor, localizationTable: EditorLocalization) {
package/src/types.ts CHANGED
@@ -9,6 +9,7 @@ import AbstractComponent from './components/AbstractComponent';
9
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
+ }