js-draw 1.2.2 → 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 +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 +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/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/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 +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/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/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
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}
|
@@ -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,
|
package/dist/mjs/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;
|
@@ -43,3 +43,13 @@ export const assertIsNumberArray = (values, allowNaN = false) => {
|
|
43
43
|
}
|
44
44
|
return true;
|
45
45
|
};
|
46
|
+
/**
|
47
|
+
* Throws an exception if `typeof value` is not a boolean.
|
48
|
+
*/
|
49
|
+
export const assertIsBoolean = (value) => {
|
50
|
+
if (typeof value !== 'boolean') {
|
51
|
+
throw new Error('Given value is not a boolean');
|
52
|
+
// return false;
|
53
|
+
}
|
54
|
+
return true;
|
55
|
+
};
|
package/dist/mjs/version.mjs
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.3.0",
|
4
4
|
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
|
5
5
|
"types": "./dist/mjs/lib.d.ts",
|
6
6
|
"main": "./dist/cjs/lib.js",
|
@@ -64,12 +64,11 @@
|
|
64
64
|
"postpack": "ts-node tools/copyREADME.ts revert"
|
65
65
|
},
|
66
66
|
"dependencies": {
|
67
|
-
"@js-draw/math": "^1.
|
67
|
+
"@js-draw/math": "^1.3.0",
|
68
68
|
"@melloware/coloris": "0.21.0"
|
69
69
|
},
|
70
70
|
"devDependencies": {
|
71
71
|
"@js-draw/build-tool": "^1.0.2",
|
72
|
-
"@types/bezier-js": "4.1.0",
|
73
72
|
"@types/jest": "29.5.3",
|
74
73
|
"@types/jsdom": "21.1.1"
|
75
74
|
},
|
@@ -87,5 +86,5 @@
|
|
87
86
|
"freehand",
|
88
87
|
"svg"
|
89
88
|
],
|
90
|
-
"gitHead": "
|
89
|
+
"gitHead": "46b3d8f819f8e083f6e3e1d01e027e4311355456"
|
91
90
|
}
|
package/src/Editor.scss
CHANGED
@@ -136,6 +136,14 @@
|
|
136
136
|
z-index: 5;
|
137
137
|
}
|
138
138
|
|
139
|
+
// TODO: Apply this change during a future major release.
|
140
|
+
// So as not to change the position of other overlays, all overlays should have
|
141
|
+
// 0 height.
|
142
|
+
// Uses the alternate overlay class name to decrease specificity.
|
143
|
+
// .js-draw-editor-overlay {
|
144
|
+
// //height: 0;
|
145
|
+
// }
|
146
|
+
|
139
147
|
@media print {
|
140
148
|
.imageEditorContainer .loadingMessage {
|
141
149
|
display: none;
|
package/src/dialogs/dialogs.scss
CHANGED
@@ -480,6 +480,8 @@
|
|
480
480
|
.toolbar-spacedList {
|
481
481
|
box-sizing: border-box;
|
482
482
|
|
483
|
+
--align-items-to-x: 105px;
|
484
|
+
|
483
485
|
& > div {
|
484
486
|
display: flex;
|
485
487
|
align-items: center;
|
@@ -491,7 +493,7 @@
|
|
491
493
|
// Align inputs (assumes labels come first)
|
492
494
|
& > label {
|
493
495
|
padding-right: 35px;
|
494
|
-
min-width:
|
496
|
+
min-width: var(--align-items-to-x);
|
495
497
|
flex-shrink: 1;
|
496
498
|
box-sizing: border-box;
|
497
499
|
}
|
@@ -499,6 +501,7 @@
|
|
499
501
|
& > input[type="checkbox"] {
|
500
502
|
width: 20px;
|
501
503
|
height: 20px;
|
504
|
+
margin-left: 0;
|
502
505
|
}
|
503
506
|
|
504
507
|
// If checkboxes have flex-grow, the checkbox region can become centered.
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
.ScrollbarTool-overlay {
|
3
|
+
width: 0;
|
4
|
+
height: 0;
|
5
|
+
overflow: visible;
|
6
|
+
|
7
|
+
$visible-opacity: 0.2;
|
8
|
+
opacity: $visible-opacity;
|
9
|
+
pointer-events: none;
|
10
|
+
|
11
|
+
--fade-out-animation: 1s ease 0s fade-out;
|
12
|
+
|
13
|
+
@media (prefers-reduced-motion: reduce) {
|
14
|
+
--fade-out-animation: none !important;
|
15
|
+
}
|
16
|
+
|
17
|
+
@keyframes fade-out {
|
18
|
+
from { opacity: $visible-opacity; }
|
19
|
+
to { opacity: 0; }
|
20
|
+
}
|
21
|
+
|
22
|
+
&:not(.just-updated) {
|
23
|
+
animation: var(--fade-out-animation);
|
24
|
+
opacity: 0;
|
25
|
+
}
|
26
|
+
|
27
|
+
--scrollbar-size: 3px;
|
28
|
+
|
29
|
+
.vertical-scrollbar, .horizontal-scrollbar {
|
30
|
+
width: var(--scrollbar-size);
|
31
|
+
height: var(--scrollbar-size);
|
32
|
+
|
33
|
+
min-width: var(--scrollbar-size);
|
34
|
+
min-height: var(--scrollbar-size);
|
35
|
+
|
36
|
+
background-color: var(--foreground-color-1);
|
37
|
+
border-radius: var(--scrollbar-size);
|
38
|
+
position: absolute;
|
39
|
+
|
40
|
+
&.represents-no-scroll {
|
41
|
+
animation: var(--fade-out-animation);
|
42
|
+
opacity: 0;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
&:not(.scrollbar-left) {
|
47
|
+
.vertical-scrollbar {
|
48
|
+
margin-left: calc(var(--editor-current-display-width-px) - var(--scrollbar-size));
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
&:not(.scrollbar-top) {
|
53
|
+
.horizontal-scrollbar {
|
54
|
+
margin-top: calc(var(--editor-current-display-height-px) - var(--scrollbar-size));
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|