js-draw 1.0.1 → 1.0.2

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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/cjs/version.js +1 -1
  4. package/dist/mjs/version.mjs +1 -1
  5. package/docs/img/readme-images/js-draw.jpg +0 -0
  6. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  7. package/package.json +5 -4
  8. package/dist-test/test_imports/package-lock.json +0 -13
  9. package/dist-test/test_imports/package.json +0 -12
  10. package/dist-test/test_imports/test-imports.js +0 -11
  11. package/dist-test/test_imports/test-require.cjs +0 -14
  12. package/src/Editor.loadFrom.test.ts +0 -24
  13. package/src/Editor.test.ts +0 -107
  14. package/src/Editor.toSVG.test.ts +0 -294
  15. package/src/Editor.ts +0 -1443
  16. package/src/EditorImage.test.ts +0 -117
  17. package/src/EditorImage.ts +0 -609
  18. package/src/EventDispatcher.test.ts +0 -123
  19. package/src/EventDispatcher.ts +0 -72
  20. package/src/Pointer.ts +0 -183
  21. package/src/SVGLoader.test.ts +0 -114
  22. package/src/SVGLoader.ts +0 -672
  23. package/src/UndoRedoHistory.test.ts +0 -34
  24. package/src/UndoRedoHistory.ts +0 -102
  25. package/src/Viewport.ts +0 -322
  26. package/src/bundle/bundled.ts +0 -7
  27. package/src/commands/Command.ts +0 -45
  28. package/src/commands/Duplicate.ts +0 -75
  29. package/src/commands/Erase.ts +0 -95
  30. package/src/commands/SerializableCommand.ts +0 -49
  31. package/src/commands/UnresolvedCommand.ts +0 -37
  32. package/src/commands/invertCommand.ts +0 -58
  33. package/src/commands/lib.ts +0 -16
  34. package/src/commands/localization.ts +0 -47
  35. package/src/commands/uniteCommands.test.ts +0 -23
  36. package/src/commands/uniteCommands.ts +0 -140
  37. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  38. package/src/components/AbstractComponent.ts +0 -383
  39. package/src/components/BackgroundComponent.test.ts +0 -44
  40. package/src/components/BackgroundComponent.ts +0 -348
  41. package/src/components/ImageComponent.ts +0 -176
  42. package/src/components/RestylableComponent.ts +0 -161
  43. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  44. package/src/components/Stroke.test.ts +0 -137
  45. package/src/components/Stroke.ts +0 -294
  46. package/src/components/TextComponent.test.ts +0 -202
  47. package/src/components/TextComponent.ts +0 -429
  48. package/src/components/UnknownSVGObject.test.ts +0 -10
  49. package/src/components/UnknownSVGObject.ts +0 -60
  50. package/src/components/builders/ArrowBuilder.ts +0 -106
  51. package/src/components/builders/CircleBuilder.ts +0 -100
  52. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  53. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  54. package/src/components/builders/LineBuilder.ts +0 -77
  55. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  56. package/src/components/builders/RectangleBuilder.ts +0 -73
  57. package/src/components/builders/types.ts +0 -15
  58. package/src/components/lib.ts +0 -31
  59. package/src/components/localization.ts +0 -24
  60. package/src/components/util/StrokeSmoother.ts +0 -302
  61. package/src/components/util/describeComponentList.ts +0 -18
  62. package/src/dialogs/makeAboutDialog.ts +0 -82
  63. package/src/inputEvents.ts +0 -143
  64. package/src/lib.ts +0 -91
  65. package/src/localization.ts +0 -34
  66. package/src/localizations/de.ts +0 -146
  67. package/src/localizations/en.ts +0 -8
  68. package/src/localizations/es.ts +0 -74
  69. package/src/localizations/getLocalizationTable.test.ts +0 -27
  70. package/src/localizations/getLocalizationTable.ts +0 -74
  71. package/src/rendering/Display.ts +0 -247
  72. package/src/rendering/RenderablePathSpec.ts +0 -88
  73. package/src/rendering/RenderingStyle.test.ts +0 -68
  74. package/src/rendering/RenderingStyle.ts +0 -55
  75. package/src/rendering/TextRenderingStyle.ts +0 -55
  76. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  77. package/src/rendering/caching/CacheRecord.ts +0 -76
  78. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  79. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  80. package/src/rendering/caching/RenderingCache.ts +0 -66
  81. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  82. package/src/rendering/caching/testUtils.ts +0 -35
  83. package/src/rendering/caching/types.ts +0 -34
  84. package/src/rendering/lib.ts +0 -8
  85. package/src/rendering/localization.ts +0 -20
  86. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  87. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  88. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  89. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  90. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  91. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  92. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  93. package/src/shortcuts/KeyBinding.test.ts +0 -61
  94. package/src/shortcuts/KeyBinding.ts +0 -257
  95. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  96. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  97. package/src/shortcuts/lib.ts +0 -3
  98. package/src/testing/createEditor.ts +0 -11
  99. package/src/testing/getUniquePointerId.ts +0 -18
  100. package/src/testing/lib.ts +0 -3
  101. package/src/testing/sendPenEvent.ts +0 -36
  102. package/src/testing/sendTouchEvent.ts +0 -71
  103. package/src/toolbar/AbstractToolbar.ts +0 -542
  104. package/src/toolbar/DropdownToolbar.ts +0 -220
  105. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  106. package/src/toolbar/EdgeToolbar.ts +0 -543
  107. package/src/toolbar/IconProvider.ts +0 -861
  108. package/src/toolbar/constants.ts +0 -1
  109. package/src/toolbar/lib.ts +0 -6
  110. package/src/toolbar/localization.ts +0 -136
  111. package/src/toolbar/types.ts +0 -13
  112. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  113. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  114. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  115. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  116. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  117. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  118. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  119. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  120. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  121. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  122. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  123. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  124. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  125. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  126. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  127. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  128. package/src/toolbar/widgets/keybindings.ts +0 -19
  129. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  130. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  131. package/src/toolbar/widgets/layout/types.ts +0 -74
  132. package/src/toolbar/widgets/lib.ts +0 -13
  133. package/src/tools/BaseTool.ts +0 -169
  134. package/src/tools/Eraser.test.ts +0 -103
  135. package/src/tools/Eraser.ts +0 -173
  136. package/src/tools/FindTool.test.ts +0 -67
  137. package/src/tools/FindTool.ts +0 -153
  138. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  139. package/src/tools/InputFilter/InputMapper.ts +0 -41
  140. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  141. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  142. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  143. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  144. package/src/tools/PanZoom.test.ts +0 -339
  145. package/src/tools/PanZoom.ts +0 -525
  146. package/src/tools/PasteHandler.ts +0 -94
  147. package/src/tools/Pen.test.ts +0 -260
  148. package/src/tools/Pen.ts +0 -284
  149. package/src/tools/PipetteTool.ts +0 -84
  150. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  151. package/src/tools/SelectionTool/Selection.ts +0 -647
  152. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  153. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  154. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  155. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  156. package/src/tools/SelectionTool/types.ts +0 -11
  157. package/src/tools/SoundUITool.ts +0 -221
  158. package/src/tools/TextTool.ts +0 -339
  159. package/src/tools/ToolController.ts +0 -224
  160. package/src/tools/ToolEnabledGroup.ts +0 -14
  161. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  162. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  163. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  164. package/src/tools/UndoRedoShortcut.ts +0 -24
  165. package/src/tools/keybindings.ts +0 -85
  166. package/src/tools/lib.ts +0 -22
  167. package/src/tools/localization.ts +0 -76
  168. package/src/types.ts +0 -151
  169. package/src/util/ReactiveValue.test.ts +0 -168
  170. package/src/util/ReactiveValue.ts +0 -241
  171. package/src/util/assertions.ts +0 -55
  172. package/src/util/fileToBase64.ts +0 -18
  173. package/src/util/guessKeyCodeFromKey.ts +0 -36
  174. package/src/util/listPrefixMatch.ts +0 -19
  175. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  176. package/src/util/untilNextAnimationFrame.ts +0 -9
  177. package/src/util/waitForAll.ts +0 -18
  178. package/src/util/waitForTimeout.ts +0 -9
  179. package/src/version.test.ts +0 -12
  180. package/src/version.ts +0 -3
  181. package/tools/allLocales.js +0 -4
  182. package/tools/copyREADME.ts +0 -62
