js-draw 1.10.0 → 1.11.0
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/bundle.js +2 -2
- 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/builders/types.d.ts +11 -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/Pen.js +5 -0
- 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 +19 -1
- 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/version.js +1 -1
- 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/builders/types.d.ts +11 -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/Pen.mjs +5 -0
- 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 +20 -2
- 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/version.mjs +1 -1
- package/package.json +2 -2
@@ -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;
|
@@ -303,6 +307,8 @@ class SelectionTool extends BaseTool_1.default {
|
|
303
307
|
const oldTransform = this.selectionBox.getTransform();
|
304
308
|
this.selectionBox.setTransform(oldTransform.rightMul(transform));
|
305
309
|
this.selectionBox.scrollTo();
|
310
|
+
// The transformation needs to be finalized at some point (on key up)
|
311
|
+
this.hasUnfinalizedTransformFromKeyPress = true;
|
306
312
|
}
|
307
313
|
if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
|
308
314
|
this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
|
@@ -328,12 +334,24 @@ class SelectionTool extends BaseTool_1.default {
|
|
328
334
|
});
|
329
335
|
return true;
|
330
336
|
}
|
337
|
+
if (this.selectionBox && shortcucts.matchesShortcut(keybindings_1.sendToBackSelectionShortcut, evt)) {
|
338
|
+
const sendToBackCommand = this.selectionBox.sendToBack();
|
339
|
+
if (sendToBackCommand) {
|
340
|
+
this.editor.dispatch(sendToBackCommand);
|
341
|
+
}
|
342
|
+
return true;
|
343
|
+
}
|
331
344
|
if (evt.key === 'Shift') {
|
332
345
|
this.shiftKeyPressed = false;
|
333
346
|
return true;
|
334
347
|
}
|
348
|
+
// If we don't need to finalize the transform
|
349
|
+
if (!this.hasUnfinalizedTransformFromKeyPress) {
|
350
|
+
return true;
|
351
|
+
}
|
335
352
|
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
|
336
353
|
this.selectionBox.finalizeTransform();
|
354
|
+
this.hasUnfinalizedTransformFromKeyPress = false;
|
337
355
|
return true;
|
338
356
|
}
|
339
357
|
return false;
|
@@ -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) {
|
@@ -15,3 +15,4 @@ export declare const zoomInKeyboardShortcutId = "jsdraw.tools.PanZoom.zoomIn";
|
|
15
15
|
export declare const zoomOutKeyboardShortcutId = "jsdraw.tools.PanZoom.zoomOut";
|
16
16
|
export declare const selectAllKeyboardShortcut = "jsdraw.tools.SelectionTool.selectAll";
|
17
17
|
export declare const duplicateSelectionShortcut = "jsdraw.tools.SelectionTool.duplicateSelection";
|
18
|
+
export declare const sendToBackSelectionShortcut = "jsdraw.tools.SelectionTool.sendToBack";
|
@@ -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.duplicateSelectionShortcut = exports.selectAllKeyboardShortcut = exports.zoomOutKeyboardShortcutId = exports.zoomInKeyboardShortcutId = exports.rotateCounterClockwiseKeyboardShortcutId = exports.rotateClockwiseKeyboardShortcutId = exports.moveDownKeyboardShortcutId = exports.moveUpKeyboardShortcutId = exports.moveRightKeyboardShortcutId = exports.moveLeftKeyboardShortcutId = exports.toggleFindVisibleShortcutId = exports.lineLockKeyboardShortcutId = exports.snapToGridKeyboardShortcutId = exports.decreaseSizeKeyboardShortcutId = exports.increaseSizeKeyboardShortcutId = exports.redoKeyboardShortcutId = exports.undoKeyboardShortcutId = void 0;
|
6
|
+
exports.sendToBackSelectionShortcut = exports.duplicateSelectionShortcut = exports.selectAllKeyboardShortcut = exports.zoomOutKeyboardShortcutId = exports.zoomInKeyboardShortcutId = exports.rotateCounterClockwiseKeyboardShortcutId = exports.rotateClockwiseKeyboardShortcutId = exports.moveDownKeyboardShortcutId = exports.moveUpKeyboardShortcutId = exports.moveRightKeyboardShortcutId = exports.moveLeftKeyboardShortcutId = exports.toggleFindVisibleShortcutId = exports.lineLockKeyboardShortcutId = exports.snapToGridKeyboardShortcutId = exports.decreaseSizeKeyboardShortcutId = exports.increaseSizeKeyboardShortcutId = exports.redoKeyboardShortcutId = exports.undoKeyboardShortcutId = void 0;
|
7
7
|
const KeyboardShortcutManager_1 = __importDefault(require("../shortcuts/KeyboardShortcutManager"));
|
8
8
|
// This file contains user-overridable tool-realted keybindings.
|
9
9
|
// Undo/redo
|
@@ -45,3 +45,5 @@ exports.selectAllKeyboardShortcut = 'jsdraw.tools.SelectionTool.selectAll';
|
|
45
45
|
KeyboardShortcutManager_1.default.registerDefaultKeyboardShortcut(exports.selectAllKeyboardShortcut, ['CtrlOrMeta+KeyA'], 'Select all');
|
46
46
|
exports.duplicateSelectionShortcut = 'jsdraw.tools.SelectionTool.duplicateSelection';
|
47
47
|
KeyboardShortcutManager_1.default.registerDefaultKeyboardShortcut(exports.duplicateSelectionShortcut, ['CtrlOrMeta+KeyD'], 'Duplicate selection');
|
48
|
+
exports.sendToBackSelectionShortcut = 'jsdraw.tools.SelectionTool.sendToBack';
|
49
|
+
KeyboardShortcutManager_1.default.registerDefaultKeyboardShortcut(exports.sendToBackSelectionShortcut, ['End'], 'Send to back');
|
package/dist/cjs/version.js
CHANGED
@@ -6,6 +6,11 @@ const invertCommand = (command) => {
|
|
6
6
|
if (command instanceof SerializableCommand) {
|
7
7
|
// SerializableCommand that does the inverse of [command]
|
8
8
|
return new class extends SerializableCommand {
|
9
|
+
constructor() {
|
10
|
+
super(...arguments);
|
11
|
+
// For debugging
|
12
|
+
this._command = command;
|
13
|
+
}
|
9
14
|
serializeToJSON() {
|
10
15
|
return command.serialize();
|
11
16
|
}
|
@@ -116,6 +116,14 @@ export default abstract class AbstractComponent {
|
|
116
116
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
117
117
|
transformBy(affineTransfm: Mat33): SerializableCommand;
|
118
118
|
setZIndex(newZIndex: number): SerializableCommand;
|
119
|
+
/**
|
120
|
+
* Combines {@link transformBy} and {@link setZIndex} into a single command.
|
121
|
+
*
|
122
|
+
* @param newZIndex - The z-index this component should have after applying this command.
|
123
|
+
* @param originalZIndex - @internal The z-index the component should revert to after unapplying
|
124
|
+
* this command.
|
125
|
+
*/
|
126
|
+
setZIndexAndTransformBy(affineTransfm: Mat33, newZIndex: number, originalZIndex?: number): SerializableCommand;
|
119
127
|
isSelectable(): boolean;
|
120
128
|
isBackground(): boolean;
|
121
129
|
getProportionalRenderingTime(): number;
|
@@ -138,13 +138,25 @@ class AbstractComponent {
|
|
138
138
|
}
|
139
139
|
// Returns a command that, when applied, transforms this by [affineTransfm] and
|
140
140
|
// updates the editor.
|
141
|
-
//
|
141
|
+
//
|
142
|
+
// The transformed component is also moved to the top (use {@link setZIndexAndTransformBy} to
|
143
|
+
// avoid this behavior).
|
142
144
|
transformBy(affineTransfm) {
|
143
145
|
return new AbstractComponent.TransformElementCommand(affineTransfm, this.getId(), this);
|
144
146
|
}
|
145
147
|
// Returns a command that updates this component's z-index.
|
146
148
|
setZIndex(newZIndex) {
|
147
|
-
return new AbstractComponent.TransformElementCommand(Mat33.identity, this.getId(), this, newZIndex
|
149
|
+
return new AbstractComponent.TransformElementCommand(Mat33.identity, this.getId(), this, newZIndex);
|
150
|
+
}
|
151
|
+
/**
|
152
|
+
* Combines {@link transformBy} and {@link setZIndex} into a single command.
|
153
|
+
*
|
154
|
+
* @param newZIndex - The z-index this component should have after applying this command.
|
155
|
+
* @param originalZIndex - @internal The z-index the component should revert to after unapplying
|
156
|
+
* this command.
|
157
|
+
*/
|
158
|
+
setZIndexAndTransformBy(affineTransfm, newZIndex, originalZIndex) {
|
159
|
+
return new AbstractComponent.TransformElementCommand(affineTransfm, this.getId(), this, newZIndex, originalZIndex);
|
148
160
|
}
|
149
161
|
// @returns true iff this component can be selected (e.g. by the selection tool.)
|
150
162
|
isSelectable() {
|
@@ -215,8 +227,12 @@ class AbstractComponent {
|
|
215
227
|
throw new Error(`Element with data ${json} cannot be deserialized.`);
|
216
228
|
}
|
217
229
|
const instance = this.deserializationCallbacks[json.name](json.data);
|
218
|
-
instance.zIndex = json.zIndex;
|
219
230
|
instance.id = json.id;
|
231
|
+
if (isFinite(json.zIndex)) {
|
232
|
+
instance.zIndex = json.zIndex;
|
233
|
+
// Ensure that new components will be added on top.
|
234
|
+
AbstractComponent.zIndexCounter = Math.max(AbstractComponent.zIndexCounter, instance.zIndex + 1);
|
235
|
+
}
|
220
236
|
// TODO: What should we do with json.loadSaveData?
|
221
237
|
// If we attach it to [instance], we create a potential security risk — loadSaveData
|
222
238
|
// is often used to store unrecognised attributes so they can be preserved on output.
|
@@ -225,6 +241,7 @@ class AbstractComponent {
|
|
225
241
|
}
|
226
242
|
}
|
227
243
|
// Topmost z-index
|
244
|
+
// TODO: Should be a property of the EditorImage.
|
228
245
|
AbstractComponent.zIndexCounter = 0;
|
229
246
|
AbstractComponent.deserializationCallbacks = {};
|
230
247
|
AbstractComponent.transformElementCommandId = 'transform-element';
|
@@ -252,7 +269,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
252
269
|
super.resolveComponent(image);
|
253
270
|
this.origZIndex ??= this.component.getZIndex();
|
254
271
|
}
|
255
|
-
updateTransform(editor, newTransfm) {
|
272
|
+
updateTransform(editor, newTransfm, targetZIndex) {
|
256
273
|
if (!this.component) {
|
257
274
|
throw new Error('this.component is undefined or null!');
|
258
275
|
}
|
@@ -264,7 +281,12 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
264
281
|
hadParent = true;
|
265
282
|
}
|
266
283
|
this.component.applyTransformation(newTransfm);
|
284
|
+
this.component.zIndex = targetZIndex;
|
267
285
|
this.component.lastChangedTime = (new Date()).getTime();
|
286
|
+
// Ensure that new components are automatically drawn above the current component.
|
287
|
+
if (targetZIndex >= AbstractComponent.zIndexCounter) {
|
288
|
+
AbstractComponent.zIndexCounter = targetZIndex + 1;
|
289
|
+
}
|
268
290
|
// Add the element back to the document.
|
269
291
|
if (hadParent) {
|
270
292
|
EditorImage.addElement(this.component).apply(editor);
|
@@ -272,14 +294,12 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
272
294
|
}
|
273
295
|
apply(editor) {
|
274
296
|
this.resolveComponent(editor.image);
|
275
|
-
this.
|
276
|
-
this.updateTransform(editor, this.affineTransfm);
|
297
|
+
this.updateTransform(editor, this.affineTransfm, this.targetZIndex);
|
277
298
|
editor.queueRerender();
|
278
299
|
}
|
279
300
|
unapply(editor) {
|
280
301
|
this.resolveComponent(editor.image);
|
281
|
-
this.
|
282
|
-
this.updateTransform(editor, this.affineTransfm.inverse());
|
302
|
+
this.updateTransform(editor, this.affineTransfm.inverse(), this.origZIndex);
|
283
303
|
editor.queueRerender();
|
284
304
|
}
|
285
305
|
description(_editor, localizationTable) {
|
@@ -7,6 +7,17 @@ export interface ComponentBuilder {
|
|
7
7
|
getBBox(): Rect2;
|
8
8
|
build(): AbstractComponent;
|
9
9
|
preview(renderer: AbstractRenderer): void;
|
10
|
+
/**
|
11
|
+
* Called when the pen is stationary (or the user otherwise
|
12
|
+
* activates autocomplete). This might attempt to fit the user's
|
13
|
+
* drawing to a particular shape.
|
14
|
+
*
|
15
|
+
* The shape returned by this function may be ignored if it has
|
16
|
+
* an empty bounding box.
|
17
|
+
*
|
18
|
+
* Although this returns a Promise, it should return *as fast as
|
19
|
+
* possible*.
|
20
|
+
*/
|
10
21
|
autocorrectShape?: () => Promise<AbstractComponent | null>;
|
11
22
|
addPoint(point: StrokeDataPoint): void;
|
12
23
|
}
|
@@ -3,6 +3,7 @@ import { ToolbarLocalization } from './localization';
|
|
3
3
|
import { ActionButtonIcon } from './types';
|
4
4
|
import BaseWidget, { ToolbarWidgetTag } from './widgets/BaseWidget';
|
5
5
|
import { DispatcherEventListener } from '../EventDispatcher';
|
6
|
+
import { BaseTool } from '../lib';
|
6
7
|
export interface SpacerOptions {
|
7
8
|
grow: number;
|
8
9
|
minSize: string;
|
@@ -131,7 +132,7 @@ export default abstract class AbstractToolbar {
|
|
131
132
|
/**
|
132
133
|
* Adds an "Exit" button that, when clicked, calls `exitCallback`.
|
133
134
|
*
|
134
|
-
* **Note**: This is roughly equivalent to
|
135
|
+
* **Note**: This is *roughly* equivalent to
|
135
136
|
* ```ts
|
136
137
|
* toolbar.addTaggedActionButton([ ToolbarWidgetTag.Exit ], {
|
137
138
|
* label: this.editor.localization.exit,
|
@@ -154,9 +155,24 @@ export default abstract class AbstractToolbar {
|
|
154
155
|
*/
|
155
156
|
addUndoRedoButtons(undoFirst?: boolean): void;
|
156
157
|
/**
|
157
|
-
* Adds
|
158
|
+
* Adds widgets for pen/eraser/selection/text/pan-zoom primary tools.
|
159
|
+
*
|
160
|
+
* If `filter` returns `false` for a tool, no widget is added for that tool.
|
161
|
+
* See {@link addDefaultToolWidgets}
|
162
|
+
*/
|
163
|
+
addWidgetsForPrimaryTools(filter?: (tool: BaseTool) => boolean): void;
|
164
|
+
/**
|
165
|
+
* Adds toolbar widgets based on the enabled tools, and additional tool-like
|
166
|
+
* buttons (e.g. {@link DocumentPropertiesWidget} and {@link InsertImageWidget}).
|
158
167
|
*/
|
159
168
|
addDefaultToolWidgets(): void;
|
169
|
+
/**
|
170
|
+
* Adds widgets that don't correspond to tools, but do allow the user to control
|
171
|
+
* the editor in some way.
|
172
|
+
*
|
173
|
+
* By default, this includes {@link DocumentPropertiesWidget} and {@link InsertImageWidget}.
|
174
|
+
*/
|
175
|
+
addDefaultEditorControlWidgets(): void;
|
160
176
|
addDefaultActionButtons(): void;
|
161
177
|
/**
|
162
178
|
* Adds both the default tool widgets and action buttons.
|
@@ -30,6 +30,7 @@ import DocumentPropertiesWidget from './widgets/DocumentPropertiesWidget.mjs';
|
|
30
30
|
import { Color4 } from '@js-draw/math';
|
31
31
|
import { toolbarCSSPrefix } from './constants.mjs';
|
32
32
|
import SaveActionWidget from './widgets/SaveActionWidget.mjs';
|
33
|
+
import ExitActionWidget from './widgets/ExitActionWidget.mjs';
|
33
34
|
class AbstractToolbar {
|
34
35
|
/** @internal */
|
35
36
|
constructor(editor, localizationTable = defaultToolbarLocalization) {
|
@@ -292,14 +293,13 @@ class AbstractToolbar {
|
|
292
293
|
*/
|
293
294
|
addSaveButton(saveCallback, labelOverride = {}) {
|
294
295
|
const widget = new SaveActionWidget(this.editor, this.localizationTable, saveCallback, labelOverride);
|
295
|
-
widget.setTags([ToolbarWidgetTag.Save]);
|
296
296
|
this.addWidget(widget);
|
297
297
|
return widget;
|
298
298
|
}
|
299
299
|
/**
|
300
300
|
* Adds an "Exit" button that, when clicked, calls `exitCallback`.
|
301
301
|
*
|
302
|
-
* **Note**: This is roughly equivalent to
|
302
|
+
* **Note**: This is *roughly* equivalent to
|
303
303
|
* ```ts
|
304
304
|
* toolbar.addTaggedActionButton([ ToolbarWidgetTag.Exit ], {
|
305
305
|
* label: this.editor.localization.exit,
|
@@ -316,15 +316,9 @@ class AbstractToolbar {
|
|
316
316
|
* @final
|
317
317
|
*/
|
318
318
|
addExitButton(exitCallback, labelOverride = {}) {
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
...labelOverride,
|
323
|
-
}, () => {
|
324
|
-
exitCallback();
|
325
|
-
}, {
|
326
|
-
autoDisableInReadOnlyEditors: false,
|
327
|
-
});
|
319
|
+
const widget = new ExitActionWidget(this.editor, this.localizationTable, exitCallback, labelOverride);
|
320
|
+
this.addWidget(widget);
|
321
|
+
return widget;
|
328
322
|
}
|
329
323
|
/**
|
330
324
|
* Adds undo and redo buttons that trigger the editor's built-in undo and redo
|
@@ -372,27 +366,49 @@ class AbstractToolbar {
|
|
372
366
|
});
|
373
367
|
}
|
374
368
|
/**
|
375
|
-
* Adds
|
369
|
+
* Adds widgets for pen/eraser/selection/text/pan-zoom primary tools.
|
370
|
+
*
|
371
|
+
* If `filter` returns `false` for a tool, no widget is added for that tool.
|
372
|
+
* See {@link addDefaultToolWidgets}
|
376
373
|
*/
|
377
|
-
|
378
|
-
const
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
374
|
+
addWidgetsForPrimaryTools(filter) {
|
375
|
+
for (const tool of this.editor.toolController.getPrimaryTools()) {
|
376
|
+
if (filter && !filter?.(tool)) {
|
377
|
+
continue;
|
378
|
+
}
|
379
|
+
if (tool instanceof PenTool) {
|
380
|
+
const widget = new PenToolWidget(this.editor, tool, this.localizationTable);
|
381
|
+
this.addWidget(widget);
|
382
|
+
}
|
383
|
+
else if (tool instanceof EraserTool) {
|
384
|
+
this.addWidget(new EraserWidget(this.editor, tool, this.localizationTable));
|
385
|
+
}
|
386
|
+
else if (tool instanceof SelectionTool) {
|
387
|
+
this.addWidget(new SelectionToolWidget(this.editor, tool, this.localizationTable));
|
388
|
+
}
|
389
|
+
else if (tool instanceof TextTool) {
|
390
|
+
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
391
|
+
}
|
392
|
+
else if (tool instanceof PanZoomTool) {
|
393
|
+
this.addWidget(new HandToolWidget(this.editor, tool, this.localizationTable));
|
394
|
+
}
|
395
395
|
}
|
396
|
+
}
|
397
|
+
/**
|
398
|
+
* Adds toolbar widgets based on the enabled tools, and additional tool-like
|
399
|
+
* buttons (e.g. {@link DocumentPropertiesWidget} and {@link InsertImageWidget}).
|
400
|
+
*/
|
401
|
+
addDefaultToolWidgets() {
|
402
|
+
this.addWidgetsForPrimaryTools();
|
403
|
+
this.addDefaultEditorControlWidgets();
|
404
|
+
}
|
405
|
+
/**
|
406
|
+
* Adds widgets that don't correspond to tools, but do allow the user to control
|
407
|
+
* the editor in some way.
|
408
|
+
*
|
409
|
+
* By default, this includes {@link DocumentPropertiesWidget} and {@link InsertImageWidget}.
|
410
|
+
*/
|
411
|
+
addDefaultEditorControlWidgets() {
|
396
412
|
this.addWidget(new DocumentPropertiesWidget(this.editor, this.localizationTable));
|
397
413
|
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
398
414
|
}
|
@@ -47,7 +47,7 @@ class BaseWidget {
|
|
47
47
|
this.layoutManager = defaultLayoutManager;
|
48
48
|
this.icon = null;
|
49
49
|
this.container = document.createElement('div');
|
50
|
-
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`, `${toolbarCSSPrefix}toolButtonContainer`);
|
50
|
+
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`, `${toolbarCSSPrefix}toolButtonContainer`, `${toolbarCSSPrefix}internalWidgetId--${id.replace(/[^a-zA-Z0-9_]/g, '-')}`);
|
51
51
|
this.dropdownContent = document.createElement('div');
|
52
52
|
__classPrivateFieldSet(this, _BaseWidget_hasDropdown, false, "f");
|
53
53
|
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,27 @@
|
|
1
|
+
import ActionButtonWidget from './ActionButtonWidget.mjs';
|
2
|
+
import { ToolbarWidgetTag } from './BaseWidget.mjs';
|
3
|
+
import { exitKeyboardShortcut } from './keybindings.mjs';
|
4
|
+
class ExitActionWidget extends ActionButtonWidget {
|
5
|
+
constructor(editor, localization, saveCallback, labelOverride = {}) {
|
6
|
+
super(editor, 'exit-button',
|
7
|
+
// Creates an icon
|
8
|
+
() => {
|
9
|
+
return labelOverride.icon ?? editor.icons.makeCloseIcon();
|
10
|
+
}, labelOverride.label ?? localization.exit, saveCallback);
|
11
|
+
this.setTags([ToolbarWidgetTag.Exit]);
|
12
|
+
}
|
13
|
+
shouldAutoDisableInReadOnlyEditor() {
|
14
|
+
return false;
|
15
|
+
}
|
16
|
+
onKeyPress(event) {
|
17
|
+
if (this.editor.shortcuts.matchesShortcut(exitKeyboardShortcut, event)) {
|
18
|
+
this.clickAction();
|
19
|
+
return true;
|
20
|
+
}
|
21
|
+
return super.onKeyPress(event);
|
22
|
+
}
|
23
|
+
mustBeInToplevelMenu() {
|
24
|
+
return true;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
export 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;
|