js-draw 1.26.0 → 1.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Editor.css +1 -1
- package/dist/bundle.js +42 -37
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +0 -2
- package/dist/cjs/components/AbstractComponent.d.ts +15 -0
- package/dist/cjs/components/AbstractComponent.js +16 -0
- package/dist/cjs/components/Stroke.d.ts +1 -0
- package/dist/cjs/components/Stroke.js +7 -0
- package/dist/cjs/toolbar/IconProvider.d.ts +2 -1
- package/dist/cjs/toolbar/IconProvider.js +18 -8
- package/dist/cjs/toolbar/localization.d.ts +2 -0
- package/dist/cjs/toolbar/localization.js +2 -0
- package/dist/cjs/toolbar/widgets/SelectionToolWidget.d.ts +7 -0
- package/dist/cjs/toolbar/widgets/SelectionToolWidget.js +109 -28
- package/dist/cjs/toolbar/widgets/components/makeButtonGrid.d.ts +17 -0
- package/dist/cjs/toolbar/widgets/components/makeButtonGrid.js +40 -0
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -3
- package/dist/cjs/tools/SelectionTool/Selection.js +19 -40
- package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.d.ts +17 -0
- package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.js +67 -0
- package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.d.ts +13 -0
- package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.js +33 -0
- package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.d.ts +15 -0
- package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.js +39 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +10 -2
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +68 -55
- package/dist/cjs/tools/SelectionTool/types.d.ts +4 -0
- package/dist/cjs/tools/SelectionTool/types.js +6 -1
- package/dist/cjs/tools/lib.d.ts +1 -1
- package/dist/cjs/tools/lib.js +2 -1
- package/dist/cjs/util/ReactiveValue.js +2 -6
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +0 -2
- package/dist/mjs/components/AbstractComponent.d.ts +15 -0
- package/dist/mjs/components/AbstractComponent.mjs +16 -0
- package/dist/mjs/components/Stroke.d.ts +1 -0
- package/dist/mjs/components/Stroke.mjs +7 -0
- package/dist/mjs/toolbar/IconProvider.d.ts +2 -1
- package/dist/mjs/toolbar/IconProvider.mjs +18 -8
- package/dist/mjs/toolbar/localization.d.ts +2 -0
- package/dist/mjs/toolbar/localization.mjs +2 -0
- package/dist/mjs/toolbar/widgets/SelectionToolWidget.d.ts +7 -0
- package/dist/mjs/toolbar/widgets/SelectionToolWidget.mjs +109 -28
- package/dist/mjs/toolbar/widgets/components/makeButtonGrid.d.ts +17 -0
- package/dist/mjs/toolbar/widgets/components/makeButtonGrid.mjs +35 -0
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -3
- package/dist/mjs/tools/SelectionTool/Selection.mjs +19 -40
- package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.d.ts +17 -0
- package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.mjs +61 -0
- package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.d.ts +13 -0
- package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.mjs +27 -0
- package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.d.ts +15 -0
- package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.mjs +36 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +10 -2
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +68 -55
- package/dist/mjs/tools/SelectionTool/types.d.ts +4 -0
- package/dist/mjs/tools/SelectionTool/types.mjs +5 -0
- package/dist/mjs/tools/lib.d.ts +1 -1
- package/dist/mjs/tools/lib.mjs +1 -1
- package/dist/mjs/util/ReactiveValue.mjs +2 -6
- package/dist/mjs/version.mjs +1 -1
- package/package.json +4 -4
- package/src/toolbar/EdgeToolbar.scss +6 -1
- package/src/toolbar/widgets/components/components.scss +1 -0
- package/src/toolbar/widgets/components/makeButtonGrid.scss +25 -0
- package/src/tools/SelectionTool/SelectionTool.scss +1 -0
- package/src/tools/util/createMenuOverlay.scss +3 -2
@@ -6,13 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const math_1 = require("@js-draw/math");
|
7
7
|
const RestylableComponent_1 = require("../../components/RestylableComponent");
|
8
8
|
const uniteCommands_1 = __importDefault(require("../../commands/uniteCommands"));
|
9
|
+
const SelectionTool_1 = require("../../tools/SelectionTool/SelectionTool");
|
9
10
|
const types_1 = require("../../types");
|
10
11
|
const makeColorInput_1 = __importDefault(require("./components/makeColorInput"));
|
11
|
-
const ActionButtonWidget_1 = __importDefault(require("./ActionButtonWidget"));
|
12
12
|
const BaseToolWidget_1 = __importDefault(require("./BaseToolWidget"));
|
13
13
|
const keybindings_1 = require("./keybindings");
|
14
14
|
const makeSeparator_1 = __importDefault(require("./components/makeSeparator"));
|
15
15
|
const constants_1 = require("../constants");
|
16
|
+
const BaseWidget_1 = __importDefault(require("./BaseWidget"));
|
17
|
+
const makeButtonGrid_1 = __importDefault(require("./components/makeButtonGrid"));
|
18
|
+
const ReactiveValue_1 = require("../../util/ReactiveValue");
|
16
19
|
const makeFormatMenu = (editor, selectionTool, localizationTable) => {
|
17
20
|
const container = document.createElement('div');
|
18
21
|
container.classList.add('selection-format-menu', `${constants_1.toolbarCSSPrefix}spacedList`, `${constants_1.toolbarCSSPrefix}indentedList`);
|
@@ -68,48 +71,63 @@ const makeFormatMenu = (editor, selectionTool, localizationTable) => {
|
|
68
71
|
},
|
69
72
|
};
|
70
73
|
};
|
74
|
+
class LassoSelectToggle extends BaseWidget_1.default {
|
75
|
+
constructor(editor, tool, localizationTable) {
|
76
|
+
super(editor, 'selection-mode-toggle', localizationTable);
|
77
|
+
this.tool = tool;
|
78
|
+
editor.notifier.on(types_1.EditorEventType.ToolUpdated, (toolEvt) => {
|
79
|
+
if (toolEvt.kind === types_1.EditorEventType.ToolUpdated && toolEvt.tool === tool) {
|
80
|
+
this.setSelected(tool.modeValue.get() === SelectionTool_1.SelectionMode.Lasso);
|
81
|
+
}
|
82
|
+
});
|
83
|
+
this.setSelected(false);
|
84
|
+
}
|
85
|
+
shouldAutoDisableInReadOnlyEditor() {
|
86
|
+
return false;
|
87
|
+
}
|
88
|
+
setModeFlag(enabled) {
|
89
|
+
this.tool.modeValue.set(enabled ? SelectionTool_1.SelectionMode.Lasso : SelectionTool_1.SelectionMode.Rectangle);
|
90
|
+
}
|
91
|
+
handleClick() {
|
92
|
+
this.setModeFlag(!this.isSelected());
|
93
|
+
}
|
94
|
+
getTitle() {
|
95
|
+
return this.localizationTable.selectionTool__lassoSelect;
|
96
|
+
}
|
97
|
+
createIcon() {
|
98
|
+
return this.editor.icons.makeSelectionIcon(SelectionTool_1.SelectionMode.Lasso);
|
99
|
+
}
|
100
|
+
fillDropdown(_dropdown) {
|
101
|
+
return false;
|
102
|
+
}
|
103
|
+
getHelpText() {
|
104
|
+
return this.localizationTable.selectionTool__lassoSelect__help;
|
105
|
+
}
|
106
|
+
}
|
71
107
|
class SelectionToolWidget extends BaseToolWidget_1.default {
|
72
108
|
constructor(editor, tool, localization) {
|
73
109
|
super(editor, tool, 'selection-tool-widget', localization);
|
74
110
|
this.tool = tool;
|
75
111
|
this.updateFormatMenu = () => { };
|
76
|
-
|
77
|
-
|
78
|
-
}, localization);
|
79
|
-
resizeButton.setHelpText(this.localizationTable.selectionDropdown__resizeToHelpText);
|
80
|
-
const deleteButton = new ActionButtonWidget_1.default(editor, 'delete-btn', () => editor.icons.makeDeleteSelectionIcon(), this.localizationTable.deleteSelection, () => {
|
81
|
-
const selection = this.tool.getSelection();
|
82
|
-
this.editor.dispatch(selection.deleteSelectedObjects());
|
83
|
-
this.tool.clearSelection();
|
84
|
-
}, localization);
|
85
|
-
deleteButton.setHelpText(this.localizationTable.selectionDropdown__deleteHelpText);
|
86
|
-
const duplicateButton = new ActionButtonWidget_1.default(editor, 'duplicate-btn', () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, async () => {
|
112
|
+
this.addSubWidget(new LassoSelectToggle(editor, tool, this.localizationTable));
|
113
|
+
const hasSelection = () => {
|
87
114
|
const selection = this.tool.getSelection();
|
88
|
-
|
89
|
-
this.setDropdownVisible(false);
|
90
|
-
}, localization);
|
91
|
-
duplicateButton.setHelpText(this.localizationTable.selectionDropdown__duplicateHelpText);
|
92
|
-
this.addSubWidget(resizeButton);
|
93
|
-
this.addSubWidget(deleteButton);
|
94
|
-
this.addSubWidget(duplicateButton);
|
95
|
-
const updateDisabled = (disabled) => {
|
96
|
-
resizeButton.setDisabled(disabled);
|
97
|
-
deleteButton.setDisabled(disabled);
|
98
|
-
duplicateButton.setDisabled(disabled);
|
115
|
+
return !!selection && selection.getSelectedItemCount() > 0;
|
99
116
|
};
|
100
|
-
|
117
|
+
this.hasSelectionValue = ReactiveValue_1.MutableReactiveValue.fromInitialValue(hasSelection());
|
101
118
|
// Enable/disable actions based on whether items are selected
|
102
119
|
this.editor.notifier.on(types_1.EditorEventType.ToolUpdated, (toolEvt) => {
|
103
120
|
if (toolEvt.kind !== types_1.EditorEventType.ToolUpdated) {
|
104
121
|
throw new Error('Invalid event type!');
|
105
122
|
}
|
106
123
|
if (toolEvt.tool === this.tool) {
|
107
|
-
|
108
|
-
const hasSelection = selection && selection.getSelectedItemCount() > 0;
|
109
|
-
updateDisabled(!hasSelection);
|
124
|
+
this.hasSelectionValue.set(hasSelection());
|
110
125
|
this.updateFormatMenu();
|
111
126
|
}
|
112
127
|
});
|
128
|
+
tool.modeValue.onUpdate(() => {
|
129
|
+
this.updateIcon();
|
130
|
+
});
|
113
131
|
}
|
114
132
|
resizeImageToSelection() {
|
115
133
|
const selection = this.tool.getSelection();
|
@@ -135,16 +153,66 @@ class SelectionToolWidget extends BaseToolWidget_1.default {
|
|
135
153
|
return this.localizationTable.select;
|
136
154
|
}
|
137
155
|
createIcon() {
|
138
|
-
return this.editor.icons.makeSelectionIcon();
|
156
|
+
return this.editor.icons.makeSelectionIcon(this.tool.modeValue.get());
|
139
157
|
}
|
140
158
|
getHelpText() {
|
141
159
|
return this.localizationTable.selectionDropdown__baseHelpText;
|
142
160
|
}
|
161
|
+
createSelectionActions(helpDisplay) {
|
162
|
+
const icons = this.editor.icons;
|
163
|
+
const grid = (0, makeButtonGrid_1.default)([
|
164
|
+
{
|
165
|
+
icon: () => icons.makeDeleteSelectionIcon(),
|
166
|
+
label: this.localizationTable.deleteSelection,
|
167
|
+
onCreated: (button) => {
|
168
|
+
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__deleteHelpText);
|
169
|
+
},
|
170
|
+
onClick: () => {
|
171
|
+
const selection = this.tool.getSelection();
|
172
|
+
this.editor.dispatch(selection.deleteSelectedObjects());
|
173
|
+
this.tool.clearSelection();
|
174
|
+
},
|
175
|
+
enabled: this.hasSelectionValue,
|
176
|
+
},
|
177
|
+
{
|
178
|
+
icon: () => icons.makeDuplicateSelectionIcon(),
|
179
|
+
label: this.localizationTable.duplicateSelection,
|
180
|
+
onCreated: (button) => {
|
181
|
+
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__duplicateHelpText);
|
182
|
+
},
|
183
|
+
onClick: async () => {
|
184
|
+
const selection = this.tool.getSelection();
|
185
|
+
const command = await selection?.duplicateSelectedObjects();
|
186
|
+
if (command) {
|
187
|
+
this.editor.dispatch(command);
|
188
|
+
}
|
189
|
+
},
|
190
|
+
enabled: this.hasSelectionValue,
|
191
|
+
},
|
192
|
+
{
|
193
|
+
icon: () => icons.makeResizeImageToSelectionIcon(),
|
194
|
+
label: this.localizationTable.resizeImageToSelection,
|
195
|
+
onCreated: (button) => {
|
196
|
+
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__resizeToHelpText);
|
197
|
+
},
|
198
|
+
onClick: () => {
|
199
|
+
this.resizeImageToSelection();
|
200
|
+
},
|
201
|
+
enabled: this.hasSelectionValue,
|
202
|
+
},
|
203
|
+
], 3);
|
204
|
+
return { container: grid.container };
|
205
|
+
}
|
143
206
|
fillDropdown(dropdown, helpDisplay) {
|
144
207
|
super.fillDropdown(dropdown, helpDisplay);
|
145
208
|
const controlsContainer = document.createElement('div');
|
146
209
|
controlsContainer.classList.add(`${constants_1.toolbarCSSPrefix}nonbutton-controls-main-list`);
|
147
210
|
dropdown.appendChild(controlsContainer);
|
211
|
+
// Actions (duplicate, delete, etc.)
|
212
|
+
(0, makeSeparator_1.default)().addTo(controlsContainer);
|
213
|
+
const actions = this.createSelectionActions(helpDisplay);
|
214
|
+
controlsContainer.appendChild(actions.container);
|
215
|
+
// Formatting
|
148
216
|
(0, makeSeparator_1.default)(this.localizationTable.reformatSelection).addTo(controlsContainer);
|
149
217
|
const formatMenu = makeFormatMenu(this.editor, this.tool, this.localizationTable);
|
150
218
|
formatMenu.addTo(controlsContainer);
|
@@ -155,5 +223,18 @@ class SelectionToolWidget extends BaseToolWidget_1.default {
|
|
155
223
|
formatMenu.update();
|
156
224
|
return true;
|
157
225
|
}
|
226
|
+
serializeState() {
|
227
|
+
return {
|
228
|
+
...super.serializeState(),
|
229
|
+
selectionMode: this.tool.modeValue.get(),
|
230
|
+
};
|
231
|
+
}
|
232
|
+
deserializeFrom(state) {
|
233
|
+
super.deserializeFrom(state);
|
234
|
+
const isValidSelectionMode = Object.values(SelectionTool_1.SelectionMode).includes(state.selectionMode);
|
235
|
+
if (isValidSelectionMode) {
|
236
|
+
this.tool.modeValue.set(state.selectionMode);
|
237
|
+
}
|
238
|
+
}
|
158
239
|
}
|
159
240
|
exports.default = SelectionToolWidget;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { ReactiveValue } from '../../../util/ReactiveValue';
|
2
|
+
import { IconElemType } from '../../IconProvider';
|
3
|
+
interface Button {
|
4
|
+
icon: () => IconElemType;
|
5
|
+
label: string;
|
6
|
+
onClick: () => void;
|
7
|
+
onCreated?: (button: HTMLElement) => void;
|
8
|
+
enabled?: ReactiveValue<boolean>;
|
9
|
+
}
|
10
|
+
/**
|
11
|
+
* Creates HTML `button` elements from `buttonSpecs` and displays them in a
|
12
|
+
* grid with `columnCount` columns.
|
13
|
+
*/
|
14
|
+
declare const makeButtonGrid: (buttonSpecs: Button[], columnCount: number) => {
|
15
|
+
container: HTMLDivElement;
|
16
|
+
};
|
17
|
+
export default makeButtonGrid;
|
@@ -0,0 +1,40 @@
|
|
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 addLongPressOrHoverCssClasses_1 = __importDefault(require("../../../util/addLongPressOrHoverCssClasses"));
|
7
|
+
/**
|
8
|
+
* Creates HTML `button` elements from `buttonSpecs` and displays them in a
|
9
|
+
* grid with `columnCount` columns.
|
10
|
+
*/
|
11
|
+
const makeButtonGrid = (buttonSpecs, columnCount) => {
|
12
|
+
const container = document.createElement('div');
|
13
|
+
container.classList.add('toolbar-button-grid');
|
14
|
+
container.style.setProperty('--column-count', `${columnCount}`);
|
15
|
+
const makeButton = (buttonSpec) => {
|
16
|
+
const buttonElement = document.createElement('button');
|
17
|
+
buttonElement.classList.add('button');
|
18
|
+
const iconElement = buttonSpec.icon();
|
19
|
+
iconElement.classList.add('icon');
|
20
|
+
const labelElement = document.createElement('label');
|
21
|
+
labelElement.textContent = buttonSpec.label;
|
22
|
+
labelElement.classList.add('button-label-text');
|
23
|
+
buttonElement.onclick = buttonSpec.onClick;
|
24
|
+
if (buttonSpec.enabled) {
|
25
|
+
buttonSpec.enabled.onUpdateAndNow((enabled) => {
|
26
|
+
buttonElement.disabled = !enabled;
|
27
|
+
});
|
28
|
+
}
|
29
|
+
buttonElement.replaceChildren(iconElement, labelElement);
|
30
|
+
container.appendChild(buttonElement);
|
31
|
+
(0, addLongPressOrHoverCssClasses_1.default)(buttonElement);
|
32
|
+
buttonSpec.onCreated?.(buttonElement);
|
33
|
+
return buttonElement;
|
34
|
+
};
|
35
|
+
buttonSpecs.map(makeButton);
|
36
|
+
return {
|
37
|
+
container,
|
38
|
+
};
|
39
|
+
};
|
40
|
+
exports.default = makeButtonGrid;
|
@@ -20,7 +20,7 @@ export default class Selection {
|
|
20
20
|
private innerContainer;
|
21
21
|
private backgroundElem;
|
22
22
|
private hasParent;
|
23
|
-
constructor(
|
23
|
+
constructor(selectedElems: AbstractComponent[], editor: Editor, showContextMenu: (anchor: Point2) => void);
|
24
24
|
getBackgroundElem(): HTMLElement;
|
25
25
|
getTransform(): Mat33;
|
26
26
|
get preTransformRegion(): Rect2;
|
@@ -43,7 +43,6 @@ export default class Selection {
|
|
43
43
|
sendToBack(): SerializableCommand | null;
|
44
44
|
private static ApplyTransformationCommand;
|
45
45
|
private previewTransformCmds;
|
46
|
-
resolveToObjects(): boolean;
|
47
46
|
recomputeRegion(): boolean;
|
48
47
|
padRegion(): void;
|
49
48
|
getMinCanvasSize(): number;
|
@@ -63,10 +62,10 @@ export default class Selection {
|
|
63
62
|
private selectionDuplicatedAnimationTimeout;
|
64
63
|
private runSelectionDuplicatedAnimation;
|
65
64
|
duplicateSelectedObjects(): Promise<Command>;
|
65
|
+
snapSelectedObjectsToGrid(): void;
|
66
66
|
setHandlesVisible(showHandles: boolean): void;
|
67
67
|
addTo(elem: HTMLElement): void;
|
68
68
|
setToPoint(point: Point2): void;
|
69
69
|
cancelSelection(): void;
|
70
|
-
setSelectedObjects(objects: AbstractComponent[], bbox: Rect2): void;
|
71
70
|
getSelectedObjects(): AbstractComponent[];
|
72
71
|
}
|
@@ -57,7 +57,7 @@ const updateChunkSize = 100;
|
|
57
57
|
const maxPreviewElemCount = 500;
|
58
58
|
// @internal
|
59
59
|
class Selection {
|
60
|
-
constructor(
|
60
|
+
constructor(selectedElems, editor, showContextMenu) {
|
61
61
|
this.editor = editor;
|
62
62
|
// The last-computed bounding box of selected content
|
63
63
|
// @see getTightBoundingBox
|
@@ -71,7 +71,9 @@ class Selection {
|
|
71
71
|
this.activeHandle = null;
|
72
72
|
this.backgroundDragging = false;
|
73
73
|
this.selectionDuplicatedAnimationTimeout = null;
|
74
|
-
|
74
|
+
selectedElems = [...selectedElems];
|
75
|
+
this.selectedElems = selectedElems;
|
76
|
+
this.originalRegion = math_1.Rect2.empty;
|
75
77
|
this.transformers = {
|
76
78
|
drag: new TransformMode_1.DragTransformer(editor, this),
|
77
79
|
resize: new TransformMode_1.ResizeTransformer(editor, this),
|
@@ -121,6 +123,7 @@ class Selection {
|
|
121
123
|
for (const widget of this.childwidgets) {
|
122
124
|
widget.addTo(this.backgroundElem);
|
123
125
|
}
|
126
|
+
this.recomputeRegion();
|
124
127
|
this.updateUI();
|
125
128
|
}
|
126
129
|
// @internal Intended for unit tests
|
@@ -243,34 +246,6 @@ class Selection {
|
|
243
246
|
wetInkRenderer.popTransform();
|
244
247
|
this.updateUI();
|
245
248
|
}
|
246
|
-
// Find the objects corresponding to this in the document,
|
247
|
-
// select them.
|
248
|
-
// Returns false iff nothing was selected.
|
249
|
-
resolveToObjects() {
|
250
|
-
let singleItemSelectionMode = false;
|
251
|
-
this.transform = math_1.Mat33.identity;
|
252
|
-
// Grow the rectangle, if necessary
|
253
|
-
if (this.region.w === 0 || this.region.h === 0) {
|
254
|
-
const padding = this.editor.viewport.visibleRect.maxDimension / 200;
|
255
|
-
this.originalRegion = math_1.Rect2.bboxOf(this.region.corners, padding);
|
256
|
-
// Only select one item if the rectangle was very small.
|
257
|
-
singleItemSelectionMode = true;
|
258
|
-
}
|
259
|
-
this.selectedElems = this.editor.image
|
260
|
-
.getElementsIntersectingRegion(this.region)
|
261
|
-
.filter((elem) => {
|
262
|
-
return elem.intersectsRect(this.region) && elem.isSelectable();
|
263
|
-
});
|
264
|
-
if (singleItemSelectionMode && this.selectedElems.length > 0) {
|
265
|
-
this.selectedElems = [this.selectedElems[this.selectedElems.length - 1]];
|
266
|
-
}
|
267
|
-
// Find the bounding box of all selected elements.
|
268
|
-
if (!this.recomputeRegion()) {
|
269
|
-
return false;
|
270
|
-
}
|
271
|
-
this.updateUI();
|
272
|
-
return true;
|
273
|
-
}
|
274
249
|
// Recompute this' region from the selected elements.
|
275
250
|
// Returns false if the selection is empty.
|
276
251
|
recomputeRegion() {
|
@@ -392,6 +367,10 @@ class Selection {
|
|
392
367
|
});
|
393
368
|
}
|
394
369
|
onDragStart(pointer) {
|
370
|
+
// If empty, it isn't possible to drag
|
371
|
+
if (this.selectedElems.length === 0) {
|
372
|
+
return false;
|
373
|
+
}
|
395
374
|
// Clear the HTML selection (prevent HTML drag and drop being triggered by this drag)
|
396
375
|
document.getSelection()?.removeAllRanges();
|
397
376
|
this.activeHandle = null;
|
@@ -517,6 +496,16 @@ class Selection {
|
|
517
496
|
}
|
518
497
|
return command;
|
519
498
|
}
|
499
|
+
snapSelectedObjectsToGrid() {
|
500
|
+
const viewport = this.editor.viewport;
|
501
|
+
// Snap the top left corner of what we have selected.
|
502
|
+
const topLeftOfBBox = this.computeTightBoundingBox().topLeft;
|
503
|
+
const snappedTopLeft = viewport.snapToGrid(topLeftOfBBox);
|
504
|
+
const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
|
505
|
+
const oldTransform = this.getTransform();
|
506
|
+
this.setTransform(oldTransform.rightMul(math_1.Mat33.translation(snapDelta)));
|
507
|
+
this.finalizeTransform();
|
508
|
+
}
|
520
509
|
setHandlesVisible(showHandles) {
|
521
510
|
if (!showHandles) {
|
522
511
|
this.innerContainer.classList.add('-hide-handles');
|
@@ -545,16 +534,6 @@ class Selection {
|
|
545
534
|
this.selectionTightBoundingBox = null;
|
546
535
|
this.hasParent = false;
|
547
536
|
}
|
548
|
-
setSelectedObjects(objects, bbox) {
|
549
|
-
this.addRemoveSelectionFromImage(true);
|
550
|
-
this.originalRegion = bbox;
|
551
|
-
this.selectionTightBoundingBox = bbox;
|
552
|
-
this.selectedElems = objects.filter((object) => object.isSelectable());
|
553
|
-
// Enforce increasing z-index invariant
|
554
|
-
this.selectedElems.sort((a, b) => a.getZIndex() - b.getZIndex());
|
555
|
-
this.padRegion();
|
556
|
-
this.updateUI();
|
557
|
-
}
|
558
537
|
getSelectedObjects() {
|
559
538
|
return [...this.selectedElems];
|
560
539
|
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { Path, Point2 } from '@js-draw/math';
|
2
|
+
import Viewport from '../../../Viewport';
|
3
|
+
import EditorImage from '../../../image/EditorImage';
|
4
|
+
import AbstractComponent from '../../../components/AbstractComponent';
|
5
|
+
import SelectionBuilder from './SelectionBuilder';
|
6
|
+
/**
|
7
|
+
* Creates lasso selections.
|
8
|
+
*/
|
9
|
+
export default class LassoSelectionBuilder extends SelectionBuilder {
|
10
|
+
private viewport;
|
11
|
+
private boundaryPoints;
|
12
|
+
private lastPoint;
|
13
|
+
constructor(startPoint: Point2, viewport: Viewport);
|
14
|
+
onPointerMove(canvasPoint: Point2): void;
|
15
|
+
previewPath(): Path;
|
16
|
+
resolveInternal(image: EditorImage): AbstractComponent[];
|
17
|
+
}
|
@@ -0,0 +1,67 @@
|
|
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 math_2 = require("@js-draw/math");
|
8
|
+
const SelectionBuilder_1 = __importDefault(require("./SelectionBuilder"));
|
9
|
+
/**
|
10
|
+
* Creates lasso selections.
|
11
|
+
*/
|
12
|
+
class LassoSelectionBuilder extends SelectionBuilder_1.default {
|
13
|
+
constructor(startPoint, viewport) {
|
14
|
+
super();
|
15
|
+
this.viewport = viewport;
|
16
|
+
this.boundaryPoints = [];
|
17
|
+
this.boundaryPoints.push(startPoint);
|
18
|
+
this.lastPoint = startPoint;
|
19
|
+
}
|
20
|
+
onPointerMove(canvasPoint) {
|
21
|
+
const lastBoundaryPoint = this.boundaryPoints[this.boundaryPoints.length - 1];
|
22
|
+
const minBoundaryDist = this.viewport.getSizeOfPixelOnCanvas() * 8;
|
23
|
+
if (lastBoundaryPoint.distanceTo(canvasPoint) >= minBoundaryDist) {
|
24
|
+
this.boundaryPoints.push(canvasPoint);
|
25
|
+
}
|
26
|
+
this.lastPoint = canvasPoint;
|
27
|
+
}
|
28
|
+
previewPath() {
|
29
|
+
const pathCommands = this.boundaryPoints.map((point) => {
|
30
|
+
return { kind: math_2.PathCommandType.LineTo, point };
|
31
|
+
});
|
32
|
+
pathCommands.push({
|
33
|
+
kind: math_2.PathCommandType.LineTo,
|
34
|
+
point: this.lastPoint,
|
35
|
+
});
|
36
|
+
return new math_1.Path(this.boundaryPoints[0], pathCommands).asClosed();
|
37
|
+
}
|
38
|
+
resolveInternal(image) {
|
39
|
+
const path = this.previewPath();
|
40
|
+
const lines = path.polylineApproximation();
|
41
|
+
const candidates = image.getElementsIntersectingRegion(path.bbox);
|
42
|
+
const componentIsInSelection = (component) => {
|
43
|
+
if (path.closedContainsRect(component.getExactBBox())) {
|
44
|
+
return true;
|
45
|
+
}
|
46
|
+
let hasKeyPoint = false;
|
47
|
+
for (const point of component.keyPoints()) {
|
48
|
+
if (path.closedContainsPoint(point)) {
|
49
|
+
hasKeyPoint = true;
|
50
|
+
break;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
if (!hasKeyPoint) {
|
54
|
+
return false;
|
55
|
+
}
|
56
|
+
// Only select if completely contained within the lasso
|
57
|
+
for (const line of lines) {
|
58
|
+
if (component.intersects(line)) {
|
59
|
+
return false;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
return true;
|
63
|
+
};
|
64
|
+
return candidates.filter(componentIsInSelection);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
exports.default = LassoSelectionBuilder;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { Path, Point2 } from '@js-draw/math';
|
2
|
+
import EditorImage from '../../../image/EditorImage';
|
3
|
+
import SelectionBuilder from './SelectionBuilder';
|
4
|
+
/**
|
5
|
+
* Creates rectangle selections
|
6
|
+
*/
|
7
|
+
export default class RectSelectionBuilder extends SelectionBuilder {
|
8
|
+
private rect;
|
9
|
+
constructor(startPoint: Point2);
|
10
|
+
onPointerMove(canvasPoint: Point2): void;
|
11
|
+
previewPath(): Path;
|
12
|
+
resolveInternal(image: EditorImage): import("../../../lib").AbstractComponent[];
|
13
|
+
}
|
@@ -0,0 +1,33 @@
|
|
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 SelectionBuilder_1 = __importDefault(require("./SelectionBuilder"));
|
8
|
+
/**
|
9
|
+
* Creates rectangle selections
|
10
|
+
*/
|
11
|
+
class RectSelectionBuilder extends SelectionBuilder_1.default {
|
12
|
+
constructor(startPoint) {
|
13
|
+
super();
|
14
|
+
this.rect = math_1.Rect2.fromCorners(startPoint, startPoint);
|
15
|
+
}
|
16
|
+
onPointerMove(canvasPoint) {
|
17
|
+
this.rect = this.rect.grownToPoint(canvasPoint);
|
18
|
+
}
|
19
|
+
previewPath() {
|
20
|
+
return math_1.Path.fromRect(this.rect);
|
21
|
+
}
|
22
|
+
resolveInternal(image) {
|
23
|
+
return image.getElementsIntersectingRegion(this.rect).filter((element) => {
|
24
|
+
// Filter out the case where the selection rectangle is completely contained
|
25
|
+
// within the element (and does not intersect it).
|
26
|
+
// This is useful, for example, if a very large stroke is used as the background
|
27
|
+
// for another drawing. This prevents the very large stroke from being selected
|
28
|
+
// unless the selection touches one of its edges.
|
29
|
+
return element.intersectsRect(this.rect);
|
30
|
+
});
|
31
|
+
}
|
32
|
+
}
|
33
|
+
exports.default = RectSelectionBuilder;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { Color4, Path, Point2 } from '@js-draw/math';
|
2
|
+
import AbstractRenderer from '../../../rendering/renderers/AbstractRenderer';
|
3
|
+
import EditorImage from '../../../image/EditorImage';
|
4
|
+
import AbstractComponent from '../../../components/AbstractComponent';
|
5
|
+
import Viewport from '../../../Viewport';
|
6
|
+
export default abstract class SelectionBuilder {
|
7
|
+
abstract onPointerMove(canvasPoint: Point2): void;
|
8
|
+
abstract previewPath(): Path;
|
9
|
+
/** Returns the components currently in the selection bounds. Used by {@link resolve}. */
|
10
|
+
protected abstract resolveInternal(image: EditorImage): AbstractComponent[];
|
11
|
+
/** Renders a preview of the selection bounds */
|
12
|
+
render(renderer: AbstractRenderer, color: Color4): void;
|
13
|
+
/** Converts the selection preview into a set of selected elements */
|
14
|
+
resolve(image: EditorImage, viewport: Viewport): AbstractComponent[];
|
15
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const RenderablePathSpec_1 = require("../../../rendering/RenderablePathSpec");
|
4
|
+
class SelectionBuilder {
|
5
|
+
/** Renders a preview of the selection bounds */
|
6
|
+
render(renderer, color) {
|
7
|
+
renderer.drawPath((0, RenderablePathSpec_1.pathToRenderable)(this.previewPath(), { fill: color }));
|
8
|
+
}
|
9
|
+
/** Converts the selection preview into a set of selected elements */
|
10
|
+
resolve(image, viewport) {
|
11
|
+
const path = this.previewPath();
|
12
|
+
const filterComponents = (components) => {
|
13
|
+
return components.filter((component) => {
|
14
|
+
return component.isSelectable();
|
15
|
+
});
|
16
|
+
};
|
17
|
+
let components;
|
18
|
+
// If the bounding box is very small, search for items **near** the bounding box,
|
19
|
+
// rather than in the bounding box.
|
20
|
+
const clickSize = viewport.getSizeOfPixelOnCanvas() * 3;
|
21
|
+
const isClick = path.bbox.maxDimension <= clickSize;
|
22
|
+
if (isClick) {
|
23
|
+
const searchRegionSize = viewport.visibleRect.maxDimension / 200;
|
24
|
+
const minSizeBox = path.bbox.grownBy(searchRegionSize);
|
25
|
+
components = image.getElementsIntersectingRegion(minSizeBox).filter((component) => {
|
26
|
+
return minSizeBox.containsRect(component.getBBox()) || component.intersectsRect(minSizeBox);
|
27
|
+
});
|
28
|
+
components = filterComponents(components);
|
29
|
+
if (components.length > 1) {
|
30
|
+
components = [components[0]];
|
31
|
+
}
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
components = filterComponents(this.resolveInternal(image));
|
35
|
+
}
|
36
|
+
return components;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
exports.default = SelectionBuilder;
|
@@ -3,13 +3,18 @@ import Editor from '../../Editor';
|
|
3
3
|
import { ContextMenuEvt, CopyEvent, KeyPressEvent, KeyUpEvent, PointerEvt } from '../../inputEvents';
|
4
4
|
import BaseTool from '../BaseTool';
|
5
5
|
import Selection from './Selection';
|
6
|
+
import { MutableReactiveValue } from '../../util/ReactiveValue';
|
7
|
+
import { SelectionMode } from './types';
|
6
8
|
export declare const cssPrefix = "selection-tool-";
|
9
|
+
export { SelectionMode };
|
7
10
|
export default class SelectionTool extends BaseTool {
|
8
11
|
private editor;
|
12
|
+
readonly modeValue: MutableReactiveValue<SelectionMode>;
|
13
|
+
private selectionBuilder;
|
9
14
|
private handleOverlay;
|
10
15
|
private prevSelectionBox;
|
11
16
|
private selectionBox;
|
12
|
-
private
|
17
|
+
private removeSelectionScheduled;
|
13
18
|
private startPoint;
|
14
19
|
private expandingSelectionBox;
|
15
20
|
private shiftKeyPressed;
|
@@ -17,8 +22,8 @@ export default class SelectionTool extends BaseTool {
|
|
17
22
|
private lastPointer;
|
18
23
|
private autoscroller;
|
19
24
|
constructor(editor: Editor, description: string);
|
25
|
+
private getSelectionColor;
|
20
26
|
private makeSelectionBox;
|
21
|
-
private snapSelectionToGrid;
|
22
27
|
private showContextMenu;
|
23
28
|
onContextMenu(event: ContextMenuEvt): boolean;
|
24
29
|
private selectionBoxHandlingEvt;
|
@@ -36,7 +41,10 @@ export default class SelectionTool extends BaseTool {
|
|
36
41
|
onCopy(event: CopyEvent): boolean;
|
37
42
|
setEnabled(enabled: boolean): void;
|
38
43
|
getSelection(): Selection | null;
|
44
|
+
/** @returns true if the selection is currently being created by the user. */
|
45
|
+
isSelecting(): boolean;
|
39
46
|
getSelectedObjects(): AbstractComponent[];
|
40
47
|
setSelection(objects: AbstractComponent[]): void;
|
48
|
+
private clearSelectionNoUpdateEvent;
|
41
49
|
clearSelection(): void;
|
42
50
|
}
|