js-draw 1.10.0 → 1.11.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 +6 -2
- package/dist/bundle.js +3 -3
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +7 -0
- package/dist/cjs/Editor.js +18 -4
- package/dist/cjs/commands/invertCommand.js +5 -0
- package/dist/cjs/components/AbstractComponent.d.ts +8 -0
- package/dist/cjs/components/AbstractComponent.js +28 -8
- package/dist/cjs/components/BackgroundComponent.d.ts +1 -1
- package/dist/cjs/components/ImageComponent.d.ts +1 -1
- package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/cjs/components/Stroke.d.ts +1 -1
- package/dist/cjs/components/builders/types.d.ts +11 -0
- package/dist/cjs/rendering/Display.js +3 -1
- package/dist/cjs/rendering/renderers/DummyRenderer.d.ts +1 -0
- package/dist/cjs/rendering/renderers/DummyRenderer.js +3 -0
- package/dist/cjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/cjs/toolbar/AbstractToolbar.js +46 -30
- package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -1
- package/dist/cjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/cjs/toolbar/widgets/ExitActionWidget.js +32 -0
- package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/cjs/toolbar/widgets/HandToolWidget.js +24 -13
- package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -1
- package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
- package/dist/cjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/cjs/tools/FindTool.js +1 -1
- package/dist/cjs/tools/Pen.js +13 -2
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/cjs/tools/SelectionTool/Selection.js +56 -12
- package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +35 -3
- package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.js +0 -1
- package/dist/cjs/tools/keybindings.d.ts +1 -0
- package/dist/cjs/tools/keybindings.js +3 -1
- package/dist/cjs/tools/localization.d.ts +2 -0
- package/dist/cjs/tools/localization.js +2 -0
- package/dist/cjs/util/listenForKeyboardEventsFrom.d.ts +5 -0
- package/dist/cjs/util/listenForKeyboardEventsFrom.js +5 -1
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +7 -0
- package/dist/mjs/Editor.mjs +18 -4
- package/dist/mjs/commands/invertCommand.mjs +5 -0
- package/dist/mjs/components/AbstractComponent.d.ts +8 -0
- package/dist/mjs/components/AbstractComponent.mjs +28 -8
- package/dist/mjs/components/BackgroundComponent.d.ts +1 -1
- package/dist/mjs/components/ImageComponent.d.ts +1 -1
- package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/mjs/components/Stroke.d.ts +1 -1
- package/dist/mjs/components/builders/types.d.ts +11 -0
- package/dist/mjs/rendering/Display.mjs +3 -1
- package/dist/mjs/rendering/renderers/DummyRenderer.d.ts +1 -0
- package/dist/mjs/rendering/renderers/DummyRenderer.mjs +3 -0
- package/dist/mjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/mjs/toolbar/AbstractToolbar.mjs +46 -30
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -1
- package/dist/mjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/mjs/toolbar/widgets/ExitActionWidget.mjs +27 -0
- package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +24 -13
- package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -1
- package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
- package/dist/mjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/mjs/tools/FindTool.mjs +1 -1
- package/dist/mjs/tools/Pen.mjs +13 -2
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/mjs/tools/SelectionTool/Selection.mjs +56 -12
- package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +36 -4
- package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.mjs +0 -1
- package/dist/mjs/tools/keybindings.d.ts +1 -0
- package/dist/mjs/tools/keybindings.mjs +2 -0
- package/dist/mjs/tools/localization.d.ts +2 -0
- package/dist/mjs/tools/localization.mjs +2 -0
- package/dist/mjs/util/listenForKeyboardEventsFrom.d.ts +5 -0
- package/dist/mjs/util/listenForKeyboardEventsFrom.mjs +5 -1
- package/dist/mjs/version.mjs +1 -1
- package/package.json +5 -5
- package/src/toolbar/AbstractToolbar.scss +3 -2
- package/src/toolbar/widgets/components/makeColorInput.scss +8 -0
@@ -35,6 +35,7 @@ const DocumentPropertiesWidget_1 = __importDefault(require("./widgets/DocumentPr
|
|
35
35
|
const math_1 = require("@js-draw/math");
|
36
36
|
const constants_1 = require("./constants");
|
37
37
|
const SaveActionWidget_1 = __importDefault(require("./widgets/SaveActionWidget"));
|
38
|
+
const ExitActionWidget_1 = __importDefault(require("./widgets/ExitActionWidget"));
|
38
39
|
class AbstractToolbar {
|
39
40
|
/** @internal */
|
40
41
|
constructor(editor, localizationTable = localization_1.defaultToolbarLocalization) {
|
@@ -297,14 +298,13 @@ class AbstractToolbar {
|
|
297
298
|
*/
|
298
299
|
addSaveButton(saveCallback, labelOverride = {}) {
|
299
300
|
const widget = new SaveActionWidget_1.default(this.editor, this.localizationTable, saveCallback, labelOverride);
|
300
|
-
widget.setTags([BaseWidget_1.ToolbarWidgetTag.Save]);
|
301
301
|
this.addWidget(widget);
|
302
302
|
return widget;
|
303
303
|
}
|
304
304
|
/**
|
305
305
|
* Adds an "Exit" button that, when clicked, calls `exitCallback`.
|
306
306
|
*
|
307
|
-
* **Note**: This is roughly equivalent to
|
307
|
+
* **Note**: This is *roughly* equivalent to
|
308
308
|
* ```ts
|
309
309
|
* toolbar.addTaggedActionButton([ ToolbarWidgetTag.Exit ], {
|
310
310
|
* label: this.editor.localization.exit,
|
@@ -321,15 +321,9 @@ class AbstractToolbar {
|
|
321
321
|
* @final
|
322
322
|
*/
|
323
323
|
addExitButton(exitCallback, labelOverride = {}) {
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
...labelOverride,
|
328
|
-
}, () => {
|
329
|
-
exitCallback();
|
330
|
-
}, {
|
331
|
-
autoDisableInReadOnlyEditors: false,
|
332
|
-
});
|
324
|
+
const widget = new ExitActionWidget_1.default(this.editor, this.localizationTable, exitCallback, labelOverride);
|
325
|
+
this.addWidget(widget);
|
326
|
+
return widget;
|
333
327
|
}
|
334
328
|
/**
|
335
329
|
* Adds undo and redo buttons that trigger the editor's built-in undo and redo
|
@@ -377,27 +371,49 @@ class AbstractToolbar {
|
|
377
371
|
});
|
378
372
|
}
|
379
373
|
/**
|
380
|
-
* Adds
|
374
|
+
* Adds widgets for pen/eraser/selection/text/pan-zoom primary tools.
|
375
|
+
*
|
376
|
+
* If `filter` returns `false` for a tool, no widget is added for that tool.
|
377
|
+
* See {@link addDefaultToolWidgets}
|
381
378
|
*/
|
382
|
-
|
383
|
-
const
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
379
|
+
addWidgetsForPrimaryTools(filter) {
|
380
|
+
for (const tool of this.editor.toolController.getPrimaryTools()) {
|
381
|
+
if (filter && !filter?.(tool)) {
|
382
|
+
continue;
|
383
|
+
}
|
384
|
+
if (tool instanceof Pen_1.default) {
|
385
|
+
const widget = new PenToolWidget_1.default(this.editor, tool, this.localizationTable);
|
386
|
+
this.addWidget(widget);
|
387
|
+
}
|
388
|
+
else if (tool instanceof Eraser_1.default) {
|
389
|
+
this.addWidget(new EraserToolWidget_1.default(this.editor, tool, this.localizationTable));
|
390
|
+
}
|
391
|
+
else if (tool instanceof SelectionTool_1.default) {
|
392
|
+
this.addWidget(new SelectionToolWidget_1.default(this.editor, tool, this.localizationTable));
|
393
|
+
}
|
394
|
+
else if (tool instanceof TextTool_1.default) {
|
395
|
+
this.addWidget(new TextToolWidget_1.default(this.editor, tool, this.localizationTable));
|
396
|
+
}
|
397
|
+
else if (tool instanceof PanZoom_1.default) {
|
398
|
+
this.addWidget(new HandToolWidget_1.default(this.editor, tool, this.localizationTable));
|
399
|
+
}
|
400
400
|
}
|
401
|
+
}
|
402
|
+
/**
|
403
|
+
* Adds toolbar widgets based on the enabled tools, and additional tool-like
|
404
|
+
* buttons (e.g. {@link DocumentPropertiesWidget} and {@link InsertImageWidget}).
|
405
|
+
*/
|
406
|
+
addDefaultToolWidgets() {
|
407
|
+
this.addWidgetsForPrimaryTools();
|
408
|
+
this.addDefaultEditorControlWidgets();
|
409
|
+
}
|
410
|
+
/**
|
411
|
+
* Adds widgets that don't correspond to tools, but do allow the user to control
|
412
|
+
* the editor in some way.
|
413
|
+
*
|
414
|
+
* By default, this includes {@link DocumentPropertiesWidget} and {@link InsertImageWidget}.
|
415
|
+
*/
|
416
|
+
addDefaultEditorControlWidgets() {
|
401
417
|
this.addWidget(new DocumentPropertiesWidget_1.default(this.editor, this.localizationTable));
|
402
418
|
this.addWidget(new InsertImageWidget_1.default(this.editor, this.localizationTable));
|
403
419
|
}
|
@@ -53,7 +53,7 @@ class BaseWidget {
|
|
53
53
|
this.layoutManager = defaultLayoutManager;
|
54
54
|
this.icon = null;
|
55
55
|
this.container = document.createElement('div');
|
56
|
-
this.container.classList.add(`${constants_1.toolbarCSSPrefix}toolContainer`, `${constants_1.toolbarCSSPrefix}toolButtonContainer`);
|
56
|
+
this.container.classList.add(`${constants_1.toolbarCSSPrefix}toolContainer`, `${constants_1.toolbarCSSPrefix}toolButtonContainer`, `${constants_1.toolbarCSSPrefix}internalWidgetId--${id.replace(/[^a-zA-Z0-9_]/g, '-')}`);
|
57
57
|
this.dropdownContent = document.createElement('div');
|
58
58
|
__classPrivateFieldSet(this, _BaseWidget_hasDropdown, false, "f");
|
59
59
|
this.button = document.createElement('div');
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { KeyPressEvent } from '../../inputEvents';
|
2
|
+
import Editor from '../../Editor';
|
3
|
+
import { ToolbarLocalization } from '../localization';
|
4
|
+
import ActionButtonWidget from './ActionButtonWidget';
|
5
|
+
import { ActionButtonIcon } from '../types';
|
6
|
+
declare class ExitActionWidget extends ActionButtonWidget {
|
7
|
+
constructor(editor: Editor, localization: ToolbarLocalization, saveCallback: () => void, labelOverride?: Partial<ActionButtonIcon>);
|
8
|
+
protected shouldAutoDisableInReadOnlyEditor(): boolean;
|
9
|
+
protected onKeyPress(event: KeyPressEvent): boolean;
|
10
|
+
mustBeInToplevelMenu(): boolean;
|
11
|
+
}
|
12
|
+
export default ExitActionWidget;
|
@@ -0,0 +1,32 @@
|
|
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 ActionButtonWidget_1 = __importDefault(require("./ActionButtonWidget"));
|
7
|
+
const BaseWidget_1 = require("./BaseWidget");
|
8
|
+
const keybindings_1 = require("./keybindings");
|
9
|
+
class ExitActionWidget extends ActionButtonWidget_1.default {
|
10
|
+
constructor(editor, localization, saveCallback, labelOverride = {}) {
|
11
|
+
super(editor, 'exit-button',
|
12
|
+
// Creates an icon
|
13
|
+
() => {
|
14
|
+
return labelOverride.icon ?? editor.icons.makeCloseIcon();
|
15
|
+
}, labelOverride.label ?? localization.exit, saveCallback);
|
16
|
+
this.setTags([BaseWidget_1.ToolbarWidgetTag.Exit]);
|
17
|
+
}
|
18
|
+
shouldAutoDisableInReadOnlyEditor() {
|
19
|
+
return false;
|
20
|
+
}
|
21
|
+
onKeyPress(event) {
|
22
|
+
if (this.editor.shortcuts.matchesShortcut(keybindings_1.exitKeyboardShortcut, event)) {
|
23
|
+
this.clickAction();
|
24
|
+
return true;
|
25
|
+
}
|
26
|
+
return super.onKeyPress(event);
|
27
|
+
}
|
28
|
+
mustBeInToplevelMenu() {
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
exports.default = ExitActionWidget;
|
@@ -4,11 +4,12 @@ import { ToolbarLocalization } from '../localization';
|
|
4
4
|
import BaseToolWidget from './BaseToolWidget';
|
5
5
|
import { SavedToolbuttonState } from './BaseWidget';
|
6
6
|
export default class HandToolWidget extends BaseToolWidget {
|
7
|
-
protected overridePanZoomTool: PanZoom;
|
8
7
|
private allowTogglingBaseTool;
|
9
|
-
|
10
|
-
|
8
|
+
protected overridePanZoomTool: PanZoom;
|
9
|
+
constructor(editor: Editor, tool: PanZoom, localizationTable: ToolbarLocalization);
|
11
10
|
private static getPrimaryHandTool;
|
11
|
+
private static getOverrideHandTool;
|
12
|
+
protected shouldAutoDisableInReadOnlyEditor(): boolean;
|
12
13
|
protected getTitle(): string;
|
13
14
|
protected createIcon(): Element;
|
14
15
|
protected handleClick(): void;
|
@@ -124,34 +124,45 @@ class HandModeWidget extends BaseWidget_1.default {
|
|
124
124
|
}
|
125
125
|
class HandToolWidget extends BaseToolWidget_1.default {
|
126
126
|
constructor(editor,
|
127
|
-
//
|
128
|
-
//
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
127
|
+
// Can either be the primary pan/zoom tool (in the primary tools list) or
|
128
|
+
// the override pan/zoom tool.
|
129
|
+
// If the override pan/zoom tool, the primary will be gotten from the editor's
|
130
|
+
// tool controller.
|
131
|
+
// If the primary, the override will be gotten from the editor's tool controller.
|
132
|
+
tool, localizationTable) {
|
133
|
+
const isGivenToolPrimary = editor.toolController.getPrimaryTools().includes(tool);
|
134
|
+
const primaryTool = (isGivenToolPrimary ? tool : HandToolWidget.getPrimaryHandTool(editor.toolController))
|
135
|
+
?? tool;
|
136
|
+
super(editor, primaryTool, 'hand-tool-widget', localizationTable);
|
137
|
+
this.overridePanZoomTool =
|
138
|
+
(isGivenToolPrimary ? HandToolWidget.getOverrideHandTool(editor.toolController) : tool)
|
139
|
+
?? tool;
|
134
140
|
// Only allow toggling a hand tool if we're using the primary hand tool and not the override
|
135
141
|
// hand tool for this button.
|
136
|
-
this.allowTogglingBaseTool =
|
142
|
+
this.allowTogglingBaseTool = primaryTool !== null;
|
137
143
|
// Allow showing/hiding the dropdown, even if `overridePanZoomTool` isn't enabled.
|
138
144
|
if (!this.allowTogglingBaseTool) {
|
139
145
|
this.container.classList.add('dropdownShowable');
|
140
146
|
}
|
141
147
|
// Controls for the overriding hand tool.
|
142
|
-
const touchPanningWidget = new HandModeWidget(editor, overridePanZoomTool, PanZoom_1.PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning, localizationTable);
|
143
|
-
const rotationLockWidget = new HandModeWidget(editor, overridePanZoomTool, PanZoom_1.PanZoomMode.RotationLocked, () => this.editor.icons.makeRotationLockIcon(), localizationTable.lockRotation, localizationTable);
|
148
|
+
const touchPanningWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoom_1.PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning, localizationTable);
|
149
|
+
const rotationLockWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoom_1.PanZoomMode.RotationLocked, () => this.editor.icons.makeRotationLockIcon(), localizationTable.lockRotation, localizationTable);
|
144
150
|
this.addSubWidget(touchPanningWidget);
|
145
151
|
this.addSubWidget(rotationLockWidget);
|
146
152
|
}
|
147
|
-
shouldAutoDisableInReadOnlyEditor() {
|
148
|
-
return false;
|
149
|
-
}
|
150
153
|
static getPrimaryHandTool(toolController) {
|
151
154
|
const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom_1.default);
|
152
155
|
const primaryPanZoomTool = primaryPanZoomToolList[0];
|
153
156
|
return primaryPanZoomTool;
|
154
157
|
}
|
158
|
+
static getOverrideHandTool(toolController) {
|
159
|
+
const panZoomToolList = toolController.getMatchingTools(PanZoom_1.default);
|
160
|
+
const panZoomTool = panZoomToolList[0];
|
161
|
+
return panZoomTool;
|
162
|
+
}
|
163
|
+
shouldAutoDisableInReadOnlyEditor() {
|
164
|
+
return false;
|
165
|
+
}
|
155
166
|
getTitle() {
|
156
167
|
return this.localizationTable.handTool;
|
157
168
|
}
|
@@ -174,7 +174,7 @@ class InsertImageWidget extends BaseWidget_1.default {
|
|
174
174
|
this.image?.reset();
|
175
175
|
};
|
176
176
|
this.statusView.replaceChildren(sizeText);
|
177
|
-
const largeImageThreshold = 0.
|
177
|
+
const largeImageThreshold = 0.12; // MiB
|
178
178
|
if (sizeInMiB > largeImageThreshold) {
|
179
179
|
this.statusView.appendChild(decreaseSizeButton);
|
180
180
|
}
|
@@ -1,3 +1,4 @@
|
|
1
1
|
export declare const resizeImageToSelectionKeyboardShortcut = "jsdraw.toolbar.SelectionTool.resizeImageToSelection";
|
2
2
|
export declare const selectStrokeTypeKeyboardShortcutIds: string[];
|
3
3
|
export declare const saveKeyboardShortcut = "jsdraw.toolbar.SaveActionWidget.save";
|
4
|
+
export declare const exitKeyboardShortcut = "jsdraw.toolbar.ExitActionWidget.exit";
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.saveKeyboardShortcut = exports.selectStrokeTypeKeyboardShortcutIds = exports.resizeImageToSelectionKeyboardShortcut = void 0;
|
6
|
+
exports.exitKeyboardShortcut = exports.saveKeyboardShortcut = exports.selectStrokeTypeKeyboardShortcutIds = exports.resizeImageToSelectionKeyboardShortcut = void 0;
|
7
7
|
const KeyboardShortcutManager_1 = __importDefault(require("../../shortcuts/KeyboardShortcutManager"));
|
8
8
|
// Selection
|
9
9
|
exports.resizeImageToSelectionKeyboardShortcut = 'jsdraw.toolbar.SelectionTool.resizeImageToSelection';
|
@@ -17,3 +17,6 @@ for (let i = 0; i < exports.selectStrokeTypeKeyboardShortcutIds.length; i++) {
|
|
17
17
|
// Save
|
18
18
|
exports.saveKeyboardShortcut = 'jsdraw.toolbar.SaveActionWidget.save';
|
19
19
|
KeyboardShortcutManager_1.default.registerDefaultKeyboardShortcut(exports.saveKeyboardShortcut, ['ctrlOrMeta+KeyS'], 'Save');
|
20
|
+
// Exit
|
21
|
+
exports.exitKeyboardShortcut = 'jsdraw.toolbar.ExitActionWidget.exit';
|
22
|
+
KeyboardShortcutManager_1.default.registerDefaultKeyboardShortcut(exports.exitKeyboardShortcut, ['Alt+KeyQ'], 'Exit');
|
@@ -39,7 +39,7 @@ class FindTool extends BaseTool_1.default {
|
|
39
39
|
}
|
40
40
|
if (matchIdx < matches.length) {
|
41
41
|
const undoable = false;
|
42
|
-
this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true), undoable);
|
42
|
+
void this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true), undoable);
|
43
43
|
this.editor.announceForAccessibility(this.editor.localization.focusedFoundText(matchIdx + 1, matches.length));
|
44
44
|
}
|
45
45
|
}
|
package/dist/cjs/tools/Pen.js
CHANGED
@@ -102,8 +102,8 @@ class Pen extends BaseTool_1.default {
|
|
102
102
|
this.currentDeviceType = current.device;
|
103
103
|
if (this.shapeAutocompletionEnabled) {
|
104
104
|
const stationaryDetectionConfig = {
|
105
|
-
maxSpeed: 5,
|
106
|
-
maxRadius:
|
105
|
+
maxSpeed: 8.5,
|
106
|
+
maxRadius: 11,
|
107
107
|
minTimeSeconds: 0.5, // s
|
108
108
|
};
|
109
109
|
this.stationaryDetector = new StationaryPenDetector_1.default(current, stationaryDetectionConfig, pointer => this.autocorrectShape(pointer));
|
@@ -146,6 +146,7 @@ class Pen extends BaseTool_1.default {
|
|
146
146
|
if (this.autocorrectedShape) {
|
147
147
|
this.removedAutocorrectedShapeTime = performance.now();
|
148
148
|
this.autocorrectedShape = null;
|
149
|
+
this.editor.announceForAccessibility(this.editor.localization.autocorrectionCanceled);
|
149
150
|
}
|
150
151
|
}
|
151
152
|
}
|
@@ -192,6 +193,13 @@ class Pen extends BaseTool_1.default {
|
|
192
193
|
if (!this.builder || !correctedShape) {
|
193
194
|
return;
|
194
195
|
}
|
196
|
+
// Don't complete to empty shapes.
|
197
|
+
const bboxArea = correctedShape.getBBox().area;
|
198
|
+
if (bboxArea === 0 || !isFinite(bboxArea)) {
|
199
|
+
return;
|
200
|
+
}
|
201
|
+
const shapeDescription = correctedShape.description(this.editor.localization);
|
202
|
+
this.editor.announceForAccessibility(this.editor.localization.autocorrectedTo(shapeDescription));
|
195
203
|
this.autocorrectedShape = correctedShape;
|
196
204
|
this.lastAutocorrectedShape = correctedShape;
|
197
205
|
this.previewStroke();
|
@@ -206,6 +214,9 @@ class Pen extends BaseTool_1.default {
|
|
206
214
|
const stroke = this.autocorrectedShape ?? this.builder.build();
|
207
215
|
this.previewStroke();
|
208
216
|
if (stroke.getBBox().area > 0) {
|
217
|
+
if (stroke === this.autocorrectedShape) {
|
218
|
+
this.editor.announceForAccessibility(this.editor.localization.autocorrectedTo(stroke.description(this.editor.localization)));
|
219
|
+
}
|
209
220
|
const canFlatten = true;
|
210
221
|
const action = EditorImage_1.default.addElement(stroke, canFlatten);
|
211
222
|
this.editor.dispatch(action);
|
@@ -2,6 +2,7 @@
|
|
2
2
|
* @internal
|
3
3
|
* @packageDocumentation
|
4
4
|
*/
|
5
|
+
import SerializableCommand from '../../commands/SerializableCommand';
|
5
6
|
import Editor from '../../Editor';
|
6
7
|
import { Mat33, Rect2, Point2 } from '@js-draw/math';
|
7
8
|
import Pointer from '../../Pointer';
|
@@ -36,7 +37,10 @@ export default class Selection {
|
|
36
37
|
getScreenRegion(): Rect2;
|
37
38
|
get screenRegionRotation(): number;
|
38
39
|
setTransform(transform: Mat33, preview?: boolean): void;
|
40
|
+
private getDeltaZIndexToMoveSelectionToTop;
|
39
41
|
finalizeTransform(): void | Promise<void>;
|
42
|
+
/** Sends all selected elements to the bottom of the visible image. */
|
43
|
+
sendToBack(): SerializableCommand | null;
|
40
44
|
private static ApplyTransformationCommand;
|
41
45
|
private previewTransformCmds;
|
42
46
|
resolveToObjects(): boolean;
|
@@ -41,6 +41,7 @@ const Duplicate_1 = __importDefault(require("../../commands/Duplicate"));
|
|
41
41
|
const TransformMode_1 = require("./TransformMode");
|
42
42
|
const types_1 = require("./types");
|
43
43
|
const EditorImage_1 = __importDefault(require("../../image/EditorImage"));
|
44
|
+
const uniteCommands_1 = __importDefault(require("../../commands/uniteCommands"));
|
44
45
|
const updateChunkSize = 100;
|
45
46
|
const maxPreviewElemCount = 500;
|
46
47
|
// @internal
|
@@ -51,6 +52,7 @@ class Selection {
|
|
51
52
|
// @see getTightBoundingBox
|
52
53
|
this.selectionTightBoundingBox = null;
|
53
54
|
this.transform = math_1.Mat33.identity;
|
55
|
+
// invariant: sorted by increasing z-index
|
54
56
|
this.selectedElems = [];
|
55
57
|
this.hasParent = true;
|
56
58
|
// Maps IDs to whether we removed the component from the image
|
@@ -161,6 +163,16 @@ class Selection {
|
|
161
163
|
this.previewTransformCmds();
|
162
164
|
}
|
163
165
|
}
|
166
|
+
getDeltaZIndexToMoveSelectionToTop() {
|
167
|
+
if (this.selectedElems.length === 0) {
|
168
|
+
return 0;
|
169
|
+
}
|
170
|
+
const selectedBottommostZIndex = this.selectedElems[0].getZIndex();
|
171
|
+
const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.region);
|
172
|
+
const topMostVisibleZIndex = visibleObjects[visibleObjects.length - 1]?.getZIndex() ?? selectedBottommostZIndex;
|
173
|
+
const deltaZIndex = (topMostVisibleZIndex + 1) - selectedBottommostZIndex;
|
174
|
+
return deltaZIndex;
|
175
|
+
}
|
164
176
|
// Applies the current transformation to the selection
|
165
177
|
finalizeTransform() {
|
166
178
|
const fullTransform = this.transform;
|
@@ -169,17 +181,35 @@ class Selection {
|
|
169
181
|
this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
|
170
182
|
this.transform = math_1.Mat33.identity;
|
171
183
|
this.scrollTo();
|
184
|
+
let transformPromise = undefined;
|
172
185
|
// Make the commands undo-able.
|
173
186
|
// Don't check for non-empty transforms because this breaks changing the
|
174
187
|
// z-index of the just-transformed commands.
|
175
|
-
|
176
|
-
|
177
|
-
|
188
|
+
if (this.selectedElems.length > 0) {
|
189
|
+
const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
|
190
|
+
transformPromise = this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, fullTransform, deltaZIndex));
|
191
|
+
}
|
178
192
|
// Clear renderings of any in-progress transformations
|
179
193
|
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
180
194
|
wetInkRenderer.clear();
|
181
195
|
return transformPromise;
|
182
196
|
}
|
197
|
+
/** Sends all selected elements to the bottom of the visible image. */
|
198
|
+
sendToBack() {
|
199
|
+
const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.editor.viewport.visibleRect);
|
200
|
+
// VisibleObjects and selectedElems should both be sorted by z-index
|
201
|
+
const lowestVisibleZIndex = visibleObjects[0]?.getZIndex() ?? 0;
|
202
|
+
const highestSelectedZIndex = this.selectedElems[this.selectedElems.length - 1]?.getZIndex() ?? 0;
|
203
|
+
const targetHighestZIndex = lowestVisibleZIndex - 1;
|
204
|
+
const deltaZIndex = targetHighestZIndex - highestSelectedZIndex;
|
205
|
+
if (deltaZIndex !== 0) {
|
206
|
+
const commands = this.selectedElems.map(elem => {
|
207
|
+
return elem.setZIndex(elem.getZIndex() + deltaZIndex);
|
208
|
+
});
|
209
|
+
return (0, uniteCommands_1.default)(commands, updateChunkSize);
|
210
|
+
}
|
211
|
+
return null;
|
212
|
+
}
|
183
213
|
// Preview the effects of the current transformation on the selection
|
184
214
|
previewTransformCmds() {
|
185
215
|
if (this.selectedElems.length === 0) {
|
@@ -193,7 +223,7 @@ class Selection {
|
|
193
223
|
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
194
224
|
wetInkRenderer.clear();
|
195
225
|
wetInkRenderer.pushTransform(this.transform);
|
196
|
-
const viewportVisibleRect = this.editor.viewport.visibleRect;
|
226
|
+
const viewportVisibleRect = this.editor.viewport.visibleRect.union(this.region);
|
197
227
|
const visibleRect = viewportVisibleRect.transformedBoundingBox(this.transform.inverse());
|
198
228
|
for (const elem of this.selectedElems) {
|
199
229
|
elem.render(wetInkRenderer, visibleRect);
|
@@ -439,7 +469,8 @@ class Selection {
|
|
439
469
|
if (wasTransforming) {
|
440
470
|
// Don't update the selection's focus when redoing/undoing
|
441
471
|
const selectionToUpdate = null;
|
442
|
-
|
472
|
+
const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
|
473
|
+
tmpApplyCommand = new _a.ApplyTransformationCommand(selectionToUpdate, this.selectedElems, this.transform, deltaZIndex);
|
443
474
|
// Transform to ensure that the duplicates are in the correct location
|
444
475
|
await tmpApplyCommand.apply(this.editor);
|
445
476
|
// Show items again
|
@@ -480,6 +511,8 @@ class Selection {
|
|
480
511
|
this.originalRegion = bbox;
|
481
512
|
this.selectionTightBoundingBox = bbox;
|
482
513
|
this.selectedElems = objects.filter(object => object.isSelectable());
|
514
|
+
// Enforce increasing z-index invariant
|
515
|
+
this.selectedElems.sort((a, b) => a.getZIndex() - b.getZIndex());
|
483
516
|
this.padRegion();
|
484
517
|
this.updateUI();
|
485
518
|
}
|
@@ -493,7 +526,8 @@ _a = Selection;
|
|
493
526
|
// The selection box is lost when serializing/deserializing. No need to store box rotation
|
494
527
|
const fullTransform = new math_1.Mat33(...json.transform);
|
495
528
|
const elemIds = (json.elems ?? []);
|
496
|
-
|
529
|
+
const deltaZIndex = parseInt(json.deltaZIndex ?? 0);
|
530
|
+
return new _a.ApplyTransformationCommand(null, elemIds, fullTransform, deltaZIndex);
|
497
531
|
});
|
498
532
|
})();
|
499
533
|
Selection.ApplyTransformationCommand = class extends SerializableCommand_1.default {
|
@@ -501,10 +535,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
|
|
501
535
|
// If a `string[]`, selectedElems is a list of element IDs.
|
502
536
|
selectedElems,
|
503
537
|
// Full transformation used to transform elements.
|
504
|
-
fullTransform) {
|
538
|
+
fullTransform, deltaZIndex) {
|
505
539
|
super('selection-tool-transform');
|
506
540
|
this.selection = selection;
|
507
541
|
this.fullTransform = fullTransform;
|
542
|
+
this.deltaZIndex = deltaZIndex;
|
508
543
|
const isIDList = (arr) => {
|
509
544
|
return typeof arr[0] === 'string';
|
510
545
|
};
|
@@ -515,11 +550,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
|
|
515
550
|
else {
|
516
551
|
this.selectedElemIds = selectedElems.map(elem => elem.getId());
|
517
552
|
this.transformCommands = selectedElems.map(elem => {
|
518
|
-
return elem.
|
553
|
+
return elem.setZIndexAndTransformBy(this.fullTransform, elem.getZIndex() + deltaZIndex);
|
519
554
|
});
|
520
555
|
}
|
521
556
|
}
|
522
|
-
resolveToElems(editor) {
|
557
|
+
resolveToElems(editor, isUndoing) {
|
523
558
|
if (this.transformCommands) {
|
524
559
|
return;
|
525
560
|
}
|
@@ -528,11 +563,19 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
|
|
528
563
|
if (!elem) {
|
529
564
|
throw new Error(`Unable to find element with ID, ${id}.`);
|
530
565
|
}
|
531
|
-
|
566
|
+
let originalZIndex = elem.getZIndex();
|
567
|
+
let targetZIndex = elem.getZIndex() + this.deltaZIndex;
|
568
|
+
// If the command has already been applied, the element should currently
|
569
|
+
// have the target z-index.
|
570
|
+
if (isUndoing) {
|
571
|
+
targetZIndex = elem.getZIndex();
|
572
|
+
originalZIndex = elem.getZIndex() - this.deltaZIndex;
|
573
|
+
}
|
574
|
+
return elem.setZIndexAndTransformBy(this.fullTransform, targetZIndex, originalZIndex);
|
532
575
|
});
|
533
576
|
}
|
534
577
|
async apply(editor) {
|
535
|
-
this.resolveToElems(editor);
|
578
|
+
this.resolveToElems(editor, false);
|
536
579
|
this.selection?.setTransform(this.fullTransform, false);
|
537
580
|
this.selection?.updateUI();
|
538
581
|
await editor.asyncApplyCommands(this.transformCommands, updateChunkSize);
|
@@ -541,7 +584,7 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
|
|
541
584
|
this.selection?.updateUI();
|
542
585
|
}
|
543
586
|
async unapply(editor) {
|
544
|
-
this.resolveToElems(editor);
|
587
|
+
this.resolveToElems(editor, true);
|
545
588
|
this.selection?.setTransform(this.fullTransform.inverse(), false);
|
546
589
|
this.selection?.updateUI();
|
547
590
|
await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
|
@@ -553,6 +596,7 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
|
|
553
596
|
return {
|
554
597
|
elems: this.selectedElemIds,
|
555
598
|
transform: this.fullTransform.toArray(),
|
599
|
+
deltaZIndex: this.deltaZIndex,
|
556
600
|
};
|
557
601
|
}
|
558
602
|
description(_editor, localizationTable) {
|
@@ -28,6 +28,7 @@ export default class SelectionTool extends BaseTool {
|
|
28
28
|
private onSelectionUpdated;
|
29
29
|
private zoomToSelection;
|
30
30
|
private static handleableKeys;
|
31
|
+
private hasUnfinalizedTransformFromKeyPress;
|
31
32
|
onKeyPress(event: KeyPressEvent): boolean;
|
32
33
|
onKeyUp(evt: KeyUpEvent): boolean;
|
33
34
|
onCopy(event: CopyEvent): boolean;
|
@@ -27,6 +27,9 @@ class SelectionTool extends BaseTool_1.default {
|
|
27
27
|
this.lastPointer = null;
|
28
28
|
this.selectionBoxHandlingEvt = false;
|
29
29
|
this.lastSelectedObjects = [];
|
30
|
+
// Whether the last keypress corresponded to an action that didn't transform the
|
31
|
+
// selection (and thus does not need to be finalized on onKeyUp).
|
32
|
+
this.hasUnfinalizedTransformFromKeyPress = false;
|
30
33
|
this.autoscroller = new ToPointerAutoscroller_1.default(editor.viewport, (scrollBy) => {
|
31
34
|
editor.dispatch(Viewport_1.default.transformBy(math_1.Mat33.translation(scrollBy)), false);
|
32
35
|
// Update the selection box/content to match the new viewport.
|
@@ -219,7 +222,8 @@ class SelectionTool extends BaseTool_1.default {
|
|
219
222
|
this.snapToGrid = true;
|
220
223
|
return true;
|
221
224
|
}
|
222
|
-
if (this.selectionBox && shortcucts.matchesShortcut(keybindings_1.duplicateSelectionShortcut, event)
|
225
|
+
if (this.selectionBox && (shortcucts.matchesShortcut(keybindings_1.duplicateSelectionShortcut, event)
|
226
|
+
|| shortcucts.matchesShortcut(keybindings_1.sendToBackSelectionShortcut, event))) {
|
223
227
|
// Handle duplication on key up — we don't want to accidentally duplicate
|
224
228
|
// many times.
|
225
229
|
return true;
|
@@ -233,9 +237,11 @@ class SelectionTool extends BaseTool_1.default {
|
|
233
237
|
// Pass it to another tool, if apliccable.
|
234
238
|
return false;
|
235
239
|
}
|
236
|
-
else if (event.key === 'Shift') {
|
240
|
+
else if (event.shiftKey || event.key === 'Shift') {
|
237
241
|
this.shiftKeyPressed = true;
|
238
|
-
|
242
|
+
if (event.key === 'Shift') {
|
243
|
+
return true;
|
244
|
+
}
|
239
245
|
}
|
240
246
|
let rotationSteps = 0;
|
241
247
|
let xTranslateSteps = 0;
|
@@ -303,6 +309,8 @@ class SelectionTool extends BaseTool_1.default {
|
|
303
309
|
const oldTransform = this.selectionBox.getTransform();
|
304
310
|
this.selectionBox.setTransform(oldTransform.rightMul(transform));
|
305
311
|
this.selectionBox.scrollTo();
|
312
|
+
// The transformation needs to be finalized at some point (on key up)
|
313
|
+
this.hasUnfinalizedTransformFromKeyPress = true;
|
306
314
|
}
|
307
315
|
if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
|
308
316
|
this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
|
@@ -328,12 +336,32 @@ class SelectionTool extends BaseTool_1.default {
|
|
328
336
|
});
|
329
337
|
return true;
|
330
338
|
}
|
339
|
+
if (this.selectionBox && shortcucts.matchesShortcut(keybindings_1.sendToBackSelectionShortcut, evt)) {
|
340
|
+
const sendToBackCommand = this.selectionBox.sendToBack();
|
341
|
+
if (sendToBackCommand) {
|
342
|
+
this.editor.dispatch(sendToBackCommand);
|
343
|
+
}
|
344
|
+
return true;
|
345
|
+
}
|
346
|
+
// Here, we check if shiftKey === false because, as of this writing,
|
347
|
+
// evt.shiftKey is an optional property. Being falsey could just mean
|
348
|
+
// that it wasn't set.
|
349
|
+
if (evt.shiftKey === false) {
|
350
|
+
this.shiftKeyPressed = false;
|
351
|
+
// Don't return immediately -- event may be otherwise handled
|
352
|
+
}
|
353
|
+
// Also check for key === 'Shift' (for the case where shiftKey is undefined)
|
331
354
|
if (evt.key === 'Shift') {
|
332
355
|
this.shiftKeyPressed = false;
|
333
356
|
return true;
|
334
357
|
}
|
358
|
+
// If we don't need to finalize the transform
|
359
|
+
if (!this.hasUnfinalizedTransformFromKeyPress) {
|
360
|
+
return true;
|
361
|
+
}
|
335
362
|
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
|
336
363
|
this.selectionBox.finalizeTransform();
|
364
|
+
this.hasUnfinalizedTransformFromKeyPress = false;
|
337
365
|
return true;
|
338
366
|
}
|
339
367
|
return false;
|
@@ -367,7 +395,11 @@ class SelectionTool extends BaseTool_1.default {
|
|
367
395
|
return true;
|
368
396
|
}
|
369
397
|
setEnabled(enabled) {
|
398
|
+
const wasEnabled = this.isEnabled();
|
370
399
|
super.setEnabled(enabled);
|
400
|
+
if (wasEnabled === enabled) {
|
401
|
+
return;
|
402
|
+
}
|
371
403
|
// Clear the selection
|
372
404
|
this.selectionBox?.cancelSelection();
|
373
405
|
this.onSelectionUpdated();
|
@@ -10,7 +10,6 @@ const BaseTool_1 = __importDefault(require("./BaseTool"));
|
|
10
10
|
*
|
11
11
|
* This is in the default set of {@link ToolController} tools.
|
12
12
|
*
|
13
|
-
* @deprecated This may be replaced in the future.
|
14
13
|
*/
|
15
14
|
class ToolSwitcherShortcut extends BaseTool_1.default {
|
16
15
|
constructor(editor) {
|