js-draw 0.15.1 → 0.16.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/.github/ISSUE_TEMPLATE/translation.yml +56 -0
- package/CHANGELOG.md +13 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +1 -1
- package/dist/src/Color4.js +5 -1
- package/dist/src/Editor.d.ts +11 -2
- package/dist/src/Editor.js +66 -33
- package/dist/src/EditorImage.d.ts +28 -3
- package/dist/src/EditorImage.js +109 -18
- package/dist/src/EventDispatcher.d.ts +4 -3
- package/dist/src/SVGLoader.d.ts +1 -0
- package/dist/src/SVGLoader.js +15 -1
- package/dist/src/Viewport.d.ts +8 -3
- package/dist/src/Viewport.js +15 -8
- package/dist/src/components/AbstractComponent.d.ts +6 -1
- package/dist/src/components/AbstractComponent.js +15 -2
- package/dist/src/components/ImageBackground.d.ts +42 -0
- package/dist/src/components/ImageBackground.js +139 -0
- package/dist/src/components/ImageComponent.js +2 -0
- package/dist/src/components/builders/ArrowBuilder.d.ts +3 -1
- package/dist/src/components/builders/ArrowBuilder.js +43 -40
- package/dist/src/components/builders/LineBuilder.d.ts +3 -1
- package/dist/src/components/builders/LineBuilder.js +25 -28
- package/dist/src/components/builders/RectangleBuilder.js +1 -1
- package/dist/src/components/lib.d.ts +2 -1
- package/dist/src/components/lib.js +2 -1
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/math/Mat33.js +43 -5
- package/dist/src/math/Path.d.ts +5 -0
- package/dist/src/math/Path.js +80 -28
- package/dist/src/math/Vec3.js +1 -1
- package/dist/src/rendering/Display.js +1 -1
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +13 -1
- package/dist/src/rendering/renderers/AbstractRenderer.js +18 -3
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +12 -2
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
- package/dist/src/testing/sendTouchEvent.d.ts +6 -0
- package/dist/src/testing/sendTouchEvent.js +26 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +25 -2
- package/dist/src/toolbar/HTMLToolbar.js +127 -15
- package/dist/src/toolbar/IconProvider.d.ts +2 -0
- package/dist/src/toolbar/IconProvider.js +45 -2
- package/dist/src/toolbar/localization.d.ts +5 -0
- package/dist/src/toolbar/localization.js +5 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +5 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -1
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +7 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +23 -1
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +19 -0
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.js +135 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +1 -1
- package/dist/src/toolbar/widgets/OverflowWidget.d.ts +25 -0
- package/dist/src/toolbar/widgets/OverflowWidget.js +65 -0
- package/dist/src/toolbar/widgets/lib.d.ts +1 -0
- package/dist/src/toolbar/widgets/lib.js +1 -0
- package/dist/src/tools/Eraser.js +5 -2
- package/dist/src/tools/PanZoom.js +12 -0
- package/dist/src/tools/PasteHandler.js +2 -2
- package/dist/src/tools/SelectionTool/Selection.d.ts +2 -1
- package/dist/src/tools/SelectionTool/Selection.js +3 -2
- package/dist/src/tools/SelectionTool/SelectionTool.js +5 -1
- package/package.json +1 -1
- package/src/Color4.test.ts +6 -0
- package/src/Color4.ts +6 -1
- package/src/Editor.loadFrom.test.ts +24 -0
- package/src/Editor.ts +73 -39
- package/src/EditorImage.ts +136 -21
- package/src/EventDispatcher.ts +4 -1
- package/src/SVGLoader.ts +12 -1
- package/src/Viewport.ts +17 -7
- package/src/components/AbstractComponent.ts +17 -1
- package/src/components/ImageBackground.test.ts +35 -0
- package/src/components/ImageBackground.ts +176 -0
- package/src/components/ImageComponent.ts +2 -0
- package/src/components/builders/ArrowBuilder.ts +44 -41
- package/src/components/builders/LineBuilder.ts +26 -28
- package/src/components/builders/RectangleBuilder.ts +1 -1
- package/src/components/lib.ts +2 -0
- package/src/components/localization.ts +4 -0
- package/src/localizations/es.ts +8 -0
- package/src/math/Mat33.test.ts +47 -3
- package/src/math/Mat33.ts +47 -5
- package/src/math/Path.ts +87 -28
- package/src/math/Vec3.test.ts +4 -0
- package/src/math/Vec3.ts +1 -1
- package/src/rendering/Display.ts +1 -1
- package/src/rendering/renderers/AbstractRenderer.ts +20 -3
- package/src/rendering/renderers/CanvasRenderer.ts +17 -4
- package/src/rendering/renderers/DummyRenderer.test.ts +1 -2
- package/src/rendering/renderers/SVGRenderer.ts +8 -1
- package/src/testing/sendTouchEvent.ts +43 -0
- package/src/toolbar/HTMLToolbar.ts +164 -16
- package/src/toolbar/IconProvider.ts +47 -2
- package/src/toolbar/localization.ts +10 -0
- package/src/toolbar/toolbar.css +2 -0
- package/src/toolbar/widgets/ActionButtonWidget.ts +5 -0
- package/src/toolbar/widgets/BaseToolWidget.ts +3 -1
- package/src/toolbar/widgets/BaseWidget.ts +34 -2
- package/src/toolbar/widgets/DocumentPropertiesWidget.ts +185 -0
- package/src/toolbar/widgets/HandToolWidget.ts +1 -1
- package/src/toolbar/widgets/OverflowWidget.css +9 -0
- package/src/toolbar/widgets/OverflowWidget.ts +83 -0
- package/src/toolbar/widgets/lib.ts +2 -1
- package/src/tools/Eraser.test.ts +24 -1
- package/src/tools/Eraser.ts +6 -2
- package/src/tools/PanZoom.test.ts +267 -23
- package/src/tools/PanZoom.ts +15 -1
- package/src/tools/PasteHandler.ts +3 -2
- package/src/tools/SelectionTool/Selection.ts +3 -2
- package/src/tools/SelectionTool/SelectionTool.ts +6 -1
- package/src/types.ts +1 -0
@@ -1,6 +1,5 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import { ComponentBuilderFactory } from '../components/builders/types';
|
3
|
-
import EventDispatcher from '../EventDispatcher';
|
4
3
|
import { Vec2 } from '../math/Vec2';
|
5
4
|
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
6
5
|
import TextStyle from '../rendering/TextRenderingStyle';
|
@@ -542,7 +541,7 @@ export default class IconProvider {
|
|
542
541
|
time: nowTime,
|
543
542
|
};
|
544
543
|
|
545
|
-
const viewport = new Viewport(
|
544
|
+
const viewport = new Viewport(() => {});
|
546
545
|
const builder = factory(startPoint, viewport);
|
547
546
|
builder.addPoint(endPoint);
|
548
547
|
|
@@ -687,5 +686,51 @@ export default class IconProvider {
|
|
687
686
|
svg.setAttribute('viewBox', '0 0 100 100');
|
688
687
|
return svg;
|
689
688
|
}
|
689
|
+
|
690
|
+
public makeConfigureDocumentIcon(): IconType {
|
691
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
692
|
+
svg.innerHTML = `
|
693
|
+
<path
|
694
|
+
d='
|
695
|
+
M 5,5 V 95 H 95 V 5 Z m 5,5 H 90 V 90 H 10 Z
|
696
|
+
m 5,10 V 30 H 50 V 25 H 20 v -5 z
|
697
|
+
m 40,0 V 50 H 85 V 20 Z
|
698
|
+
m 2,2 H 83 V 39 L 77,28 70,42 64,35 57,45 Z
|
699
|
+
m 8.5,5 C 64.67,27 64,27.67 64,28.5 64,29.33 64.67,30 65.5,30 66.33,30 67,29.33 67,28.5 67,27.67 66.33,27 65.5,27 Z
|
700
|
+
M 15,40 v 5 h 35 v -5 z
|
701
|
+
m 0,15 v 5 h 70 v -5 z
|
702
|
+
m 0,15 v 5 h 70 v -5 z
|
703
|
+
'
|
704
|
+
style='fill: var(--icon-color);'
|
705
|
+
/>
|
706
|
+
`;
|
707
|
+
svg.setAttribute('viewBox', '0 0 100 100');
|
708
|
+
return svg;
|
709
|
+
}
|
710
|
+
|
711
|
+
public makeOverflowIcon(): IconType {
|
712
|
+
return this.makeIconFromPath(`
|
713
|
+
M 15 40
|
714
|
+
A 12.5 12.5 0 0 0 2.5 52.5
|
715
|
+
A 12.5 12.5 0 0 0 15 65
|
716
|
+
A 12.5 12.5 0 0 0 27.5 52.5
|
717
|
+
A 12.5 12.5 0 0 0 15 40
|
718
|
+
z
|
719
|
+
|
720
|
+
M 50 40
|
721
|
+
A 12.5 12.5 0 0 0 37.5 52.5
|
722
|
+
A 12.5 12.5 0 0 0 50 65
|
723
|
+
A 12.5 12.5 0 0 0 62.5 52.5
|
724
|
+
A 12.5 12.5 0 0 0 50 40
|
725
|
+
z
|
726
|
+
|
727
|
+
M 85 40
|
728
|
+
A 12.5 12.5 0 0 0 72.5 52.5
|
729
|
+
A 12.5 12.5 0 0 0 85 65
|
730
|
+
A 12.5 12.5 0 0 0 97.5 52.5
|
731
|
+
A 12.5 12.5 0 0 0 85 40
|
732
|
+
z
|
733
|
+
`);
|
734
|
+
}
|
690
735
|
|
691
736
|
}
|
@@ -35,6 +35,11 @@ export interface ToolbarLocalization {
|
|
35
35
|
resetView: string;
|
36
36
|
selectionToolKeyboardShortcuts: string;
|
37
37
|
paste: string;
|
38
|
+
documentProperties: string;
|
39
|
+
backgroundColor: string;
|
40
|
+
imageWidthOption: string;
|
41
|
+
imageHeightOption: string;
|
42
|
+
toggleOverflow: string,
|
38
43
|
|
39
44
|
errorImageHasZeroSize: string;
|
40
45
|
|
@@ -72,6 +77,11 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
72
77
|
pickColorFromScreen: 'Pick color from screen',
|
73
78
|
clickToPickColorAnnouncement: 'Click on the screen to pick a color',
|
74
79
|
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
|
80
|
+
documentProperties: 'Document',
|
81
|
+
backgroundColor: 'Background Color: ',
|
82
|
+
imageWidthOption: 'Width: ',
|
83
|
+
imageHeightOption: 'Height: ',
|
84
|
+
toggleOverflow: 'More',
|
75
85
|
|
76
86
|
touchPanning: 'Touchscreen panning',
|
77
87
|
|
package/src/toolbar/toolbar.css
CHANGED
@@ -12,6 +12,7 @@ export default class ActionButtonWidget extends BaseWidget {
|
|
12
12
|
protected clickAction: ()=>void,
|
13
13
|
|
14
14
|
localizationTable?: ToolbarLocalization,
|
15
|
+
protected mustBeToplevel: boolean = false,
|
15
16
|
) {
|
16
17
|
super(editor, id, localizationTable);
|
17
18
|
}
|
@@ -31,4 +32,8 @@ export default class ActionButtonWidget extends BaseWidget {
|
|
31
32
|
protected fillDropdown(_dropdown: HTMLElement): boolean {
|
32
33
|
return false;
|
33
34
|
}
|
35
|
+
|
36
|
+
public canBeInOverflowMenu(): boolean {
|
37
|
+
return !this.mustBeToplevel;
|
38
|
+
}
|
34
39
|
}
|
@@ -48,7 +48,9 @@ export default abstract class BaseToolWidget extends BaseWidget {
|
|
48
48
|
}
|
49
49
|
|
50
50
|
public addTo(parent: HTMLElement) {
|
51
|
-
super.addTo(parent);
|
51
|
+
const result = super.addTo(parent);
|
52
52
|
this.setSelected(this.targetTool.isEnabled());
|
53
|
+
|
54
|
+
return result;
|
53
55
|
}
|
54
56
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
|
+
import { DispatcherEventListener } from '../../EventDispatcher';
|
2
3
|
import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler';
|
3
4
|
import { EditorEventType, InputEvtType, KeyPressEvent } from '../../types';
|
4
5
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
@@ -168,7 +169,10 @@ export default abstract class BaseWidget {
|
|
168
169
|
this.subWidgets[id] = widget;
|
169
170
|
}
|
170
171
|
|
172
|
+
private toolbarWidgetToggleListener: DispatcherEventListener|null = null;
|
173
|
+
|
171
174
|
// Adds this to [parent]. This can only be called once for each ToolbarWidget.
|
175
|
+
// Returns the element that was just added to `parent`.
|
172
176
|
// @internal
|
173
177
|
public addTo(parent: HTMLElement) {
|
174
178
|
this.label.innerText = this.getTitle();
|
@@ -178,6 +182,8 @@ export default abstract class BaseWidget {
|
|
178
182
|
this.icon = null;
|
179
183
|
this.updateIcon();
|
180
184
|
|
185
|
+
this.container.replaceChildren();
|
186
|
+
|
181
187
|
this.button.replaceChildren(this.icon!, this.label);
|
182
188
|
this.container.appendChild(this.button);
|
183
189
|
|
@@ -187,7 +193,11 @@ export default abstract class BaseWidget {
|
|
187
193
|
this.button.appendChild(this.dropdownIcon);
|
188
194
|
this.container.appendChild(this.dropdownContainer);
|
189
195
|
|
190
|
-
this.
|
196
|
+
if (this.toolbarWidgetToggleListener) {
|
197
|
+
this.toolbarWidgetToggleListener.remove();
|
198
|
+
}
|
199
|
+
|
200
|
+
this.toolbarWidgetToggleListener = this.editor.notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
|
191
201
|
if (
|
192
202
|
evt.kind === EditorEventType.ToolbarDropdownShown
|
193
203
|
&& evt.parentWidget !== this
|
@@ -202,7 +212,13 @@ export default abstract class BaseWidget {
|
|
202
212
|
}
|
203
213
|
|
204
214
|
this.setDropdownVisible(false);
|
215
|
+
|
216
|
+
if (this.container.parentElement) {
|
217
|
+
this.container.remove();
|
218
|
+
}
|
219
|
+
|
205
220
|
parent.appendChild(this.container);
|
221
|
+
return this.container;
|
206
222
|
}
|
207
223
|
|
208
224
|
|
@@ -272,6 +288,22 @@ export default abstract class BaseWidget {
|
|
272
288
|
this.repositionDropdown();
|
273
289
|
}
|
274
290
|
|
291
|
+
public canBeInOverflowMenu(): boolean {
|
292
|
+
return true;
|
293
|
+
}
|
294
|
+
|
295
|
+
public getButtonWidth(): number {
|
296
|
+
return this.button.clientWidth;
|
297
|
+
}
|
298
|
+
|
299
|
+
public isHidden(): boolean {
|
300
|
+
return this.container.style.display === 'none';
|
301
|
+
}
|
302
|
+
|
303
|
+
public setHidden(hidden: boolean) {
|
304
|
+
this.container.style.display = hidden ? 'none' : '';
|
305
|
+
}
|
306
|
+
|
275
307
|
protected repositionDropdown() {
|
276
308
|
const dropdownBBox = this.dropdownContainer.getBoundingClientRect();
|
277
309
|
const screenWidth = document.body.clientWidth;
|
@@ -286,7 +318,7 @@ export default abstract class BaseWidget {
|
|
286
318
|
}
|
287
319
|
|
288
320
|
/** Set whether the widget is contained within another. @internal */
|
289
|
-
|
321
|
+
public setIsToplevel(toplevel: boolean) {
|
290
322
|
this.toplevel = toplevel;
|
291
323
|
}
|
292
324
|
|
@@ -0,0 +1,185 @@
|
|
1
|
+
import Color4 from '../../Color4';
|
2
|
+
import ImageBackground from '../../components/ImageBackground';
|
3
|
+
import Editor from '../../Editor';
|
4
|
+
import { EditorImageEventType } from '../../EditorImage';
|
5
|
+
import Rect2 from '../../math/Rect2';
|
6
|
+
import { EditorEventType } from '../../types';
|
7
|
+
import { ToolbarLocalization } from '../localization';
|
8
|
+
import makeColorInput from '../makeColorInput';
|
9
|
+
import BaseWidget from './BaseWidget';
|
10
|
+
|
11
|
+
export default class DocumentPropertiesWidget extends BaseWidget {
|
12
|
+
private updateDropdownContent: ()=>void = () => {};
|
13
|
+
|
14
|
+
public constructor(editor: Editor, localizationTable?: ToolbarLocalization) {
|
15
|
+
super(editor, 'zoom-widget', localizationTable);
|
16
|
+
|
17
|
+
// Make it possible to open the dropdown, even if this widget isn't selected.
|
18
|
+
this.container.classList.add('dropdownShowable');
|
19
|
+
|
20
|
+
this.editor.notifier.on(EditorEventType.UndoRedoStackUpdated, () => {
|
21
|
+
this.queueDropdownUpdate();
|
22
|
+
});
|
23
|
+
|
24
|
+
|
25
|
+
this.editor.image.notifier.on(EditorImageEventType.ExportViewportChanged, () => {
|
26
|
+
this.queueDropdownUpdate();
|
27
|
+
});
|
28
|
+
}
|
29
|
+
|
30
|
+
protected getTitle(): string {
|
31
|
+
return this.localizationTable.documentProperties;
|
32
|
+
}
|
33
|
+
|
34
|
+
protected createIcon(): Element {
|
35
|
+
return this.editor.icons.makeConfigureDocumentIcon();
|
36
|
+
}
|
37
|
+
|
38
|
+
protected handleClick() {
|
39
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
40
|
+
this.queueDropdownUpdate();
|
41
|
+
}
|
42
|
+
|
43
|
+
private dropdownUpdateQueued: boolean = false;
|
44
|
+
private queueDropdownUpdate() {
|
45
|
+
if (!this.dropdownUpdateQueued) {
|
46
|
+
requestAnimationFrame(() => this.updateDropdown());
|
47
|
+
this.dropdownUpdateQueued = true;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
private updateDropdown() {
|
52
|
+
this.dropdownUpdateQueued = false;
|
53
|
+
|
54
|
+
if (this.isDropdownVisible()) {
|
55
|
+
this.updateDropdownContent();
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
private getBackgroundElem() {
|
60
|
+
const backgroundComponents = [];
|
61
|
+
|
62
|
+
for (const component of this.editor.image.getBackgroundComponents()) {
|
63
|
+
if (component instanceof ImageBackground) {
|
64
|
+
backgroundComponents.push(component);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
if (backgroundComponents.length === 0) {
|
69
|
+
return null;
|
70
|
+
}
|
71
|
+
|
72
|
+
// Return the last background component in the list — the component with highest z-index.
|
73
|
+
return backgroundComponents[backgroundComponents.length - 1];
|
74
|
+
}
|
75
|
+
|
76
|
+
private setBackgroundColor(color: Color4) {
|
77
|
+
this.editor.dispatch(this.editor.setBackgroundColor(color));
|
78
|
+
}
|
79
|
+
|
80
|
+
private getBackgroundColor() {
|
81
|
+
const background = this.getBackgroundElem();
|
82
|
+
|
83
|
+
return background?.getStyle()?.color ?? Color4.transparent;
|
84
|
+
}
|
85
|
+
|
86
|
+
private updateImportExportRectSize(size: { width?: number, height?: number }) {
|
87
|
+
const filterDimension = (dim: number|undefined) => {
|
88
|
+
if (dim !== undefined && (!isFinite(dim) || dim <= 0)) {
|
89
|
+
dim = 100;
|
90
|
+
}
|
91
|
+
|
92
|
+
return dim;
|
93
|
+
};
|
94
|
+
|
95
|
+
const width = filterDimension(size.width);
|
96
|
+
const height = filterDimension(size.height);
|
97
|
+
|
98
|
+
const currentRect = this.editor.getImportExportRect();
|
99
|
+
const newRect = new Rect2(
|
100
|
+
currentRect.x, currentRect.y,
|
101
|
+
width ?? currentRect.w, height ?? currentRect.h
|
102
|
+
);
|
103
|
+
|
104
|
+
this.editor.dispatch(this.editor.image.setImportExportRect(newRect));
|
105
|
+
this.editor.queueRerender();
|
106
|
+
}
|
107
|
+
|
108
|
+
private static idCounter = 0;
|
109
|
+
|
110
|
+
protected fillDropdown(dropdown: HTMLElement): boolean {
|
111
|
+
const container = document.createElement('div');
|
112
|
+
|
113
|
+
const backgroundColorRow = document.createElement('div');
|
114
|
+
const backgroundColorLabel = document.createElement('label');
|
115
|
+
|
116
|
+
backgroundColorLabel.innerText = this.localizationTable.backgroundColor;
|
117
|
+
|
118
|
+
const [ colorInput, backgroundColorInputContainer, setBgColorInputValue ] = makeColorInput(this.editor, color => {
|
119
|
+
if (!color.eq(this.getBackgroundColor())) {
|
120
|
+
this.setBackgroundColor(color);
|
121
|
+
}
|
122
|
+
});
|
123
|
+
|
124
|
+
colorInput.id = `document-properties-color-input-${DocumentPropertiesWidget.idCounter++}`;
|
125
|
+
backgroundColorLabel.htmlFor = colorInput.id;
|
126
|
+
|
127
|
+
backgroundColorRow.replaceChildren(backgroundColorLabel, backgroundColorInputContainer);
|
128
|
+
|
129
|
+
const addDimensionRow = (labelContent: string, onChange: (value: number)=>void) => {
|
130
|
+
const row = document.createElement('div');
|
131
|
+
const label = document.createElement('label');
|
132
|
+
const spacer = document.createElement('span');
|
133
|
+
const input = document.createElement('input');
|
134
|
+
|
135
|
+
label.innerText = labelContent;
|
136
|
+
input.type = 'number';
|
137
|
+
input.min = '0';
|
138
|
+
input.id = `document-properties-dimension-row-${DocumentPropertiesWidget.idCounter++}`;
|
139
|
+
label.htmlFor = input.id;
|
140
|
+
|
141
|
+
spacer.style.flexGrow = '1';
|
142
|
+
input.style.flexGrow = '2';
|
143
|
+
input.style.width = '25px';
|
144
|
+
|
145
|
+
row.style.display = 'flex';
|
146
|
+
|
147
|
+
input.oninput = () => {
|
148
|
+
onChange(parseFloat(input.value));
|
149
|
+
};
|
150
|
+
|
151
|
+
row.replaceChildren(label, spacer, input);
|
152
|
+
|
153
|
+
return {
|
154
|
+
setValue: (value: number) => {
|
155
|
+
input.value = value.toString();
|
156
|
+
},
|
157
|
+
element: row,
|
158
|
+
};
|
159
|
+
};
|
160
|
+
|
161
|
+
const imageWidthRow = addDimensionRow(this.localizationTable.imageWidthOption, (value: number) => {
|
162
|
+
this.updateImportExportRectSize({ width: value });
|
163
|
+
});
|
164
|
+
const imageHeightRow = addDimensionRow(this.localizationTable.imageHeightOption, (value: number) => {
|
165
|
+
this.updateImportExportRectSize({ height: value });
|
166
|
+
});
|
167
|
+
|
168
|
+
this.updateDropdownContent = () => {
|
169
|
+
setBgColorInputValue(this.getBackgroundColor());
|
170
|
+
|
171
|
+
const importExportRect = this.editor.getImportExportRect();
|
172
|
+
imageWidthRow.setValue(importExportRect.width);
|
173
|
+
imageHeightRow.setValue(importExportRect.height);
|
174
|
+
};
|
175
|
+
this.updateDropdownContent();
|
176
|
+
|
177
|
+
|
178
|
+
container.replaceChildren(
|
179
|
+
backgroundColorRow, imageWidthRow.element, imageHeightRow.element
|
180
|
+
);
|
181
|
+
dropdown.replaceChildren(container);
|
182
|
+
|
183
|
+
return true;
|
184
|
+
}
|
185
|
+
}
|
@@ -94,7 +94,7 @@ class ZoomWidget extends BaseWidget {
|
|
94
94
|
}
|
95
95
|
|
96
96
|
protected fillDropdown(dropdown: HTMLElement): boolean {
|
97
|
-
dropdown.
|
97
|
+
dropdown.replaceChildren(makeZoomControl(this.localizationTable, this.editor));
|
98
98
|
return true;
|
99
99
|
}
|
100
100
|
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import { ToolbarLocalization } from '../localization';
|
3
|
+
import BaseWidget from './BaseWidget';
|
4
|
+
|
5
|
+
|
6
|
+
export default class OverflowWidget extends BaseWidget {
|
7
|
+
private overflowChildren: BaseWidget[] = [];
|
8
|
+
private overflowContainer: HTMLElement;
|
9
|
+
|
10
|
+
public constructor(editor: Editor, localizationTable?: ToolbarLocalization) {
|
11
|
+
super(editor, 'overflow-widget', localizationTable);
|
12
|
+
|
13
|
+
// Make the dropdown openable
|
14
|
+
this.container.classList.add('dropdownShowable');
|
15
|
+
this.overflowContainer ??= document.createElement('div');
|
16
|
+
}
|
17
|
+
|
18
|
+
protected getTitle(): string {
|
19
|
+
return this.localizationTable.toggleOverflow;
|
20
|
+
}
|
21
|
+
|
22
|
+
protected createIcon(): Element | null {
|
23
|
+
return this.editor.icons.makeOverflowIcon();
|
24
|
+
}
|
25
|
+
|
26
|
+
protected handleClick(): void {
|
27
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
28
|
+
}
|
29
|
+
|
30
|
+
protected fillDropdown(dropdown: HTMLElement) {
|
31
|
+
this.overflowContainer ??= document.createElement('div');
|
32
|
+
if (this.overflowContainer.parentElement) {
|
33
|
+
this.overflowContainer.remove();
|
34
|
+
}
|
35
|
+
|
36
|
+
this.overflowContainer.classList.add('toolbar-overflow-widget-overflow-list');
|
37
|
+
dropdown.appendChild(this.overflowContainer);
|
38
|
+
|
39
|
+
return true;
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Removes all `BaseWidget`s from this and returns them.
|
44
|
+
*/
|
45
|
+
public clearChildren(): BaseWidget[] {
|
46
|
+
this.overflowContainer.replaceChildren();
|
47
|
+
|
48
|
+
const overflowChildren = this.overflowChildren;
|
49
|
+
this.overflowChildren = [];
|
50
|
+
return overflowChildren;
|
51
|
+
}
|
52
|
+
|
53
|
+
public getChildWidgets(): BaseWidget[] {
|
54
|
+
return [ ...this.overflowChildren ];
|
55
|
+
}
|
56
|
+
|
57
|
+
public hasAsChild(widget: BaseWidget) {
|
58
|
+
for (const otherWidget of this.overflowChildren) {
|
59
|
+
if (widget === otherWidget) {
|
60
|
+
return true;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
return false;
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Adds `widget` to this.
|
69
|
+
* `widget`'s previous parent is still responsible
|
70
|
+
* for serializing/deserializing its state.
|
71
|
+
*/
|
72
|
+
public addToOverflow(widget: BaseWidget) {
|
73
|
+
this.overflowChildren.push(widget);
|
74
|
+
widget.addTo(this.overflowContainer);
|
75
|
+
widget.setIsToplevel(false);
|
76
|
+
}
|
77
|
+
|
78
|
+
// This always returns false.
|
79
|
+
// Don't try to move the overflow menu to itself.
|
80
|
+
public canBeInOverflowMenu(): boolean {
|
81
|
+
return false;
|
82
|
+
}
|
83
|
+
}
|
@@ -9,4 +9,5 @@ export { default as HandToolWidget } from './HandToolWidget';
|
|
9
9
|
export { default as SelectionToolWidget } from './SelectionToolWidget';
|
10
10
|
export { default as EraserToolWidget } from './EraserToolWidget';
|
11
11
|
|
12
|
-
export { default as InsertImageWidget } from './InsertImageWidget';
|
12
|
+
export { default as InsertImageWidget } from './InsertImageWidget';
|
13
|
+
export { default as DocumentPropertiesWidget } from './DocumentPropertiesWidget';
|
package/src/tools/Eraser.test.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
import UnknownSVGObject from '../components/UnknownSVGObject';
|
1
2
|
import Editor from '../Editor';
|
2
|
-
import { Rect2, StrokeComponent } from '../lib';
|
3
|
+
import { EditorImage, Rect2, StrokeComponent } from '../lib';
|
3
4
|
import { Vec2 } from '../math/Vec2';
|
4
5
|
import createEditor from '../testing/createEditor';
|
5
6
|
import { InputEvtType } from '../types';
|
@@ -76,4 +77,26 @@ describe('Eraser', () => {
|
|
76
77
|
|
77
78
|
expect(getAllStrokes(editor)).toHaveLength(0);
|
78
79
|
});
|
80
|
+
|
81
|
+
it('should not erase unselectable objects', () => {
|
82
|
+
const editor = createEditor();
|
83
|
+
const unerasableObj = new UnknownSVGObject(document.createElementNS('http://www.w3.org/2000/svg', 'arc'));
|
84
|
+
|
85
|
+
// Add to the image
|
86
|
+
expect(editor.image.getAllElements()).toHaveLength(0);
|
87
|
+
editor.dispatch(EditorImage.addElement(unerasableObj));
|
88
|
+
expect(editor.image.getAllElements()).toHaveLength(1);
|
89
|
+
|
90
|
+
|
91
|
+
const eraser = selectEraser(editor);
|
92
|
+
eraser.setThickness(100);
|
93
|
+
|
94
|
+
// Try to erase it.
|
95
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
96
|
+
jest.advanceTimersByTime(100);
|
97
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(3, 0));
|
98
|
+
|
99
|
+
// Should not have been erased
|
100
|
+
expect(editor.image.getAllElements()).toHaveLength(1);
|
101
|
+
});
|
79
102
|
});
|
package/src/tools/Eraser.ts
CHANGED
@@ -67,11 +67,15 @@ export default class Eraser extends BaseTool {
|
|
67
67
|
return component.intersects(line) || component.intersectsRect(eraserRect);
|
68
68
|
});
|
69
69
|
|
70
|
+
// Only erase components that could be selected (and thus interacted with)
|
71
|
+
// by the user.
|
72
|
+
const toErase = intersectingElems.filter(elem => elem.isSelectable());
|
73
|
+
|
70
74
|
// Remove any intersecting elements.
|
71
|
-
this.toRemove.push(...
|
75
|
+
this.toRemove.push(...toErase);
|
72
76
|
|
73
77
|
// Create new Erase commands for the now-to-be-erased elements and apply them.
|
74
|
-
const newPartialCommands =
|
78
|
+
const newPartialCommands = toErase.map(elem => new Erase([ elem ]));
|
75
79
|
newPartialCommands.forEach(cmd => cmd.apply(this.editor));
|
76
80
|
|
77
81
|
this.partialCommands.push(...newPartialCommands);
|