js-draw 1.2.2 → 1.3.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 +29 -29
- package/dist/Editor.css +65 -4
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +73 -40
- package/dist/cjs/Editor.js +90 -24
- package/dist/cjs/EditorImage.d.ts +58 -6
- package/dist/cjs/EditorImage.js +336 -60
- package/dist/cjs/SVGLoader.d.ts +10 -4
- package/dist/cjs/SVGLoader.js +67 -32
- package/dist/cjs/UndoRedoHistory.d.ts +2 -2
- package/dist/cjs/UndoRedoHistory.js +4 -2
- package/dist/cjs/Viewport.d.ts +2 -1
- package/dist/cjs/Viewport.js +12 -3
- package/dist/cjs/commands/Command.d.ts +1 -0
- package/dist/cjs/commands/Command.js +1 -0
- package/dist/cjs/commands/Erase.js +1 -1
- package/dist/cjs/commands/SerializableCommand.d.ts +1 -1
- package/dist/cjs/commands/SerializableCommand.js +16 -2
- package/dist/cjs/commands/localization.d.ts +2 -0
- package/dist/cjs/commands/localization.js +2 -0
- package/dist/cjs/components/AbstractComponent.d.ts +38 -0
- package/dist/cjs/components/AbstractComponent.js +31 -0
- package/dist/cjs/components/BackgroundComponent.d.ts +10 -1
- package/dist/cjs/components/BackgroundComponent.js +61 -6
- package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -1
- package/dist/cjs/components/SVGGlobalAttributesObject.js +30 -1
- package/dist/cjs/components/Stroke.d.ts +1 -0
- package/dist/cjs/components/Stroke.js +44 -0
- package/dist/cjs/components/UnknownSVGObject.d.ts +2 -1
- package/dist/cjs/components/UnknownSVGObject.js +30 -1
- package/dist/cjs/lib.d.ts +2 -45
- package/dist/cjs/lib.js +2 -45
- package/dist/cjs/rendering/RenderingStyle.d.ts +1 -0
- package/dist/cjs/rendering/renderers/AbstractRenderer.js +1 -1
- package/dist/cjs/rendering/renderers/SVGRenderer.js +8 -19
- package/dist/cjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
- package/dist/cjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
- package/dist/cjs/shortcuts/KeyboardShortcutManager.js +2 -2
- package/dist/cjs/toolbar/localization.d.ts +1 -0
- package/dist/cjs/toolbar/localization.js +1 -0
- package/dist/cjs/toolbar/widgets/BaseWidget.js +5 -0
- package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +54 -25
- package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +8 -0
- package/dist/cjs/tools/PanZoom.js +13 -8
- package/dist/cjs/tools/ScrollbarTool.d.ts +18 -0
- package/dist/cjs/tools/ScrollbarTool.js +85 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
- package/dist/cjs/tools/ToolController.js +2 -0
- package/dist/cjs/types.d.ts +3 -1
- package/dist/cjs/util/assertions.d.ts +4 -0
- package/dist/cjs/util/assertions.js +12 -1
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +73 -40
- package/dist/mjs/Editor.mjs +90 -24
- package/dist/mjs/EditorImage.d.ts +58 -6
- package/dist/mjs/EditorImage.mjs +313 -61
- package/dist/mjs/SVGLoader.d.ts +10 -4
- package/dist/mjs/SVGLoader.mjs +66 -31
- package/dist/mjs/UndoRedoHistory.d.ts +2 -2
- package/dist/mjs/UndoRedoHistory.mjs +4 -2
- package/dist/mjs/Viewport.d.ts +2 -1
- package/dist/mjs/Viewport.mjs +12 -3
- package/dist/mjs/commands/Command.d.ts +1 -0
- package/dist/mjs/commands/Command.mjs +1 -0
- package/dist/mjs/commands/Erase.mjs +1 -1
- package/dist/mjs/commands/SerializableCommand.d.ts +1 -1
- package/dist/mjs/commands/SerializableCommand.mjs +16 -2
- package/dist/mjs/commands/localization.d.ts +2 -0
- package/dist/mjs/commands/localization.mjs +2 -0
- package/dist/mjs/components/AbstractComponent.d.ts +38 -0
- package/dist/mjs/components/AbstractComponent.mjs +30 -0
- package/dist/mjs/components/BackgroundComponent.d.ts +10 -1
- package/dist/mjs/components/BackgroundComponent.mjs +38 -6
- package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -1
- package/dist/mjs/components/SVGGlobalAttributesObject.mjs +7 -1
- package/dist/mjs/components/Stroke.d.ts +1 -0
- package/dist/mjs/components/Stroke.mjs +44 -0
- package/dist/mjs/components/UnknownSVGObject.d.ts +2 -1
- package/dist/mjs/components/UnknownSVGObject.mjs +7 -1
- package/dist/mjs/lib.d.ts +2 -45
- package/dist/mjs/lib.mjs +2 -45
- package/dist/mjs/rendering/RenderingStyle.d.ts +1 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +1 -1
- package/dist/mjs/rendering/renderers/SVGRenderer.mjs +8 -19
- package/dist/mjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
- package/dist/mjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
- package/dist/mjs/shortcuts/KeyboardShortcutManager.mjs +2 -2
- package/dist/mjs/toolbar/localization.d.ts +1 -0
- package/dist/mjs/toolbar/localization.mjs +1 -0
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +5 -0
- package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -25
- package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +8 -0
- package/dist/mjs/tools/PanZoom.mjs +13 -8
- package/dist/mjs/tools/ScrollbarTool.d.ts +18 -0
- package/dist/mjs/tools/ScrollbarTool.mjs +79 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
- package/dist/mjs/tools/ToolController.mjs +2 -0
- package/dist/mjs/types.d.ts +3 -1
- package/dist/mjs/util/assertions.d.ts +4 -0
- package/dist/mjs/util/assertions.mjs +10 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +3 -4
- package/src/Editor.scss +8 -0
- package/src/dialogs/dialogs.scss +2 -1
- package/src/toolbar/EdgeToolbar.scss +4 -1
- package/src/toolbar/widgets/DocumentPropertiesWidget.scss +12 -0
- package/src/toolbar/widgets/components/makeGridSelector.scss +1 -1
- package/src/tools/ScrollbarTool.scss +57 -0
- package/src/tools/{SoundUITool.css → SoundUITool.scss} +4 -0
- package/src/tools/tools.scss +2 -1
@@ -0,0 +1,85 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const math_1 = require("@js-draw/math");
|
7
|
+
const types_1 = require("../types");
|
8
|
+
const BaseTool_1 = __importDefault(require("./BaseTool"));
|
9
|
+
/**
|
10
|
+
* This tool, when enabled, renders scrollbars reflecting the current position
|
11
|
+
* of the view relative to the import/export area of the image.
|
12
|
+
*
|
13
|
+
* **Note**: These scrollbars are currently not draggable. This may change in
|
14
|
+
* a future release.
|
15
|
+
*/
|
16
|
+
class ScrollbarTool extends BaseTool_1.default {
|
17
|
+
constructor(editor) {
|
18
|
+
super(editor.notifier, 'scrollbar');
|
19
|
+
this.editor = editor;
|
20
|
+
this.fadeOutTimeout = null;
|
21
|
+
this.scrollbarOverlay = document.createElement('div');
|
22
|
+
this.scrollbarOverlay.classList.add('ScrollbarTool-overlay');
|
23
|
+
this.verticalScrollbar = document.createElement('div');
|
24
|
+
this.verticalScrollbar.classList.add('vertical-scrollbar');
|
25
|
+
this.horizontalScrollbar = document.createElement('div');
|
26
|
+
this.horizontalScrollbar.classList.add('horizontal-scrollbar');
|
27
|
+
this.scrollbarOverlay.replaceChildren(this.verticalScrollbar, this.horizontalScrollbar);
|
28
|
+
let overlay = null;
|
29
|
+
let viewportListener = null;
|
30
|
+
this.enabledValue().onUpdateAndNow(enabled => {
|
31
|
+
overlay?.remove();
|
32
|
+
viewportListener?.remove();
|
33
|
+
viewportListener = null;
|
34
|
+
overlay = null;
|
35
|
+
if (enabled) {
|
36
|
+
viewportListener = editor.notifier.on(types_1.EditorEventType.ViewportChanged, _event => {
|
37
|
+
this.updateScrollbars();
|
38
|
+
});
|
39
|
+
this.updateScrollbars();
|
40
|
+
overlay = editor.createHTMLOverlay(this.scrollbarOverlay);
|
41
|
+
}
|
42
|
+
});
|
43
|
+
}
|
44
|
+
updateScrollbars() {
|
45
|
+
const viewport = this.editor.viewport;
|
46
|
+
const screenSize = viewport.getScreenRectSize();
|
47
|
+
const screenRect = new math_1.Rect2(0, 0, screenSize.x, screenSize.y);
|
48
|
+
const imageRect = this.editor.getImportExportRect()
|
49
|
+
// The scrollbars are positioned in screen coordinates, so the exportRect also needs
|
50
|
+
// to be in screen coordinates
|
51
|
+
.transformedBoundingBox(viewport.canvasToScreenTransform)
|
52
|
+
// If the screenRect is outside of the exportRect, expand the image rectangle
|
53
|
+
.union(screenRect);
|
54
|
+
const scrollbarWidth = screenRect.width / imageRect.width * screenSize.x;
|
55
|
+
const scrollbarHeight = screenRect.height / imageRect.height * screenSize.y;
|
56
|
+
const scrollbarX = (screenRect.x - imageRect.x) / imageRect.width * (screenSize.x);
|
57
|
+
const scrollbarY = (screenRect.y - imageRect.y) / imageRect.height * (screenSize.y);
|
58
|
+
this.horizontalScrollbar.style.width = `${scrollbarWidth}px`;
|
59
|
+
this.verticalScrollbar.style.height = `${scrollbarHeight}px`;
|
60
|
+
this.horizontalScrollbar.style.marginLeft = `${scrollbarX}px`;
|
61
|
+
this.verticalScrollbar.style.marginTop = `${scrollbarY}px`;
|
62
|
+
// Style the scrollbars differently when there's no scroll (all content visible)
|
63
|
+
const handleNoScrollStyling = (scrollbar, size, fillSize) => {
|
64
|
+
const fillsWindowClass = 'represents-no-scroll';
|
65
|
+
if (Math.abs(size - fillSize) < 1e-8) {
|
66
|
+
scrollbar.classList.add(fillsWindowClass);
|
67
|
+
}
|
68
|
+
else {
|
69
|
+
scrollbar.classList.remove(fillsWindowClass);
|
70
|
+
}
|
71
|
+
};
|
72
|
+
handleNoScrollStyling(this.horizontalScrollbar, scrollbarWidth, screenSize.x);
|
73
|
+
handleNoScrollStyling(this.verticalScrollbar, scrollbarHeight, screenSize.y);
|
74
|
+
// Fade out after a delay.
|
75
|
+
if (this.fadeOutTimeout !== null) {
|
76
|
+
clearTimeout(this.fadeOutTimeout);
|
77
|
+
}
|
78
|
+
const fadeOutDelay = 3000;
|
79
|
+
this.fadeOutTimeout = setTimeout(() => {
|
80
|
+
this.scrollbarOverlay.classList.remove('just-updated');
|
81
|
+
}, fadeOutDelay);
|
82
|
+
this.scrollbarOverlay.classList.add('just-updated');
|
83
|
+
}
|
84
|
+
}
|
85
|
+
exports.default = ScrollbarTool;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -46,6 +46,7 @@ const SoundUITool_1 = __importDefault(require("./SoundUITool"));
|
|
46
46
|
const inputEvents_1 = require("../inputEvents");
|
47
47
|
const InputPipeline_1 = __importDefault(require("./InputFilter/InputPipeline"));
|
48
48
|
const InputStabilizer_1 = __importDefault(require("./InputFilter/InputStabilizer"));
|
49
|
+
const ScrollbarTool_1 = __importDefault(require("./ScrollbarTool"));
|
49
50
|
class ToolController {
|
50
51
|
/** @internal */
|
51
52
|
constructor(editor, localization) {
|
@@ -79,6 +80,7 @@ class ToolController {
|
|
79
80
|
const soundExplorer = new SoundUITool_1.default(editor, localization.soundExplorer);
|
80
81
|
soundExplorer.setEnabled(false);
|
81
82
|
this.tools = [
|
83
|
+
new ScrollbarTool_1.default(editor),
|
82
84
|
new PipetteTool_1.default(editor, localization.pipetteTool),
|
83
85
|
soundExplorer,
|
84
86
|
panZoomTool,
|
package/dist/cjs/types.d.ts
CHANGED
@@ -82,7 +82,9 @@ export interface ToolbarDropdownShownEvent {
|
|
82
82
|
export type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | SelectionUpdated | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
|
83
83
|
export type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null | void;
|
84
84
|
export type ComponentAddedListener = (component: AbstractComponent) => Promise<void> | void;
|
85
|
-
export type OnDetermineExportRectListener = (exportRect: Rect2
|
85
|
+
export type OnDetermineExportRectListener = (exportRect: Rect2, options?: {
|
86
|
+
autoresize: boolean;
|
87
|
+
}) => void;
|
86
88
|
export interface ImageLoader {
|
87
89
|
start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener): Promise<void>;
|
88
90
|
}
|
@@ -21,3 +21,7 @@ export declare const assertIsNumber: (value: any, allowNaN?: boolean) => value i
|
|
21
21
|
* Throws if any of `values` is not of type number.
|
22
22
|
*/
|
23
23
|
export declare const assertIsNumberArray: (values: any[], allowNaN?: boolean) => values is number[];
|
24
|
+
/**
|
25
|
+
* Throws an exception if `typeof value` is not a boolean.
|
26
|
+
*/
|
27
|
+
export declare const assertIsBoolean: (value: any) => value is boolean;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.assertIsNumberArray = exports.assertIsNumber = exports.assertUnreachable = void 0;
|
3
|
+
exports.assertIsBoolean = exports.assertIsNumberArray = exports.assertIsNumber = exports.assertUnreachable = void 0;
|
4
4
|
/**
|
5
5
|
* Compile-time assertion that a branch of code is unreachable.
|
6
6
|
* @internal
|
@@ -49,3 +49,14 @@ const assertIsNumberArray = (values, allowNaN = false) => {
|
|
49
49
|
return true;
|
50
50
|
};
|
51
51
|
exports.assertIsNumberArray = assertIsNumberArray;
|
52
|
+
/**
|
53
|
+
* Throws an exception if `typeof value` is not a boolean.
|
54
|
+
*/
|
55
|
+
const assertIsBoolean = (value) => {
|
56
|
+
if (typeof value !== 'boolean') {
|
57
|
+
throw new Error('Given value is not a boolean');
|
58
|
+
// return false;
|
59
|
+
}
|
60
|
+
return true;
|
61
|
+
};
|
62
|
+
exports.assertIsBoolean = assertIsBoolean;
|
package/dist/cjs/version.js
CHANGED
package/dist/mjs/Editor.d.ts
CHANGED
@@ -16,7 +16,13 @@ import KeyBinding from './shortcuts/KeyBinding';
|
|
16
16
|
import AbstractToolbar from './toolbar/AbstractToolbar';
|
17
17
|
import RenderablePathSpec from './rendering/RenderablePathSpec';
|
18
18
|
import { AboutDialogEntry } from './dialogs/makeAboutDialog';
|
19
|
-
/**
|
19
|
+
/**
|
20
|
+
* Provides settings to an instance of an editor. See the Editor {@link Editor.constructor}.
|
21
|
+
*
|
22
|
+
* ## Example
|
23
|
+
*
|
24
|
+
* [[include:doc-pages/inline-examples/settings-example-1.md]]
|
25
|
+
*/
|
20
26
|
export interface EditorSettings {
|
21
27
|
/** Defaults to `RenderingMode.CanvasRenderer` */
|
22
28
|
renderingMode: RenderingMode;
|
@@ -36,41 +42,21 @@ export interface EditorSettings {
|
|
36
42
|
* Overrides for keyboard shortcuts. For example,
|
37
43
|
* ```ts
|
38
44
|
* {
|
39
|
-
* 'some.shortcut.id': [
|
45
|
+
* 'some.shortcut.id': [ KeyBinding.keyboardShortcutFromString('ctrl+a') ],
|
40
46
|
* 'another.shortcut.id': [ ]
|
41
47
|
* }
|
42
48
|
* ```
|
43
49
|
* where shortcut IDs map to lists of associated keybindings.
|
50
|
+
*
|
51
|
+
* @see
|
52
|
+
* - {@link KeyBinding}
|
53
|
+
* - {@link KeyboardShortcutManager}
|
44
54
|
*/
|
45
55
|
keyboardShortcutOverrides: Record<string, Array<KeyBinding>>;
|
46
56
|
/**
|
47
57
|
* Provides a set of icons for the editor.
|
48
58
|
*
|
49
59
|
* See, for example, the `@js-draw/material-icons` package.
|
50
|
-
*
|
51
|
-
* @example
|
52
|
-
* ```ts,runnable
|
53
|
-
* import * as jsdraw from 'js-draw';
|
54
|
-
* import MaterialIconProvider from '@js-draw/material-icons';
|
55
|
-
* import 'js-draw/styles';
|
56
|
-
*
|
57
|
-
* const settings: Partial<jsdraw.EditorSettings> = {
|
58
|
-
* // Default to material icons
|
59
|
-
* iconProvider: new MaterialIconProvider(),
|
60
|
-
*
|
61
|
-
* // Only scroll the editor if it's focused.
|
62
|
-
* wheelEventsEnabled: 'only-if-focused',
|
63
|
-
* };
|
64
|
-
*
|
65
|
-
* // Add an editor to the document, using the above settings
|
66
|
-
* const editor = new jsdraw.Editor(document.body, settings);
|
67
|
-
*
|
68
|
-
* // Add a toolbar to the editor
|
69
|
-
* const toolbar = jsdraw.makeEdgeToolbar(editor);
|
70
|
-
*
|
71
|
-
* // Add the default tool items
|
72
|
-
* toolbar.addDefaults();
|
73
|
-
* ```
|
74
60
|
*/
|
75
61
|
iconProvider: IconProvider;
|
76
62
|
/**
|
@@ -81,20 +67,22 @@ export interface EditorSettings {
|
|
81
67
|
/**
|
82
68
|
* The main entrypoint for the full editor.
|
83
69
|
*
|
84
|
-
*
|
70
|
+
* ## Example
|
85
71
|
* To create an editor with a toolbar,
|
86
|
-
* ```
|
72
|
+
* ```ts,runnable
|
73
|
+
* import { Editor } from 'js-draw';
|
74
|
+
*
|
87
75
|
* const editor = new Editor(document.body);
|
88
76
|
*
|
89
77
|
* const toolbar = editor.addToolbar();
|
90
|
-
* toolbar.
|
78
|
+
* toolbar.addSaveButton(() => {
|
91
79
|
* const saveData = editor.toSVG().outerHTML;
|
92
80
|
* // Do something with saveData...
|
93
81
|
* });
|
94
82
|
* ```
|
95
83
|
*
|
96
84
|
* See also
|
97
|
-
* [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts
|
85
|
+
* [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts).
|
98
86
|
*/
|
99
87
|
export declare class Editor {
|
100
88
|
private container;
|
@@ -120,12 +108,13 @@ export declare class Editor {
|
|
120
108
|
* Data structure for adding/removing/querying objects in the image.
|
121
109
|
*
|
122
110
|
* @example
|
123
|
-
* ```
|
111
|
+
* ```ts,runnable
|
112
|
+
* import { Editor, Stroke, Path, Color4, pathToRenderable } from 'js-draw';
|
124
113
|
* const editor = new Editor(document.body);
|
125
114
|
*
|
126
115
|
* // Create a path.
|
127
116
|
* const stroke = new Stroke([
|
128
|
-
* Path.fromString('M0,0
|
117
|
+
* pathToRenderable(Path.fromString('M0,0 L100,100 L300,30 z'), { fill: Color4.red }),
|
129
118
|
* ]);
|
130
119
|
* const addElementCommand = editor.image.addElement(stroke);
|
131
120
|
*
|
@@ -166,7 +155,9 @@ export declare class Editor {
|
|
166
155
|
private settings;
|
167
156
|
/**
|
168
157
|
* @example
|
169
|
-
* ```
|
158
|
+
* ```ts,runnable
|
159
|
+
* import { Editor } from 'js-draw';
|
160
|
+
*
|
170
161
|
* const container = document.body;
|
171
162
|
*
|
172
163
|
* // Create an editor
|
@@ -179,13 +170,16 @@ export declare class Editor {
|
|
179
170
|
* // Add the default toolbar
|
180
171
|
* const toolbar = editor.addToolbar();
|
181
172
|
*
|
182
|
-
*
|
173
|
+
* const createCustomIcon = () => {
|
174
|
+
* // Create/return an icon here.
|
175
|
+
* };
|
176
|
+
*
|
177
|
+
* // Add a custom button
|
183
178
|
* toolbar.addActionButton({
|
184
|
-
* label: '
|
185
|
-
* icon:
|
179
|
+
* label: 'Custom Button'
|
180
|
+
* icon: createCustomIcon(),
|
186
181
|
* }, () => {
|
187
|
-
*
|
188
|
-
* // Do something with saveData
|
182
|
+
* // Do something here
|
189
183
|
* });
|
190
184
|
* ```
|
191
185
|
*/
|
@@ -330,6 +324,9 @@ export declare class Editor {
|
|
330
324
|
/**
|
331
325
|
* Clears the wet ink display.
|
332
326
|
*
|
327
|
+
* The wet ink display can be used by the currently active tool to display a preview
|
328
|
+
* of an in-progress action.
|
329
|
+
*
|
333
330
|
* @see {@link Display.getWetInkRenderer}
|
334
331
|
*/
|
335
332
|
clearWetInk(): void;
|
@@ -341,12 +338,19 @@ export declare class Editor {
|
|
341
338
|
* Creates an element that will be positioned on top of the dry/wet ink
|
342
339
|
* renderers.
|
343
340
|
*
|
341
|
+
* So as not to change the position of other overlays, `overlay` should either
|
342
|
+
* be styled to have 0 height or have `position: absolute`.
|
343
|
+
*
|
344
344
|
* This is useful for displaying content on top of the rendered content
|
345
345
|
* (e.g. a selection box).
|
346
346
|
*/
|
347
347
|
createHTMLOverlay(overlay: HTMLElement): {
|
348
348
|
remove: () => void;
|
349
349
|
};
|
350
|
+
/**
|
351
|
+
* Creates a CSS stylesheet with `content` and applies it to the document
|
352
|
+
* (and thus, to this editor).
|
353
|
+
*/
|
350
354
|
addStyleSheet(content: string): HTMLStyleElement;
|
351
355
|
/**
|
352
356
|
* Dispatch a keyboard event to the currently selected tool.
|
@@ -370,7 +374,21 @@ export declare class Editor {
|
|
370
374
|
*/
|
371
375
|
sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
|
372
376
|
addAndCenterComponents(components: AbstractComponent[], selectComponents?: boolean): Promise<void>;
|
377
|
+
/**
|
378
|
+
* Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
|
379
|
+
* If `format` is not `image/png`, a PNG image URL may still be returned (as in the
|
380
|
+
* case of `HTMLCanvasElement::toDataURL`).
|
381
|
+
*
|
382
|
+
* The export resolution is the same as the size of the drawing canvas, unless `outputSize`
|
383
|
+
* is given.
|
384
|
+
*/
|
373
385
|
toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp', outputSize?: Vec2): string;
|
386
|
+
/**
|
387
|
+
* Converts the editor's content into an SVG image.
|
388
|
+
*
|
389
|
+
* @see
|
390
|
+
* {@link SVGRenderer}
|
391
|
+
*/
|
374
392
|
toSVG(): SVGElement;
|
375
393
|
/**
|
376
394
|
* Load editor data from an `ImageLoader` (e.g. an {@link SVGLoader}).
|
@@ -393,8 +411,23 @@ export declare class Editor {
|
|
393
411
|
/**
|
394
412
|
* Alias for `loadFrom(SVGLoader.fromString)`.
|
395
413
|
*
|
396
|
-
*
|
397
|
-
*
|
414
|
+
* @example
|
415
|
+
* ```ts,runnable
|
416
|
+
* import {Editor} from 'js-draw';
|
417
|
+
* const editor = new Editor(document.body);
|
418
|
+
*
|
419
|
+
* ---visible---
|
420
|
+
* await editor.loadFromSVG(`
|
421
|
+
* <svg viewBox="5 23 52 30" width="52" height="16" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
|
422
|
+
* <text style="
|
423
|
+
* transform: matrix(0.181846, 0.1, 0, 0.181846, 11.4, 33.2);
|
424
|
+
* font-family: serif;
|
425
|
+
* font-size: 32px;
|
426
|
+
* fill: rgb(100, 140, 61);
|
427
|
+
* ">An SVG image!</text>
|
428
|
+
* </svg>
|
429
|
+
* `);
|
430
|
+
* ```
|
398
431
|
*/
|
399
432
|
loadFromSVG(svgData: string, sanitize?: boolean): Promise<void>;
|
400
433
|
private closeAboutDialog;
|
package/dist/mjs/Editor.mjs
CHANGED
@@ -8,7 +8,7 @@ import EventDispatcher from './EventDispatcher.mjs';
|
|
8
8
|
import { Vec2, Vec3, Color4, Mat33, toRoundedString } from '@js-draw/math';
|
9
9
|
import Display, { RenderingMode } from './rendering/Display.mjs';
|
10
10
|
import SVGRenderer from './rendering/renderers/SVGRenderer.mjs';
|
11
|
-
import SVGLoader from './SVGLoader.mjs';
|
11
|
+
import SVGLoader, { svgLoaderAutoresizeClassName } from './SVGLoader.mjs';
|
12
12
|
import Pointer from './Pointer.mjs';
|
13
13
|
import getLocalizationTable from './localizations/getLocalizationTable.mjs';
|
14
14
|
import IconProvider from './toolbar/IconProvider.mjs';
|
@@ -29,25 +29,29 @@ import version from './version.mjs';
|
|
29
29
|
/**
|
30
30
|
* The main entrypoint for the full editor.
|
31
31
|
*
|
32
|
-
*
|
32
|
+
* ## Example
|
33
33
|
* To create an editor with a toolbar,
|
34
|
-
* ```
|
34
|
+
* ```ts,runnable
|
35
|
+
* import { Editor } from 'js-draw';
|
36
|
+
*
|
35
37
|
* const editor = new Editor(document.body);
|
36
38
|
*
|
37
39
|
* const toolbar = editor.addToolbar();
|
38
|
-
* toolbar.
|
40
|
+
* toolbar.addSaveButton(() => {
|
39
41
|
* const saveData = editor.toSVG().outerHTML;
|
40
42
|
* // Do something with saveData...
|
41
43
|
* });
|
42
44
|
* ```
|
43
45
|
*
|
44
46
|
* See also
|
45
|
-
* [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts
|
47
|
+
* [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts).
|
46
48
|
*/
|
47
49
|
export class Editor {
|
48
50
|
/**
|
49
51
|
* @example
|
50
|
-
* ```
|
52
|
+
* ```ts,runnable
|
53
|
+
* import { Editor } from 'js-draw';
|
54
|
+
*
|
51
55
|
* const container = document.body;
|
52
56
|
*
|
53
57
|
* // Create an editor
|
@@ -60,13 +64,16 @@ export class Editor {
|
|
60
64
|
* // Add the default toolbar
|
61
65
|
* const toolbar = editor.addToolbar();
|
62
66
|
*
|
63
|
-
*
|
67
|
+
* const createCustomIcon = () => {
|
68
|
+
* // Create/return an icon here.
|
69
|
+
* };
|
70
|
+
*
|
71
|
+
* // Add a custom button
|
64
72
|
* toolbar.addActionButton({
|
65
|
-
* label: '
|
66
|
-
* icon:
|
73
|
+
* label: 'Custom Button'
|
74
|
+
* icon: createCustomIcon(),
|
67
75
|
* }, () => {
|
68
|
-
*
|
69
|
-
* // Do something with saveData
|
76
|
+
* // Do something here
|
70
77
|
* });
|
71
78
|
* ```
|
72
79
|
*/
|
@@ -99,6 +106,10 @@ export class Editor {
|
|
99
106
|
iconProvider: settings.iconProvider ?? new IconProvider(),
|
100
107
|
notices: [],
|
101
108
|
};
|
109
|
+
// Validate settings
|
110
|
+
if (this.settings.minZoom > this.settings.maxZoom) {
|
111
|
+
throw new Error('Minimum zoom must be lesser than maximum zoom!');
|
112
|
+
}
|
102
113
|
this.icons = this.settings.iconProvider;
|
103
114
|
this.shortcuts = new KeyboardShortcutManager(this.settings.keyboardShortcutOverrides);
|
104
115
|
this.container = document.createElement('div');
|
@@ -153,6 +164,10 @@ export class Editor {
|
|
153
164
|
if (oldZoom <= this.settings.maxZoom && oldZoom >= this.settings.minZoom) {
|
154
165
|
resetTransform = evt.oldTransform;
|
155
166
|
}
|
167
|
+
else {
|
168
|
+
// If 1x zoom isn't acceptable, try a zoom between the minimum and maximum.
|
169
|
+
resetTransform = Mat33.scaling2D((this.settings.minZoom + this.settings.maxZoom) / 2);
|
170
|
+
}
|
156
171
|
this.viewport.resetTransform(resetTransform);
|
157
172
|
}
|
158
173
|
}
|
@@ -206,6 +221,7 @@ export class Editor {
|
|
206
221
|
registerListeners() {
|
207
222
|
this.handlePointerEventsFrom(this.renderingRegion);
|
208
223
|
this.handleKeyEventsFrom(this.renderingRegion);
|
224
|
+
this.handlePointerEventsFrom(this.accessibilityAnnounceArea);
|
209
225
|
this.container.addEventListener('wheel', evt => {
|
210
226
|
let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
|
211
227
|
// Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
|
@@ -282,6 +298,8 @@ export class Editor {
|
|
282
298
|
// still fill the screen.
|
283
299
|
this.container.style.setProperty('--editor-current-width-px', `${this.container.clientWidth}px`);
|
284
300
|
this.container.style.setProperty('--editor-current-height-px', `${this.container.clientHeight}px`);
|
301
|
+
this.container.style.setProperty('--editor-current-display-width-px', `${this.renderingRegion.clientWidth}px`);
|
302
|
+
this.container.style.setProperty('--editor-current-display-height-px', `${this.renderingRegion.clientHeight}px`);
|
285
303
|
}
|
286
304
|
getPointerList() {
|
287
305
|
const nowTime = performance.now();
|
@@ -702,12 +720,12 @@ export class Editor {
|
|
702
720
|
this.display.setDraftMode(false);
|
703
721
|
this.hideLoadingWarning();
|
704
722
|
}
|
705
|
-
// @see {@link
|
723
|
+
// @see {@link asyncApplyOrUnapplyCommands }
|
706
724
|
asyncApplyCommands(commands, chunkSize) {
|
707
725
|
return this.asyncApplyOrUnapplyCommands(commands, true, chunkSize);
|
708
726
|
}
|
709
727
|
// If `unapplyInReverseOrder`, commands are reversed before unapplying.
|
710
|
-
// @see {@link
|
728
|
+
// @see {@link asyncApplyOrUnapplyCommands }
|
711
729
|
asyncUnapplyCommands(commands, chunkSize, unapplyInReverseOrder = false) {
|
712
730
|
if (unapplyInReverseOrder) {
|
713
731
|
commands = [...commands]; // copy
|
@@ -754,8 +772,8 @@ export class Editor {
|
|
754
772
|
}
|
755
773
|
const renderer = this.display.getDryInkRenderer();
|
756
774
|
this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
|
757
|
-
|
758
|
-
|
775
|
+
// Draw a rectangle around the region that will be visible on save
|
776
|
+
if (showImageBounds && !this.image.getAutoresizeEnabled()) {
|
759
777
|
const exportRectFill = { fill: Color4.fromHex('#44444455') };
|
760
778
|
const exportRectStrokeWidth = 5 * this.viewport.getSizeOfPixelOnCanvas();
|
761
779
|
renderer.drawRect(this.getImportExportRect(), exportRectStrokeWidth, exportRectFill);
|
@@ -778,6 +796,9 @@ export class Editor {
|
|
778
796
|
/**
|
779
797
|
* Clears the wet ink display.
|
780
798
|
*
|
799
|
+
* The wet ink display can be used by the currently active tool to display a preview
|
800
|
+
* of an in-progress action.
|
801
|
+
*
|
781
802
|
* @see {@link Display.getWetInkRenderer}
|
782
803
|
*/
|
783
804
|
clearWetInk() {
|
@@ -793,16 +814,23 @@ export class Editor {
|
|
793
814
|
* Creates an element that will be positioned on top of the dry/wet ink
|
794
815
|
* renderers.
|
795
816
|
*
|
817
|
+
* So as not to change the position of other overlays, `overlay` should either
|
818
|
+
* be styled to have 0 height or have `position: absolute`.
|
819
|
+
*
|
796
820
|
* This is useful for displaying content on top of the rendered content
|
797
821
|
* (e.g. a selection box).
|
798
822
|
*/
|
799
823
|
createHTMLOverlay(overlay) {
|
800
|
-
overlay.classList.add('overlay');
|
824
|
+
overlay.classList.add('overlay', 'js-draw-editor-overlay');
|
801
825
|
this.container.appendChild(overlay);
|
802
826
|
return {
|
803
827
|
remove: () => overlay.remove(),
|
804
828
|
};
|
805
829
|
}
|
830
|
+
/**
|
831
|
+
* Creates a CSS stylesheet with `content` and applies it to the document
|
832
|
+
* (and thus, to this editor).
|
833
|
+
*/
|
806
834
|
addStyleSheet(content) {
|
807
835
|
const styleSheet = document.createElement('style');
|
808
836
|
styleSheet.innerText = content;
|
@@ -883,11 +911,14 @@ export class Editor {
|
|
883
911
|
}
|
884
912
|
}
|
885
913
|
}
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
914
|
+
/**
|
915
|
+
* Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
|
916
|
+
* If `format` is not `image/png`, a PNG image URL may still be returned (as in the
|
917
|
+
* case of `HTMLCanvasElement::toDataURL`).
|
918
|
+
*
|
919
|
+
* The export resolution is the same as the size of the drawing canvas, unless `outputSize`
|
920
|
+
* is given.
|
921
|
+
*/
|
891
922
|
toDataURL(format = 'image/png', outputSize) {
|
892
923
|
const canvas = document.createElement('canvas');
|
893
924
|
const importExportViewport = this.image.getImportExportViewport();
|
@@ -904,6 +935,12 @@ export class Editor {
|
|
904
935
|
const dataURL = canvas.toDataURL(format);
|
905
936
|
return dataURL;
|
906
937
|
}
|
938
|
+
/**
|
939
|
+
* Converts the editor's content into an SVG image.
|
940
|
+
*
|
941
|
+
* @see
|
942
|
+
* {@link SVGRenderer}
|
943
|
+
*/
|
907
944
|
toSVG() {
|
908
945
|
const importExportViewport = this.image.getImportExportViewport().getTemporaryClone();
|
909
946
|
const sanitize = false;
|
@@ -919,6 +956,12 @@ export class Editor {
|
|
919
956
|
result.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
|
920
957
|
result.setAttribute('width', toRoundedString(rect.w));
|
921
958
|
result.setAttribute('height', toRoundedString(rect.h));
|
959
|
+
if (this.image.getAutoresizeEnabled()) {
|
960
|
+
result.classList.add(svgLoaderAutoresizeClassName);
|
961
|
+
}
|
962
|
+
else {
|
963
|
+
result.classList.remove(svgLoaderAutoresizeClassName);
|
964
|
+
}
|
922
965
|
return result;
|
923
966
|
}
|
924
967
|
/**
|
@@ -931,6 +974,7 @@ export class Editor {
|
|
931
974
|
this.display.setDraftMode(true);
|
932
975
|
const originalBackgrounds = this.image.getBackgroundComponents();
|
933
976
|
const eraseBackgroundCommand = new Erase(originalBackgrounds);
|
977
|
+
let autoresizeEnabled = false;
|
934
978
|
await loader.start(async (component) => {
|
935
979
|
await this.dispatchNoAnnounce(EditorImage.addElement(component));
|
936
980
|
}, (countProcessed, totalToProcess) => {
|
@@ -940,10 +984,17 @@ export class Editor {
|
|
940
984
|
return untilNextAnimationFrame();
|
941
985
|
}
|
942
986
|
return null;
|
943
|
-
}, (importExportRect) => {
|
987
|
+
}, (importExportRect, options) => {
|
944
988
|
this.dispatchNoAnnounce(this.setImportExportRect(importExportRect), false);
|
945
989
|
this.dispatchNoAnnounce(this.viewport.zoomTo(importExportRect), false);
|
990
|
+
if (options) {
|
991
|
+
autoresizeEnabled = options.autoresize;
|
992
|
+
}
|
946
993
|
});
|
994
|
+
// TODO: Move this call into the callback above. Currently, this would cause
|
995
|
+
// decrease in performance as the main background would be repeatedly added
|
996
|
+
// and removed from the editor every time another component is added.
|
997
|
+
this.dispatchNoAnnounce(this.image.setAutoresizeEnabled(autoresizeEnabled), false);
|
947
998
|
// Ensure that we don't have multiple overlapping BackgroundComponents. Remove
|
948
999
|
// old BackgroundComponents.
|
949
1000
|
// Overlapping BackgroundComponents may cause changing the background color to
|
@@ -1004,8 +1055,23 @@ export class Editor {
|
|
1004
1055
|
/**
|
1005
1056
|
* Alias for `loadFrom(SVGLoader.fromString)`.
|
1006
1057
|
*
|
1007
|
-
*
|
1008
|
-
*
|
1058
|
+
* @example
|
1059
|
+
* ```ts,runnable
|
1060
|
+
* import {Editor} from 'js-draw';
|
1061
|
+
* const editor = new Editor(document.body);
|
1062
|
+
*
|
1063
|
+
* ---visible---
|
1064
|
+
* await editor.loadFromSVG(`
|
1065
|
+
* <svg viewBox="5 23 52 30" width="52" height="16" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
|
1066
|
+
* <text style="
|
1067
|
+
* transform: matrix(0.181846, 0.1, 0, 0.181846, 11.4, 33.2);
|
1068
|
+
* font-family: serif;
|
1069
|
+
* font-size: 32px;
|
1070
|
+
* fill: rgb(100, 140, 61);
|
1071
|
+
* ">An SVG image!</text>
|
1072
|
+
* </svg>
|
1073
|
+
* `);
|
1074
|
+
* ```
|
1009
1075
|
*/
|
1010
1076
|
async loadFromSVG(svgData, sanitize = false) {
|
1011
1077
|
const loader = SVGLoader.fromString(svgData, sanitize);
|