js-draw 1.25.0 → 1.27.1
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +1 -1
- package/dist/Editor.css +1 -1935
- package/dist/bundle.js +478 -4
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +0 -2
- package/dist/cjs/Editor.js +1 -1
- package/dist/cjs/bundle/bundled.js +2 -1
- 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/image/EditorImage.d.ts +2 -1
- package/dist/cjs/image/EditorImage.js +21 -6
- package/dist/cjs/toolbar/AbstractToolbar.js +9 -2
- 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/BaseWidget.js +6 -1
- package/dist/cjs/toolbar/widgets/HandToolWidget.js +3 -3
- 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 +30 -46
- 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/SelectionMenuShortcut.d.ts +3 -1
- package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.js +13 -4
- 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/TextTool.js +5 -2
- 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/util/assertions.d.ts +7 -6
- package/dist/cjs/util/assertions.js +35 -29
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +0 -2
- package/dist/mjs/Editor.mjs +1 -1
- package/dist/mjs/bundle/bundled.mjs +2 -1
- 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/image/EditorImage.d.ts +2 -1
- package/dist/mjs/image/EditorImage.mjs +21 -6
- package/dist/mjs/toolbar/AbstractToolbar.mjs +9 -2
- 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/BaseWidget.mjs +6 -1
- package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +3 -3
- 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 +30 -46
- 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/SelectionMenuShortcut.d.ts +3 -1
- package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.mjs +13 -4
- 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/TextTool.mjs +5 -2
- 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/util/assertions.d.ts +7 -6
- package/dist/mjs/util/assertions.mjs +28 -24
- 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 +12 -1
- package/src/tools/util/createMenuOverlay.scss +5 -3
@@ -28,6 +28,12 @@ export var ComponentSizingMode;
|
|
28
28
|
})(ComponentSizingMode || (ComponentSizingMode = {}));
|
29
29
|
/**
|
30
30
|
* A base class for everything that can be added to an {@link EditorImage}.
|
31
|
+
*
|
32
|
+
* In addition to the `abstract` methods, there are a few methods that should be
|
33
|
+
* overridden when creating a selectable/erasable subclass:
|
34
|
+
* - {@link keyPoints}: Overriding this may improve how the component interacts with the selection tool.
|
35
|
+
* - {@link withRegionErased}: Override/implement this to allow the component to be partially erased
|
36
|
+
* by the partial stroke eraser.
|
31
37
|
*/
|
32
38
|
class AbstractComponent {
|
33
39
|
constructor(
|
@@ -136,6 +142,16 @@ class AbstractComponent {
|
|
136
142
|
const testLines = rect.getEdges();
|
137
143
|
return testLines.some((edge) => this.intersects(edge));
|
138
144
|
}
|
145
|
+
/**
|
146
|
+
* Returns a selection of points within this object. Each contiguous section
|
147
|
+
* of this object should have a point in the returned array.
|
148
|
+
*
|
149
|
+
* Subclasses should override this method if the center of the bounding box is
|
150
|
+
* not contained within the object.
|
151
|
+
*/
|
152
|
+
keyPoints() {
|
153
|
+
return [this.getBBox().center];
|
154
|
+
}
|
139
155
|
// @returns true iff this component can be selected (e.g. by the selection tool.)
|
140
156
|
isSelectable() {
|
141
157
|
return true;
|
@@ -54,6 +54,7 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
|
|
54
54
|
/** @beta -- May fail for concave `path`s */
|
55
55
|
withRegionErased(eraserPath: Path, viewport: Viewport): Stroke[];
|
56
56
|
intersects(line: LineSegment2): boolean;
|
57
|
+
keyPoints(): import("@js-draw/math").Vec3[];
|
57
58
|
intersectsRect(rect: Rect2): boolean;
|
58
59
|
private simplifiedPath;
|
59
60
|
private computeSimplifiedPathFor;
|
@@ -294,6 +294,13 @@ export default class Stroke extends AbstractComponent {
|
|
294
294
|
}
|
295
295
|
return false;
|
296
296
|
}
|
297
|
+
keyPoints() {
|
298
|
+
return this.parts
|
299
|
+
.map((part) => {
|
300
|
+
return part.startPoint;
|
301
|
+
})
|
302
|
+
.flat();
|
303
|
+
}
|
297
304
|
intersectsRect(rect) {
|
298
305
|
// AbstractComponent::intersectsRect can be inexact for strokes with non-zero
|
299
306
|
// stroke radius (has many false negatives). As such, additional checks are
|
@@ -225,7 +225,7 @@ export declare class ImageNode {
|
|
225
225
|
getContent(): AbstractComponent | null;
|
226
226
|
getParent(): ImageNode | null;
|
227
227
|
protected getChildrenIntersectingRegion(region: Rect2, isTooSmallFilter?: TooSmallToRenderCheck): ImageNode[];
|
228
|
-
getChildrenOrSelfIntersectingRegion(region: Rect2): ImageNode[];
|
228
|
+
getChildrenOrSelfIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[];
|
229
229
|
/**
|
230
230
|
* Returns a list of `ImageNode`s with content (and thus no children).
|
231
231
|
* Override getChildrenIntersectingRegion to customize how this method
|
@@ -258,6 +258,7 @@ export declare class RootImageNode extends ImageNode {
|
|
258
258
|
private fullscreenChildren;
|
259
259
|
private dataComponents;
|
260
260
|
protected getChildrenIntersectingRegion(region: Rect2, _isTooSmall?: TooSmallToRenderCheck): ImageNode[];
|
261
|
+
getChildrenOrSelfIntersectingRegion(region: Rect2, _isTooSmall?: TooSmallToRenderCheck): ImageNode[];
|
261
262
|
getLeaves(): ImageNode[];
|
262
263
|
removeChild(child: ImageNode): void;
|
263
264
|
getChildWithContent(target: AbstractComponent): ImageNode | null;
|
@@ -538,11 +538,11 @@ export class ImageNode {
|
|
538
538
|
return !isTooSmallFilter?.(bbox) && bbox.intersects(region);
|
539
539
|
});
|
540
540
|
}
|
541
|
-
getChildrenOrSelfIntersectingRegion(region) {
|
542
|
-
if (this.content) {
|
541
|
+
getChildrenOrSelfIntersectingRegion(region, isTooSmall) {
|
542
|
+
if (this.content && this.bbox.intersects(region) && !isTooSmall?.(this.bbox)) {
|
543
543
|
return [this];
|
544
544
|
}
|
545
|
-
return this.getChildrenIntersectingRegion(region);
|
545
|
+
return this.getChildrenIntersectingRegion(region, isTooSmall);
|
546
546
|
}
|
547
547
|
/**
|
548
548
|
* Returns a list of `ImageNode`s with content (and thus no children).
|
@@ -560,10 +560,17 @@ export class ImageNode {
|
|
560
560
|
workList.push(this);
|
561
561
|
while (workList.length > 0) {
|
562
562
|
const current = workList.pop();
|
563
|
-
|
564
|
-
|
563
|
+
// Split the children into leaves and non-leaves
|
564
|
+
const processed = current.getChildrenOrSelfIntersectingRegion(region, isTooSmall);
|
565
|
+
for (const item of processed) {
|
566
|
+
if (item.content) {
|
567
|
+
result.push(item);
|
568
|
+
}
|
569
|
+
else {
|
570
|
+
// Non-leaves need to be processed
|
571
|
+
workList.push(item);
|
572
|
+
}
|
565
573
|
}
|
566
|
-
workList.push(...current.getChildrenIntersectingRegion(region, isTooSmall));
|
567
574
|
}
|
568
575
|
return result;
|
569
576
|
}
|
@@ -917,6 +924,14 @@ export class RootImageNode extends ImageNode {
|
|
917
924
|
}
|
918
925
|
return result;
|
919
926
|
}
|
927
|
+
getChildrenOrSelfIntersectingRegion(region, _isTooSmall) {
|
928
|
+
const content = this.getContent();
|
929
|
+
// Fullscreen components always intersect/contain
|
930
|
+
if (content && content.getSizingMode() === ComponentSizingMode.FillScreen) {
|
931
|
+
return [this];
|
932
|
+
}
|
933
|
+
return super.getChildrenOrSelfIntersectingRegion(region, _isTooSmall);
|
934
|
+
}
|
920
935
|
getLeaves() {
|
921
936
|
const leaves = super.getLeaves();
|
922
937
|
// Add fullscreen/data components — this method should
|
@@ -31,6 +31,7 @@ import { Color4 } from '@js-draw/math';
|
|
31
31
|
import { toolbarCSSPrefix } from './constants.mjs';
|
32
32
|
import SaveActionWidget from './widgets/SaveActionWidget.mjs';
|
33
33
|
import ExitActionWidget from './widgets/ExitActionWidget.mjs';
|
34
|
+
import { assertIsObject, assertTruthy } from '../util/assertions.mjs';
|
34
35
|
/**
|
35
36
|
* Abstract base class for js-draw editor toolbars.
|
36
37
|
*
|
@@ -205,8 +206,12 @@ class AbstractToolbar {
|
|
205
206
|
*/
|
206
207
|
deserializeState(state) {
|
207
208
|
const data = JSON.parse(state);
|
209
|
+
assertIsObject(data);
|
210
|
+
assertTruthy(data);
|
208
211
|
const rootId = AbstractToolbar.rootToolbarId;
|
209
|
-
|
212
|
+
if (rootId in data && typeof data[rootId] !== 'undefined') {
|
213
|
+
this.deserializeInternal(data[rootId]);
|
214
|
+
}
|
210
215
|
for (const widgetId in data) {
|
211
216
|
if (widgetId === rootId) {
|
212
217
|
continue;
|
@@ -215,7 +220,9 @@ class AbstractToolbar {
|
|
215
220
|
console.warn(`Unable to deserialize widget ${widgetId} — no such widget.`);
|
216
221
|
continue;
|
217
222
|
}
|
218
|
-
|
223
|
+
if (typeof data[widgetId] === 'object' && data[widgetId]) {
|
224
|
+
__classPrivateFieldGet(this, _AbstractToolbar_widgetsById, "f")[widgetId].deserializeFrom(data[widgetId]);
|
225
|
+
}
|
219
226
|
}
|
220
227
|
}
|
221
228
|
/**
|
@@ -2,6 +2,7 @@ import { Color4 } from '@js-draw/math';
|
|
2
2
|
import TextRenderingStyle from '../rendering/TextRenderingStyle';
|
3
3
|
import { PenStyle } from '../tools/Pen';
|
4
4
|
import { EraserMode } from '../tools/Eraser';
|
5
|
+
import { SelectionMode } from '../tools/SelectionTool/types';
|
5
6
|
export type IconElemType = HTMLImageElement | SVGElement;
|
6
7
|
/**
|
7
8
|
* Provides icons that can be used in the toolbar and other locations.
|
@@ -41,7 +42,7 @@ export default class IconProvider {
|
|
41
42
|
makeRedoIcon(): IconElemType;
|
42
43
|
makeDropdownIcon(): IconElemType;
|
43
44
|
makeEraserIcon(eraserSize?: number, mode?: EraserMode): IconElemType;
|
44
|
-
makeSelectionIcon(): IconElemType;
|
45
|
+
makeSelectionIcon(mode?: SelectionMode): IconElemType;
|
45
46
|
makeRotateIcon(): IconElemType;
|
46
47
|
makeHandToolIcon(): IconElemType;
|
47
48
|
makeTouchPanningIcon(): IconElemType;
|
@@ -11,6 +11,7 @@ import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBu
|
|
11
11
|
import { makePolylineBuilder } from '../components/builders/PolylineBuilder.mjs';
|
12
12
|
import { EraserMode } from '../tools/Eraser.mjs';
|
13
13
|
import { createSvgElement, createSvgElements, createSvgPaths } from '../util/createElement.mjs';
|
14
|
+
import { SelectionMode } from '../tools/SelectionTool/types.mjs';
|
14
15
|
const svgNamespace = 'http://www.w3.org/2000/svg';
|
15
16
|
let checkerboardIdCounter = 0;
|
16
17
|
const makeCheckerboardPattern = () => {
|
@@ -164,15 +165,24 @@ class IconProvider {
|
|
164
165
|
});
|
165
166
|
return icon;
|
166
167
|
}
|
167
|
-
makeSelectionIcon() {
|
168
|
+
makeSelectionIcon(mode = SelectionMode.Rectangle) {
|
168
169
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
170
|
+
if (mode === SelectionMode.Rectangle) {
|
171
|
+
icon.innerHTML = `
|
172
|
+
<g>
|
173
|
+
<rect x="10" y="10" width="70" height="70" fill="pink" stroke="black" stroke-dasharray="32 9"/>
|
174
|
+
<rect x="75" y="75" width="10" height="10" fill="white" stroke="black"/>
|
175
|
+
</g>
|
176
|
+
`;
|
177
|
+
}
|
178
|
+
else {
|
179
|
+
icon.innerHTML = `
|
180
|
+
<g>
|
181
|
+
<rect x="10" y="10" width="76" height="76" rx="50" stroke-dasharray="32 9" fill="pink" stroke="black"/>
|
182
|
+
<rect x="71" y="71" width="10" height="10" fill="white" stroke="black"/>
|
183
|
+
</g>
|
184
|
+
`;
|
185
|
+
}
|
176
186
|
icon.setAttribute('viewBox', '0 0 100 100');
|
177
187
|
return icon;
|
178
188
|
}
|
@@ -44,6 +44,8 @@ export interface ToolbarLocalization extends ToolbarUtilsLocalization {
|
|
44
44
|
resetView: string;
|
45
45
|
reformatSelection: string;
|
46
46
|
selectionToolKeyboardShortcuts: string;
|
47
|
+
selectionTool__lassoSelect: string;
|
48
|
+
selectionTool__lassoSelect__help: string;
|
47
49
|
paste: string;
|
48
50
|
documentProperties: string;
|
49
51
|
backgroundColor: string;
|
@@ -34,6 +34,8 @@ export const defaultToolbarLocalization = {
|
|
34
34
|
pickColorFromScreen: 'Pick color from screen',
|
35
35
|
clickToPickColorAnnouncement: 'Click on the screen to pick a color',
|
36
36
|
colorSelectionCanceledAnnouncement: 'Color selection canceled',
|
37
|
+
selectionTool__lassoSelect: 'Freeform selection',
|
38
|
+
selectionTool__lassoSelect__help: 'When enabled, dragging creates a freeform (lasso) selection.',
|
37
39
|
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
|
38
40
|
documentProperties: 'Page',
|
39
41
|
backgroundColor: 'Background color',
|
@@ -16,6 +16,7 @@ import { toolbarCSSPrefix } from '../constants.mjs';
|
|
16
16
|
import DropdownLayoutManager from './layout/DropdownLayoutManager.mjs';
|
17
17
|
import addLongPressOrHoverCssClasses from '../../util/addLongPressOrHoverCssClasses.mjs';
|
18
18
|
import HelpDisplay from '../utils/HelpDisplay.mjs';
|
19
|
+
import { assertIsObject } from '../../util/assertions.mjs';
|
19
20
|
/**
|
20
21
|
* A set of labels that allow toolbar themes to treat buttons differently.
|
21
22
|
*/
|
@@ -446,10 +447,14 @@ class BaseWidget {
|
|
446
447
|
*/
|
447
448
|
deserializeFrom(state) {
|
448
449
|
if (state.subwidgetState) {
|
450
|
+
assertIsObject(state.subwidgetState);
|
449
451
|
// Deserialize all subwidgets.
|
450
452
|
for (const subwidgetId in state.subwidgetState) {
|
451
453
|
if (subwidgetId in this.subWidgets) {
|
452
|
-
|
454
|
+
const serializedSubwidgetState = state.subwidgetState[subwidgetId];
|
455
|
+
if (serializedSubwidgetState) {
|
456
|
+
this.subWidgets[subwidgetId].deserializeFrom(serializedSubwidgetState);
|
457
|
+
}
|
453
458
|
}
|
454
459
|
}
|
455
460
|
}
|
@@ -28,7 +28,7 @@ const makeZoomControl = (localizationTable, editor, helpDisplay) => {
|
|
28
28
|
zoomLevel = Math.round(zoomLevel * 1000) / 1000;
|
29
29
|
}
|
30
30
|
if (zoomLevel !== lastZoom) {
|
31
|
-
zoomLevelDisplay.
|
31
|
+
zoomLevelDisplay.textContent = localizationTable.zoomLevel(zoomLevel);
|
32
32
|
lastZoom = zoomLevel;
|
33
33
|
}
|
34
34
|
};
|
@@ -189,10 +189,10 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
189
189
|
}
|
190
190
|
deserializeFrom(state) {
|
191
191
|
if (state.touchPanning !== undefined) {
|
192
|
-
this.overridePanZoomTool.setModeEnabled(PanZoomMode.OneFingerTouchGestures, state.touchPanning);
|
192
|
+
this.overridePanZoomTool.setModeEnabled(PanZoomMode.OneFingerTouchGestures, !!state.touchPanning);
|
193
193
|
}
|
194
194
|
if (state.rotationLocked !== undefined) {
|
195
|
-
this.overridePanZoomTool.setModeEnabled(PanZoomMode.RotationLocked, state.rotationLocked);
|
195
|
+
this.overridePanZoomTool.setModeEnabled(PanZoomMode.RotationLocked, !!state.rotationLocked);
|
196
196
|
}
|
197
197
|
super.deserializeFrom(state);
|
198
198
|
}
|
@@ -4,14 +4,21 @@ import { KeyPressEvent } from '../../inputEvents';
|
|
4
4
|
import { ToolbarLocalization } from '../localization';
|
5
5
|
import BaseToolWidget from './BaseToolWidget';
|
6
6
|
import HelpDisplay from '../utils/HelpDisplay';
|
7
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
7
8
|
export default class SelectionToolWidget extends BaseToolWidget {
|
8
9
|
private tool;
|
9
10
|
private updateFormatMenu;
|
11
|
+
private hasSelectionValue;
|
10
12
|
constructor(editor: Editor, tool: SelectionTool, localization?: ToolbarLocalization);
|
11
13
|
private resizeImageToSelection;
|
12
14
|
protected onKeyPress(event: KeyPressEvent): boolean;
|
13
15
|
protected getTitle(): string;
|
14
16
|
protected createIcon(): Element;
|
15
17
|
protected getHelpText(): string;
|
18
|
+
protected createSelectionActions(helpDisplay?: HelpDisplay): {
|
19
|
+
container: HTMLDivElement;
|
20
|
+
};
|
16
21
|
protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
|
22
|
+
serializeState(): SavedToolbuttonState;
|
23
|
+
deserializeFrom(state: SavedToolbuttonState): void;
|
17
24
|
}
|
@@ -1,13 +1,16 @@
|
|
1
1
|
import { Color4 } from '@js-draw/math';
|
2
2
|
import { isRestylableComponent } from '../../components/RestylableComponent.mjs';
|
3
3
|
import uniteCommands from '../../commands/uniteCommands.mjs';
|
4
|
+
import { SelectionMode } from '../../tools/SelectionTool/SelectionTool.mjs';
|
4
5
|
import { EditorEventType } from '../../types.mjs';
|
5
6
|
import makeColorInput from './components/makeColorInput.mjs';
|
6
|
-
import ActionButtonWidget from './ActionButtonWidget.mjs';
|
7
7
|
import BaseToolWidget from './BaseToolWidget.mjs';
|
8
8
|
import { resizeImageToSelectionKeyboardShortcut } from './keybindings.mjs';
|
9
9
|
import makeSeparator from './components/makeSeparator.mjs';
|
10
10
|
import { toolbarCSSPrefix } from '../constants.mjs';
|
11
|
+
import BaseWidget from './BaseWidget.mjs';
|
12
|
+
import makeButtonGrid from './components/makeButtonGrid.mjs';
|
13
|
+
import { MutableReactiveValue } from '../../util/ReactiveValue.mjs';
|
11
14
|
const makeFormatMenu = (editor, selectionTool, localizationTable) => {
|
12
15
|
const container = document.createElement('div');
|
13
16
|
container.classList.add('selection-format-menu', `${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}indentedList`);
|
@@ -63,48 +66,63 @@ const makeFormatMenu = (editor, selectionTool, localizationTable) => {
|
|
63
66
|
},
|
64
67
|
};
|
65
68
|
};
|
69
|
+
class LassoSelectToggle extends BaseWidget {
|
70
|
+
constructor(editor, tool, localizationTable) {
|
71
|
+
super(editor, 'selection-mode-toggle', localizationTable);
|
72
|
+
this.tool = tool;
|
73
|
+
editor.notifier.on(EditorEventType.ToolUpdated, (toolEvt) => {
|
74
|
+
if (toolEvt.kind === EditorEventType.ToolUpdated && toolEvt.tool === tool) {
|
75
|
+
this.setSelected(tool.modeValue.get() === SelectionMode.Lasso);
|
76
|
+
}
|
77
|
+
});
|
78
|
+
this.setSelected(false);
|
79
|
+
}
|
80
|
+
shouldAutoDisableInReadOnlyEditor() {
|
81
|
+
return false;
|
82
|
+
}
|
83
|
+
setModeFlag(enabled) {
|
84
|
+
this.tool.modeValue.set(enabled ? SelectionMode.Lasso : SelectionMode.Rectangle);
|
85
|
+
}
|
86
|
+
handleClick() {
|
87
|
+
this.setModeFlag(!this.isSelected());
|
88
|
+
}
|
89
|
+
getTitle() {
|
90
|
+
return this.localizationTable.selectionTool__lassoSelect;
|
91
|
+
}
|
92
|
+
createIcon() {
|
93
|
+
return this.editor.icons.makeSelectionIcon(SelectionMode.Lasso);
|
94
|
+
}
|
95
|
+
fillDropdown(_dropdown) {
|
96
|
+
return false;
|
97
|
+
}
|
98
|
+
getHelpText() {
|
99
|
+
return this.localizationTable.selectionTool__lassoSelect__help;
|
100
|
+
}
|
101
|
+
}
|
66
102
|
export default class SelectionToolWidget extends BaseToolWidget {
|
67
103
|
constructor(editor, tool, localization) {
|
68
104
|
super(editor, tool, 'selection-tool-widget', localization);
|
69
105
|
this.tool = tool;
|
70
106
|
this.updateFormatMenu = () => { };
|
71
|
-
|
72
|
-
|
73
|
-
}, localization);
|
74
|
-
resizeButton.setHelpText(this.localizationTable.selectionDropdown__resizeToHelpText);
|
75
|
-
const deleteButton = new ActionButtonWidget(editor, 'delete-btn', () => editor.icons.makeDeleteSelectionIcon(), this.localizationTable.deleteSelection, () => {
|
76
|
-
const selection = this.tool.getSelection();
|
77
|
-
this.editor.dispatch(selection.deleteSelectedObjects());
|
78
|
-
this.tool.clearSelection();
|
79
|
-
}, localization);
|
80
|
-
deleteButton.setHelpText(this.localizationTable.selectionDropdown__deleteHelpText);
|
81
|
-
const duplicateButton = new ActionButtonWidget(editor, 'duplicate-btn', () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, async () => {
|
107
|
+
this.addSubWidget(new LassoSelectToggle(editor, tool, this.localizationTable));
|
108
|
+
const hasSelection = () => {
|
82
109
|
const selection = this.tool.getSelection();
|
83
|
-
|
84
|
-
this.setDropdownVisible(false);
|
85
|
-
}, localization);
|
86
|
-
duplicateButton.setHelpText(this.localizationTable.selectionDropdown__duplicateHelpText);
|
87
|
-
this.addSubWidget(resizeButton);
|
88
|
-
this.addSubWidget(deleteButton);
|
89
|
-
this.addSubWidget(duplicateButton);
|
90
|
-
const updateDisabled = (disabled) => {
|
91
|
-
resizeButton.setDisabled(disabled);
|
92
|
-
deleteButton.setDisabled(disabled);
|
93
|
-
duplicateButton.setDisabled(disabled);
|
110
|
+
return !!selection && selection.getSelectedItemCount() > 0;
|
94
111
|
};
|
95
|
-
|
112
|
+
this.hasSelectionValue = MutableReactiveValue.fromInitialValue(hasSelection());
|
96
113
|
// Enable/disable actions based on whether items are selected
|
97
114
|
this.editor.notifier.on(EditorEventType.ToolUpdated, (toolEvt) => {
|
98
115
|
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
99
116
|
throw new Error('Invalid event type!');
|
100
117
|
}
|
101
118
|
if (toolEvt.tool === this.tool) {
|
102
|
-
|
103
|
-
const hasSelection = selection && selection.getSelectedItemCount() > 0;
|
104
|
-
updateDisabled(!hasSelection);
|
119
|
+
this.hasSelectionValue.set(hasSelection());
|
105
120
|
this.updateFormatMenu();
|
106
121
|
}
|
107
122
|
});
|
123
|
+
tool.modeValue.onUpdate(() => {
|
124
|
+
this.updateIcon();
|
125
|
+
});
|
108
126
|
}
|
109
127
|
resizeImageToSelection() {
|
110
128
|
const selection = this.tool.getSelection();
|
@@ -130,16 +148,66 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
130
148
|
return this.localizationTable.select;
|
131
149
|
}
|
132
150
|
createIcon() {
|
133
|
-
return this.editor.icons.makeSelectionIcon();
|
151
|
+
return this.editor.icons.makeSelectionIcon(this.tool.modeValue.get());
|
134
152
|
}
|
135
153
|
getHelpText() {
|
136
154
|
return this.localizationTable.selectionDropdown__baseHelpText;
|
137
155
|
}
|
156
|
+
createSelectionActions(helpDisplay) {
|
157
|
+
const icons = this.editor.icons;
|
158
|
+
const grid = makeButtonGrid([
|
159
|
+
{
|
160
|
+
icon: () => icons.makeDeleteSelectionIcon(),
|
161
|
+
label: this.localizationTable.deleteSelection,
|
162
|
+
onCreated: (button) => {
|
163
|
+
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__deleteHelpText);
|
164
|
+
},
|
165
|
+
onClick: () => {
|
166
|
+
const selection = this.tool.getSelection();
|
167
|
+
this.editor.dispatch(selection.deleteSelectedObjects());
|
168
|
+
this.tool.clearSelection();
|
169
|
+
},
|
170
|
+
enabled: this.hasSelectionValue,
|
171
|
+
},
|
172
|
+
{
|
173
|
+
icon: () => icons.makeDuplicateSelectionIcon(),
|
174
|
+
label: this.localizationTable.duplicateSelection,
|
175
|
+
onCreated: (button) => {
|
176
|
+
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__duplicateHelpText);
|
177
|
+
},
|
178
|
+
onClick: async () => {
|
179
|
+
const selection = this.tool.getSelection();
|
180
|
+
const command = await selection?.duplicateSelectedObjects();
|
181
|
+
if (command) {
|
182
|
+
this.editor.dispatch(command);
|
183
|
+
}
|
184
|
+
},
|
185
|
+
enabled: this.hasSelectionValue,
|
186
|
+
},
|
187
|
+
{
|
188
|
+
icon: () => icons.makeResizeImageToSelectionIcon(),
|
189
|
+
label: this.localizationTable.resizeImageToSelection,
|
190
|
+
onCreated: (button) => {
|
191
|
+
helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__resizeToHelpText);
|
192
|
+
},
|
193
|
+
onClick: () => {
|
194
|
+
this.resizeImageToSelection();
|
195
|
+
},
|
196
|
+
enabled: this.hasSelectionValue,
|
197
|
+
},
|
198
|
+
], 3);
|
199
|
+
return { container: grid.container };
|
200
|
+
}
|
138
201
|
fillDropdown(dropdown, helpDisplay) {
|
139
202
|
super.fillDropdown(dropdown, helpDisplay);
|
140
203
|
const controlsContainer = document.createElement('div');
|
141
204
|
controlsContainer.classList.add(`${toolbarCSSPrefix}nonbutton-controls-main-list`);
|
142
205
|
dropdown.appendChild(controlsContainer);
|
206
|
+
// Actions (duplicate, delete, etc.)
|
207
|
+
makeSeparator().addTo(controlsContainer);
|
208
|
+
const actions = this.createSelectionActions(helpDisplay);
|
209
|
+
controlsContainer.appendChild(actions.container);
|
210
|
+
// Formatting
|
143
211
|
makeSeparator(this.localizationTable.reformatSelection).addTo(controlsContainer);
|
144
212
|
const formatMenu = makeFormatMenu(this.editor, this.tool, this.localizationTable);
|
145
213
|
formatMenu.addTo(controlsContainer);
|
@@ -150,4 +218,17 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
150
218
|
formatMenu.update();
|
151
219
|
return true;
|
152
220
|
}
|
221
|
+
serializeState() {
|
222
|
+
return {
|
223
|
+
...super.serializeState(),
|
224
|
+
selectionMode: this.tool.modeValue.get(),
|
225
|
+
};
|
226
|
+
}
|
227
|
+
deserializeFrom(state) {
|
228
|
+
super.deserializeFrom(state);
|
229
|
+
const isValidSelectionMode = Object.values(SelectionMode).includes(state.selectionMode);
|
230
|
+
if (isValidSelectionMode) {
|
231
|
+
this.tool.modeValue.set(state.selectionMode);
|
232
|
+
}
|
233
|
+
}
|
153
234
|
}
|
@@ -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,35 @@
|
|
1
|
+
import addLongPressOrHoverCssClasses from '../../../util/addLongPressOrHoverCssClasses.mjs';
|
2
|
+
/**
|
3
|
+
* Creates HTML `button` elements from `buttonSpecs` and displays them in a
|
4
|
+
* grid with `columnCount` columns.
|
5
|
+
*/
|
6
|
+
const makeButtonGrid = (buttonSpecs, columnCount) => {
|
7
|
+
const container = document.createElement('div');
|
8
|
+
container.classList.add('toolbar-button-grid');
|
9
|
+
container.style.setProperty('--column-count', `${columnCount}`);
|
10
|
+
const makeButton = (buttonSpec) => {
|
11
|
+
const buttonElement = document.createElement('button');
|
12
|
+
buttonElement.classList.add('button');
|
13
|
+
const iconElement = buttonSpec.icon();
|
14
|
+
iconElement.classList.add('icon');
|
15
|
+
const labelElement = document.createElement('label');
|
16
|
+
labelElement.textContent = buttonSpec.label;
|
17
|
+
labelElement.classList.add('button-label-text');
|
18
|
+
buttonElement.onclick = buttonSpec.onClick;
|
19
|
+
if (buttonSpec.enabled) {
|
20
|
+
buttonSpec.enabled.onUpdateAndNow((enabled) => {
|
21
|
+
buttonElement.disabled = !enabled;
|
22
|
+
});
|
23
|
+
}
|
24
|
+
buttonElement.replaceChildren(iconElement, labelElement);
|
25
|
+
container.appendChild(buttonElement);
|
26
|
+
addLongPressOrHoverCssClasses(buttonElement);
|
27
|
+
buttonSpec.onCreated?.(buttonElement);
|
28
|
+
return buttonElement;
|
29
|
+
};
|
30
|
+
buttonSpecs.map(makeButton);
|
31
|
+
return {
|
32
|
+
container,
|
33
|
+
};
|
34
|
+
};
|
35
|
+
export 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
|
}
|