js-draw 1.2.1 → 1.3.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/README.md +30 -30
- package/dist/Editor.css +70 -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 +30 -10
- 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 +60 -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/components/lib.d.ts +2 -2
- package/dist/cjs/components/lib.js +15 -2
- package/dist/cjs/lib.d.ts +2 -45
- package/dist/cjs/lib.js +2 -45
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +1 -0
- package/dist/cjs/rendering/RenderablePathSpec.js +1 -0
- package/dist/cjs/rendering/RenderingStyle.d.ts +1 -0
- package/dist/cjs/rendering/lib.d.ts +1 -0
- package/dist/cjs/rendering/lib.js +5 -1
- package/dist/cjs/rendering/renderers/AbstractRenderer.js +1 -1
- 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/adjustEditorThemeForContrast.js +1 -0
- 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 +29 -9
- 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 +37 -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/components/lib.d.ts +2 -2
- package/dist/mjs/components/lib.mjs +2 -2
- package/dist/mjs/lib.d.ts +2 -45
- package/dist/mjs/lib.mjs +2 -45
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +1 -0
- package/dist/mjs/rendering/RenderablePathSpec.mjs +1 -0
- package/dist/mjs/rendering/RenderingStyle.d.ts +1 -0
- package/dist/mjs/rendering/lib.d.ts +1 -0
- package/dist/mjs/rendering/lib.mjs +1 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +1 -1
- 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/adjustEditorThemeForContrast.mjs +1 -0
- 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/AbstractToolbar.scss +3 -0
- package/src/toolbar/EdgeToolbar.scss +4 -1
- package/src/toolbar/widgets/DocumentPropertiesWidget.scss +12 -0
- package/src/toolbar/widgets/components/makeGridSelector.scss +6 -1
- package/src/tools/ScrollbarTool.scss +57 -0
- package/src/tools/{SoundUITool.css → SoundUITool.scss} +4 -0
- package/src/tools/tools.scss +2 -1
@@ -122,6 +122,50 @@ export default class Stroke extends AbstractComponent {
|
|
122
122
|
}
|
123
123
|
return false;
|
124
124
|
}
|
125
|
+
intersectsRect(rect) {
|
126
|
+
// AbstractComponent::intersectsRect can be inexact for strokes with non-zero
|
127
|
+
// stroke radius (has many false negatives). As such, additional checks are
|
128
|
+
// done here, before passing to the superclass.
|
129
|
+
if (!rect.intersects(this.getBBox())) {
|
130
|
+
return false;
|
131
|
+
}
|
132
|
+
// The following check only checks for the positive case:
|
133
|
+
// Sample a set of points that are known to be within each part of this
|
134
|
+
// stroke. For example, the points marked with an "x" below:
|
135
|
+
// ___________________
|
136
|
+
// / \
|
137
|
+
// | x x |
|
138
|
+
// \_____________ |
|
139
|
+
// | x |
|
140
|
+
// \_____/
|
141
|
+
//
|
142
|
+
// Because we don't want the following case to result in selection,
|
143
|
+
// __________________
|
144
|
+
// /.___. \
|
145
|
+
// || x | x | <- /* The
|
146
|
+
// |·---· | .___.
|
147
|
+
// \____________ | | |
|
148
|
+
// | x | ·---·
|
149
|
+
// \_____/ denotes the input rectangle */
|
150
|
+
//
|
151
|
+
// we need to ensure that the rectangle intersects each point **and** the
|
152
|
+
// edge of the rectangle.
|
153
|
+
for (const part of this.parts) {
|
154
|
+
// As such, we need to shrink the input rectangle to verify that the original,
|
155
|
+
// unshrunken rectangle would have intersected the edge of the stroke if it
|
156
|
+
// intersects a point within the stroke.
|
157
|
+
const interiorRect = rect.grownBy(-(part.style.stroke?.width ?? 0));
|
158
|
+
if (interiorRect.area === 0) {
|
159
|
+
continue;
|
160
|
+
}
|
161
|
+
for (const point of part.path.startEndPoints()) {
|
162
|
+
if (interiorRect.containsPoint(point)) {
|
163
|
+
return true;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
return super.intersectsRect(rect);
|
168
|
+
}
|
125
169
|
render(canvas, visibleRect) {
|
126
170
|
canvas.startObject(this.getBBox());
|
127
171
|
for (const part of this.parts) {
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { LineSegment2, Mat33, Rect2 } from '@js-draw/math';
|
2
2
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
3
|
-
import AbstractComponent from './AbstractComponent';
|
3
|
+
import AbstractComponent, { ComponentSizingMode } from './AbstractComponent';
|
4
4
|
import { ImageComponentLocalization } from './localization';
|
5
5
|
export default class UnknownSVGObject extends AbstractComponent {
|
6
6
|
private svgObject;
|
@@ -10,6 +10,7 @@ export default class UnknownSVGObject extends AbstractComponent {
|
|
10
10
|
intersects(lineSegment: LineSegment2): boolean;
|
11
11
|
protected applyTransformation(_affineTransfm: Mat33): void;
|
12
12
|
isSelectable(): boolean;
|
13
|
+
getSizingMode(): ComponentSizingMode;
|
13
14
|
protected createClone(): AbstractComponent;
|
14
15
|
description(localization: ImageComponentLocalization): string;
|
15
16
|
protected serializeToJSON(): string | null;
|
@@ -5,7 +5,7 @@
|
|
5
5
|
//
|
6
6
|
import { Rect2 } from '@js-draw/math';
|
7
7
|
import SVGRenderer from '../rendering/renderers/SVGRenderer.mjs';
|
8
|
-
import AbstractComponent from './AbstractComponent.mjs';
|
8
|
+
import AbstractComponent, { ComponentSizingMode } from './AbstractComponent.mjs';
|
9
9
|
const componentId = 'unknown-svg-object';
|
10
10
|
export default class UnknownSVGObject extends AbstractComponent {
|
11
11
|
constructor(svgObject) {
|
@@ -30,6 +30,12 @@ export default class UnknownSVGObject extends AbstractComponent {
|
|
30
30
|
isSelectable() {
|
31
31
|
return false;
|
32
32
|
}
|
33
|
+
getSizingMode() {
|
34
|
+
// This component can be shown anywhere (it won't be
|
35
|
+
// visible to the user, it just needs to be saved with
|
36
|
+
// the image).
|
37
|
+
return ComponentSizingMode.Anywhere;
|
38
|
+
}
|
33
39
|
createClone() {
|
34
40
|
return new UnknownSVGObject(this.svgObject.cloneNode(true));
|
35
41
|
}
|
@@ -10,7 +10,7 @@ import TextComponent from './TextComponent';
|
|
10
10
|
import ImageComponent from './ImageComponent';
|
11
11
|
import RestyleableComponent from './RestylableComponent';
|
12
12
|
import { createRestyleComponentCommand, isRestylableComponent, ComponentStyle as RestyleableComponentStyle } from './RestylableComponent';
|
13
|
-
import BackgroundComponent from './BackgroundComponent';
|
13
|
+
import BackgroundComponent, { BackgroundType } from './BackgroundComponent';
|
14
14
|
export { Stroke, RestyleableComponent, createRestyleComponentCommand, isRestylableComponent, RestyleableComponentStyle, TextComponent,
|
15
15
|
/** @deprecated use {@link TextComponent} */
|
16
|
-
TextComponent as Text, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
|
16
|
+
TextComponent as Text, Stroke as StrokeComponent, BackgroundComponent, BackgroundType as BackgroundComponentBackgroundType, ImageComponent, };
|
@@ -9,7 +9,7 @@ import Stroke from './Stroke.mjs';
|
|
9
9
|
import TextComponent from './TextComponent.mjs';
|
10
10
|
import ImageComponent from './ImageComponent.mjs';
|
11
11
|
import { createRestyleComponentCommand, isRestylableComponent } from './RestylableComponent.mjs';
|
12
|
-
import BackgroundComponent from './BackgroundComponent.mjs';
|
12
|
+
import BackgroundComponent, { BackgroundType } from './BackgroundComponent.mjs';
|
13
13
|
export { Stroke, createRestyleComponentCommand, isRestylableComponent, TextComponent,
|
14
14
|
/** @deprecated use {@link TextComponent} */
|
15
|
-
TextComponent as Text, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
|
15
|
+
TextComponent as Text, Stroke as StrokeComponent, BackgroundComponent, BackgroundType as BackgroundComponentBackgroundType, ImageComponent, };
|
package/dist/mjs/lib.d.ts
CHANGED
@@ -2,52 +2,9 @@
|
|
2
2
|
* The main entrypoint for the NPM package. Everything exported by this file
|
3
3
|
* is available through the [`js-draw` package](https://www.npmjs.com/package/js-draw).
|
4
4
|
*
|
5
|
-
*
|
6
|
-
* ```ts,runnable
|
7
|
-
* import { Editor, Vec3, Mat33, ToolbarWidgetTag } from 'js-draw';
|
5
|
+
* ## Example
|
8
6
|
*
|
9
|
-
*
|
10
|
-
* import { MaterialIconProvider } from '@js-draw/material-icons';
|
11
|
-
*
|
12
|
-
* // Apply js-draw CSS
|
13
|
-
* import 'js-draw/styles';
|
14
|
-
* // If your bundler doesn't support the above, try
|
15
|
-
* // import 'js-draw/bundledStyles';
|
16
|
-
*
|
17
|
-
* (async () => {
|
18
|
-
* const editor = new Editor(document.body, {
|
19
|
-
* iconProvider: new MaterialIconProvider(),
|
20
|
-
* });
|
21
|
-
* const toolbar = editor.addToolbar();
|
22
|
-
*
|
23
|
-
* // Increases the minimum height of the editor
|
24
|
-
* editor.getRootElement().style.height = '600px';
|
25
|
-
*
|
26
|
-
* // Loads from SVG data
|
27
|
-
* await editor.loadFromSVG(`
|
28
|
-
* <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
|
29
|
-
* <style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style>
|
30
|
-
* <path d="M500,500L500,0L0,0L0,500L500,500" fill="#aaa" class="js-draw-image-background"></path>
|
31
|
-
* <text style="transform: matrix(1, 0, 0, 1, 57, 192); font-family: serif; font-size: 32px; fill: #111;">Testing...</text>
|
32
|
-
* </svg>
|
33
|
-
* `);
|
34
|
-
*
|
35
|
-
* // Adding tags to a toolbar button allows different styles to be applied.
|
36
|
-
* // Also see addActionButton.
|
37
|
-
* const buttonLabels = [ ToolbarWidgetTag.Save ];
|
38
|
-
*
|
39
|
-
* toolbar.addSaveButton(() => {
|
40
|
-
* const saveData = editor.toSVG().outerHTML;
|
41
|
-
*
|
42
|
-
* // Do something with saveData
|
43
|
-
* });
|
44
|
-
*
|
45
|
-
* toolbar.addExitButton(() => {
|
46
|
-
* // Save/confirm exiting here?
|
47
|
-
* editor.remove();
|
48
|
-
* });
|
49
|
-
* })();
|
50
|
-
* ```
|
7
|
+
* [[include:doc-pages/inline-examples/main-js-draw-example.md]]
|
51
8
|
*
|
52
9
|
* @see
|
53
10
|
* - {@link Editor}
|
package/dist/mjs/lib.mjs
CHANGED
@@ -2,52 +2,9 @@
|
|
2
2
|
* The main entrypoint for the NPM package. Everything exported by this file
|
3
3
|
* is available through the [`js-draw` package](https://www.npmjs.com/package/js-draw).
|
4
4
|
*
|
5
|
-
*
|
6
|
-
* ```ts,runnable
|
7
|
-
* import { Editor, Vec3, Mat33, ToolbarWidgetTag } from 'js-draw';
|
5
|
+
* ## Example
|
8
6
|
*
|
9
|
-
*
|
10
|
-
* import { MaterialIconProvider } from '@js-draw/material-icons';
|
11
|
-
*
|
12
|
-
* // Apply js-draw CSS
|
13
|
-
* import 'js-draw/styles';
|
14
|
-
* // If your bundler doesn't support the above, try
|
15
|
-
* // import 'js-draw/bundledStyles';
|
16
|
-
*
|
17
|
-
* (async () => {
|
18
|
-
* const editor = new Editor(document.body, {
|
19
|
-
* iconProvider: new MaterialIconProvider(),
|
20
|
-
* });
|
21
|
-
* const toolbar = editor.addToolbar();
|
22
|
-
*
|
23
|
-
* // Increases the minimum height of the editor
|
24
|
-
* editor.getRootElement().style.height = '600px';
|
25
|
-
*
|
26
|
-
* // Loads from SVG data
|
27
|
-
* await editor.loadFromSVG(`
|
28
|
-
* <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
|
29
|
-
* <style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style>
|
30
|
-
* <path d="M500,500L500,0L0,0L0,500L500,500" fill="#aaa" class="js-draw-image-background"></path>
|
31
|
-
* <text style="transform: matrix(1, 0, 0, 1, 57, 192); font-family: serif; font-size: 32px; fill: #111;">Testing...</text>
|
32
|
-
* </svg>
|
33
|
-
* `);
|
34
|
-
*
|
35
|
-
* // Adding tags to a toolbar button allows different styles to be applied.
|
36
|
-
* // Also see addActionButton.
|
37
|
-
* const buttonLabels = [ ToolbarWidgetTag.Save ];
|
38
|
-
*
|
39
|
-
* toolbar.addSaveButton(() => {
|
40
|
-
* const saveData = editor.toSVG().outerHTML;
|
41
|
-
*
|
42
|
-
* // Do something with saveData
|
43
|
-
* });
|
44
|
-
*
|
45
|
-
* toolbar.addExitButton(() => {
|
46
|
-
* // Save/confirm exiting here?
|
47
|
-
* editor.remove();
|
48
|
-
* });
|
49
|
-
* })();
|
50
|
-
* ```
|
7
|
+
* [[include:doc-pages/inline-examples/main-js-draw-example.md]]
|
51
8
|
*
|
52
9
|
* @see
|
53
10
|
* - {@link Editor}
|
@@ -6,6 +6,7 @@ interface RenderablePathSpec {
|
|
6
6
|
style: RenderingStyle;
|
7
7
|
path?: Path;
|
8
8
|
}
|
9
|
+
/** Converts a renderable path (a path with a `startPoint`, `commands`, and `style`). */
|
9
10
|
export declare const pathFromRenderable: (renderable: RenderablePathSpec) => Path;
|
10
11
|
export declare const pathToRenderable: (path: Path, style: RenderingStyle) => RenderablePathSpec;
|
11
12
|
/**
|
@@ -5,3 +5,4 @@ export { default as CanvasRenderer } from './renderers/CanvasRenderer';
|
|
5
5
|
export { default as Display, RenderingMode } from './Display';
|
6
6
|
export { default as TextRenderingStyle } from './TextRenderingStyle';
|
7
7
|
export { default as RenderingStyle } from './RenderingStyle';
|
8
|
+
export { pathToRenderable, pathFromRenderable, visualEquivalent as pathVisualEquivalent, default as RenderablePathSpec, } from './RenderablePathSpec';
|
@@ -3,3 +3,4 @@ export { default as DummyRenderer } from './renderers/DummyRenderer.mjs';
|
|
3
3
|
export { default as SVGRenderer } from './renderers/SVGRenderer.mjs';
|
4
4
|
export { default as CanvasRenderer } from './renderers/CanvasRenderer.mjs';
|
5
5
|
export { default as Display, RenderingMode } from './Display.mjs';
|
6
|
+
export { pathToRenderable, pathFromRenderable, visualEquivalent as pathVisualEquivalent, } from './RenderablePathSpec.mjs';
|
@@ -63,7 +63,7 @@ export default class AbstractRenderer {
|
|
63
63
|
drawPath(path) {
|
64
64
|
// If we're being called outside of an object,
|
65
65
|
// we can't delay rendering
|
66
|
-
if (this.objectLevel === 0) {
|
66
|
+
if (this.objectLevel === 0 || this.currentPaths === null) {
|
67
67
|
this.currentPaths = [path];
|
68
68
|
this.flushPath();
|
69
69
|
this.currentPaths = null;
|
@@ -33,8 +33,8 @@ export default class KeyboardShortcutManager {
|
|
33
33
|
* const shortcutId = 'io.github.personalizedrefrigerator.js-draw.select-all';
|
34
34
|
*
|
35
35
|
* // Associate two shortcuts with the same ID
|
36
|
-
* const shortcut1 =
|
37
|
-
* const shortcut2 =
|
36
|
+
* const shortcut1 = KeyBinding.fromString('ctrlOrMeta+a');
|
37
|
+
* const shortcut2 = KeyBinding.fromString('ctrlOrMeta+shift+a');
|
38
38
|
* KeyboardShortcutManager.registerDefaultKeyboardShortcut(
|
39
39
|
* shortcutId,
|
40
40
|
* [ shortcut1, shortcut2 ],
|
@@ -54,8 +54,8 @@ class KeyboardShortcutManager {
|
|
54
54
|
* const shortcutId = 'io.github.personalizedrefrigerator.js-draw.select-all';
|
55
55
|
*
|
56
56
|
* // Associate two shortcuts with the same ID
|
57
|
-
* const shortcut1 =
|
58
|
-
* const shortcut2 =
|
57
|
+
* const shortcut1 = KeyBinding.fromString('ctrlOrMeta+a');
|
58
|
+
* const shortcut2 = KeyBinding.fromString('ctrlOrMeta+shift+a');
|
59
59
|
* KeyboardShortcutManager.registerDefaultKeyboardShortcut(
|
60
60
|
* shortcutId,
|
61
61
|
* [ shortcut1, shortcut2 ],
|
@@ -34,6 +34,7 @@ export const defaultToolbarLocalization = {
|
|
34
34
|
imageWidthOption: 'Width',
|
35
35
|
imageHeightOption: 'Height',
|
36
36
|
useGridOption: 'Grid',
|
37
|
+
enableAutoresizeOption: 'Auto-resize',
|
37
38
|
toggleOverflow: 'More',
|
38
39
|
about: 'About',
|
39
40
|
inputStabilization: 'Input stabilization',
|
@@ -50,6 +50,11 @@ class BaseWidget {
|
|
50
50
|
this.label = document.createElement('label');
|
51
51
|
this.button.setAttribute('role', 'button');
|
52
52
|
this.button.tabIndex = 0;
|
53
|
+
// Disable the context menu. This allows long-press gestures to trigger the button's
|
54
|
+
// tooltip instead.
|
55
|
+
this.button.oncontextmenu = event => {
|
56
|
+
event.preventDefault();
|
57
|
+
};
|
53
58
|
const toolbarShortcutHandlers = this.editor.toolController.getMatchingTools(ToolbarShortcutHandler);
|
54
59
|
// If the onKeyPress function has been extended and the editor is configured to send keypress events to
|
55
60
|
// toolbar widgets,
|
@@ -94,39 +94,49 @@ class DocumentPropertiesWidget extends BaseWidget {
|
|
94
94
|
const container = document.createElement('div');
|
95
95
|
container.classList.add(`${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}nonbutton-controls-main-list`, `${toolbarCSSPrefix}document-properties-widget`);
|
96
96
|
// Background color input
|
97
|
-
const
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
this.
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
97
|
+
const makeBackgroundColorInput = () => {
|
98
|
+
const backgroundColorRow = document.createElement('div');
|
99
|
+
const backgroundColorLabel = document.createElement('label');
|
100
|
+
backgroundColorLabel.innerText = this.localizationTable.backgroundColor;
|
101
|
+
const { input: colorInput, container: backgroundColorInputContainer, setValue: setBgColorInputValue } = makeColorInput(this.editor, color => {
|
102
|
+
if (!color.eq(this.getBackgroundColor())) {
|
103
|
+
this.setBackgroundColor(color);
|
104
|
+
}
|
105
|
+
});
|
106
|
+
colorInput.id = `${toolbarCSSPrefix}docPropertiesColorInput-${DocumentPropertiesWidget.idCounter++}`;
|
107
|
+
backgroundColorLabel.htmlFor = colorInput.id;
|
108
|
+
backgroundColorRow.replaceChildren(backgroundColorLabel, backgroundColorInputContainer);
|
109
|
+
return { setBgColorInputValue, backgroundColorRow };
|
110
|
+
};
|
111
|
+
const { backgroundColorRow, setBgColorInputValue } = makeBackgroundColorInput();
|
112
|
+
const makeCheckboxRow = (labelText, onChange) => {
|
113
|
+
const rowContainer = document.createElement('div');
|
114
|
+
const labelElement = document.createElement('label');
|
115
|
+
const checkboxElement = document.createElement('input');
|
116
|
+
checkboxElement.id = `${toolbarCSSPrefix}docPropertiesCheckbox-${DocumentPropertiesWidget.idCounter++}`;
|
117
|
+
labelElement.htmlFor = checkboxElement.id;
|
118
|
+
checkboxElement.type = 'checkbox';
|
119
|
+
labelElement.innerText = labelText;
|
120
|
+
checkboxElement.oninput = () => {
|
121
|
+
onChange(checkboxElement.checked);
|
122
|
+
};
|
123
|
+
rowContainer.replaceChildren(labelElement, checkboxElement);
|
124
|
+
return { container: rowContainer, checkbox: checkboxElement };
|
125
|
+
};
|
108
126
|
// Background style selector
|
109
|
-
const useGridRow =
|
110
|
-
const useGridLabel = document.createElement('label');
|
111
|
-
const useGridCheckbox = document.createElement('input');
|
112
|
-
useGridCheckbox.id = `${toolbarCSSPrefix}docPropertiesUseGridCheckbox-${DocumentPropertiesWidget.idCounter++}`;
|
113
|
-
useGridLabel.htmlFor = useGridCheckbox.id;
|
114
|
-
useGridCheckbox.type = 'checkbox';
|
115
|
-
useGridLabel.innerText = this.localizationTable.useGridOption;
|
116
|
-
useGridCheckbox.oninput = () => {
|
127
|
+
const { container: useGridRow, checkbox: useGridCheckbox } = makeCheckboxRow(this.localizationTable.useGridOption, (checked) => {
|
117
128
|
const prevBackgroundType = this.getBackgroundType();
|
118
129
|
const wasGrid = prevBackgroundType === BackgroundType.Grid;
|
119
|
-
if (wasGrid ===
|
130
|
+
if (wasGrid === checked) {
|
120
131
|
// Already the requested background type.
|
121
132
|
return;
|
122
133
|
}
|
123
134
|
let newBackgroundType = BackgroundType.SolidColor;
|
124
|
-
if (
|
135
|
+
if (checked) {
|
125
136
|
newBackgroundType = BackgroundType.Grid;
|
126
137
|
}
|
127
138
|
this.editor.dispatch(this.setBackgroundType(newBackgroundType));
|
128
|
-
};
|
129
|
-
useGridRow.replaceChildren(useGridLabel, useGridCheckbox);
|
139
|
+
});
|
130
140
|
// Adds a width/height input
|
131
141
|
const addDimensionRow = (labelContent, onChange) => {
|
132
142
|
const row = document.createElement('div');
|
@@ -139,15 +149,25 @@ class DocumentPropertiesWidget extends BaseWidget {
|
|
139
149
|
label.htmlFor = input.id;
|
140
150
|
input.style.flexGrow = '2';
|
141
151
|
input.style.width = '25px';
|
142
|
-
row.style.display = 'flex';
|
143
152
|
input.oninput = () => {
|
144
153
|
onChange(parseFloat(input.value));
|
145
154
|
};
|
155
|
+
row.classList.add('js-draw-size-input-row');
|
146
156
|
row.replaceChildren(label, input);
|
147
157
|
return {
|
148
158
|
setValue: (value) => {
|
149
159
|
input.value = value.toString();
|
150
160
|
},
|
161
|
+
setIsAutomaticSize: (automatic) => {
|
162
|
+
input.disabled = automatic;
|
163
|
+
const automaticSizeClass = 'size-input-row--automatic-size';
|
164
|
+
if (automatic) {
|
165
|
+
row.classList.add(automaticSizeClass);
|
166
|
+
}
|
167
|
+
else {
|
168
|
+
row.classList.remove(automaticSizeClass);
|
169
|
+
}
|
170
|
+
},
|
151
171
|
element: row,
|
152
172
|
};
|
153
173
|
};
|
@@ -157,6 +177,11 @@ class DocumentPropertiesWidget extends BaseWidget {
|
|
157
177
|
const imageHeightRow = addDimensionRow(this.localizationTable.imageHeightOption, (value) => {
|
158
178
|
this.updateImportExportRectSize({ height: value });
|
159
179
|
});
|
180
|
+
// The autoresize checkbox
|
181
|
+
const { container: auroresizeRow, checkbox: autoresizeCheckbox } = makeCheckboxRow(this.localizationTable.enableAutoresizeOption, (checked) => {
|
182
|
+
const image = this.editor.image;
|
183
|
+
this.editor.dispatch(image.setAutoresizeEnabled(checked));
|
184
|
+
});
|
160
185
|
// The "About..." button
|
161
186
|
const aboutButton = document.createElement('button');
|
162
187
|
aboutButton.classList.add('about-button');
|
@@ -166,13 +191,17 @@ class DocumentPropertiesWidget extends BaseWidget {
|
|
166
191
|
};
|
167
192
|
this.updateDropdownContent = () => {
|
168
193
|
setBgColorInputValue(this.getBackgroundColor());
|
194
|
+
const autoresize = this.editor.image.getAutoresizeEnabled();
|
169
195
|
const importExportRect = this.editor.getImportExportRect();
|
170
196
|
imageWidthRow.setValue(importExportRect.width);
|
171
197
|
imageHeightRow.setValue(importExportRect.height);
|
198
|
+
autoresizeCheckbox.checked = autoresize;
|
199
|
+
imageWidthRow.setIsAutomaticSize(autoresize);
|
200
|
+
imageHeightRow.setIsAutomaticSize(autoresize);
|
172
201
|
useGridCheckbox.checked = this.getBackgroundType() === BackgroundType.Grid;
|
173
202
|
};
|
174
203
|
this.updateDropdownContent();
|
175
|
-
container.replaceChildren(backgroundColorRow, useGridRow, imageWidthRow.element, imageHeightRow.element, aboutButton);
|
204
|
+
container.replaceChildren(backgroundColorRow, useGridRow, imageWidthRow.element, imageHeightRow.element, auroresizeRow, aboutButton);
|
176
205
|
dropdown.replaceChildren(container);
|
177
206
|
return true;
|
178
207
|
}
|
@@ -68,6 +68,14 @@ labelText, defaultId, choices) => {
|
|
68
68
|
}
|
69
69
|
updateButtonCSS();
|
70
70
|
};
|
71
|
+
button.onfocus = () => {
|
72
|
+
if (buttonContainer.querySelector(':focus-visible')) {
|
73
|
+
buttonContainer.classList.add('focus-visible');
|
74
|
+
}
|
75
|
+
};
|
76
|
+
button.onblur = () => {
|
77
|
+
buttonContainer.classList.remove('focus-visible');
|
78
|
+
};
|
71
79
|
buttonContainer.replaceChildren(button, labelContainer);
|
72
80
|
menuContainer.appendChild(buttonContainer);
|
73
81
|
// Set whether the current button is checked
|
@@ -206,24 +206,26 @@ export default class PanZoom extends BaseTool {
|
|
206
206
|
this.lastScreenCenter = screenCenter;
|
207
207
|
this.lastDist = dist;
|
208
208
|
this.transform = Viewport.transformBy(this.transform.transform.rightMul(transformUpdate));
|
209
|
+
return transformUpdate;
|
209
210
|
}
|
210
211
|
handleOneFingerMove(pointer) {
|
211
212
|
const delta = this.getCenterDelta(pointer.screenPos);
|
212
|
-
|
213
|
+
const transformUpdate = Mat33.translation(delta);
|
214
|
+
this.transform = Viewport.transformBy(this.transform.transform.rightMul(transformUpdate));
|
213
215
|
this.updateVelocity(pointer.screenPos);
|
214
216
|
this.lastScreenCenter = pointer.screenPos;
|
217
|
+
return transformUpdate;
|
215
218
|
}
|
216
219
|
onPointerMove({ allPointers }) {
|
217
220
|
this.transform ??= Viewport.transformBy(Mat33.identity);
|
218
|
-
|
221
|
+
let transformUpdate = Mat33.identity;
|
219
222
|
if (allPointers.length === 2) {
|
220
|
-
this.handleTwoFingerMove(allPointers);
|
223
|
+
transformUpdate = this.handleTwoFingerMove(allPointers);
|
221
224
|
}
|
222
225
|
else if (allPointers.length === 1) {
|
223
|
-
this.handleOneFingerMove(allPointers[0]);
|
226
|
+
transformUpdate = this.handleOneFingerMove(allPointers[0]);
|
224
227
|
}
|
225
|
-
|
226
|
-
this.transform.apply(this.editor);
|
228
|
+
Viewport.transformBy(transformUpdate).apply(this.editor);
|
227
229
|
this.lastTimestamp = performance.now();
|
228
230
|
}
|
229
231
|
onPointerUp(event) {
|
@@ -303,8 +305,11 @@ export default class PanZoom extends BaseTool {
|
|
303
305
|
const toCanvas = this.editor.viewport.screenToCanvasTransform;
|
304
306
|
// Transform without including translation
|
305
307
|
const translation = toCanvas.transformVec3(Vec3.of(-delta.x, -delta.y, 0));
|
306
|
-
|
307
|
-
|
308
|
+
let pinchAmount = delta.z;
|
309
|
+
// Clamp the magnitude of pinchAmount
|
310
|
+
pinchAmount = Math.atan(pinchAmount / 2) * 2;
|
311
|
+
const pinchZoomScaleFactor = 1.04;
|
312
|
+
const transformUpdate = Mat33.scaling2D(Math.max(0.4, Math.min(Math.pow(pinchZoomScaleFactor, -pinchAmount), 4)), canvasPos).rightMul(Mat33.translation(translation));
|
308
313
|
this.updateTransform(transformUpdate, true);
|
309
314
|
return true;
|
310
315
|
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import Editor from '../Editor';
|
2
|
+
import BaseTool from './BaseTool';
|
3
|
+
/**
|
4
|
+
* This tool, when enabled, renders scrollbars reflecting the current position
|
5
|
+
* of the view relative to the import/export area of the image.
|
6
|
+
*
|
7
|
+
* **Note**: These scrollbars are currently not draggable. This may change in
|
8
|
+
* a future release.
|
9
|
+
*/
|
10
|
+
export default class ScrollbarTool extends BaseTool {
|
11
|
+
private editor;
|
12
|
+
private scrollbarOverlay;
|
13
|
+
private verticalScrollbar;
|
14
|
+
private horizontalScrollbar;
|
15
|
+
constructor(editor: Editor);
|
16
|
+
private fadeOutTimeout;
|
17
|
+
private updateScrollbars;
|
18
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import { Rect2 } from '@js-draw/math';
|
2
|
+
import { EditorEventType } from '../types.mjs';
|
3
|
+
import BaseTool from './BaseTool.mjs';
|
4
|
+
/**
|
5
|
+
* This tool, when enabled, renders scrollbars reflecting the current position
|
6
|
+
* of the view relative to the import/export area of the image.
|
7
|
+
*
|
8
|
+
* **Note**: These scrollbars are currently not draggable. This may change in
|
9
|
+
* a future release.
|
10
|
+
*/
|
11
|
+
export default class ScrollbarTool extends BaseTool {
|
12
|
+
constructor(editor) {
|
13
|
+
super(editor.notifier, 'scrollbar');
|
14
|
+
this.editor = editor;
|
15
|
+
this.fadeOutTimeout = null;
|
16
|
+
this.scrollbarOverlay = document.createElement('div');
|
17
|
+
this.scrollbarOverlay.classList.add('ScrollbarTool-overlay');
|
18
|
+
this.verticalScrollbar = document.createElement('div');
|
19
|
+
this.verticalScrollbar.classList.add('vertical-scrollbar');
|
20
|
+
this.horizontalScrollbar = document.createElement('div');
|
21
|
+
this.horizontalScrollbar.classList.add('horizontal-scrollbar');
|
22
|
+
this.scrollbarOverlay.replaceChildren(this.verticalScrollbar, this.horizontalScrollbar);
|
23
|
+
let overlay = null;
|
24
|
+
let viewportListener = null;
|
25
|
+
this.enabledValue().onUpdateAndNow(enabled => {
|
26
|
+
overlay?.remove();
|
27
|
+
viewportListener?.remove();
|
28
|
+
viewportListener = null;
|
29
|
+
overlay = null;
|
30
|
+
if (enabled) {
|
31
|
+
viewportListener = editor.notifier.on(EditorEventType.ViewportChanged, _event => {
|
32
|
+
this.updateScrollbars();
|
33
|
+
});
|
34
|
+
this.updateScrollbars();
|
35
|
+
overlay = editor.createHTMLOverlay(this.scrollbarOverlay);
|
36
|
+
}
|
37
|
+
});
|
38
|
+
}
|
39
|
+
updateScrollbars() {
|
40
|
+
const viewport = this.editor.viewport;
|
41
|
+
const screenSize = viewport.getScreenRectSize();
|
42
|
+
const screenRect = new Rect2(0, 0, screenSize.x, screenSize.y);
|
43
|
+
const imageRect = this.editor.getImportExportRect()
|
44
|
+
// The scrollbars are positioned in screen coordinates, so the exportRect also needs
|
45
|
+
// to be in screen coordinates
|
46
|
+
.transformedBoundingBox(viewport.canvasToScreenTransform)
|
47
|
+
// If the screenRect is outside of the exportRect, expand the image rectangle
|
48
|
+
.union(screenRect);
|
49
|
+
const scrollbarWidth = screenRect.width / imageRect.width * screenSize.x;
|
50
|
+
const scrollbarHeight = screenRect.height / imageRect.height * screenSize.y;
|
51
|
+
const scrollbarX = (screenRect.x - imageRect.x) / imageRect.width * (screenSize.x);
|
52
|
+
const scrollbarY = (screenRect.y - imageRect.y) / imageRect.height * (screenSize.y);
|
53
|
+
this.horizontalScrollbar.style.width = `${scrollbarWidth}px`;
|
54
|
+
this.verticalScrollbar.style.height = `${scrollbarHeight}px`;
|
55
|
+
this.horizontalScrollbar.style.marginLeft = `${scrollbarX}px`;
|
56
|
+
this.verticalScrollbar.style.marginTop = `${scrollbarY}px`;
|
57
|
+
// Style the scrollbars differently when there's no scroll (all content visible)
|
58
|
+
const handleNoScrollStyling = (scrollbar, size, fillSize) => {
|
59
|
+
const fillsWindowClass = 'represents-no-scroll';
|
60
|
+
if (Math.abs(size - fillSize) < 1e-8) {
|
61
|
+
scrollbar.classList.add(fillsWindowClass);
|
62
|
+
}
|
63
|
+
else {
|
64
|
+
scrollbar.classList.remove(fillsWindowClass);
|
65
|
+
}
|
66
|
+
};
|
67
|
+
handleNoScrollStyling(this.horizontalScrollbar, scrollbarWidth, screenSize.x);
|
68
|
+
handleNoScrollStyling(this.verticalScrollbar, scrollbarHeight, screenSize.y);
|
69
|
+
// Fade out after a delay.
|
70
|
+
if (this.fadeOutTimeout !== null) {
|
71
|
+
clearTimeout(this.fadeOutTimeout);
|
72
|
+
}
|
73
|
+
const fadeOutDelay = 3000;
|
74
|
+
this.fadeOutTimeout = setTimeout(() => {
|
75
|
+
this.scrollbarOverlay.classList.remove('just-updated');
|
76
|
+
}, fadeOutDelay);
|
77
|
+
this.scrollbarOverlay.classList.add('just-updated');
|
78
|
+
}
|
79
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -18,6 +18,7 @@ import SoundUITool from './SoundUITool.mjs';
|
|
18
18
|
import { InputEvtType } from '../inputEvents.mjs';
|
19
19
|
import InputPipeline from './InputFilter/InputPipeline.mjs';
|
20
20
|
import InputStabilizer from './InputFilter/InputStabilizer.mjs';
|
21
|
+
import ScrollbarTool from './ScrollbarTool.mjs';
|
21
22
|
export default class ToolController {
|
22
23
|
/** @internal */
|
23
24
|
constructor(editor, localization) {
|
@@ -51,6 +52,7 @@ export default class ToolController {
|
|
51
52
|
const soundExplorer = new SoundUITool(editor, localization.soundExplorer);
|
52
53
|
soundExplorer.setEnabled(false);
|
53
54
|
this.tools = [
|
55
|
+
new ScrollbarTool(editor),
|
54
56
|
new PipetteTool(editor, localization.pipetteTool),
|
55
57
|
soundExplorer,
|
56
58
|
panZoomTool,
|