js-draw 1.21.1 → 1.21.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. package/dist/Editor.css +4 -2
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +1 -1
  5. package/dist/cjs/tools/BaseTool.d.ts +61 -0
  6. package/dist/cjs/tools/BaseTool.js +179 -0
  7. package/dist/cjs/tools/Eraser.d.ts +60 -0
  8. package/dist/cjs/tools/Eraser.js +299 -0
  9. package/dist/cjs/tools/Eraser.test.d.ts +1 -0
  10. package/dist/cjs/tools/FindTool.d.ts +21 -0
  11. package/dist/cjs/tools/FindTool.js +137 -0
  12. package/dist/cjs/tools/FindTool.test.d.ts +1 -0
  13. package/dist/cjs/tools/InputFilter/ContextMenuRecognizer.d.ts +17 -0
  14. package/dist/cjs/tools/InputFilter/ContextMenuRecognizer.js +105 -0
  15. package/dist/cjs/tools/InputFilter/ContextMenuRecognizer.test.d.ts +1 -0
  16. package/dist/cjs/tools/InputFilter/FunctionMapper.d.ts +12 -0
  17. package/dist/cjs/tools/InputFilter/FunctionMapper.js +21 -0
  18. package/dist/cjs/tools/InputFilter/InputMapper.d.ts +23 -0
  19. package/dist/cjs/tools/InputFilter/InputMapper.js +38 -0
  20. package/dist/cjs/tools/InputFilter/InputPipeline.d.ts +15 -0
  21. package/dist/cjs/tools/InputFilter/InputPipeline.js +54 -0
  22. package/dist/cjs/tools/InputFilter/InputPipeline.test.d.ts +1 -0
  23. package/dist/cjs/tools/InputFilter/InputStabilizer.d.ts +29 -0
  24. package/dist/cjs/tools/InputFilter/InputStabilizer.js +181 -0
  25. package/dist/cjs/tools/InputFilter/StrokeKeyboardControl.d.ts +21 -0
  26. package/dist/cjs/tools/InputFilter/StrokeKeyboardControl.js +84 -0
  27. package/dist/cjs/tools/PanZoom.d.ts +125 -0
  28. package/dist/cjs/tools/PanZoom.js +517 -0
  29. package/dist/cjs/tools/PanZoom.test.d.ts +1 -0
  30. package/dist/cjs/tools/PasteHandler.d.ts +23 -0
  31. package/dist/cjs/tools/PasteHandler.js +109 -0
  32. package/dist/cjs/tools/Pen.d.ts +54 -0
  33. package/dist/cjs/tools/Pen.js +335 -0
  34. package/dist/cjs/tools/Pen.test.d.ts +1 -0
  35. package/dist/cjs/tools/PipetteTool.d.ts +28 -0
  36. package/dist/cjs/tools/PipetteTool.js +69 -0
  37. package/dist/cjs/tools/ScrollbarTool.d.ts +18 -0
  38. package/dist/cjs/tools/ScrollbarTool.js +85 -0
  39. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +9 -0
  40. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.js +32 -0
  41. package/dist/cjs/tools/SelectionTool/Selection.d.ts +72 -0
  42. package/dist/cjs/tools/SelectionTool/Selection.js +634 -0
  43. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +62 -0
  44. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +141 -0
  45. package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.d.ts +32 -0
  46. package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.js +86 -0
  47. package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +42 -0
  48. package/dist/cjs/tools/SelectionTool/SelectionTool.js +500 -0
  49. package/dist/cjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  50. package/dist/cjs/tools/SelectionTool/SelectionTool.test.d.ts +1 -0
  51. package/dist/cjs/tools/SelectionTool/ToPointerAutoscroller.d.ts +23 -0
  52. package/dist/cjs/tools/SelectionTool/ToPointerAutoscroller.js +83 -0
  53. package/dist/cjs/tools/SelectionTool/TransformMode.d.ts +42 -0
  54. package/dist/cjs/tools/SelectionTool/TransformMode.js +155 -0
  55. package/dist/cjs/tools/SelectionTool/keybindings.d.ts +15 -0
  56. package/dist/cjs/tools/SelectionTool/keybindings.js +38 -0
  57. package/dist/cjs/tools/SelectionTool/types.d.ts +35 -0
  58. package/dist/cjs/tools/SelectionTool/types.js +14 -0
  59. package/dist/cjs/tools/SelectionTool/util/makeClipboardErrorHandlers.d.ts +6 -0
  60. package/dist/cjs/tools/SelectionTool/util/makeClipboardErrorHandlers.js +50 -0
  61. package/dist/cjs/tools/SelectionTool/util/showSelectionContextMenu.d.ts +5 -0
  62. package/dist/cjs/tools/SelectionTool/util/showSelectionContextMenu.js +43 -0
  63. package/dist/cjs/tools/SoundUITool.d.ts +26 -0
  64. package/dist/cjs/tools/SoundUITool.js +171 -0
  65. package/dist/cjs/tools/TextTool.d.ts +36 -0
  66. package/dist/cjs/tools/TextTool.js +285 -0
  67. package/dist/cjs/tools/TextTool.test.d.ts +1 -0
  68. package/dist/cjs/tools/ToolController.d.ts +73 -0
  69. package/dist/cjs/tools/ToolController.js +304 -0
  70. package/dist/cjs/tools/ToolController.test.d.ts +1 -0
  71. package/dist/cjs/tools/ToolEnabledGroup.d.ts +6 -0
  72. package/dist/cjs/tools/ToolEnabledGroup.js +13 -0
  73. package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +16 -0
  74. package/dist/cjs/tools/ToolSwitcherShortcut.js +40 -0
  75. package/dist/cjs/tools/ToolbarShortcutHandler.d.ts +12 -0
  76. package/dist/cjs/tools/ToolbarShortcutHandler.js +34 -0
  77. package/dist/cjs/tools/UndoRedoShortcut.d.ts +8 -0
  78. package/dist/cjs/tools/UndoRedoShortcut.js +27 -0
  79. package/dist/cjs/tools/UndoRedoShortcut.test.d.ts +1 -0
  80. package/dist/cjs/tools/keybindings.d.ts +16 -0
  81. package/dist/cjs/tools/keybindings.js +58 -0
  82. package/dist/cjs/tools/lib.d.ts +14 -0
  83. package/dist/cjs/tools/lib.js +36 -0
  84. package/dist/cjs/tools/localization.d.ts +43 -0
  85. package/dist/cjs/tools/localization.js +45 -0
  86. package/dist/cjs/tools/util/StationaryPenDetector.d.ts +25 -0
  87. package/dist/cjs/tools/util/StationaryPenDetector.js +107 -0
  88. package/dist/cjs/tools/util/createMenuOverlay.d.ts +10 -0
  89. package/dist/cjs/tools/util/createMenuOverlay.js +126 -0
  90. package/dist/cjs/tools/util/createMenuOverlay.test.d.ts +1 -0
  91. package/dist/cjs/version.js +1 -1
  92. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +1 -1
  93. package/dist/mjs/tools/BaseTool.d.ts +61 -0
  94. package/dist/mjs/tools/BaseTool.mjs +177 -0
  95. package/dist/mjs/tools/Eraser.d.ts +60 -0
  96. package/dist/mjs/tools/Eraser.mjs +292 -0
  97. package/dist/mjs/tools/Eraser.test.d.ts +1 -0
  98. package/dist/mjs/tools/FindTool.d.ts +21 -0
  99. package/dist/mjs/tools/FindTool.mjs +131 -0
  100. package/dist/mjs/tools/FindTool.test.d.ts +1 -0
  101. package/dist/mjs/tools/InputFilter/ContextMenuRecognizer.d.ts +17 -0
  102. package/dist/mjs/tools/InputFilter/ContextMenuRecognizer.mjs +76 -0
  103. package/dist/mjs/tools/InputFilter/ContextMenuRecognizer.test.d.ts +1 -0
  104. package/dist/mjs/tools/InputFilter/FunctionMapper.d.ts +12 -0
  105. package/dist/mjs/tools/InputFilter/FunctionMapper.mjs +15 -0
  106. package/dist/mjs/tools/InputFilter/InputMapper.d.ts +23 -0
  107. package/dist/mjs/tools/InputFilter/InputMapper.mjs +36 -0
  108. package/dist/mjs/tools/InputFilter/InputPipeline.d.ts +15 -0
  109. package/dist/mjs/tools/InputFilter/InputPipeline.mjs +49 -0
  110. package/dist/mjs/tools/InputFilter/InputPipeline.test.d.ts +1 -0
  111. package/dist/mjs/tools/InputFilter/InputStabilizer.d.ts +29 -0
  112. package/dist/mjs/tools/InputFilter/InputStabilizer.mjs +175 -0
  113. package/dist/mjs/tools/InputFilter/StrokeKeyboardControl.d.ts +21 -0
  114. package/dist/mjs/tools/InputFilter/StrokeKeyboardControl.mjs +78 -0
  115. package/dist/mjs/tools/PanZoom.d.ts +125 -0
  116. package/dist/mjs/tools/PanZoom.mjs +510 -0
  117. package/dist/mjs/tools/PanZoom.test.d.ts +1 -0
  118. package/dist/mjs/tools/PasteHandler.d.ts +23 -0
  119. package/dist/mjs/tools/PasteHandler.mjs +103 -0
  120. package/dist/mjs/tools/Pen.d.ts +54 -0
  121. package/dist/mjs/tools/Pen.mjs +306 -0
  122. package/dist/mjs/tools/Pen.test.d.ts +1 -0
  123. package/dist/mjs/tools/PipetteTool.d.ts +28 -0
  124. package/dist/mjs/tools/PipetteTool.mjs +63 -0
  125. package/dist/mjs/tools/ScrollbarTool.d.ts +18 -0
  126. package/dist/mjs/tools/ScrollbarTool.mjs +79 -0
  127. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +9 -0
  128. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.mjs +26 -0
  129. package/dist/mjs/tools/SelectionTool/Selection.d.ts +72 -0
  130. package/dist/mjs/tools/SelectionTool/Selection.mjs +606 -0
  131. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +62 -0
  132. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +137 -0
  133. package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.d.ts +32 -0
  134. package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.mjs +83 -0
  135. package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +42 -0
  136. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +493 -0
  137. package/dist/mjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  138. package/dist/mjs/tools/SelectionTool/SelectionTool.test.d.ts +1 -0
  139. package/dist/mjs/tools/SelectionTool/ToPointerAutoscroller.d.ts +23 -0
  140. package/dist/mjs/tools/SelectionTool/ToPointerAutoscroller.mjs +77 -0
  141. package/dist/mjs/tools/SelectionTool/TransformMode.d.ts +42 -0
  142. package/dist/mjs/tools/SelectionTool/TransformMode.mjs +146 -0
  143. package/dist/mjs/tools/SelectionTool/keybindings.d.ts +15 -0
  144. package/dist/mjs/tools/SelectionTool/keybindings.mjs +32 -0
  145. package/dist/mjs/tools/SelectionTool/types.d.ts +35 -0
  146. package/dist/mjs/tools/SelectionTool/types.mjs +11 -0
  147. package/dist/mjs/tools/SelectionTool/util/makeClipboardErrorHandlers.d.ts +6 -0
  148. package/dist/mjs/tools/SelectionTool/util/makeClipboardErrorHandlers.mjs +45 -0
  149. package/dist/mjs/tools/SelectionTool/util/showSelectionContextMenu.d.ts +5 -0
  150. package/dist/mjs/tools/SelectionTool/util/showSelectionContextMenu.mjs +38 -0
  151. package/dist/mjs/tools/SoundUITool.d.ts +26 -0
  152. package/dist/mjs/tools/SoundUITool.mjs +165 -0
  153. package/dist/mjs/tools/TextTool.d.ts +36 -0
  154. package/dist/mjs/tools/TextTool.mjs +279 -0
  155. package/dist/mjs/tools/TextTool.test.d.ts +1 -0
  156. package/dist/mjs/tools/ToolController.d.ts +73 -0
  157. package/dist/mjs/tools/ToolController.mjs +275 -0
  158. package/dist/mjs/tools/ToolController.test.d.ts +1 -0
  159. package/dist/mjs/tools/ToolEnabledGroup.d.ts +6 -0
  160. package/dist/mjs/tools/ToolEnabledGroup.mjs +10 -0
  161. package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +16 -0
  162. package/dist/mjs/tools/ToolSwitcherShortcut.mjs +34 -0
  163. package/dist/mjs/tools/ToolbarShortcutHandler.d.ts +12 -0
  164. package/dist/mjs/tools/ToolbarShortcutHandler.mjs +28 -0
  165. package/dist/mjs/tools/UndoRedoShortcut.d.ts +8 -0
  166. package/dist/mjs/tools/UndoRedoShortcut.mjs +21 -0
  167. package/dist/mjs/tools/UndoRedoShortcut.test.d.ts +1 -0
  168. package/dist/mjs/tools/keybindings.d.ts +16 -0
  169. package/dist/mjs/tools/keybindings.mjs +38 -0
  170. package/dist/mjs/tools/lib.d.ts +14 -0
  171. package/dist/mjs/tools/lib.mjs +14 -0
  172. package/dist/mjs/tools/localization.d.ts +43 -0
  173. package/dist/mjs/tools/localization.mjs +42 -0
  174. package/dist/mjs/tools/util/StationaryPenDetector.d.ts +25 -0
  175. package/dist/mjs/tools/util/StationaryPenDetector.mjs +103 -0
  176. package/dist/mjs/tools/util/createMenuOverlay.d.ts +10 -0
  177. package/dist/mjs/tools/util/createMenuOverlay.mjs +121 -0
  178. package/dist/mjs/tools/util/createMenuOverlay.test.d.ts +1 -0
  179. package/dist/mjs/version.mjs +1 -1
  180. package/package.json +4 -4
  181. package/src/tools/FindTool.css +7 -0
  182. package/src/tools/ScrollbarTool.scss +57 -0
  183. package/src/tools/SelectionTool/SelectionTool.scss +165 -0
  184. package/src/tools/SelectionTool/util/makeClipboardErrorHandlers.scss +15 -0
  185. package/src/tools/SoundUITool.scss +22 -0
  186. package/src/tools/tools.scss +6 -0
  187. package/src/tools/util/createMenuOverlay.scss +67 -0
