js-draw 1.0.1 → 1.1.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 (197) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Editor.css +1 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/toolbar/AbstractToolbar.d.ts +9 -13
  6. package/dist/cjs/toolbar/AbstractToolbar.js +14 -19
  7. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  8. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +26 -0
  9. package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
  10. package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/mjs/toolbar/AbstractToolbar.d.ts +9 -13
  13. package/dist/mjs/toolbar/AbstractToolbar.mjs +14 -19
  14. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  15. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +21 -0
  16. package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
  17. package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
  18. package/dist/mjs/version.mjs +1 -1
  19. package/docs/img/readme-images/js-draw.jpg +0 -0
  20. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  21. package/package.json +5 -4
  22. package/src/toolbar/EdgeToolbar.scss +1 -0
  23. package/dist-test/test_imports/package-lock.json +0 -13
  24. package/dist-test/test_imports/package.json +0 -12
  25. package/dist-test/test_imports/test-imports.js +0 -11
  26. package/dist-test/test_imports/test-require.cjs +0 -14
  27. package/src/Editor.loadFrom.test.ts +0 -24
  28. package/src/Editor.test.ts +0 -107
  29. package/src/Editor.toSVG.test.ts +0 -294
  30. package/src/Editor.ts +0 -1443
  31. package/src/EditorImage.test.ts +0 -117
  32. package/src/EditorImage.ts +0 -609
  33. package/src/EventDispatcher.test.ts +0 -123
  34. package/src/EventDispatcher.ts +0 -72
  35. package/src/Pointer.ts +0 -183
  36. package/src/SVGLoader.test.ts +0 -114
  37. package/src/SVGLoader.ts +0 -672
  38. package/src/UndoRedoHistory.test.ts +0 -34
  39. package/src/UndoRedoHistory.ts +0 -102
  40. package/src/Viewport.ts +0 -322
  41. package/src/bundle/bundled.ts +0 -7
  42. package/src/commands/Command.ts +0 -45
  43. package/src/commands/Duplicate.ts +0 -75
  44. package/src/commands/Erase.ts +0 -95
  45. package/src/commands/SerializableCommand.ts +0 -49
  46. package/src/commands/UnresolvedCommand.ts +0 -37
  47. package/src/commands/invertCommand.ts +0 -58
  48. package/src/commands/lib.ts +0 -16
  49. package/src/commands/localization.ts +0 -47
  50. package/src/commands/uniteCommands.test.ts +0 -23
  51. package/src/commands/uniteCommands.ts +0 -140
  52. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  53. package/src/components/AbstractComponent.ts +0 -383
  54. package/src/components/BackgroundComponent.test.ts +0 -44
  55. package/src/components/BackgroundComponent.ts +0 -348
  56. package/src/components/ImageComponent.ts +0 -176
  57. package/src/components/RestylableComponent.ts +0 -161
  58. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  59. package/src/components/Stroke.test.ts +0 -137
  60. package/src/components/Stroke.ts +0 -294
  61. package/src/components/TextComponent.test.ts +0 -202
  62. package/src/components/TextComponent.ts +0 -429
  63. package/src/components/UnknownSVGObject.test.ts +0 -10
  64. package/src/components/UnknownSVGObject.ts +0 -60
  65. package/src/components/builders/ArrowBuilder.ts +0 -106
  66. package/src/components/builders/CircleBuilder.ts +0 -100
  67. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  68. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  69. package/src/components/builders/LineBuilder.ts +0 -77
  70. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  71. package/src/components/builders/RectangleBuilder.ts +0 -73
  72. package/src/components/builders/types.ts +0 -15
  73. package/src/components/lib.ts +0 -31
  74. package/src/components/localization.ts +0 -24
  75. package/src/components/util/StrokeSmoother.ts +0 -302
  76. package/src/components/util/describeComponentList.ts +0 -18
  77. package/src/dialogs/makeAboutDialog.ts +0 -82
  78. package/src/inputEvents.ts +0 -143
  79. package/src/lib.ts +0 -91
  80. package/src/localization.ts +0 -34
  81. package/src/localizations/de.ts +0 -146
  82. package/src/localizations/en.ts +0 -8
  83. package/src/localizations/es.ts +0 -74
  84. package/src/localizations/getLocalizationTable.test.ts +0 -27
  85. package/src/localizations/getLocalizationTable.ts +0 -74
  86. package/src/rendering/Display.ts +0 -247
  87. package/src/rendering/RenderablePathSpec.ts +0 -88
  88. package/src/rendering/RenderingStyle.test.ts +0 -68
  89. package/src/rendering/RenderingStyle.ts +0 -55
  90. package/src/rendering/TextRenderingStyle.ts +0 -55
  91. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  92. package/src/rendering/caching/CacheRecord.ts +0 -76
  93. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  94. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  95. package/src/rendering/caching/RenderingCache.ts +0 -66
  96. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  97. package/src/rendering/caching/testUtils.ts +0 -35
  98. package/src/rendering/caching/types.ts +0 -34
  99. package/src/rendering/lib.ts +0 -8
  100. package/src/rendering/localization.ts +0 -20
  101. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  102. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  103. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  104. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  105. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  106. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  107. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  108. package/src/shortcuts/KeyBinding.test.ts +0 -61
  109. package/src/shortcuts/KeyBinding.ts +0 -257
  110. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  111. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  112. package/src/shortcuts/lib.ts +0 -3
  113. package/src/testing/createEditor.ts +0 -11
  114. package/src/testing/getUniquePointerId.ts +0 -18
  115. package/src/testing/lib.ts +0 -3
  116. package/src/testing/sendPenEvent.ts +0 -36
  117. package/src/testing/sendTouchEvent.ts +0 -71
  118. package/src/toolbar/AbstractToolbar.ts +0 -542
  119. package/src/toolbar/DropdownToolbar.ts +0 -220
  120. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  121. package/src/toolbar/EdgeToolbar.ts +0 -543
  122. package/src/toolbar/IconProvider.ts +0 -861
  123. package/src/toolbar/constants.ts +0 -1
  124. package/src/toolbar/lib.ts +0 -6
  125. package/src/toolbar/localization.ts +0 -136
  126. package/src/toolbar/types.ts +0 -13
  127. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  128. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  129. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  130. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  131. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  132. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  133. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  134. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  135. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  136. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  137. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  138. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  139. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  140. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  141. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  142. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  143. package/src/toolbar/widgets/keybindings.ts +0 -19
  144. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  145. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  146. package/src/toolbar/widgets/layout/types.ts +0 -74
  147. package/src/toolbar/widgets/lib.ts +0 -13
  148. package/src/tools/BaseTool.ts +0 -169
  149. package/src/tools/Eraser.test.ts +0 -103
  150. package/src/tools/Eraser.ts +0 -173
  151. package/src/tools/FindTool.test.ts +0 -67
  152. package/src/tools/FindTool.ts +0 -153
  153. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  154. package/src/tools/InputFilter/InputMapper.ts +0 -41
  155. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  156. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  157. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  158. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  159. package/src/tools/PanZoom.test.ts +0 -339
  160. package/src/tools/PanZoom.ts +0 -525
  161. package/src/tools/PasteHandler.ts +0 -94
  162. package/src/tools/Pen.test.ts +0 -260
  163. package/src/tools/Pen.ts +0 -284
  164. package/src/tools/PipetteTool.ts +0 -84
  165. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  166. package/src/tools/SelectionTool/Selection.ts +0 -647
  167. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  168. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  169. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  170. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  171. package/src/tools/SelectionTool/types.ts +0 -11
  172. package/src/tools/SoundUITool.ts +0 -221
  173. package/src/tools/TextTool.ts +0 -339
  174. package/src/tools/ToolController.ts +0 -224
  175. package/src/tools/ToolEnabledGroup.ts +0 -14
  176. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  177. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  178. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  179. package/src/tools/UndoRedoShortcut.ts +0 -24
  180. package/src/tools/keybindings.ts +0 -85
  181. package/src/tools/lib.ts +0 -22
  182. package/src/tools/localization.ts +0 -76
  183. package/src/types.ts +0 -151
  184. package/src/util/ReactiveValue.test.ts +0 -168
  185. package/src/util/ReactiveValue.ts +0 -241
  186. package/src/util/assertions.ts +0 -55
  187. package/src/util/fileToBase64.ts +0 -18
  188. package/src/util/guessKeyCodeFromKey.ts +0 -36
  189. package/src/util/listPrefixMatch.ts +0 -19
  190. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  191. package/src/util/untilNextAnimationFrame.ts +0 -9
  192. package/src/util/waitForAll.ts +0 -18
  193. package/src/util/waitForTimeout.ts +0 -9
  194. package/src/version.test.ts +0 -12
  195. package/src/version.ts +0 -3
  196. package/tools/allLocales.js +0 -4
  197. 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
-