js-draw 1.5.0 → 1.6.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/README.md +11 -2
- package/dist/Editor.css +2 -2
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +8 -0
- package/dist/cjs/Editor.js +32 -9
- package/dist/cjs/SVGLoader.d.ts +6 -0
- package/dist/cjs/SVGLoader.js +100 -56
- package/dist/cjs/commands/Erase.d.ts +7 -1
- package/dist/cjs/commands/Erase.js +13 -4
- package/dist/cjs/commands/invertCommand.js +1 -1
- package/dist/cjs/components/Stroke.d.ts +1 -1
- package/dist/cjs/components/Stroke.js +1 -1
- package/dist/cjs/image/EditorImage.js +9 -9
- package/dist/cjs/toolbar/AbstractToolbar.d.ts +4 -1
- package/dist/cjs/toolbar/AbstractToolbar.js +7 -1
- package/dist/cjs/toolbar/widgets/BaseToolWidget.js +4 -13
- package/dist/cjs/toolbar/widgets/BaseToolWidget.test.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +5 -1
- package/dist/cjs/toolbar/widgets/BaseWidget.js +45 -28
- package/dist/cjs/toolbar/widgets/BaseWidget.test.d.ts +1 -0
- package/dist/cjs/tools/BaseTool.js +1 -0
- package/dist/cjs/tools/SelectionTool/Selection.js +7 -5
- package/dist/cjs/tools/ToolController.d.ts +23 -0
- package/dist/cjs/tools/ToolController.js +65 -4
- package/dist/cjs/tools/ToolController.test.d.ts +1 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +8 -0
- package/dist/mjs/Editor.mjs +32 -9
- package/dist/mjs/SVGLoader.d.ts +6 -0
- package/dist/mjs/SVGLoader.mjs +99 -55
- package/dist/mjs/commands/Erase.d.ts +7 -1
- package/dist/mjs/commands/Erase.mjs +13 -4
- package/dist/mjs/commands/invertCommand.mjs +1 -1
- package/dist/mjs/components/Stroke.d.ts +1 -1
- package/dist/mjs/components/Stroke.mjs +1 -1
- package/dist/mjs/image/EditorImage.mjs +9 -9
- package/dist/mjs/toolbar/AbstractToolbar.d.ts +4 -1
- package/dist/mjs/toolbar/AbstractToolbar.mjs +7 -1
- package/dist/mjs/toolbar/widgets/BaseToolWidget.mjs +4 -13
- package/dist/mjs/toolbar/widgets/BaseToolWidget.test.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +5 -1
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +45 -28
- package/dist/mjs/toolbar/widgets/BaseWidget.test.d.ts +1 -0
- package/dist/mjs/tools/BaseTool.mjs +1 -0
- package/dist/mjs/tools/SelectionTool/Selection.mjs +7 -5
- package/dist/mjs/tools/ToolController.d.ts +23 -0
- package/dist/mjs/tools/ToolController.mjs +65 -4
- package/dist/mjs/tools/ToolController.test.d.ts +1 -0
- package/dist/mjs/version.mjs +1 -1
- package/docs/img/readme-images/js-draw.png +0 -0
- package/docs/img/readme-images/logo.svg +1 -0
- package/package.json +6 -6
- package/src/Editor.scss +4 -2
@@ -58,7 +58,7 @@ class EditorImage {
|
|
58
58
|
this.settingExportRect = false;
|
59
59
|
this.root = new RootImageNode();
|
60
60
|
this.background = new RootImageNode();
|
61
|
-
this.componentsById =
|
61
|
+
this.componentsById = Object.create(null);
|
62
62
|
this.notifier = new EventDispatcher_1.default();
|
63
63
|
this.importExportViewport = new Viewport_1.default(() => {
|
64
64
|
this.onExportViewportChanged();
|
@@ -212,11 +212,11 @@ class EditorImage {
|
|
212
212
|
* @see {@link Display.flatten}
|
213
213
|
*/
|
214
214
|
static addElement(elem, applyByFlattening = false) {
|
215
|
-
return new
|
215
|
+
return new _a.AddElementCommand(elem, applyByFlattening);
|
216
216
|
}
|
217
217
|
/** @see EditorImage.addElement */
|
218
218
|
addElement(elem, applyByFlattening) {
|
219
|
-
return
|
219
|
+
return _a.addElement(elem, applyByFlattening);
|
220
220
|
}
|
221
221
|
/**
|
222
222
|
* @returns a `Viewport` for rendering the image when importing/exporting.
|
@@ -235,7 +235,7 @@ class EditorImage {
|
|
235
235
|
* autoresize (if it was previously enabled).
|
236
236
|
*/
|
237
237
|
setImportExportRect(imageRect) {
|
238
|
-
return
|
238
|
+
return _a.SetImportExportRectCommand.of(this, imageRect, false);
|
239
239
|
}
|
240
240
|
getAutoresizeEnabled() {
|
241
241
|
return this.shouldAutoresizeExportViewport;
|
@@ -246,7 +246,7 @@ class EditorImage {
|
|
246
246
|
return Command_1.default.empty;
|
247
247
|
}
|
248
248
|
const newBBox = this.root.getBBox();
|
249
|
-
return
|
249
|
+
return _a.SetImportExportRectCommand.of(this, newBBox, autoresize);
|
250
250
|
}
|
251
251
|
setAutoresizeEnabledDirectly(shouldAutoresize) {
|
252
252
|
if (shouldAutoresize !== this.shouldAutoresizeExportViewport) {
|
@@ -352,7 +352,7 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand_1.defaul
|
|
352
352
|
const id = json.elemData.id;
|
353
353
|
const foundElem = editor.image.lookupElement(id);
|
354
354
|
const elem = foundElem ?? AbstractComponent_1.default.deserialize(json.elemData);
|
355
|
-
const result = new
|
355
|
+
const result = new _a.AddElementCommand(elem);
|
356
356
|
result.serializedElem = json.elemData;
|
357
357
|
return result;
|
358
358
|
});
|
@@ -361,7 +361,7 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand_1.defaul
|
|
361
361
|
// Handles resizing the background import/export region of the image.
|
362
362
|
EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand_1.default {
|
363
363
|
constructor(originalSize, originalTransform, originalAutoresize, newExportRect, newAutoresize) {
|
364
|
-
super(
|
364
|
+
super(_a.SetImportExportRectCommand.commandId);
|
365
365
|
this.originalSize = originalSize;
|
366
366
|
this.originalTransform = originalTransform;
|
367
367
|
this.originalAutoresize = originalAutoresize;
|
@@ -374,7 +374,7 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
|
|
374
374
|
const originalSize = importExportViewport.visibleRect.size;
|
375
375
|
const originalTransform = importExportViewport.canvasToScreenTransform;
|
376
376
|
const originalAutoresize = image.getAutoresizeEnabled();
|
377
|
-
return new
|
377
|
+
return new _a.SetImportExportRectCommand(originalSize, originalTransform, originalAutoresize, newExportRect, newAutoresize);
|
378
378
|
}
|
379
379
|
apply(editor) {
|
380
380
|
editor.image.setAutoresizeEnabledDirectly(this.newAutoresize);
|
@@ -435,7 +435,7 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
|
|
435
435
|
const finalRect = new math_1.Rect2(json.newRegion.x, json.newRegion.y, json.newRegion.w, json.newRegion.h);
|
436
436
|
const autoresize = json.autoresize ?? false;
|
437
437
|
const originalAutoresize = json.originalAutoresize ?? false;
|
438
|
-
return new
|
438
|
+
return new _a.SetImportExportRectCommand(originalSize, originalTransform, originalAutoresize, finalRect, autoresize);
|
439
439
|
});
|
440
440
|
})(),
|
441
441
|
_c);
|
@@ -162,7 +162,10 @@ export default abstract class AbstractToolbar {
|
|
162
162
|
* Adds both the default tool widgets and action buttons.
|
163
163
|
*/
|
164
164
|
abstract addDefaults(): void;
|
165
|
-
/**
|
165
|
+
/**
|
166
|
+
* Remove this toolbar from its container and clean up listeners.
|
167
|
+
* This should only be called **once** for a given toolbar.
|
168
|
+
*/
|
166
169
|
remove(): void;
|
167
170
|
/**
|
168
171
|
* Removes `listener` when {@link remove} is called.
|
@@ -404,7 +404,10 @@ class AbstractToolbar {
|
|
404
404
|
addDefaultActionButtons() {
|
405
405
|
this.addUndoRedoButtons();
|
406
406
|
}
|
407
|
-
/**
|
407
|
+
/**
|
408
|
+
* Remove this toolbar from its container and clean up listeners.
|
409
|
+
* This should only be called **once** for a given toolbar.
|
410
|
+
*/
|
408
411
|
remove() {
|
409
412
|
this.closeColorPickerOverlay?.remove();
|
410
413
|
for (const listener of __classPrivateFieldGet(this, _AbstractToolbar_listeners, "f")) {
|
@@ -412,6 +415,9 @@ class AbstractToolbar {
|
|
412
415
|
}
|
413
416
|
__classPrivateFieldSet(this, _AbstractToolbar_listeners, [], "f");
|
414
417
|
this.onRemove();
|
418
|
+
for (const widget of __classPrivateFieldGet(this, _AbstractToolbar_widgetList, "f")) {
|
419
|
+
widget.remove();
|
420
|
+
}
|
415
421
|
}
|
416
422
|
/**
|
417
423
|
* Removes `listener` when {@link remove} is called.
|
@@ -3,7 +3,6 @@ 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
|
-
const types_1 = require("../../types");
|
7
6
|
const BaseWidget_1 = __importDefault(require("./BaseWidget"));
|
8
7
|
const constants_1 = require("../constants");
|
9
8
|
const isToolWidgetFocused = () => {
|
@@ -14,11 +13,8 @@ class BaseToolWidget extends BaseWidget_1.default {
|
|
14
13
|
constructor(editor, targetTool, id, localizationTable) {
|
15
14
|
super(editor, id, localizationTable);
|
16
15
|
this.targetTool = targetTool;
|
17
|
-
|
18
|
-
if (
|
19
|
-
throw new Error('Incorrect event type! (Expected ToolEnabled)');
|
20
|
-
}
|
21
|
-
if (toolEvt.tool === targetTool) {
|
16
|
+
this.targetTool.enabledValue().onUpdateAndNow(enabled => {
|
17
|
+
if (enabled) {
|
22
18
|
this.setSelected(true);
|
23
19
|
// Transfer focus to the current button, only if another toolbar button is
|
24
20
|
// focused.
|
@@ -28,12 +24,7 @@ class BaseToolWidget extends BaseWidget_1.default {
|
|
28
24
|
this.focus();
|
29
25
|
}
|
30
26
|
}
|
31
|
-
|
32
|
-
editor.notifier.on(types_1.EditorEventType.ToolDisabled, toolEvt => {
|
33
|
-
if (toolEvt.kind !== types_1.EditorEventType.ToolDisabled) {
|
34
|
-
throw new Error('Incorrect event type! (Expected ToolDisabled)');
|
35
|
-
}
|
36
|
-
if (toolEvt.tool === targetTool) {
|
27
|
+
else {
|
37
28
|
this.setSelected(false);
|
38
29
|
this.setDropdownVisible(false);
|
39
30
|
}
|
@@ -57,7 +48,7 @@ class BaseToolWidget extends BaseWidget_1.default {
|
|
57
48
|
}
|
58
49
|
}
|
59
50
|
onKeyPress(event) {
|
60
|
-
if (this.isSelected() && event.
|
51
|
+
if (this.isSelected() && event.code === 'Space' && this.hasDropdown) {
|
61
52
|
this.handleClick();
|
62
53
|
return true;
|
63
54
|
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -81,13 +81,17 @@ export default abstract class BaseWidget {
|
|
81
81
|
* @internal
|
82
82
|
*/
|
83
83
|
addTo(parent: HTMLElement): HTMLElement;
|
84
|
+
/**
|
85
|
+
* Remove this. This allows the widget to be added to a toolbar again
|
86
|
+
* in the future using {@link addTo}.
|
87
|
+
*/
|
88
|
+
remove(): void;
|
84
89
|
focus(): void;
|
85
90
|
/**
|
86
91
|
* @internal
|
87
92
|
*/
|
88
93
|
addCSSClassToContainer(className: string): void;
|
89
94
|
removeCSSClassFromContainer(className: string): void;
|
90
|
-
remove(): void;
|
91
95
|
protected updateIcon(): void;
|
92
96
|
setDisabled(disabled: boolean): void;
|
93
97
|
setSelected(selected: boolean): void;
|
@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
13
13
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
14
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
15
15
|
};
|
16
|
-
var _BaseWidget_hasDropdown, _BaseWidget_disabledDueToReadOnlyEditor, _BaseWidget_tags,
|
16
|
+
var _BaseWidget_instances, _a, _BaseWidget_hasDropdown, _BaseWidget_disabledDueToReadOnlyEditor, _BaseWidget_tags, _BaseWidget_removeEditorListeners, _BaseWidget_addEditorListeners;
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
18
18
|
exports.ToolbarWidgetTag = void 0;
|
19
19
|
const ToolbarShortcutHandler_1 = __importDefault(require("../../tools/ToolbarShortcutHandler"));
|
@@ -32,6 +32,7 @@ var ToolbarWidgetTag;
|
|
32
32
|
})(ToolbarWidgetTag || (exports.ToolbarWidgetTag = ToolbarWidgetTag = {}));
|
33
33
|
class BaseWidget {
|
34
34
|
constructor(editor, id, localizationTable) {
|
35
|
+
_BaseWidget_instances.add(this);
|
35
36
|
this.editor = editor;
|
36
37
|
this.id = id;
|
37
38
|
this.dropdown = null;
|
@@ -44,8 +45,7 @@ class BaseWidget {
|
|
44
45
|
// Maps subWidget IDs to subWidgets.
|
45
46
|
this.subWidgets = {};
|
46
47
|
this.toplevel = true;
|
47
|
-
|
48
|
-
_BaseWidget_readOnlyListener.set(this, null);
|
48
|
+
_BaseWidget_removeEditorListeners.set(this, null);
|
49
49
|
this.localizationTable = localizationTable ?? editor.localization;
|
50
50
|
// Default layout manager
|
51
51
|
const defaultLayoutManager = new DropdownLayoutManager_1.default((text) => this.editor.announceForAccessibility(text), this.localizationTable);
|
@@ -66,12 +66,6 @@ class BaseWidget {
|
|
66
66
|
this.button.oncontextmenu = event => {
|
67
67
|
event.preventDefault();
|
68
68
|
};
|
69
|
-
const toolbarShortcutHandlers = this.editor.toolController.getMatchingTools(ToolbarShortcutHandler_1.default);
|
70
|
-
// If the onKeyPress function has been extended and the editor is configured to send keypress events to
|
71
|
-
// toolbar widgets,
|
72
|
-
if (toolbarShortcutHandlers.length > 0 && this.onKeyPress !== BaseWidget.prototype.onKeyPress) {
|
73
|
-
toolbarShortcutHandlers[0].registerListener(event => this.onKeyPress(event));
|
74
|
-
}
|
75
69
|
}
|
76
70
|
/**
|
77
71
|
* Should return a constant true or false value. If true (the default),
|
@@ -274,22 +268,18 @@ class BaseWidget {
|
|
274
268
|
if (this.container.parentElement) {
|
275
269
|
this.container.remove();
|
276
270
|
}
|
277
|
-
|
278
|
-
if (readOnly && this.shouldAutoDisableInReadOnlyEditor() && !this.disabled) {
|
279
|
-
this.setDisabled(true);
|
280
|
-
__classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, true, "f");
|
281
|
-
if (__classPrivateFieldGet(this, _BaseWidget_hasDropdown, "f")) {
|
282
|
-
this.dropdown?.requestHide();
|
283
|
-
}
|
284
|
-
}
|
285
|
-
else if (!readOnly && __classPrivateFieldGet(this, _BaseWidget_disabledDueToReadOnlyEditor, "f")) {
|
286
|
-
__classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, false, "f");
|
287
|
-
this.setDisabled(false);
|
288
|
-
}
|
289
|
-
}), "f");
|
271
|
+
__classPrivateFieldGet(this, _BaseWidget_instances, "m", _BaseWidget_addEditorListeners).call(this);
|
290
272
|
parent.appendChild(this.container);
|
291
273
|
return this.container;
|
292
274
|
}
|
275
|
+
/**
|
276
|
+
* Remove this. This allows the widget to be added to a toolbar again
|
277
|
+
* in the future using {@link addTo}.
|
278
|
+
*/
|
279
|
+
remove() {
|
280
|
+
this.container.remove();
|
281
|
+
__classPrivateFieldGet(this, _BaseWidget_removeEditorListeners, "f")?.call(this);
|
282
|
+
}
|
293
283
|
focus() {
|
294
284
|
this.button.focus();
|
295
285
|
}
|
@@ -302,11 +292,6 @@ class BaseWidget {
|
|
302
292
|
removeCSSClassFromContainer(className) {
|
303
293
|
this.container.classList.remove(className);
|
304
294
|
}
|
305
|
-
remove() {
|
306
|
-
this.container.remove();
|
307
|
-
__classPrivateFieldGet(this, _BaseWidget_readOnlyListener, "f")?.remove();
|
308
|
-
__classPrivateFieldSet(this, _BaseWidget_readOnlyListener, null, "f");
|
309
|
-
}
|
310
295
|
updateIcon() {
|
311
296
|
let newIcon = this.createIcon();
|
312
297
|
if (!newIcon) {
|
@@ -443,5 +428,37 @@ class BaseWidget {
|
|
443
428
|
}
|
444
429
|
}
|
445
430
|
}
|
446
|
-
_BaseWidget_hasDropdown = new WeakMap(), _BaseWidget_disabledDueToReadOnlyEditor = new WeakMap(), _BaseWidget_tags = new WeakMap(),
|
431
|
+
_a = BaseWidget, _BaseWidget_hasDropdown = new WeakMap(), _BaseWidget_disabledDueToReadOnlyEditor = new WeakMap(), _BaseWidget_tags = new WeakMap(), _BaseWidget_removeEditorListeners = new WeakMap(), _BaseWidget_instances = new WeakSet(), _BaseWidget_addEditorListeners = function _BaseWidget_addEditorListeners() {
|
432
|
+
__classPrivateFieldGet(this, _BaseWidget_removeEditorListeners, "f")?.call(this);
|
433
|
+
const toolbarShortcutHandlers = this.editor.toolController.getMatchingTools(ToolbarShortcutHandler_1.default);
|
434
|
+
let removeKeyPressListener = null;
|
435
|
+
// If the onKeyPress function has been extended and the editor is configured to send keypress events to
|
436
|
+
// toolbar widgets,
|
437
|
+
if (toolbarShortcutHandlers.length > 0 && this.onKeyPress !== _a.prototype.onKeyPress) {
|
438
|
+
const keyPressListener = (event) => this.onKeyPress(event);
|
439
|
+
const handler = toolbarShortcutHandlers[0];
|
440
|
+
handler.registerListener(keyPressListener);
|
441
|
+
removeKeyPressListener = () => {
|
442
|
+
handler.removeListener(keyPressListener);
|
443
|
+
};
|
444
|
+
}
|
445
|
+
const readOnlyListener = this.editor.isReadOnlyReactiveValue().onUpdateAndNow(readOnly => {
|
446
|
+
if (readOnly && this.shouldAutoDisableInReadOnlyEditor() && !this.disabled) {
|
447
|
+
this.setDisabled(true);
|
448
|
+
__classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, true, "f");
|
449
|
+
if (__classPrivateFieldGet(this, _BaseWidget_hasDropdown, "f")) {
|
450
|
+
this.dropdown?.requestHide();
|
451
|
+
}
|
452
|
+
}
|
453
|
+
else if (!readOnly && __classPrivateFieldGet(this, _BaseWidget_disabledDueToReadOnlyEditor, "f")) {
|
454
|
+
__classPrivateFieldSet(this, _BaseWidget_disabledDueToReadOnlyEditor, false, "f");
|
455
|
+
this.setDisabled(false);
|
456
|
+
}
|
457
|
+
});
|
458
|
+
__classPrivateFieldSet(this, _BaseWidget_removeEditorListeners, () => {
|
459
|
+
readOnlyListener.remove();
|
460
|
+
removeKeyPressListener?.();
|
461
|
+
__classPrivateFieldSet(this, _BaseWidget_removeEditorListeners, null, "f");
|
462
|
+
}, "f");
|
463
|
+
};
|
447
464
|
exports.default = BaseWidget;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -167,6 +167,7 @@ class BaseTool {
|
|
167
167
|
onDestroy() {
|
168
168
|
__classPrivateFieldGet(this, _BaseTool_readOnlyEditorChangeListener, "f")?.remove();
|
169
169
|
__classPrivateFieldSet(this, _BaseTool_readOnlyEditorChangeListener, null, "f");
|
170
|
+
__classPrivateFieldSet(this, _BaseTool_group, null, "f");
|
170
171
|
}
|
171
172
|
}
|
172
173
|
_BaseTool_enabled = new WeakMap(), _BaseTool_group = new WeakMap(), _BaseTool_inputMapper = new WeakMap(), _BaseTool_readOnlyEditorChangeListener = new WeakMap();
|
@@ -157,10 +157,12 @@ class Selection {
|
|
157
157
|
// Reset for the next drag
|
158
158
|
this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
|
159
159
|
this.transform = math_1.Mat33.identity;
|
160
|
-
// Make the commands undo-able
|
161
|
-
|
162
|
-
|
163
|
-
|
160
|
+
// Make the commands undo-able.
|
161
|
+
// Don't check for non-empty transforms because this breaks changing the
|
162
|
+
// z-index of the just-transformed commands.
|
163
|
+
//
|
164
|
+
// TODO: Check whether the selectedElems are already all toplevel.
|
165
|
+
await this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, fullTransform));
|
164
166
|
// Clear renderings of any in-progress transformations
|
165
167
|
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
166
168
|
wetInkRenderer.clear();
|
@@ -410,7 +412,7 @@ class Selection {
|
|
410
412
|
if (wasTransforming) {
|
411
413
|
// Don't update the selection's focus when redoing/undoing
|
412
414
|
const selectionToUpdate = null;
|
413
|
-
tmpApplyCommand = new
|
415
|
+
tmpApplyCommand = new _a.ApplyTransformationCommand(selectionToUpdate, this.selectedElems, this.transform);
|
414
416
|
// Transform to ensure that the duplicates are in the correct location
|
415
417
|
await tmpApplyCommand.apply(this.editor);
|
416
418
|
// Show items again
|
@@ -13,9 +13,32 @@ export default class ToolController implements InputEventListener {
|
|
13
13
|
/** @internal */
|
14
14
|
constructor(editor: Editor, localization: ToolLocalization);
|
15
15
|
setTools(tools: BaseTool[], primaryToolGroup?: ToolEnabledGroup): void;
|
16
|
+
/**
|
17
|
+
* Add a tool that acts like one of the primary tools (only one primary tool can be enabled at a time).
|
18
|
+
*
|
19
|
+
* If the tool is already added to this, the tool is converted to a primary tool.
|
20
|
+
*
|
21
|
+
* This should be called before creating the app's toolbar.
|
22
|
+
*/
|
16
23
|
addPrimaryTool(tool: BaseTool): void;
|
17
24
|
getPrimaryTools(): BaseTool[];
|
18
25
|
addTool(tool: BaseTool): void;
|
26
|
+
/**
|
27
|
+
* Removes **and destroys** all tools in `tools` from this.
|
28
|
+
*/
|
29
|
+
removeAndDestroyTools(tools: BaseTool[]): void;
|
30
|
+
private insertTools;
|
31
|
+
/**
|
32
|
+
* Removes a tool from this' tool list and replaces it with `replaceWith`.
|
33
|
+
*
|
34
|
+
* If any of `toolsToInsert` have already been added to this, the tools are
|
35
|
+
* moved.
|
36
|
+
*
|
37
|
+
* This should be called before creating the editor's toolbar.
|
38
|
+
*/
|
39
|
+
insertToolsAfter(insertAfter: BaseTool, toolsToInsert: BaseTool[]): void;
|
40
|
+
/** @see {@link insertToolsAfter} */
|
41
|
+
insertToolsBefore(insertBefore: BaseTool, toolsToInsert: BaseTool[]): void;
|
19
42
|
private onEventInternal;
|
20
43
|
/** Alias for {@link dispatchInputEvent}. */
|
21
44
|
onEvent(event: InputEvt): boolean;
|
@@ -115,14 +115,21 @@ class ToolController {
|
|
115
115
|
this.tools = tools;
|
116
116
|
this.primaryToolGroup = primaryToolGroup ?? new ToolEnabledGroup_1.default();
|
117
117
|
}
|
118
|
-
|
119
|
-
|
118
|
+
/**
|
119
|
+
* Add a tool that acts like one of the primary tools (only one primary tool can be enabled at a time).
|
120
|
+
*
|
121
|
+
* If the tool is already added to this, the tool is converted to a primary tool.
|
122
|
+
*
|
123
|
+
* This should be called before creating the app's toolbar.
|
124
|
+
*/
|
120
125
|
addPrimaryTool(tool) {
|
121
126
|
tool.setToolGroup(this.primaryToolGroup);
|
122
127
|
if (tool.isEnabled()) {
|
123
128
|
this.primaryToolGroup.notifyEnabled(tool);
|
124
129
|
}
|
125
|
-
this.
|
130
|
+
if (!this.tools.includes(tool)) {
|
131
|
+
this.addTool(tool);
|
132
|
+
}
|
126
133
|
}
|
127
134
|
getPrimaryTools() {
|
128
135
|
return this.tools.filter(tool => {
|
@@ -131,8 +138,62 @@ class ToolController {
|
|
131
138
|
}
|
132
139
|
// Add a tool to the end of this' tool list (the added tool receives events after tools already added to this).
|
133
140
|
// This should be called before creating the app's toolbar.
|
141
|
+
//
|
142
|
+
// A tool should only be added once.
|
134
143
|
addTool(tool) {
|
135
|
-
|
144
|
+
// Only add if not already present.
|
145
|
+
if (!this.tools.includes(tool)) {
|
146
|
+
this.tools.push(tool);
|
147
|
+
}
|
148
|
+
}
|
149
|
+
/**
|
150
|
+
* Removes **and destroys** all tools in `tools` from this.
|
151
|
+
*/
|
152
|
+
removeAndDestroyTools(tools) {
|
153
|
+
const newTools = [];
|
154
|
+
for (const tool of this.tools) {
|
155
|
+
if (tools.includes(tool)) {
|
156
|
+
if (this.activeTool === tool) {
|
157
|
+
this.activeTool = null;
|
158
|
+
}
|
159
|
+
tool.onDestroy();
|
160
|
+
}
|
161
|
+
else {
|
162
|
+
newTools.push(tool);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
this.tools = newTools;
|
166
|
+
}
|
167
|
+
insertTools(insertNear, toolsToInsert, mode) {
|
168
|
+
this.tools = this.tools.filter(tool => !toolsToInsert.includes(tool));
|
169
|
+
const newTools = [];
|
170
|
+
for (const tool of this.tools) {
|
171
|
+
if (mode === 'after') {
|
172
|
+
newTools.push(tool);
|
173
|
+
}
|
174
|
+
if (tool === insertNear) {
|
175
|
+
newTools.push(...toolsToInsert);
|
176
|
+
}
|
177
|
+
if (mode === 'before') {
|
178
|
+
newTools.push(tool);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
this.tools = newTools;
|
182
|
+
}
|
183
|
+
/**
|
184
|
+
* Removes a tool from this' tool list and replaces it with `replaceWith`.
|
185
|
+
*
|
186
|
+
* If any of `toolsToInsert` have already been added to this, the tools are
|
187
|
+
* moved.
|
188
|
+
*
|
189
|
+
* This should be called before creating the editor's toolbar.
|
190
|
+
*/
|
191
|
+
insertToolsAfter(insertAfter, toolsToInsert) {
|
192
|
+
this.insertTools(insertAfter, toolsToInsert, 'after');
|
193
|
+
}
|
194
|
+
/** @see {@link insertToolsAfter} */
|
195
|
+
insertToolsBefore(insertBefore, toolsToInsert) {
|
196
|
+
this.insertTools(insertBefore, toolsToInsert, 'before');
|
136
197
|
}
|
137
198
|
// @internal use `dispatchEvent` rather than calling `onEvent` directly.
|
138
199
|
onEventInternal(event) {
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
package/dist/cjs/version.js
CHANGED
package/dist/mjs/Editor.d.ts
CHANGED
@@ -64,6 +64,14 @@ export interface EditorSettings {
|
|
64
64
|
* Additional messages to show in the "about" dialog.
|
65
65
|
*/
|
66
66
|
notices: AboutDialogEntry[];
|
67
|
+
/**
|
68
|
+
* Information about the app/website js-draw is running within
|
69
|
+
* to show at the beginning of the about dialog.
|
70
|
+
*/
|
71
|
+
appInfo: {
|
72
|
+
name: string;
|
73
|
+
version?: string;
|
74
|
+
} | null;
|
67
75
|
}
|
68
76
|
/**
|
69
77
|
* The main entrypoint for the full editor.
|
package/dist/mjs/Editor.mjs
CHANGED
@@ -106,6 +106,7 @@ export class Editor {
|
|
106
106
|
keyboardShortcutOverrides: settings.keyboardShortcutOverrides ?? {},
|
107
107
|
iconProvider: settings.iconProvider ?? new IconProvider(),
|
108
108
|
notices: [],
|
109
|
+
appInfo: settings.appInfo ? { ...settings.appInfo } : null,
|
109
110
|
};
|
110
111
|
// Validate settings
|
111
112
|
if (this.settings.minZoom > this.settings.maxZoom) {
|
@@ -644,12 +645,16 @@ export class Editor {
|
|
644
645
|
htmlEvent.preventDefault();
|
645
646
|
}
|
646
647
|
});
|
647
|
-
elem.addEventListener('
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
648
|
+
elem.addEventListener('focusout', (event) => {
|
649
|
+
const stillHasFocus = event.relatedTarget && elem.contains(event.relatedTarget);
|
650
|
+
if (!stillHasFocus) {
|
651
|
+
for (const event of keysDown) {
|
652
|
+
this.toolController.dispatchInputEvent({
|
653
|
+
...event,
|
654
|
+
kind: InputEvtType.KeyUpEvent,
|
655
|
+
});
|
656
|
+
}
|
657
|
+
keysDown = [];
|
653
658
|
}
|
654
659
|
});
|
655
660
|
// Allow drop.
|
@@ -1116,16 +1121,34 @@ export class Editor {
|
|
1116
1121
|
showAboutDialog() {
|
1117
1122
|
const iconLicenseText = this.icons.licenseInfo();
|
1118
1123
|
const notices = [];
|
1124
|
+
if (this.settings.appInfo) {
|
1125
|
+
const versionLines = [];
|
1126
|
+
if (this.settings.appInfo.version) {
|
1127
|
+
versionLines.push(`v${this.settings.appInfo.version}`, '');
|
1128
|
+
}
|
1129
|
+
notices.push({
|
1130
|
+
heading: `${this.settings.appInfo.name}`,
|
1131
|
+
text: [
|
1132
|
+
...versionLines,
|
1133
|
+
`Image editor library: js-draw v${version.number}.`,
|
1134
|
+
].join('\n'),
|
1135
|
+
});
|
1136
|
+
}
|
1137
|
+
else {
|
1138
|
+
notices.push({
|
1139
|
+
heading: 'js-draw',
|
1140
|
+
text: `v${version.number}`,
|
1141
|
+
});
|
1142
|
+
}
|
1119
1143
|
notices.push({
|
1120
|
-
heading: '
|
1144
|
+
heading: 'Developer information',
|
1121
1145
|
text: [
|
1122
|
-
`v${version.number}`,
|
1123
|
-
'',
|
1124
1146
|
'Image debug information (from when this dialog was opened):',
|
1125
1147
|
` ${this.viewport.getScaleFactor()}x zoom, ${180 / Math.PI * this.viewport.getRotationAngle()} rotation`,
|
1126
1148
|
` ${this.image.estimateNumElements()} components`,
|
1127
1149
|
` ${this.getImportExportRect().w}x${this.getImportExportRect().h} size`,
|
1128
1150
|
].join('\n'),
|
1151
|
+
minimized: true,
|
1129
1152
|
});
|
1130
1153
|
notices.push({
|
1131
1154
|
heading: 'Libraries',
|
package/dist/mjs/SVGLoader.d.ts
CHANGED
@@ -11,9 +11,14 @@ export type SVGLoaderUnknownStyleAttribute = {
|
|
11
11
|
value: string;
|
12
12
|
priority?: string;
|
13
13
|
};
|
14
|
+
export declare enum SVGLoaderLoadMethod {
|
15
|
+
IFrame = "iframe",
|
16
|
+
DOMParser = "domparser"
|
17
|
+
}
|
14
18
|
export interface SVGLoaderOptions {
|
15
19
|
sanitize?: boolean;
|
16
20
|
disableUnknownObjectWarnings?: boolean;
|
21
|
+
loadMethod?: 'iframe' | 'domparser';
|
17
22
|
}
|
18
23
|
export default class SVGLoader implements ImageLoader {
|
19
24
|
private source;
|
@@ -32,6 +37,7 @@ export default class SVGLoader implements ImageLoader {
|
|
32
37
|
private attachUnrecognisedAttrs;
|
33
38
|
private addPath;
|
34
39
|
private addBackground;
|
40
|
+
private getComputedStyle;
|
35
41
|
private getTransform;
|
36
42
|
private makeText;
|
37
43
|
private addText;
|