@@ -1,647 +0,0 @@
1
- /**
2
- * @internal
3
- * @packageDocumentation
4
- */
5
-
6
- import SerializableCommand from '../../commands/SerializableCommand';
7
- import Editor from '../../Editor';
8
- import { Mat33, Rect2, Point2, Vec2, Mat33Array } from '@js-draw/math';
9
- import Pointer from '../../Pointer';
10
- import SelectionHandle, { HandleShape, handleSize } from './SelectionHandle';
11
- import { cssPrefix } from './SelectionTool';
12
- import AbstractComponent from '../../components/AbstractComponent';
13
- import { EditorLocalization } from '../../localization';
14
- import Viewport from '../../Viewport';
15
- import Erase from '../../commands/Erase';
16
- import Duplicate from '../../commands/Duplicate';
17
- import Command from '../../commands/Command';
18
- import { DragTransformer, ResizeTransformer, RotateTransformer } from './TransformMode';
19
- import { ResizeMode } from './types';
20
- import EditorImage from '../../EditorImage';
21
-
22
- const updateChunkSize = 100;
23
- const maxPreviewElemCount = 500;
24
-
25
- // @internal
26
- export default class Selection {
27
- private handles: SelectionHandle[];
28
- private originalRegion: Rect2;
29
-
30
- private transformers;
31
- private transform: Mat33 = Mat33.identity;
32
-
33
- private selectedElems: AbstractComponent[] = [];
34
-
35
- private container: HTMLElement;
36
- private backgroundElem: HTMLElement;
37
-
38
- private hasParent: boolean = true;
39
-
40
- public constructor(startPoint: Point2, private editor: Editor) {
41
- this.originalRegion = new Rect2(startPoint.x, startPoint.y, 0, 0);
42
- this.transformers = {
43
- drag: new DragTransformer(editor, this),
44
- resize: new ResizeTransformer(editor, this),
45
- rotate: new RotateTransformer(editor, this),
46
- };
47
-
48
- this.container = document.createElement('div');
49
- this.backgroundElem = document.createElement('div');
50
- this.backgroundElem.classList.add(`${cssPrefix}selection-background`);
51
- this.container.appendChild(this.backgroundElem);
52
-
53
- const resizeHorizontalHandle = new SelectionHandle(
54
- HandleShape.Square,
55
- Vec2.of(1, 0.5),
56
- this,
57
- this.editor.viewport,
58
- (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.HorizontalOnly),
59
- (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint),
60
- () => this.transformers.resize.onDragEnd(),
61
- );
62
-
63
- const resizeVerticalHandle = new SelectionHandle(
64
- HandleShape.Square,
65
- Vec2.of(0.5, 1),
66
- this,
67
- this.editor.viewport,
68
- (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.VerticalOnly),
69
- (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint),
70
- () => this.transformers.resize.onDragEnd(),
71
- );
72
-
73
- const resizeBothHandle = new SelectionHandle(
74
- HandleShape.Square,
75
- Vec2.of(1, 1),
76
- this,
77
- this.editor.viewport,
78
- (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.Both),
79
- (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint),
80
- () => this.transformers.resize.onDragEnd(),
81
- );
82
-
83
- const rotationHandle = new SelectionHandle(
84
- HandleShape.Circle,
85
- Vec2.of(0.5, 0),
86
- this,
87
- this.editor.viewport,
88
- (startPoint) => this.transformers.rotate.onDragStart(startPoint),
89
- (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint),
90
- () => this.transformers.rotate.onDragEnd(),
91
- );
92
-
93
- this.handles = [
94
- resizeBothHandle,
95
- resizeHorizontalHandle,
96
- resizeVerticalHandle,
97
- rotationHandle,
98
- ];
99
-
100
- for (const handle of this.handles) {
101
- handle.addTo(this.backgroundElem);
102
- }
103
- }
104
-
105
- // @internal Intended for unit tests
106
- public getBackgroundElem(): HTMLElement {
107
- return this.backgroundElem;
108
- }
109
-
110
- public getTransform(): Mat33 {
111
- return this.transform;
112
- }
113
-
114
- public get preTransformRegion(): Rect2 {
115
- return this.originalRegion;
116
- }
117
-
118
- public get region(): Rect2 {
119
- // TODO: This currently assumes that the region rotates about its center.
120
- // This may not be true.
121
- const rotationMatrix = Mat33.zRotation(this.regionRotation, this.originalRegion.center);
122
- const scaleAndTranslateMat = this.transform.rightMul(rotationMatrix.inverse());
123
- return this.originalRegion.transformedBoundingBox(scaleAndTranslateMat);
124
- }
125
-
126
- /**
127
- * Computes and returns the bounding box of the selection without
128
- * any additional padding. Computes directly from the elements that are selected.
129
- * @internal
130
- */
131
- public computeTightBoundingBox() {
132
- const bbox = this.selectedElems.reduce((
133
- accumulator: Rect2|null, elem: AbstractComponent
134
- ): Rect2 => {
135
- return (accumulator ?? elem.getBBox()).union(elem.getBBox());
136
- }, null);
137
-
138
- return bbox ?? Rect2.empty;
139
- }
140
-
141
- public get regionRotation(): number {
142
- return this.transform.transformVec3(Vec2.unitX).angle();
143
- }
144
-
145
- public get preTransformedScreenRegion(): Rect2 {
146
- const toScreen = (vec: Point2) => this.editor.viewport.canvasToScreen(vec);
147
- return Rect2.fromCorners(
148
- toScreen(this.preTransformRegion.topLeft),
149
- toScreen(this.preTransformRegion.bottomRight)
150
- );
151
- }
152
-
153
- public get preTransformedScreenRegionRotation(): number {
154
- return this.editor.viewport.getRotationAngle();
155
- }
156
-
157
- public get screenRegion(): Rect2 {
158
- const toScreen = this.editor.viewport.canvasToScreenTransform;
159
- const scaleFactor = this.editor.viewport.getScaleFactor();
160
-
161
- const screenCenter = toScreen.transformVec2(this.region.center);
162
-
163
- return new Rect2(
164
- screenCenter.x, screenCenter.y, scaleFactor * this.region.width, scaleFactor * this.region.height
165
- ).translatedBy(this.region.size.times(-scaleFactor/2));
166
- }
167
-
168
- public get screenRegionRotation(): number {
169
- return this.regionRotation + this.editor.viewport.getRotationAngle();
170
- }
171
-
172
- // Applies, previews, but doesn't finalize the given transformation.
173
- public setTransform(transform: Mat33, preview: boolean = true) {
174
- this.transform = transform;
175
-
176
- if (preview && this.hasParent) {
177
- this.scrollTo();
178
- this.previewTransformCmds();
179
- }
180
- }
181
-
182
- // Applies the current transformation to the selection
183
- public async finalizeTransform() {
184
- const fullTransform = this.transform;
185
- const selectedElems = this.selectedElems;
186
-
187
- // Reset for the next drag
188
- this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
189
- this.transform = Mat33.identity;
190
-
191
- // Make the commands undo-able
192
- await this.editor.dispatch(new Selection.ApplyTransformationCommand(
193
- this, selectedElems, fullTransform
194
- ));
195
-
196
- // Clear renderings of any in-progress transformations
197
- const wetInkRenderer = this.editor.display.getWetInkRenderer();
198
- wetInkRenderer.clear();
199
- }
200
-
201
- static {
202
- SerializableCommand.register('selection-tool-transform', (json: any, _editor) => {
203
- // The selection box is lost when serializing/deserializing. No need to store box rotation
204
- const fullTransform: Mat33 = new Mat33(...(json.transform as Mat33Array));
205
- const elemIds: string[] = (json.elems as any[] ?? []);
206
-
207
- return new this.ApplyTransformationCommand(null, elemIds, fullTransform);
208
- });
209
- }
210
-
211
- private static ApplyTransformationCommand = class extends SerializableCommand {
212
- private transformCommands: Command[];
213
- private selectedElemIds: string[];
214
-
215
- public constructor(
216
- private selection: Selection|null,
217
-
218
- // If a `string[]`, selectedElems is a list of element IDs.
219
- selectedElems: AbstractComponent[]|string[],
220
-
221
- // Full transformation used to transform elements.
222
- private fullTransform: Mat33,
223
- ) {
224
- super('selection-tool-transform');
225
-
226
- const isIDList = (arr: AbstractComponent[]|string[]): arr is string[] => {
227
- return typeof arr[0] === 'string';
228
- };
229
-
230
- // If a list of element IDs,
231
- if (isIDList(selectedElems)) {
232
- this.selectedElemIds = selectedElems as string[];
233
- } else {
234
- this.selectedElemIds = (selectedElems as AbstractComponent[]).map(elem => elem.getId());
235
- this.transformCommands = selectedElems.map(elem => {
236
- return elem.transformBy(this.fullTransform);
237
- });
238
- }
239
- }
240
-
241
- private resolveToElems(editor: Editor) {
242
- if (this.transformCommands) {
243
- return;
244
- }
245
-
246
- this.transformCommands = this.selectedElemIds.map(id => {
247
- const elem = editor.image.lookupElement(id);
248
-
249
- if (!elem) {
250
- throw new Error(`Unable to find element with ID, ${id}.`);
251
- }
252
-
253
- return elem.transformBy(this.fullTransform);
254
- });
255
- }
256
-
257
- public async apply(editor: Editor) {
258
- this.resolveToElems(editor);
259
-
260
- this.selection?.setTransform(this.fullTransform, false);
261
- this.selection?.updateUI();
262
- await editor.asyncApplyCommands(this.transformCommands, updateChunkSize);
263
- this.selection?.setTransform(Mat33.identity, false);
264
- this.selection?.recomputeRegion();
265
- this.selection?.updateUI();
266
- }
267
-
268
- public async unapply(editor: Editor) {
269
- this.resolveToElems(editor);
270
-
271
- this.selection?.setTransform(this.fullTransform.inverse(), false);
272
- this.selection?.updateUI();
273
-
274
- await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
275
- this.selection?.setTransform(Mat33.identity, false);
276
- this.selection?.recomputeRegion();
277
- this.selection?.updateUI();
278
- }
279
-
280
- protected serializeToJSON() {
281
- return {
282
- elems: this.selectedElemIds,
283
- transform: this.fullTransform.toArray(),
284
- };
285
- }
286
-
287
- public description(_editor: Editor, localizationTable: EditorLocalization) {
288
- return localizationTable.transformedElements(this.selectedElemIds.length);
289
- }
290
- };
291
-
292
- // Preview the effects of the current transformation on the selection
293
- private previewTransformCmds() {
294
- // Don't render what we're moving if it's likely to be slow.
295
- if (this.selectedElems.length > maxPreviewElemCount) {
296
- this.updateUI();
297
- return;
298
- }
299
-
300
- const wetInkRenderer = this.editor.display.getWetInkRenderer();
301
- wetInkRenderer.clear();
302
- wetInkRenderer.pushTransform(this.transform);
303
-
304
- const viewportVisibleRect = this.editor.viewport.visibleRect;
305
- const visibleRect = viewportVisibleRect.transformedBoundingBox(this.transform.inverse());
306
-
307
- for (const elem of this.selectedElems) {
308
- elem.render(wetInkRenderer, visibleRect);
309
- }
310
-
311
- wetInkRenderer.popTransform();
312
-
313
- this.updateUI();
314
- }
315
-
316
- // Find the objects corresponding to this in the document,
317
- // select them.
318
- // Returns false iff nothing was selected.
319
- public resolveToObjects(): boolean {
320
- let singleItemSelectionMode = false;
321
- this.transform = Mat33.identity;
322
-
323
- // Grow the rectangle, if necessary
324
- if (this.region.w === 0 || this.region.h === 0) {
325
- const padding = this.editor.viewport.visibleRect.maxDimension / 200;
326
- this.originalRegion = Rect2.bboxOf(this.region.corners, padding);
327
-
328
- // Only select one item if the rectangle was very small.
329
- singleItemSelectionMode = true;
330
- }
331
-
332
- this.selectedElems = this.editor.image.getElementsIntersectingRegion(this.region).filter(elem => {
333
- return elem.intersectsRect(this.region) && elem.isSelectable();
334
- });
335
-
336
- if (singleItemSelectionMode && this.selectedElems.length > 0) {
337
- this.selectedElems = [ this.selectedElems[this.selectedElems.length - 1] ];
338
- }
339
-
340
- // Find the bounding box of all selected elements.
341
- if (!this.recomputeRegion()) {
342
- return false;
343
- }
344
- this.updateUI();
345
-
346
- return true;
347
- }
348
-
349
- // Recompute this' region from the selected elements.
350
- // Returns false if the selection is empty.
351
- public recomputeRegion(): boolean {
352
- const newRegion = this.computeTightBoundingBox();
353
-
354
- if (!newRegion) {
355
- this.cancelSelection();
356
- return false;
357
- }
358
-
359
- this.originalRegion = newRegion;
360
-
361
- const minSize = this.getMinCanvasSize();
362
- if (this.originalRegion.w < minSize || this.originalRegion.h < minSize) {
363
- // Add padding
364
- const padding = minSize / 2;
365
- this.originalRegion = Rect2.bboxOf(
366
- this.originalRegion.corners, padding
367
- );
368
- }
369
-
370
- return true;
371
- }
372
-
373
- public getMinCanvasSize(): number {
374
- const canvasHandleSize = handleSize / this.editor.viewport.getScaleFactor();
375
- return canvasHandleSize * 2;
376
- }
377
-
378
- public getSelectedItemCount() {
379
- return this.selectedElems.length;
380
- }
381
-
382
- // @internal
383
- public updateUI() {
384
- // Don't update old selections.
385
- if (!this.hasParent) {
386
- return;
387
- }
388
-
389
- // marginLeft, marginTop: Display relative to the top left of the selection overlay.
390
- // left, top don't work for this.
391
- this.backgroundElem.style.marginLeft = `${this.screenRegion.topLeft.x}px`;
392
- this.backgroundElem.style.marginTop = `${this.screenRegion.topLeft.y}px`;
393
-
394
- this.backgroundElem.style.width = `${this.screenRegion.width}px`;
395
- this.backgroundElem.style.height = `${this.screenRegion.height}px`;
396
-
397
- const rotationDeg = this.screenRegionRotation * 180 / Math.PI;
398
- this.backgroundElem.style.transform = `rotate(${rotationDeg}deg)`;
399
- this.backgroundElem.style.transformOrigin = 'center';
400
-
401
- for (const handle of this.handles) {
402
- handle.updatePosition();
403
- }
404
- }
405
-
406
- // Maps IDs to whether we removed the component from the image
407
- private removedFromImage: Record<string, boolean> = {};
408
-
409
- // Add/remove the contents of this' seleciton from the editor.
410
- // Used to prevent previewed content from looking like duplicate content
411
- // while dragging.
412
- //
413
- // Does nothing if a large number of elements are selected (and so modifying
414
- // the editor image is likely to be slow.)
415
- //
416
- // If removed from the image, selected elements are drawn as wet ink.
417
- private addRemoveSelectionFromImage(inImage: boolean) {
418
- // Don't hide elements if doing so will be slow.
419
- if (!inImage && this.selectedElems.length > maxPreviewElemCount) {
420
- return;
421
- }
422
-
423
- for (const elem of this.selectedElems) {
424
- const parent = this.editor.image.findParent(elem);
425
-
426
- if (!inImage && parent) {
427
- this.removedFromImage[elem.getId()] = true;
428
- parent.remove();
429
- }
430
- // If we're making things visible and the selected object wasn't previously
431
- // visible,
432
- else if (!parent && this.removedFromImage[elem.getId()]) {
433
- EditorImage.addElement(elem).apply(this.editor);
434
-
435
- this.removedFromImage[elem.getId()] = false;
436
- delete this.removedFromImage[elem.getId()];
437
- }
438
- }
439
-
440
- // Don't await queueRerender. If we're running in a test, the re-render might never
441
- // happen.
442
- this.editor.queueRerender().then(() => {
443
- if (!inImage) {
444
- this.previewTransformCmds();
445
- }
446
- });
447
- }
448
-
449
- private removeDeletedElemsFromSelection() {
450
- // Remove any deleted elements from the selection.
451
- this.selectedElems = this.selectedElems.filter(elem => {
452
- const hasParent = !!this.editor.image.findParent(elem);
453
-
454
- // If we removed the element and haven't added it back yet, don't remove it
455
- // from the selection.
456
- const weRemoved = this.removedFromImage[elem.getId()];
457
- return hasParent || weRemoved;
458
- });
459
- }
460
-
461
- private targetHandle: SelectionHandle|null = null;
462
- private backgroundDragging: boolean = false;
463
- public onDragStart(pointer: Pointer): boolean {
464
- // Clear the HTML selection (prevent HTML drag and drop being triggered by this drag)
465
- document.getSelection()?.removeAllRanges();
466
-
467
- this.targetHandle = null;
468
-
469
- let result = false;
470
-
471
- for (const handle of this.handles) {
472
- if (handle.containsPoint(pointer.canvasPos)) {
473
- this.targetHandle = handle;
474
- result = true;
475
- }
476
- }
477
-
478
- this.backgroundDragging = false;
479
- if (this.region.containsPoint(pointer.canvasPos)) {
480
- this.backgroundDragging = true;
481
- result = true;
482
- }
483
-
484
- if (result) {
485
- this.removeDeletedElemsFromSelection();
486
- this.addRemoveSelectionFromImage(false);
487
- }
488
-
489
- if (this.targetHandle) {
490
- this.targetHandle.handleDragStart(pointer);
491
- }
492
-
493
- if (this.backgroundDragging) {
494
- this.transformers.drag.onDragStart(pointer.canvasPos);
495
- }
496
-
497
- return result;
498
- }
499
-
500
- public onDragUpdate(pointer: Pointer) {
501
- if (this.backgroundDragging) {
502
- this.transformers.drag.onDragUpdate(pointer.canvasPos);
503
- }
504
-
505
- if (this.targetHandle) {
506
- this.targetHandle.handleDragUpdate(pointer);
507
- }
508
- }
509
-
510
- public onDragEnd() {
511
- if (this.backgroundDragging) {
512
- this.transformers.drag.onDragEnd();
513
- }
514
- else if (this.targetHandle) {
515
- this.targetHandle.handleDragEnd();
516
- }
517
-
518
- this.addRemoveSelectionFromImage(true);
519
-
520
- this.backgroundDragging = false;
521
- this.targetHandle = null;
522
- this.updateUI();
523
- }
524
-
525
- public onDragCancel() {
526
- this.backgroundDragging = false;
527
- this.targetHandle = null;
528
- this.setTransform(Mat33.identity);
529
-
530
- this.addRemoveSelectionFromImage(true);
531
- }
532
-
533
- // Scroll the viewport to this. Does not zoom
534
- public async scrollTo() {
535
- if (this.selectedElems.length === 0) {
536
- return;
537
- }
538
-
539
- const screenRect = new Rect2(0, 0, this.editor.display.width, this.editor.display.height);
540
- if (!screenRect.containsPoint(this.screenRegion.center)) {
541
- const closestPoint = screenRect.getClosestPointOnBoundaryTo(this.screenRegion.center);
542
- const screenDelta = this.screenRegion.center.minus(closestPoint);
543
- const delta = this.editor.viewport.screenToCanvasTransform.transformVec3(screenDelta);
544
- await this.editor.dispatchNoAnnounce(
545
- Viewport.transformBy(Mat33.translation(delta.times(-1))), false
546
- );
547
-
548
- // Re-renders clear wet ink, so we need to re-draw the preview
549
- // after the full re-render.
550
- await this.editor.queueRerender();
551
- this.previewTransformCmds();
552
- }
553
- }
554
-
555
- public deleteSelectedObjects(): Command {
556
- if (this.backgroundDragging || this.targetHandle) {
557
- this.onDragEnd();
558
- }
559
-
560
- return new Erase(this.selectedElems);
561
- }
562
-
563
- private selectionDuplicatedAnimationTimeout: ReturnType<typeof setTimeout>|null = null;
564
- private runSelectionDuplicatedAnimation() {
565
- if (this.selectionDuplicatedAnimationTimeout) {
566
- clearTimeout(this.selectionDuplicatedAnimationTimeout);
567
- }
568
-
569
- const animationDuration = 400; // ms
570
- this.backgroundElem.style.animation = `${animationDuration}ms ease selection-duplicated-animation`;
571
-
572
- this.selectionDuplicatedAnimationTimeout = setTimeout(() => {
573
- this.backgroundElem.style.animation = '';
574
- this.selectionDuplicatedAnimationTimeout = null;
575
- }, animationDuration);
576
- }
577
-
578
- public async duplicateSelectedObjects(): Promise<Command> {
579
- const wasTransforming = this.backgroundDragging || this.targetHandle;
580
- let tmpApplyCommand: Command|null = null;
581
-
582
- if (!wasTransforming) {
583
- this.runSelectionDuplicatedAnimation();
584
- }
585
-
586
- if (wasTransforming) {
587
- // Don't update the selection's focus when redoing/undoing
588
- const selectionToUpdate: Selection|null = null;
589
- tmpApplyCommand = new Selection.ApplyTransformationCommand(
590
- selectionToUpdate, this.selectedElems, this.transform
591
- );
592
-
593
- // Transform to ensure that the duplicates are in the correct location
594
- await tmpApplyCommand.apply(this.editor);
595
-
596
- // Show items again
597
- this.addRemoveSelectionFromImage(true);
598
- }
599
-
600
- const duplicateCommand = new Duplicate(this.selectedElems);
601
-
602
- if (wasTransforming) {
603
- // Move the selected objects back to the correct location.
604
- await tmpApplyCommand?.unapply(this.editor);
605
- this.addRemoveSelectionFromImage(false);
606
-
607
- this.previewTransformCmds();
608
- this.updateUI();
609
- }
610
-
611
- return duplicateCommand;
612
- }
613
-
614
- public addTo(elem: HTMLElement) {
615
- if (this.container.parentElement) {
616
- this.container.remove();
617
- }
618
-
619
- elem.appendChild(this.container);
620
- this.hasParent = true;
621
- }
622
-
623
- public setToPoint(point: Point2) {
624
- this.originalRegion = this.originalRegion.grownToPoint(point);
625
- this.updateUI();
626
- }
627
-
628
- public cancelSelection() {
629
- if (this.container.parentElement) {
630
- this.container.remove();
631
- }
632
- this.originalRegion = Rect2.empty;
633
- this.hasParent = false;
634
- }
635
-
636
- public setSelectedObjects(objects: AbstractComponent[], bbox: Rect2) {
637
- this.addRemoveSelectionFromImage(true);
638
- this.originalRegion = bbox;
639
- this.selectedElems = objects.filter(object => object.isSelectable());
640
- this.updateUI();
641
- }
642
-
643
- public getSelectedObjects(): AbstractComponent[] {
644
- return [...this.selectedElems];
645
- }
646
- }
647
-