@@ -0,0 +1,500 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cssPrefix = void 0;
7
+ const math_1 = require("@js-draw/math");
8
+ const types_1 = require("../../types");
9
+ const Viewport_1 = __importDefault(require("../../Viewport"));
10
+ const BaseTool_1 = __importDefault(require("../BaseTool"));
11
+ const CanvasRenderer_1 = __importDefault(require("../../rendering/renderers/CanvasRenderer"));
12
+ const SVGRenderer_1 = __importDefault(require("../../rendering/renderers/SVGRenderer"));
13
+ const Selection_1 = __importDefault(require("./Selection"));
14
+ const TextComponent_1 = __importDefault(require("../../components/TextComponent"));
15
+ const keybindings_1 = require("../keybindings");
16
+ const ToPointerAutoscroller_1 = __importDefault(require("./ToPointerAutoscroller"));
17
+ const showSelectionContextMenu_1 = __importDefault(require("./util/showSelectionContextMenu"));
18
+ exports.cssPrefix = 'selection-tool-';
19
+ // Allows users to select/transform portions of the `EditorImage`.
20
+ // With respect to `extend`ing, `SelectionTool` is not stable.
21
+ class SelectionTool extends BaseTool_1.default {
22
+ constructor(editor, description) {
23
+ super(editor.notifier, description);
24
+ this.editor = editor;
25
+ // True if clearing and recreating the selectionBox has been deferred. This is used to prevent the selection
26
+ // from vanishing on pointerdown events that are intended to form other gestures (e.g. long press) that would
27
+ // ultimately restore the selection.
28
+ this.rebuildSelectionScheduled = false;
29
+ this.startPoint = null; // canvas position
30
+ this.expandingSelectionBox = false;
31
+ this.shiftKeyPressed = false;
32
+ this.snapToGrid = false;
33
+ this.lastPointer = null;
34
+ this.showContextMenu = async (canvasAnchor, preferSelectionMenu = true) => {
35
+ await (0, showSelectionContextMenu_1.default)(this.selectionBox, this.editor, canvasAnchor, preferSelectionMenu, () => this.clearSelection());
36
+ };
37
+ this.selectionBoxHandlingEvt = false;
38
+ this.lastSelectedObjects = [];
39
+ // Whether the last keypress corresponded to an action that didn't transform the
40
+ // selection (and thus does not need to be finalized on onKeyUp).
41
+ this.hasUnfinalizedTransformFromKeyPress = false;
42
+ this.autoscroller = new ToPointerAutoscroller_1.default(editor.viewport, (scrollBy) => {
43
+ editor.dispatch(Viewport_1.default.transformBy(math_1.Mat33.translation(scrollBy)), false);
44
+ // Update the selection box/content to match the new viewport.
45
+ if (this.lastPointer) {
46
+ // The viewport has changed -- ensure that the screen and canvas positions
47
+ // of the pointer are both correct
48
+ const updatedPointer = this.lastPointer.withScreenPosition(this.lastPointer.screenPos, editor.viewport);
49
+ this.onMainPointerUpdated(updatedPointer);
50
+ }
51
+ });
52
+ this.handleOverlay = document.createElement('div');
53
+ editor.createHTMLOverlay(this.handleOverlay);
54
+ this.handleOverlay.style.display = 'none';
55
+ this.handleOverlay.classList.add('handleOverlay');
56
+ editor.notifier.on(types_1.EditorEventType.ViewportChanged, _data => {
57
+ // The selection box could be using the wet ink display if its transformation
58
+ // hasn't been finalized yet. Clear before updating the UI.
59
+ this.editor.clearWetInk();
60
+ // If not currently selecting, ensure that the selection box
61
+ // is large enough.
62
+ if (!this.expandingSelectionBox) {
63
+ this.selectionBox?.padRegion();
64
+ }
65
+ this.selectionBox?.updateUI();
66
+ });
67
+ this.editor.handleKeyEventsFrom(this.handleOverlay);
68
+ this.editor.handlePointerEventsFrom(this.handleOverlay);
69
+ }
70
+ makeSelectionBox(selectionStartPos) {
71
+ this.prevSelectionBox = this.selectionBox;
72
+ this.selectionBox = new Selection_1.default(selectionStartPos, this.editor, this.showContextMenu);
73
+ if (!this.expandingSelectionBox) {
74
+ // Remove any previous selection rects
75
+ this.prevSelectionBox?.cancelSelection();
76
+ }
77
+ this.selectionBox.addTo(this.handleOverlay);
78
+ }
79
+ snapSelectionToGrid() {
80
+ if (!this.selectionBox)
81
+ throw new Error('No selection to snap!');
82
+ // Snap the top left corner of what we have selected.
83
+ const topLeftOfBBox = this.selectionBox.computeTightBoundingBox().topLeft;
84
+ const snappedTopLeft = this.editor.viewport.snapToGrid(topLeftOfBBox);
85
+ const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
86
+ const oldTransform = this.selectionBox.getTransform();
87
+ this.selectionBox.setTransform(oldTransform.rightMul(math_1.Mat33.translation(snapDelta)));
88
+ this.selectionBox.finalizeTransform();
89
+ }
90
+ onContextMenu(event) {
91
+ const canShowSelectionMenu = this.selectionBox?.getScreenRegion()?.containsPoint(event.screenPos);
92
+ void this.showContextMenu(event.canvasPos, canShowSelectionMenu);
93
+ return true;
94
+ }
95
+ onPointerDown({ allPointers, current }) {
96
+ const snapToGrid = this.snapToGrid;
97
+ if (snapToGrid) {
98
+ current = current.snappedToGrid(this.editor.viewport);
99
+ }
100
+ // Don't rely on .isPrimary -- it's buggy in Firefox. See https://github.com/personalizedrefrigerator/js-draw/issues/71
101
+ if (allPointers.length === 1) {
102
+ this.startPoint = current.canvasPos;
103
+ let transforming = false;
104
+ if (this.selectionBox) {
105
+ if (snapToGrid) {
106
+ this.snapSelectionToGrid();
107
+ }
108
+ const dragStartResult = this.selectionBox.onDragStart(current);
109
+ if (dragStartResult) {
110
+ transforming = true;
111
+ this.selectionBoxHandlingEvt = true;
112
+ this.expandingSelectionBox = false;
113
+ }
114
+ }
115
+ if (!transforming) {
116
+ // Shift key: Combine the new and old selection boxes at the end of the gesture.
117
+ this.expandingSelectionBox = this.shiftKeyPressed;
118
+ this.rebuildSelectionScheduled = true;
119
+ }
120
+ else {
121
+ // Only autoscroll if we're transforming an existing selection
122
+ this.autoscroller.start();
123
+ }
124
+ return true;
125
+ }
126
+ return false;
127
+ }
128
+ onPointerMove(event) {
129
+ this.onMainPointerUpdated(event.current);
130
+ }
131
+ onMainPointerUpdated(currentPointer) {
132
+ this.lastPointer = currentPointer;
133
+ if (this.rebuildSelectionScheduled) {
134
+ this.rebuildSelectionScheduled = false;
135
+ this.makeSelectionBox(this.startPoint ?? currentPointer.canvasPos);
136
+ this.selectionBox?.setHandlesVisible(false);
137
+ }
138
+ if (!this.selectionBox)
139
+ return;
140
+ this.autoscroller.onPointerMove(currentPointer.screenPos);
141
+ if (!this.expandingSelectionBox && this.shiftKeyPressed && this.startPoint) {
142
+ const screenPos = this.editor.viewport.canvasToScreen(this.startPoint);
143
+ currentPointer = currentPointer.lockedToXYAxesScreen(screenPos, this.editor.viewport);
144
+ }
145
+ if (this.snapToGrid) {
146
+ currentPointer = currentPointer.snappedToGrid(this.editor.viewport);
147
+ }
148
+ if (this.selectionBoxHandlingEvt) {
149
+ this.selectionBox.onDragUpdate(currentPointer);
150
+ }
151
+ else {
152
+ this.selectionBox.setToPoint(currentPointer.canvasPos);
153
+ }
154
+ }
155
+ onPointerUp(event) {
156
+ this.onMainPointerUpdated(event.current);
157
+ this.autoscroller.stop();
158
+ if (!this.selectionBox)
159
+ return;
160
+ this.selectionBox.setHandlesVisible(true);
161
+ // Were we expanding the previous selection?
162
+ if (this.expandingSelectionBox && this.prevSelectionBox) {
163
+ // If so, finish expanding.
164
+ this.expandingSelectionBox = false;
165
+ this.selectionBox.resolveToObjects();
166
+ this.setSelection([
167
+ ...this.selectionBox.getSelectedObjects(),
168
+ ...this.prevSelectionBox.getSelectedObjects(),
169
+ ]);
170
+ }
171
+ else {
172
+ if (!this.selectionBoxHandlingEvt) {
173
+ // Expand/shrink the selection rectangle, if applicable
174
+ this.selectionBox.resolveToObjects();
175
+ this.onSelectionUpdated();
176
+ }
177
+ else {
178
+ this.selectionBox.onDragEnd();
179
+ }
180
+ this.selectionBoxHandlingEvt = false;
181
+ this.lastPointer = null;
182
+ }
183
+ }
184
+ onGestureCancel() {
185
+ this.autoscroller.stop();
186
+ if (this.selectionBoxHandlingEvt) {
187
+ this.selectionBox?.onDragCancel();
188
+ }
189
+ else if (!this.rebuildSelectionScheduled) {
190
+ // Revert to the previous selection, if any.
191
+ this.selectionBox?.cancelSelection();
192
+ this.selectionBox = this.prevSelectionBox;
193
+ this.selectionBox?.addTo(this.handleOverlay);
194
+ this.selectionBox?.recomputeRegion();
195
+ this.prevSelectionBox = null;
196
+ }
197
+ this.rebuildSelectionScheduled = false;
198
+ this.expandingSelectionBox = false;
199
+ this.lastPointer = null;
200
+ this.selectionBoxHandlingEvt = false;
201
+ }
202
+ onSelectionUpdated() {
203
+ const selectedItemCount = this.selectionBox?.getSelectedItemCount() ?? 0;
204
+ const selectedObjects = this.selectionBox?.getSelectedObjects() ?? [];
205
+ const hasDifferentSelection = this.lastSelectedObjects.length !== selectedItemCount
206
+ || selectedObjects.some((obj, i) => this.lastSelectedObjects[i] !== obj);
207
+ if (hasDifferentSelection) {
208
+ this.lastSelectedObjects = selectedObjects;
209
+ // Note that the selection has changed
210
+ this.editor.notifier.dispatch(types_1.EditorEventType.ToolUpdated, {
211
+ kind: types_1.EditorEventType.ToolUpdated,
212
+ tool: this,
213
+ });
214
+ // Only fire the SelectionUpdated event if the selection really updated.
215
+ this.editor.notifier.dispatch(types_1.EditorEventType.SelectionUpdated, {
216
+ kind: types_1.EditorEventType.SelectionUpdated,
217
+ selectedComponents: selectedObjects,
218
+ tool: this,
219
+ });
220
+ if (selectedItemCount > 0) {
221
+ this.editor.announceForAccessibility(this.editor.localization.selectedElements(selectedItemCount));
222
+ this.zoomToSelection();
223
+ }
224
+ }
225
+ if (selectedItemCount === 0 && this.selectionBox) {
226
+ this.selectionBox.cancelSelection();
227
+ this.prevSelectionBox = this.selectionBox;
228
+ this.selectionBox = null;
229
+ }
230
+ }
231
+ zoomToSelection() {
232
+ if (this.selectionBox) {
233
+ const selectionRect = this.selectionBox.region;
234
+ this.editor.dispatchNoAnnounce(this.editor.viewport.zoomTo(selectionRect, false), false);
235
+ }
236
+ }
237
+ onKeyPress(event) {
238
+ const shortcucts = this.editor.shortcuts;
239
+ if (shortcucts.matchesShortcut(keybindings_1.snapToGridKeyboardShortcutId, event)) {
240
+ this.snapToGrid = true;
241
+ return true;
242
+ }
243
+ if (this.selectionBox && (shortcucts.matchesShortcut(keybindings_1.duplicateSelectionShortcut, event)
244
+ || shortcucts.matchesShortcut(keybindings_1.sendToBackSelectionShortcut, event))) {
245
+ // Handle duplication on key up — we don't want to accidentally duplicate
246
+ // many times.
247
+ return true;
248
+ }
249
+ else if (shortcucts.matchesShortcut(keybindings_1.selectAllKeyboardShortcut, event)) {
250
+ this.setSelection(this.editor.image.getAllElements());
251
+ return true;
252
+ }
253
+ else if (event.ctrlKey) {
254
+ // Don't transform the selection with, for example, ctrl+i.
255
+ // Pass it to another tool, if apliccable.
256
+ return false;
257
+ }
258
+ else if (event.shiftKey || event.key === 'Shift') {
259
+ this.shiftKeyPressed = true;
260
+ if (event.key === 'Shift') {
261
+ return true;
262
+ }
263
+ }
264
+ let rotationSteps = 0;
265
+ let xTranslateSteps = 0;
266
+ let yTranslateSteps = 0;
267
+ let xScaleSteps = 0;
268
+ let yScaleSteps = 0;
269
+ if (shortcucts.matchesShortcut(keybindings_1.translateLeftSelectionShortcutId, event)) {
270
+ xTranslateSteps -= 1;
271
+ }
272
+ else if (shortcucts.matchesShortcut(keybindings_1.translateRightSelectionShortcutId, event)) {
273
+ xTranslateSteps += 1;
274
+ }
275
+ else if (shortcucts.matchesShortcut(keybindings_1.translateUpSelectionShortcutId, event)) {
276
+ yTranslateSteps -= 1;
277
+ }
278
+ else if (shortcucts.matchesShortcut(keybindings_1.translateDownSelectionShortcutId, event)) {
279
+ yTranslateSteps += 1;
280
+ }
281
+ else if (shortcucts.matchesShortcut(keybindings_1.rotateClockwiseSelectionShortcutId, event)) {
282
+ rotationSteps += 1;
283
+ }
284
+ else if (shortcucts.matchesShortcut(keybindings_1.rotateCounterClockwiseSelectionShortcutId, event)) {
285
+ rotationSteps -= 1;
286
+ }
287
+ else if (shortcucts.matchesShortcut(keybindings_1.shrinkXSelectionShortcutId, event)) {
288
+ xScaleSteps -= 1;
289
+ }
290
+ else if (shortcucts.matchesShortcut(keybindings_1.stretchXSelectionShortcutId, event)) {
291
+ xScaleSteps += 1;
292
+ }
293
+ else if (shortcucts.matchesShortcut(keybindings_1.shrinkYSelectionShortcutId, event)) {
294
+ yScaleSteps -= 1;
295
+ }
296
+ else if (shortcucts.matchesShortcut(keybindings_1.stretchYSelectionShortcutId, event)) {
297
+ yScaleSteps += 1;
298
+ }
299
+ else if (shortcucts.matchesShortcut(keybindings_1.shrinkXYSelectionShortcutId, event)) {
300
+ xScaleSteps -= 1;
301
+ yScaleSteps -= 1;
302
+ }
303
+ else if (shortcucts.matchesShortcut(keybindings_1.stretchXYSelectionShortcutId, event)) {
304
+ xScaleSteps += 1;
305
+ yScaleSteps += 1;
306
+ }
307
+ let handled = xTranslateSteps !== 0
308
+ || yTranslateSteps !== 0
309
+ || rotationSteps !== 0
310
+ || xScaleSteps !== 0
311
+ || yScaleSteps !== 0;
312
+ if (!this.selectionBox) {
313
+ handled = false;
314
+ }
315
+ else if (handled) {
316
+ const translateStepSize = 10 * this.editor.viewport.getSizeOfPixelOnCanvas();
317
+ const rotateStepSize = Math.PI / 8;
318
+ const scaleStepSize = 5 / 4;
319
+ const region = this.selectionBox.region;
320
+ const scaleFactor = math_1.Vec2.of(scaleStepSize ** xScaleSteps, scaleStepSize ** yScaleSteps);
321
+ const rotationMat = math_1.Mat33.zRotation(rotationSteps * rotateStepSize);
322
+ const roundedRotationMatrix = rotationMat.mapEntries(component => Viewport_1.default.roundScaleRatio(component));
323
+ const regionCenter = this.editor.viewport.roundPoint(region.center);
324
+ const transform = math_1.Mat33.scaling2D(scaleFactor, this.editor.viewport.roundPoint(region.topLeft)).rightMul(math_1.Mat33.translation(regionCenter).rightMul(roundedRotationMatrix).rightMul(math_1.Mat33.translation(regionCenter.times(-1)))).rightMul(math_1.Mat33.translation(this.editor.viewport.roundPoint(math_1.Vec2.of(xTranslateSteps, yTranslateSteps).times(translateStepSize))));
325
+ const oldTransform = this.selectionBox.getTransform();
326
+ this.selectionBox.setTransform(oldTransform.rightMul(transform));
327
+ this.selectionBox.scrollTo();
328
+ // The transformation needs to be finalized at some point (on key up)
329
+ this.hasUnfinalizedTransformFromKeyPress = true;
330
+ }
331
+ if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
332
+ this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
333
+ this.clearSelection();
334
+ handled = true;
335
+ }
336
+ return handled;
337
+ }
338
+ onKeyUp(evt) {
339
+ const shortcucts = this.editor.shortcuts;
340
+ if (shortcucts.matchesShortcut(keybindings_1.snapToGridKeyboardShortcutId, evt)) {
341
+ this.snapToGrid = false;
342
+ return true;
343
+ }
344
+ if (shortcucts.matchesShortcut(keybindings_1.selectAllKeyboardShortcut, evt)) {
345
+ // Selected all in onKeyDown. Don't finalizeTransform.
346
+ return true;
347
+ }
348
+ if (this.selectionBox && shortcucts.matchesShortcut(keybindings_1.duplicateSelectionShortcut, evt)) {
349
+ // Finalize duplicating the selection
350
+ this.selectionBox.duplicateSelectedObjects().then(command => {
351
+ this.editor.dispatch(command);
352
+ });
353
+ return true;
354
+ }
355
+ if (this.selectionBox && shortcucts.matchesShortcut(keybindings_1.sendToBackSelectionShortcut, evt)) {
356
+ const sendToBackCommand = this.selectionBox.sendToBack();
357
+ if (sendToBackCommand) {
358
+ this.editor.dispatch(sendToBackCommand);
359
+ }
360
+ return true;
361
+ }
362
+ // Here, we check if shiftKey === false because, as of this writing,
363
+ // evt.shiftKey is an optional property. Being falsey could just mean
364
+ // that it wasn't set.
365
+ if (evt.shiftKey === false) {
366
+ this.shiftKeyPressed = false;
367
+ // Don't return immediately -- event may be otherwise handled
368
+ }
369
+ // Also check for key === 'Shift' (for the case where shiftKey is undefined)
370
+ if (evt.key === 'Shift') {
371
+ this.shiftKeyPressed = false;
372
+ return true;
373
+ }
374
+ // If we don't need to finalize the transform
375
+ if (!this.hasUnfinalizedTransformFromKeyPress) {
376
+ return true;
377
+ }
378
+ if (this.selectionBox) {
379
+ this.selectionBox.finalizeTransform();
380
+ this.hasUnfinalizedTransformFromKeyPress = false;
381
+ return true;
382
+ }
383
+ return false;
384
+ }
385
+ onCopy(event) {
386
+ if (!this.selectionBox) {
387
+ return false;
388
+ }
389
+ const selectedElems = this.selectionBox.getSelectedObjects();
390
+ const bbox = this.selectionBox.region;
391
+ if (selectedElems.length === 0) {
392
+ return false;
393
+ }
394
+ const exportViewport = new Viewport_1.default(() => { });
395
+ const selectionScreenSize = this.selectionBox.getScreenRegion().size.times(this.editor.display.getDevicePixelRatio());
396
+ // Update the viewport to have screen size roughly equal to the size of the selection box
397
+ let scaleFactor = selectionScreenSize.maximumEntryMagnitude() / (bbox.size.maximumEntryMagnitude() || 1);
398
+ // Round to a nearby power of two
399
+ scaleFactor = Math.pow(2, Math.ceil(Math.log2(scaleFactor)));
400
+ exportViewport.updateScreenSize(bbox.size.times(scaleFactor));
401
+ exportViewport.resetTransform(math_1.Mat33.scaling2D(scaleFactor)
402
+ // Move the selection onto the screen
403
+ .rightMul(math_1.Mat33.translation(bbox.topLeft.times(-1))));
404
+ const { element: svgExportElem, renderer: svgRenderer } = SVGRenderer_1.default.fromViewport(exportViewport, { sanitize: true, useViewBoxForPositioning: true });
405
+ const { element: canvas, renderer: canvasRenderer } = CanvasRenderer_1.default.fromViewport(exportViewport, { maxCanvasDimen: 4096 });
406
+ const text = [];
407
+ for (const elem of selectedElems) {
408
+ elem.render(svgRenderer);
409
+ elem.render(canvasRenderer);
410
+ if (elem instanceof TextComponent_1.default) {
411
+ text.push(elem.getText());
412
+ }
413
+ }
414
+ event.setData('image/svg+xml', svgExportElem.outerHTML);
415
+ event.setData('text/html', svgExportElem.outerHTML);
416
+ event.setData('image/png', new Promise((resolve, reject) => {
417
+ canvas.toBlob((blob) => {
418
+ if (blob) {
419
+ resolve(blob);
420
+ }
421
+ else {
422
+ reject(new Error('Failed to convert canvas to blob.'));
423
+ }
424
+ }, 'image/png');
425
+ }));
426
+ if (text.length > 0) {
427
+ event.setData('text/plain', text.join('\n'));
428
+ }
429
+ return true;
430
+ }
431
+ setEnabled(enabled) {
432
+ const wasEnabled = this.isEnabled();
433
+ super.setEnabled(enabled);
434
+ if (wasEnabled === enabled) {
435
+ return;
436
+ }
437
+ // Clear the selection
438
+ this.selectionBox?.cancelSelection();
439
+ this.onSelectionUpdated();
440
+ this.handleOverlay.replaceChildren();
441
+ this.selectionBox = null;
442
+ this.shiftKeyPressed = false;
443
+ this.snapToGrid = false;
444
+ this.handleOverlay.style.display = enabled ? 'block' : 'none';
445
+ if (enabled) {
446
+ this.handleOverlay.tabIndex = 0;
447
+ this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
448
+ }
449
+ else {
450
+ this.handleOverlay.tabIndex = -1;
451
+ }
452
+ }
453
+ // Get the object responsible for displaying this' selection.
454
+ // @internal
455
+ getSelection() {
456
+ return this.selectionBox;
457
+ }
458
+ getSelectedObjects() {
459
+ return this.selectionBox?.getSelectedObjects() ?? [];
460
+ }
461
+ // Select the given `objects`. Any non-selectable objects in `objects` are ignored.
462
+ setSelection(objects) {
463
+ // Only select selectable objects.
464
+ objects = objects.filter(obj => obj.isSelectable());
465
+ // Sort by z-index
466
+ objects.sort((a, b) => a.getZIndex() - b.getZIndex());
467
+ // Remove duplicates
468
+ objects = objects.filter((current, idx) => {
469
+ if (idx > 0) {
470
+ return current !== objects[idx - 1];
471
+ }
472
+ return true;
473
+ });
474
+ let bbox = null;
475
+ for (const object of objects) {
476
+ if (bbox) {
477
+ bbox = bbox.union(object.getBBox());
478
+ }
479
+ else {
480
+ bbox = object.getBBox();
481
+ }
482
+ }
483
+ if (!bbox) {
484
+ return;
485
+ }
486
+ this.clearSelection();
487
+ if (!this.selectionBox) {
488
+ this.makeSelectionBox(bbox.topLeft);
489
+ }
490
+ this.selectionBox.setSelectedObjects(objects, bbox);
491
+ this.onSelectionUpdated();
492
+ }
493
+ clearSelection() {
494
+ this.handleOverlay.replaceChildren();
495
+ this.prevSelectionBox = this.selectionBox;
496
+ this.selectionBox = null;
497
+ this.onSelectionUpdated();
498
+ }
499
+ }
500
+ exports.default = SelectionTool;
@@ -0,0 +1,23 @@
1
+ import { Point2, Vec2 } from '@js-draw/math';
2
+ import Viewport from '../../Viewport';
3
+ type ScrollByCallback = (delta: Vec2) => void;
4
+ /**
5
+ * Automatically scrolls the viewport such that the user's pointer is visible.
6
+ */
7
+ export default class ToPointerAutoscroller {
8
+ private viewport;
9
+ private scrollByCanvasDelta;
10
+ private started;
11
+ private updateLoopId;
12
+ private updateLoopRunning;
13
+ private targetPoint;
14
+ private scrollRate;
15
+ constructor(viewport: Viewport, scrollByCanvasDelta: ScrollByCallback);
16
+ private getScrollForPoint;
17
+ start(): void;
18
+ onPointerMove(pointerScreenPosition: Point2): void;
19
+ stop(): void;
20
+ private startUpdateLoop;
21
+ private stopUpdateLoop;
22
+ }
23
+ export {};
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const math_1 = require("@js-draw/math");
7
+ const untilNextAnimationFrame_1 = __importDefault(require("../../util/untilNextAnimationFrame"));
8
+ /**
9
+ * Automatically scrolls the viewport such that the user's pointer is visible.
10
+ */
11
+ class ToPointerAutoscroller {
12
+ constructor(viewport, scrollByCanvasDelta) {
13
+ this.viewport = viewport;
14
+ this.scrollByCanvasDelta = scrollByCanvasDelta;
15
+ this.started = false;
16
+ this.updateLoopId = 0;
17
+ this.updateLoopRunning = false;
18
+ this.targetPoint = null;
19
+ this.scrollRate = 1000; // px/s
20
+ }
21
+ getScrollForPoint(screenPoint) {
22
+ const screenSize = this.viewport.getScreenRectSize();
23
+ const screenRect = new math_1.Rect2(0, 0, screenSize.x, screenSize.y);
24
+ // Starts autoscrolling when the cursor is **outside of** this region
25
+ const marginSize = 44;
26
+ const autoscrollBoundary = screenRect.grownBy(-marginSize);
27
+ if (autoscrollBoundary.containsPoint(screenPoint)) {
28
+ return math_1.Vec2.zero;
29
+ }
30
+ const closestEdgePoint = autoscrollBoundary.getClosestPointOnBoundaryTo(screenPoint);
31
+ const distToEdge = closestEdgePoint.distanceTo(screenPoint);
32
+ const toEdge = closestEdgePoint.minus(screenPoint);
33
+ // Go faster for points further away from the boundary.
34
+ const maximumScaleFactor = 1.25;
35
+ const scaleFactor = Math.min(distToEdge / marginSize, maximumScaleFactor);
36
+ return toEdge.normalizedOrZero().times(scaleFactor);
37
+ }
38
+ start() {
39
+ this.started = true;
40
+ }
41
+ onPointerMove(pointerScreenPosition) {
42
+ if (!this.started) {
43
+ return;
44
+ }
45
+ if (this.getScrollForPoint(pointerScreenPosition) === math_1.Vec2.zero) {
46
+ this.stopUpdateLoop();
47
+ }
48
+ else {
49
+ this.targetPoint = pointerScreenPosition;
50
+ this.startUpdateLoop();
51
+ }
52
+ }
53
+ stop() {
54
+ this.targetPoint = null;
55
+ this.started = false;
56
+ this.stopUpdateLoop();
57
+ }
58
+ startUpdateLoop() {
59
+ if (this.updateLoopRunning) {
60
+ return;
61
+ }
62
+ (async () => {
63
+ this.updateLoopId++;
64
+ const currentUpdateLoopId = this.updateLoopId;
65
+ let lastUpdateTime = performance.now();
66
+ while (this.updateLoopId === currentUpdateLoopId && this.targetPoint) {
67
+ this.updateLoopRunning = true;
68
+ const currentTime = performance.now();
69
+ const deltaTimeMs = currentTime - lastUpdateTime;
70
+ const scrollDirection = this.getScrollForPoint(this.targetPoint);
71
+ const screenScrollAmount = scrollDirection.times(this.scrollRate * deltaTimeMs / 1000);
72
+ this.scrollByCanvasDelta(this.viewport.screenToCanvasTransform.transformVec3(screenScrollAmount));
73
+ lastUpdateTime = currentTime;
74
+ await (0, untilNextAnimationFrame_1.default)();
75
+ }
76
+ this.updateLoopRunning = false;
77
+ })();
78
+ }
79
+ stopUpdateLoop() {
80
+ this.updateLoopId++;
81
+ }
82
+ }
83
+ exports.default = ToPointerAutoscroller;
@@ -0,0 +1,42 @@
1
+ import Editor from '../../Editor';
2
+ import { Vec3 } from '@js-draw/math';
3
+ import Selection from './Selection';
4
+ import { ResizeMode } from './types';
5
+ export declare class DragTransformer {
6
+ private readonly editor;
7
+ private selection;
8
+ private dragStartPoint;
9
+ constructor(editor: Editor, selection: Selection);
10
+ onDragStart(startPoint: Vec3): void;
11
+ onDragUpdate(canvasPos: Vec3): void;
12
+ onDragEnd(): void | Promise<void>;
13
+ }
14
+ export declare class ResizeTransformer {
15
+ private readonly editor;
16
+ private selection;
17
+ private mode;
18
+ private dragStartPoint;
19
+ private transformOrigin;
20
+ private scaleRate;
21
+ constructor(editor: Editor, selection: Selection);
22
+ onDragStart(startPoint: Vec3, mode: ResizeMode): void;
23
+ private computeOriginAndScaleRate;
24
+ onDragUpdate(canvasPos: Vec3): void;
25
+ onDragEnd(): void | Promise<void>;
26
+ }
27
+ export declare class RotateTransformer {
28
+ private readonly editor;
29
+ private selection;
30
+ private startAngle;
31
+ private targetRotation;
32
+ private maximumDistFromStart;
33
+ private startPoint;
34
+ private startTime;
35
+ constructor(editor: Editor, selection: Selection);
36
+ private getAngle;
37
+ private roundAngle;
38
+ onDragStart(startPoint: Vec3): void;
39
+ private setRotationTo;
40
+ onDragUpdate(canvasPos: Vec3): void;
41
+ onDragEnd(): void | Promise<void>;
42
+ }