js-draw 1.26.0 → 1.27.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
}
|