js-draw 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Editor.css +48 -1
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +41 -0
- package/dist/cjs/Editor.js +9 -0
- package/dist/cjs/Pointer.js +1 -1
- package/dist/cjs/commands/Erase.d.ts +22 -2
- package/dist/cjs/commands/Erase.js +22 -2
- package/dist/cjs/commands/invertCommand.js +5 -0
- package/dist/cjs/commands/uniteCommands.d.ts +36 -0
- package/dist/cjs/commands/uniteCommands.js +36 -0
- package/dist/cjs/components/AbstractComponent.d.ts +8 -0
- package/dist/cjs/components/AbstractComponent.js +28 -8
- package/dist/cjs/components/ImageComponent.d.ts +12 -0
- package/dist/cjs/components/ImageComponent.js +16 -9
- package/dist/cjs/components/Stroke.d.ts +16 -2
- package/dist/cjs/components/Stroke.js +17 -1
- package/dist/cjs/components/builders/ArrowBuilder.js +3 -3
- package/dist/cjs/components/builders/CircleBuilder.js +3 -3
- package/dist/cjs/components/builders/FreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/LineBuilder.js +3 -3
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/RectangleBuilder.js +5 -6
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +168 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.js +46 -0
- package/dist/cjs/components/builders/types.d.ts +12 -0
- package/dist/cjs/image/EditorImage.d.ts +32 -1
- package/dist/cjs/image/EditorImage.js +32 -1
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/cjs/rendering/RenderablePathSpec.js +4 -0
- package/dist/cjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/cjs/toolbar/AbstractToolbar.js +46 -30
- package/dist/cjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/cjs/toolbar/IconProvider.js +17 -0
- package/dist/cjs/toolbar/localization.d.ts +3 -0
- package/dist/cjs/toolbar/localization.js +4 -1
- package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -1
- package/dist/cjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/cjs/toolbar/widgets/ExitActionWidget.js +32 -0
- package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/cjs/toolbar/widgets/HandToolWidget.js +24 -13
- package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/cjs/toolbar/widgets/InsertImageWidget.js +102 -22
- package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/cjs/toolbar/widgets/PenToolWidget.js +50 -20
- package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
- package/dist/cjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/cjs/tools/Pen.d.ts +9 -0
- package/dist/cjs/tools/Pen.js +82 -3
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/cjs/tools/SelectionTool/Selection.js +56 -12
- package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +19 -1
- package/dist/cjs/tools/TextTool.js +5 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.js +0 -1
- package/dist/cjs/tools/keybindings.d.ts +1 -0
- package/dist/cjs/tools/keybindings.js +3 -1
- package/dist/cjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/cjs/tools/util/StationaryPenDetector.js +95 -0
- package/dist/cjs/util/ReactiveValue.d.ts +2 -0
- package/dist/cjs/util/ReactiveValue.js +2 -0
- package/dist/cjs/util/lib.d.ts +1 -0
- package/dist/cjs/util/lib.js +4 -1
- package/dist/cjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/cjs/util/waitForImageLoaded.js +12 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +41 -0
- package/dist/mjs/Editor.mjs +9 -0
- package/dist/mjs/Pointer.mjs +1 -1
- package/dist/mjs/commands/Erase.d.ts +22 -2
- package/dist/mjs/commands/Erase.mjs +22 -2
- package/dist/mjs/commands/invertCommand.mjs +5 -0
- package/dist/mjs/commands/uniteCommands.d.ts +36 -0
- package/dist/mjs/commands/uniteCommands.mjs +36 -0
- package/dist/mjs/components/AbstractComponent.d.ts +8 -0
- package/dist/mjs/components/AbstractComponent.mjs +28 -8
- package/dist/mjs/components/ImageComponent.d.ts +12 -0
- package/dist/mjs/components/ImageComponent.mjs +16 -9
- package/dist/mjs/components/Stroke.d.ts +16 -2
- package/dist/mjs/components/Stroke.mjs +17 -1
- package/dist/mjs/components/builders/ArrowBuilder.mjs +3 -2
- package/dist/mjs/components/builders/CircleBuilder.mjs +3 -2
- package/dist/mjs/components/builders/FreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/LineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/RectangleBuilder.mjs +5 -4
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +166 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.mjs +44 -0
- package/dist/mjs/components/builders/types.d.ts +12 -0
- package/dist/mjs/image/EditorImage.d.ts +32 -1
- package/dist/mjs/image/EditorImage.mjs +32 -1
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/mjs/rendering/RenderablePathSpec.mjs +4 -0
- package/dist/mjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/mjs/toolbar/AbstractToolbar.mjs +46 -30
- package/dist/mjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/mjs/toolbar/IconProvider.mjs +17 -0
- package/dist/mjs/toolbar/localization.d.ts +3 -0
- package/dist/mjs/toolbar/localization.mjs +4 -1
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -1
- package/dist/mjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/mjs/toolbar/widgets/ExitActionWidget.mjs +27 -0
- package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +24 -13
- package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +102 -22
- package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +50 -20
- package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
- package/dist/mjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/mjs/tools/Pen.d.ts +9 -0
- package/dist/mjs/tools/Pen.mjs +82 -3
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/mjs/tools/SelectionTool/Selection.mjs +56 -12
- package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +20 -2
- package/dist/mjs/tools/TextTool.mjs +5 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.mjs +0 -1
- package/dist/mjs/tools/keybindings.d.ts +1 -0
- package/dist/mjs/tools/keybindings.mjs +2 -0
- package/dist/mjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/mjs/tools/util/StationaryPenDetector.mjs +92 -0
- package/dist/mjs/util/ReactiveValue.d.ts +2 -0
- package/dist/mjs/util/ReactiveValue.mjs +2 -0
- package/dist/mjs/util/lib.d.ts +1 -0
- package/dist/mjs/util/lib.mjs +1 -0
- package/dist/mjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/mjs/util/waitForImageLoaded.mjs +10 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +3 -3
- package/src/Editor.scss +7 -0
- package/src/toolbar/AbstractToolbar.scss +20 -0
- package/src/toolbar/toolbar.scss +1 -1
- package/src/toolbar/widgets/InsertImageWidget.scss +6 -1
- package/src/toolbar/widgets/PenToolWidget.scss +33 -0
- package/src/tools/SelectionTool/SelectionTool.scss +6 -0
- package/src/toolbar/widgets/PenToolWidget.css +0 -2
@@ -51,6 +51,8 @@ export default class IconProvider {
|
|
51
51
|
makePenIcon(penStyle: PenStyle): IconElemType;
|
52
52
|
makeIconFromFactory(penStyle: PenStyle): IconElemType;
|
53
53
|
makePipetteIcon(color?: Color4): IconElemType;
|
54
|
+
makeShapeAutocorrectIcon(): IconElemType;
|
55
|
+
makeStrokeSmoothingIcon(): IconElemType;
|
54
56
|
/** Unused. @deprecated */
|
55
57
|
makeFormatSelectionIcon(): IconElemType;
|
56
58
|
makeResizeImageToSelectionIcon(): IconElemType;
|
@@ -644,6 +644,23 @@ class IconProvider {
|
|
644
644
|
icon.setAttribute('viewBox', '5 -40 140 140');
|
645
645
|
return icon;
|
646
646
|
}
|
647
|
+
makeShapeAutocorrectIcon() {
|
648
|
+
const fill = 'none';
|
649
|
+
const strokeColor = 'var(--icon-color)';
|
650
|
+
return this.makeIconFromPath(`
|
651
|
+
m 79.129476,33.847107 9.967823,-0.03218 v 55 h -55 l 0.03218,-9.96782
|
652
|
+
M 71.1,40.8 a 30,30 0 0 1 -30,30 30,30 0 0 1 -30,-30 30,30 0 0 1 30,-30 30,30 0 0 1 30,30 L 71.1,40.8
|
653
|
+
M 34.1,58.8 v -25 h 25 v 0
|
654
|
+
`, fill, strokeColor, '7px');
|
655
|
+
}
|
656
|
+
makeStrokeSmoothingIcon() {
|
657
|
+
const fill = 'none';
|
658
|
+
const strokeColor = 'var(--icon-color)';
|
659
|
+
return this.makeIconFromPath(`
|
660
|
+
m 31,83.2 c -50,0 30,-65 -20,-65
|
661
|
+
M 75,17.3 40,59.7 38.2,77.6 55.5,72.4 90.5,30 Z
|
662
|
+
`, fill, strokeColor, '7px');
|
663
|
+
}
|
647
664
|
/** Unused. @deprecated */
|
648
665
|
makeFormatSelectionIcon() {
|
649
666
|
return this.makeIconFromPath(`
|
@@ -10,6 +10,8 @@ export interface ToolbarLocalization {
|
|
10
10
|
arrowPen: string;
|
11
11
|
image: string;
|
12
12
|
inputAltText: string;
|
13
|
+
decreaseImageSize: string;
|
14
|
+
resetImage: string;
|
13
15
|
chooseFile: string;
|
14
16
|
dragAndDropHereOrBrowse: string;
|
15
17
|
cancel: string;
|
@@ -48,6 +50,7 @@ export interface ToolbarLocalization {
|
|
48
50
|
toggleOverflow: string;
|
49
51
|
about: string;
|
50
52
|
inputStabilization: string;
|
53
|
+
strokeAutocorrect: string;
|
51
54
|
errorImageHasZeroSize: string;
|
52
55
|
closeSidebar: (toolName: string) => string;
|
53
56
|
dropdownShown: (toolName: string) => string;
|
@@ -7,6 +7,8 @@ export const defaultToolbarLocalization = {
|
|
7
7
|
image: 'Image',
|
8
8
|
reformatSelection: 'Format selection',
|
9
9
|
inputAltText: 'Alt text',
|
10
|
+
decreaseImageSize: 'Decrease size',
|
11
|
+
resetImage: 'Reset',
|
10
12
|
chooseFile: 'Choose file',
|
11
13
|
dragAndDropHereOrBrowse: 'Drag and drop here\nor\n{{browse}}',
|
12
14
|
submit: 'Submit',
|
@@ -37,7 +39,8 @@ export const defaultToolbarLocalization = {
|
|
37
39
|
enableAutoresizeOption: 'Auto-resize',
|
38
40
|
toggleOverflow: 'More',
|
39
41
|
about: 'About',
|
40
|
-
inputStabilization: '
|
42
|
+
inputStabilization: 'Stabilization',
|
43
|
+
strokeAutocorrect: 'Autocorrect',
|
41
44
|
touchPanning: 'Touchscreen panning',
|
42
45
|
roundedTipPen: 'Round',
|
43
46
|
flatTipPen: 'Flat',
|
@@ -47,7 +47,7 @@ class BaseWidget {
|
|
47
47
|
this.layoutManager = defaultLayoutManager;
|
48
48
|
this.icon = null;
|
49
49
|
this.container = document.createElement('div');
|
50
|
-
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`, `${toolbarCSSPrefix}toolButtonContainer`);
|
50
|
+
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`, `${toolbarCSSPrefix}toolButtonContainer`, `${toolbarCSSPrefix}internalWidgetId--${id.replace(/[^a-zA-Z0-9_]/g, '-')}`);
|
51
51
|
this.dropdownContent = document.createElement('div');
|
52
52
|
__classPrivateFieldSet(this, _BaseWidget_hasDropdown, false, "f");
|
53
53
|
this.button = document.createElement('div');
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { KeyPressEvent } from '../../inputEvents';
|
2
|
+
import Editor from '../../Editor';
|
3
|
+
import { ToolbarLocalization } from '../localization';
|
4
|
+
import ActionButtonWidget from './ActionButtonWidget';
|
5
|
+
import { ActionButtonIcon } from '../types';
|
6
|
+
declare class ExitActionWidget extends ActionButtonWidget {
|
7
|
+
constructor(editor: Editor, localization: ToolbarLocalization, saveCallback: () => void, labelOverride?: Partial<ActionButtonIcon>);
|
8
|
+
protected shouldAutoDisableInReadOnlyEditor(): boolean;
|
9
|
+
protected onKeyPress(event: KeyPressEvent): boolean;
|
10
|
+
mustBeInToplevelMenu(): boolean;
|
11
|
+
}
|
12
|
+
export default ExitActionWidget;
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import ActionButtonWidget from './ActionButtonWidget.mjs';
|
2
|
+
import { ToolbarWidgetTag } from './BaseWidget.mjs';
|
3
|
+
import { exitKeyboardShortcut } from './keybindings.mjs';
|
4
|
+
class ExitActionWidget extends ActionButtonWidget {
|
5
|
+
constructor(editor, localization, saveCallback, labelOverride = {}) {
|
6
|
+
super(editor, 'exit-button',
|
7
|
+
// Creates an icon
|
8
|
+
() => {
|
9
|
+
return labelOverride.icon ?? editor.icons.makeCloseIcon();
|
10
|
+
}, labelOverride.label ?? localization.exit, saveCallback);
|
11
|
+
this.setTags([ToolbarWidgetTag.Exit]);
|
12
|
+
}
|
13
|
+
shouldAutoDisableInReadOnlyEditor() {
|
14
|
+
return false;
|
15
|
+
}
|
16
|
+
onKeyPress(event) {
|
17
|
+
if (this.editor.shortcuts.matchesShortcut(exitKeyboardShortcut, event)) {
|
18
|
+
this.clickAction();
|
19
|
+
return true;
|
20
|
+
}
|
21
|
+
return super.onKeyPress(event);
|
22
|
+
}
|
23
|
+
mustBeInToplevelMenu() {
|
24
|
+
return true;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
export default ExitActionWidget;
|
@@ -4,11 +4,12 @@ import { ToolbarLocalization } from '../localization';
|
|
4
4
|
import BaseToolWidget from './BaseToolWidget';
|
5
5
|
import { SavedToolbuttonState } from './BaseWidget';
|
6
6
|
export default class HandToolWidget extends BaseToolWidget {
|
7
|
-
protected overridePanZoomTool: PanZoom;
|
8
7
|
private allowTogglingBaseTool;
|
9
|
-
|
10
|
-
|
8
|
+
protected overridePanZoomTool: PanZoom;
|
9
|
+
constructor(editor: Editor, tool: PanZoom, localizationTable: ToolbarLocalization);
|
11
10
|
private static getPrimaryHandTool;
|
11
|
+
private static getOverrideHandTool;
|
12
|
+
protected shouldAutoDisableInReadOnlyEditor(): boolean;
|
12
13
|
protected getTitle(): string;
|
13
14
|
protected createIcon(): Element;
|
14
15
|
protected handleClick(): void;
|
@@ -96,34 +96,45 @@ class HandModeWidget extends BaseWidget {
|
|
96
96
|
}
|
97
97
|
export default class HandToolWidget extends BaseToolWidget {
|
98
98
|
constructor(editor,
|
99
|
-
//
|
100
|
-
//
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
99
|
+
// Can either be the primary pan/zoom tool (in the primary tools list) or
|
100
|
+
// the override pan/zoom tool.
|
101
|
+
// If the override pan/zoom tool, the primary will be gotten from the editor's
|
102
|
+
// tool controller.
|
103
|
+
// If the primary, the override will be gotten from the editor's tool controller.
|
104
|
+
tool, localizationTable) {
|
105
|
+
const isGivenToolPrimary = editor.toolController.getPrimaryTools().includes(tool);
|
106
|
+
const primaryTool = (isGivenToolPrimary ? tool : HandToolWidget.getPrimaryHandTool(editor.toolController))
|
107
|
+
?? tool;
|
108
|
+
super(editor, primaryTool, 'hand-tool-widget', localizationTable);
|
109
|
+
this.overridePanZoomTool =
|
110
|
+
(isGivenToolPrimary ? HandToolWidget.getOverrideHandTool(editor.toolController) : tool)
|
111
|
+
?? tool;
|
106
112
|
// Only allow toggling a hand tool if we're using the primary hand tool and not the override
|
107
113
|
// hand tool for this button.
|
108
|
-
this.allowTogglingBaseTool =
|
114
|
+
this.allowTogglingBaseTool = primaryTool !== null;
|
109
115
|
// Allow showing/hiding the dropdown, even if `overridePanZoomTool` isn't enabled.
|
110
116
|
if (!this.allowTogglingBaseTool) {
|
111
117
|
this.container.classList.add('dropdownShowable');
|
112
118
|
}
|
113
119
|
// Controls for the overriding hand tool.
|
114
|
-
const touchPanningWidget = new HandModeWidget(editor, overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning, localizationTable);
|
115
|
-
const rotationLockWidget = new HandModeWidget(editor, overridePanZoomTool, PanZoomMode.RotationLocked, () => this.editor.icons.makeRotationLockIcon(), localizationTable.lockRotation, localizationTable);
|
120
|
+
const touchPanningWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning, localizationTable);
|
121
|
+
const rotationLockWidget = new HandModeWidget(editor, this.overridePanZoomTool, PanZoomMode.RotationLocked, () => this.editor.icons.makeRotationLockIcon(), localizationTable.lockRotation, localizationTable);
|
116
122
|
this.addSubWidget(touchPanningWidget);
|
117
123
|
this.addSubWidget(rotationLockWidget);
|
118
124
|
}
|
119
|
-
shouldAutoDisableInReadOnlyEditor() {
|
120
|
-
return false;
|
121
|
-
}
|
122
125
|
static getPrimaryHandTool(toolController) {
|
123
126
|
const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom);
|
124
127
|
const primaryPanZoomTool = primaryPanZoomToolList[0];
|
125
128
|
return primaryPanZoomTool;
|
126
129
|
}
|
130
|
+
static getOverrideHandTool(toolController) {
|
131
|
+
const panZoomToolList = toolController.getMatchingTools(PanZoom);
|
132
|
+
const panZoomTool = panZoomToolList[0];
|
133
|
+
return panZoomTool;
|
134
|
+
}
|
135
|
+
shouldAutoDisableInReadOnlyEditor() {
|
136
|
+
return false;
|
137
|
+
}
|
127
138
|
getTitle() {
|
128
139
|
return this.localizationTable.handTool;
|
129
140
|
}
|
@@ -3,10 +3,10 @@ import { ToolbarLocalization } from '../localization';
|
|
3
3
|
import BaseWidget from './BaseWidget';
|
4
4
|
export default class InsertImageWidget extends BaseWidget {
|
5
5
|
private imagePreview;
|
6
|
+
private image;
|
6
7
|
private selectedFiles;
|
7
8
|
private imageAltTextInput;
|
8
9
|
private statusView;
|
9
|
-
private imageBase64URL;
|
10
10
|
private submitButton;
|
11
11
|
constructor(editor: Editor, localization?: ToolbarLocalization);
|
12
12
|
protected getTitle(): string;
|
@@ -15,6 +15,7 @@ export default class InsertImageWidget extends BaseWidget {
|
|
15
15
|
protected handleClick(): void;
|
16
16
|
private static nextInputId;
|
17
17
|
protected fillDropdown(dropdown: HTMLElement): boolean;
|
18
|
+
private onImageDataUpdate;
|
18
19
|
private hideDialog;
|
19
20
|
private updateImageSizeDisplay;
|
20
21
|
private updateInputs;
|
@@ -9,10 +9,48 @@ import BaseWidget from './BaseWidget.mjs';
|
|
9
9
|
import { EditorEventType } from '../../types.mjs';
|
10
10
|
import { toolbarCSSPrefix } from '../constants.mjs';
|
11
11
|
import makeFileInput from './components/makeFileInput.mjs';
|
12
|
+
class ImageWrapper {
|
13
|
+
constructor(imageBase64Url, preview, onUrlUpdate) {
|
14
|
+
this.imageBase64Url = imageBase64Url;
|
15
|
+
this.preview = preview;
|
16
|
+
this.onUrlUpdate = onUrlUpdate;
|
17
|
+
this.originalSrc = imageBase64Url;
|
18
|
+
preview.src = imageBase64Url;
|
19
|
+
}
|
20
|
+
updateImageData(base64DataUrl) {
|
21
|
+
this.preview.src = base64DataUrl;
|
22
|
+
this.imageBase64Url = base64DataUrl;
|
23
|
+
this.onUrlUpdate();
|
24
|
+
}
|
25
|
+
decreaseSize(resizeFactor = 3 / 4) {
|
26
|
+
const canvas = document.createElement('canvas');
|
27
|
+
canvas.width = this.preview.naturalWidth * resizeFactor;
|
28
|
+
canvas.height = this.preview.naturalHeight * resizeFactor;
|
29
|
+
const ctx = canvas.getContext('2d');
|
30
|
+
ctx?.drawImage(this.preview, 0, 0, canvas.width, canvas.height);
|
31
|
+
// JPEG can be much smaller than PNG for the same image size. Prefer it if
|
32
|
+
// the image is already a JPEG.
|
33
|
+
const format = this.originalSrc?.startsWith('data:image/jpeg;') ? 'image/jpeg' : 'image/png';
|
34
|
+
this.updateImageData(canvas.toDataURL(format));
|
35
|
+
}
|
36
|
+
reset() {
|
37
|
+
this.updateImageData(this.originalSrc);
|
38
|
+
}
|
39
|
+
isChanged() {
|
40
|
+
return this.imageBase64Url !== this.originalSrc;
|
41
|
+
}
|
42
|
+
getBase64Url() {
|
43
|
+
return this.imageBase64Url;
|
44
|
+
}
|
45
|
+
static fromSrcAndPreview(initialBase64Src, preview, onUrlUpdate) {
|
46
|
+
return new ImageWrapper(initialBase64Src, preview, onUrlUpdate);
|
47
|
+
}
|
48
|
+
}
|
12
49
|
class InsertImageWidget extends BaseWidget {
|
13
50
|
constructor(editor, localization) {
|
14
51
|
localization ??= editor.localization;
|
15
52
|
super(editor, 'insert-image-widget', localization);
|
53
|
+
this.image = null;
|
16
54
|
// Make the dropdown showable
|
17
55
|
this.container.classList.add('dropdownShowable');
|
18
56
|
editor.notifier.on(EditorEventType.SelectionUpdated, event => {
|
@@ -46,6 +84,7 @@ class InsertImageWidget extends BaseWidget {
|
|
46
84
|
this.statusView = document.createElement('div');
|
47
85
|
const actionButtonRow = document.createElement('div');
|
48
86
|
actionButtonRow.classList.add('action-button-row');
|
87
|
+
this.statusView.classList.add('insert-image-image-status-view');
|
49
88
|
this.submitButton = document.createElement('button');
|
50
89
|
this.selectedFiles = selectedFiles;
|
51
90
|
this.imageAltTextInput = document.createElement('input');
|
@@ -60,9 +99,8 @@ class InsertImageWidget extends BaseWidget {
|
|
60
99
|
this.submitButton.innerText = this.localizationTable.submit;
|
61
100
|
this.selectedFiles.onUpdateAndNow(async (files) => {
|
62
101
|
if (files.length === 0) {
|
63
|
-
this.
|
64
|
-
this.
|
65
|
-
this.submitButton.style.display = 'none';
|
102
|
+
this.image = null;
|
103
|
+
this.onImageDataUpdate();
|
66
104
|
return;
|
67
105
|
}
|
68
106
|
this.imagePreview.style.display = 'block';
|
@@ -74,18 +112,13 @@ class InsertImageWidget extends BaseWidget {
|
|
74
112
|
catch (e) {
|
75
113
|
this.statusView.innerText = this.localizationTable.imageLoadError(e);
|
76
114
|
}
|
77
|
-
this.imageBase64URL = data;
|
78
115
|
if (data) {
|
79
|
-
this.
|
80
|
-
this.submitButton.disabled = false;
|
81
|
-
this.submitButton.style.display = '';
|
82
|
-
this.updateImageSizeDisplay();
|
116
|
+
this.image = ImageWrapper.fromSrcAndPreview(data, this.imagePreview, () => this.onImageDataUpdate());
|
83
117
|
}
|
84
118
|
else {
|
85
|
-
this.
|
86
|
-
this.submitButton.style.display = 'none';
|
87
|
-
this.statusView.innerText = '';
|
119
|
+
this.image = null;
|
88
120
|
}
|
121
|
+
this.onImageDataUpdate();
|
89
122
|
});
|
90
123
|
altTextRow.replaceChildren(imageAltTextLabel, this.imageAltTextInput);
|
91
124
|
actionButtonRow.replaceChildren(this.submitButton);
|
@@ -93,11 +126,27 @@ class InsertImageWidget extends BaseWidget {
|
|
93
126
|
dropdown.replaceChildren(container);
|
94
127
|
return true;
|
95
128
|
}
|
129
|
+
onImageDataUpdate() {
|
130
|
+
const base64Data = this.image?.getBase64Url();
|
131
|
+
if (base64Data) {
|
132
|
+
this.submitButton.disabled = false;
|
133
|
+
this.submitButton.style.display = '';
|
134
|
+
this.imagePreview.style.display = '';
|
135
|
+
this.updateImageSizeDisplay();
|
136
|
+
}
|
137
|
+
else {
|
138
|
+
this.submitButton.disabled = true;
|
139
|
+
this.submitButton.style.display = 'none';
|
140
|
+
this.statusView.innerText = '';
|
141
|
+
this.imagePreview.style.display = 'none';
|
142
|
+
this.submitButton.disabled = true;
|
143
|
+
}
|
144
|
+
}
|
96
145
|
hideDialog() {
|
97
146
|
this.setDropdownVisible(false);
|
98
147
|
}
|
99
148
|
updateImageSizeDisplay() {
|
100
|
-
const imageData = this.
|
149
|
+
const imageData = this.image?.getBase64Url() ?? '';
|
101
150
|
const sizeInKiB = imageData.length / 1024;
|
102
151
|
const sizeInMiB = sizeInKiB / 1024;
|
103
152
|
let units = 'KiB';
|
@@ -106,7 +155,27 @@ class InsertImageWidget extends BaseWidget {
|
|
106
155
|
size = sizeInMiB;
|
107
156
|
units = 'MiB';
|
108
157
|
}
|
109
|
-
|
158
|
+
const sizeText = document.createElement('span');
|
159
|
+
sizeText.innerText = this.localizationTable.imageSize(Math.round(size), units);
|
160
|
+
// Add a button to allow decreasing the size of large images.
|
161
|
+
const decreaseSizeButton = document.createElement('button');
|
162
|
+
decreaseSizeButton.innerText = this.localizationTable.decreaseImageSize;
|
163
|
+
decreaseSizeButton.onclick = () => {
|
164
|
+
this.image?.decreaseSize();
|
165
|
+
};
|
166
|
+
const resetSizeButton = document.createElement('button');
|
167
|
+
resetSizeButton.innerText = this.localizationTable.resetImage;
|
168
|
+
resetSizeButton.onclick = () => {
|
169
|
+
this.image?.reset();
|
170
|
+
};
|
171
|
+
this.statusView.replaceChildren(sizeText);
|
172
|
+
const largeImageThreshold = 0.12; // MiB
|
173
|
+
if (sizeInMiB > largeImageThreshold) {
|
174
|
+
this.statusView.appendChild(decreaseSizeButton);
|
175
|
+
}
|
176
|
+
else if (this.image?.isChanged()) {
|
177
|
+
this.statusView.appendChild(resetSizeButton);
|
178
|
+
}
|
110
179
|
}
|
111
180
|
updateInputs() {
|
112
181
|
const resetInputs = () => {
|
@@ -126,11 +195,8 @@ class InsertImageWidget extends BaseWidget {
|
|
126
195
|
if (selectedObjects.length === 1 && selectedObjects[0] instanceof ImageComponent) {
|
127
196
|
editingImage = selectedObjects[0];
|
128
197
|
this.imageAltTextInput.value = editingImage.getAltText() ?? '';
|
129
|
-
this.
|
130
|
-
this.
|
131
|
-
this.imageBase64URL = editingImage.getURL();
|
132
|
-
this.imagePreview.src = this.imageBase64URL;
|
133
|
-
this.updateImageSizeDisplay();
|
198
|
+
this.image = ImageWrapper.fromSrcAndPreview(editingImage.getURL(), this.imagePreview, () => this.onImageDataUpdate());
|
199
|
+
this.onImageDataUpdate();
|
134
200
|
}
|
135
201
|
else if (selectedObjects.length > 0) {
|
136
202
|
// If not, clear the selection.
|
@@ -144,13 +210,21 @@ class InsertImageWidget extends BaseWidget {
|
|
144
210
|
}
|
145
211
|
};
|
146
212
|
this.submitButton.onclick = async () => {
|
147
|
-
if (!this.
|
213
|
+
if (!this.image) {
|
148
214
|
return;
|
149
215
|
}
|
150
216
|
const image = new Image();
|
151
|
-
image.src = this.
|
217
|
+
image.src = this.image.getBase64Url();
|
152
218
|
image.setAttribute('alt', this.imageAltTextInput.value);
|
153
|
-
|
219
|
+
let component;
|
220
|
+
try {
|
221
|
+
component = await ImageComponent.fromImage(image, Mat33.identity);
|
222
|
+
}
|
223
|
+
catch (error) {
|
224
|
+
console.error('Error loading image', error);
|
225
|
+
this.statusView.innerText = this.localizationTable.imageLoadError(error);
|
226
|
+
return;
|
227
|
+
}
|
154
228
|
if (component.getBBox().area === 0) {
|
155
229
|
this.statusView.innerText = this.localizationTable.errorImageHasZeroSize;
|
156
230
|
return;
|
@@ -158,9 +232,15 @@ class InsertImageWidget extends BaseWidget {
|
|
158
232
|
this.hideDialog();
|
159
233
|
if (editingImage) {
|
160
234
|
const eraseCommand = new Erase([editingImage]);
|
235
|
+
// Try to preserve the original width
|
236
|
+
const originalTransform = editingImage.getTransformation();
|
237
|
+
// || 1: Prevent division by zero
|
238
|
+
const originalWidth = editingImage.getBBox().width || 1;
|
239
|
+
const newWidth = component.getBBox().transformedBoundingBox(originalTransform).width || 1;
|
240
|
+
const widthAdjustTransform = Mat33.scaling2D(originalWidth / newWidth);
|
161
241
|
await this.editor.dispatch(uniteCommands([
|
162
242
|
EditorImage.addElement(component),
|
163
|
-
component.transformBy(
|
243
|
+
component.transformBy(originalTransform.rightMul(widthAdjustTransform)),
|
164
244
|
component.setZIndex(editingImage.getZIndex()),
|
165
245
|
eraseCommand,
|
166
246
|
]));
|
@@ -24,8 +24,7 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
24
24
|
private createIconForRecord;
|
25
25
|
protected createIcon(): Element;
|
26
26
|
private createPenTypeSelector;
|
27
|
-
|
28
|
-
protected createStabilizationOption(): {
|
27
|
+
protected createStrokeCorrectionOptions(): {
|
29
28
|
update: () => void;
|
30
29
|
addTo: (parent: HTMLElement) => void;
|
31
30
|
};
|
@@ -147,27 +147,51 @@ class PenToolWidget extends BaseToolWidget {
|
|
147
147
|
},
|
148
148
|
};
|
149
149
|
}
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
150
|
+
createStrokeCorrectionOptions() {
|
151
|
+
const container = document.createElement('div');
|
152
|
+
container.classList.add('action-button-row', `${toolbarCSSPrefix}-pen-tool-toggle-buttons`);
|
153
|
+
const addToggleButton = (labelText, icon) => {
|
154
|
+
const button = document.createElement('button');
|
155
|
+
button.classList.add(`${toolbarCSSPrefix}-toggle-button`);
|
156
|
+
const iconElement = icon.cloneNode(true);
|
157
|
+
iconElement.classList.add('icon');
|
158
|
+
const label = document.createElement('span');
|
159
|
+
label.innerText = labelText;
|
160
|
+
button.replaceChildren(iconElement, label);
|
161
|
+
button.setAttribute('role', 'switch');
|
162
|
+
container.appendChild(button);
|
163
|
+
let checked = false;
|
164
|
+
let onChangeListener = (_checked) => { };
|
165
|
+
const result = {
|
166
|
+
setChecked(newChecked) {
|
167
|
+
checked = newChecked;
|
168
|
+
button.setAttribute('aria-checked', `${checked}`);
|
169
|
+
onChangeListener(checked);
|
170
|
+
},
|
171
|
+
setOnInputListener(listener) {
|
172
|
+
onChangeListener = listener;
|
173
|
+
},
|
174
|
+
};
|
175
|
+
button.onclick = () => {
|
176
|
+
result.setChecked(!checked);
|
177
|
+
};
|
178
|
+
return result;
|
164
179
|
};
|
180
|
+
const stabilizationOption = addToggleButton(this.localizationTable.inputStabilization, this.editor.icons.makeStrokeSmoothingIcon());
|
181
|
+
stabilizationOption.setOnInputListener(enabled => {
|
182
|
+
this.tool.setHasStabilization(enabled);
|
183
|
+
});
|
184
|
+
const autocorrectOption = addToggleButton(this.localizationTable.strokeAutocorrect, this.editor.icons.makeShapeAutocorrectIcon());
|
185
|
+
autocorrectOption.setOnInputListener(enabled => {
|
186
|
+
this.tool.setStrokeAutocorrectEnabled(enabled);
|
187
|
+
});
|
165
188
|
return {
|
166
189
|
update: () => {
|
167
|
-
|
190
|
+
stabilizationOption.setChecked(!!this.tool.getInputMapper());
|
191
|
+
autocorrectOption.setChecked(this.tool.getStrokeAutocorrectionEnabled());
|
168
192
|
},
|
169
193
|
addTo: (parent) => {
|
170
|
-
parent.appendChild(
|
194
|
+
parent.appendChild(container);
|
171
195
|
}
|
172
196
|
};
|
173
197
|
}
|
@@ -189,20 +213,22 @@ class PenToolWidget extends BaseToolWidget {
|
|
189
213
|
colorLabel.setAttribute('for', colorInput.id);
|
190
214
|
colorRow.appendChild(colorLabel);
|
191
215
|
colorRow.appendChild(colorInputContainer);
|
192
|
-
const
|
216
|
+
const toggleButtonRow = this.createStrokeCorrectionOptions();
|
193
217
|
this.updateInputs = () => {
|
194
218
|
setColorInputValue(this.tool.getColor());
|
195
219
|
setThickness(this.tool.getThickness());
|
196
220
|
penTypeSelect.updateIcons();
|
197
221
|
// Update the selected stroke factory.
|
198
222
|
penTypeSelect.setValue(this.getCurrentPenTypeIdx());
|
199
|
-
|
223
|
+
toggleButtonRow.update();
|
200
224
|
};
|
201
225
|
this.updateInputs();
|
202
226
|
container.replaceChildren(colorRow, thicknessRow);
|
203
227
|
penTypeSelect.addTo(container);
|
204
|
-
stabilizationOption.addTo(container);
|
205
228
|
dropdown.replaceChildren(container);
|
229
|
+
// Add the toggle button row *outside* of the main content (use different
|
230
|
+
// spacing with respect to the sides of the container).
|
231
|
+
toggleButtonRow.addTo(dropdown);
|
206
232
|
return true;
|
207
233
|
}
|
208
234
|
onKeyPress(event) {
|
@@ -232,6 +258,7 @@ class PenToolWidget extends BaseToolWidget {
|
|
232
258
|
thickness: this.tool.getThickness(),
|
233
259
|
strokeFactoryId: this.getCurrentPenType()?.id,
|
234
260
|
inputStabilization: !!this.tool.getInputMapper(),
|
261
|
+
strokeAutocorrect: this.tool.getStrokeAutocorrectionEnabled(),
|
235
262
|
};
|
236
263
|
}
|
237
264
|
deserializeFrom(state) {
|
@@ -262,7 +289,10 @@ class PenToolWidget extends BaseToolWidget {
|
|
262
289
|
}
|
263
290
|
}
|
264
291
|
if (state.inputStabilization !== undefined) {
|
265
|
-
this.
|
292
|
+
this.tool.setHasStabilization(!!state.inputStabilization);
|
293
|
+
}
|
294
|
+
if (state.strokeAutocorrect !== undefined) {
|
295
|
+
this.tool.setStrokeAutocorrectEnabled(!!state.strokeAutocorrect);
|
266
296
|
}
|
267
297
|
}
|
268
298
|
}
|
@@ -1,3 +1,4 @@
|
|
1
1
|
export declare const resizeImageToSelectionKeyboardShortcut = "jsdraw.toolbar.SelectionTool.resizeImageToSelection";
|
2
2
|
export declare const selectStrokeTypeKeyboardShortcutIds: string[];
|
3
3
|
export declare const saveKeyboardShortcut = "jsdraw.toolbar.SaveActionWidget.save";
|
4
|
+
export declare const exitKeyboardShortcut = "jsdraw.toolbar.ExitActionWidget.exit";
|
@@ -11,3 +11,6 @@ for (let i = 0; i < selectStrokeTypeKeyboardShortcutIds.length; i++) {
|
|
11
11
|
// Save
|
12
12
|
export const saveKeyboardShortcut = 'jsdraw.toolbar.SaveActionWidget.save';
|
13
13
|
KeyboardShortcutManager.registerDefaultKeyboardShortcut(saveKeyboardShortcut, ['ctrlOrMeta+KeyS'], 'Save');
|
14
|
+
// Exit
|
15
|
+
export const exitKeyboardShortcut = 'jsdraw.toolbar.ExitActionWidget.exit';
|
16
|
+
KeyboardShortcutManager.registerDefaultKeyboardShortcut(exitKeyboardShortcut, ['Alt+KeyQ'], 'Exit');
|
package/dist/mjs/tools/Pen.d.ts
CHANGED
@@ -19,6 +19,11 @@ export default class Pen extends BaseTool {
|
|
19
19
|
private currentDeviceType;
|
20
20
|
private styleValue;
|
21
21
|
private style;
|
22
|
+
private shapeAutocompletionEnabled;
|
23
|
+
private autocorrectedShape;
|
24
|
+
private lastAutocorrectedShape;
|
25
|
+
private removedAutocorrectedShapeTime;
|
26
|
+
private stationaryDetector;
|
22
27
|
constructor(editor: Editor, description: string, style: Partial<PenStyle>);
|
23
28
|
private getPressureMultiplier;
|
24
29
|
protected toStrokePoint(pointer: Pointer): StrokeDataPoint;
|
@@ -30,12 +35,16 @@ export default class Pen extends BaseTool {
|
|
30
35
|
onPointerMove({ current }: PointerEvt): void;
|
31
36
|
onPointerUp({ current }: PointerEvt): boolean;
|
32
37
|
onGestureCancel(): void;
|
38
|
+
private removedAutocorrectedShapeRecently;
|
39
|
+
private autocorrectShape;
|
33
40
|
private finalizeStroke;
|
34
41
|
private noteUpdated;
|
35
42
|
setColor(color: Color4): void;
|
36
43
|
setThickness(thickness: number): void;
|
37
44
|
setStrokeFactory(factory: ComponentBuilderFactory): void;
|
38
45
|
setHasStabilization(hasStabilization: boolean): void;
|
46
|
+
setStrokeAutocorrectEnabled(enabled: boolean): void;
|
47
|
+
getStrokeAutocorrectionEnabled(): boolean;
|
39
48
|
getThickness(): number;
|
40
49
|
getColor(): Color4;
|
41
50
|
getStrokeFactory(): ComponentBuilderFactory;
|