js-draw 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +3 -0
- package/dist/src/Editor.d.ts +2 -0
- package/dist/src/Editor.js +31 -6
- package/dist/src/SVGLoader.js +5 -7
- package/dist/src/Viewport.js +2 -2
- package/dist/src/components/Stroke.js +2 -2
- package/dist/src/components/builders/LineBuilder.js +4 -0
- package/dist/src/components/util/StrokeSmoother.js +1 -1
- package/dist/src/math/Path.js +6 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +6 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +3 -0
- package/dist/src/toolbar/HTMLToolbar.js +24 -0
- package/dist/src/toolbar/IconProvider.d.ts +1 -0
- package/dist/src/toolbar/IconProvider.js +43 -1
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/toolbar/makeColorInput.d.ts +2 -1
- package/dist/src/toolbar/makeColorInput.js +13 -2
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +2 -2
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -2
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -3
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +32 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +67 -6
- package/dist/src/toolbar/widgets/EraserToolWidget.d.ts +4 -0
- package/dist/src/toolbar/widgets/EraserToolWidget.js +3 -0
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/HandToolWidget.js +22 -13
- package/dist/src/toolbar/widgets/PenToolWidget.d.ts +7 -1
- package/dist/src/toolbar/widgets/PenToolWidget.js +78 -12
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +7 -7
- package/dist/src/toolbar/widgets/TextToolWidget.d.ts +4 -1
- package/dist/src/toolbar/widgets/TextToolWidget.js +17 -3
- package/dist/src/tools/PanZoom.d.ts +4 -1
- package/dist/src/tools/PanZoom.js +24 -1
- package/dist/src/tools/SelectionTool/Selection.js +1 -1
- package/package.json +1 -1
- package/src/Color4.ts +2 -0
- package/src/Editor.ts +43 -9
- package/src/SVGLoader.ts +8 -8
- package/src/Viewport.ts +2 -2
- package/src/components/Stroke.ts +1 -1
- package/src/components/builders/LineBuilder.ts +4 -0
- package/src/components/util/StrokeSmoother.ts +1 -1
- package/src/math/Path.test.ts +24 -0
- package/src/math/Path.ts +7 -1
- package/src/rendering/renderers/SVGRenderer.ts +5 -1
- package/src/toolbar/HTMLToolbar.ts +33 -0
- package/src/toolbar/IconProvider.ts +49 -1
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/makeColorInput.ts +21 -3
- package/src/toolbar/widgets/ActionButtonWidget.ts +6 -3
- package/src/toolbar/widgets/BaseToolWidget.ts +4 -3
- package/src/toolbar/widgets/BaseWidget.ts +83 -5
- package/src/toolbar/widgets/EraserToolWidget.ts +11 -0
- package/src/toolbar/widgets/HandToolWidget.ts +48 -17
- package/src/toolbar/widgets/PenToolWidget.ts +105 -13
- package/src/toolbar/widgets/SelectionToolWidget.ts +8 -5
- package/src/toolbar/widgets/TextToolWidget.ts +29 -4
- package/src/tools/PanZoom.ts +28 -1
- package/src/tools/SelectionTool/Selection.ts +1 -1
- package/.firebase/hosting.ZG9jcw.cache +0 -338
package/src/math/Path.test.ts
CHANGED
@@ -171,4 +171,28 @@ describe('Path', () => {
|
|
171
171
|
).toBe(true);
|
172
172
|
});
|
173
173
|
});
|
174
|
+
|
175
|
+
describe('fromRect', () => {
|
176
|
+
const filledRect = Path.fromRect(Rect2.unitSquare);
|
177
|
+
const strokedRect = Path.fromRect(Rect2.unitSquare, 0.1);
|
178
|
+
|
179
|
+
it('filled should be closed shape', () => {
|
180
|
+
const lastSegment = filledRect.parts[filledRect.parts.length - 1];
|
181
|
+
|
182
|
+
if (lastSegment.kind !== PathCommandType.LineTo) {
|
183
|
+
throw new Error('Rectangles should only be made up of lines');
|
184
|
+
}
|
185
|
+
|
186
|
+
expect(filledRect.startPoint).objEq(lastSegment.point);
|
187
|
+
});
|
188
|
+
|
189
|
+
it('stroked should be closed shape', () => {
|
190
|
+
const lastSegment = strokedRect.parts[strokedRect.parts.length - 1];
|
191
|
+
if (lastSegment.kind !== PathCommandType.LineTo) {
|
192
|
+
throw new Error('Rectangles should only be made up of lines');
|
193
|
+
}
|
194
|
+
|
195
|
+
expect(strokedRect.startPoint).objEq(lastSegment.point);
|
196
|
+
});
|
197
|
+
});
|
174
198
|
});
|
package/src/math/Path.ts
CHANGED
@@ -285,7 +285,7 @@ export default class Path {
|
|
285
285
|
}
|
286
286
|
const isClosed = this.startPoint.eq(this.getEndPoint());
|
287
287
|
|
288
|
-
if (isClosed && strokeWidth
|
288
|
+
if (isClosed && strokeWidth === 0) {
|
289
289
|
return this.closedRoughlyIntersects(rect);
|
290
290
|
}
|
291
291
|
|
@@ -401,6 +401,12 @@ export default class Path {
|
|
401
401
|
});
|
402
402
|
}
|
403
403
|
|
404
|
+
// Close the shape
|
405
|
+
commands.push({
|
406
|
+
kind: PathCommandType.LineTo,
|
407
|
+
point: startPoint,
|
408
|
+
});
|
409
|
+
|
404
410
|
return new Path(startPoint, commands);
|
405
411
|
}
|
406
412
|
|
@@ -94,7 +94,11 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
94
94
|
pathElem.setAttribute('d', this.lastPathString.join(' '));
|
95
95
|
|
96
96
|
const style = this.lastPathStyle;
|
97
|
-
|
97
|
+
if (style.fill.a > 0) {
|
98
|
+
pathElem.setAttribute('fill', style.fill.toHexString());
|
99
|
+
} else {
|
100
|
+
pathElem.setAttribute('fill', 'none');
|
101
|
+
}
|
98
102
|
|
99
103
|
if (style.stroke) {
|
100
104
|
pathElem.setAttribute('stroke', style.stroke.color.toHexString());
|
@@ -24,6 +24,8 @@ type UpdateColorisCallback = ()=>void;
|
|
24
24
|
export default class HTMLToolbar {
|
25
25
|
private container: HTMLElement;
|
26
26
|
|
27
|
+
private widgets: Record<string, BaseWidget> = {};
|
28
|
+
|
27
29
|
private static colorisStarted: boolean = false;
|
28
30
|
private updateColoris: UpdateColorisCallback|null = null;
|
29
31
|
|
@@ -121,10 +123,41 @@ export default class HTMLToolbar {
|
|
121
123
|
// Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
|
122
124
|
// (i.e. its `addTo` method should not have been called).
|
123
125
|
public addWidget(widget: BaseWidget) {
|
126
|
+
// Prevent name collisions
|
127
|
+
const id = widget.getUniqueIdIn(this.widgets);
|
128
|
+
|
129
|
+
// Add the widget
|
130
|
+
this.widgets[id] = widget;
|
131
|
+
|
132
|
+
// Add HTML elements.
|
124
133
|
widget.addTo(this.container);
|
125
134
|
this.setupColorPickers();
|
126
135
|
}
|
127
136
|
|
137
|
+
public serializeState(): string {
|
138
|
+
const result: Record<string, any> = {};
|
139
|
+
|
140
|
+
for (const widgetId in this.widgets) {
|
141
|
+
result[widgetId] = this.widgets[widgetId].serializeState();
|
142
|
+
}
|
143
|
+
|
144
|
+
return JSON.stringify(result);
|
145
|
+
}
|
146
|
+
|
147
|
+
// Deserialize toolbar widgets from the given state.
|
148
|
+
// Assumes that toolbar widgets are in the same order as when state was serialized.
|
149
|
+
public deserializeState(state: string) {
|
150
|
+
const data = JSON.parse(state);
|
151
|
+
|
152
|
+
for (const widgetId in data) {
|
153
|
+
if (!(widgetId in this.widgets)) {
|
154
|
+
console.warn(`Unable to deserialize widget ${widgetId} — no such widget.`);
|
155
|
+
}
|
156
|
+
|
157
|
+
this.widgets[widgetId].deserializeFrom(data[widgetId]);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
128
161
|
public addActionButton(title: string|ActionButtonIcon, command: ()=> void, parent?: Element) {
|
129
162
|
const button = document.createElement('button');
|
130
163
|
button.classList.add(`${toolbarCSSPrefix}button`);
|
@@ -8,6 +8,9 @@ import Pen from '../tools/Pen';
|
|
8
8
|
import { StrokeDataPoint } from '../types';
|
9
9
|
import Viewport from '../Viewport';
|
10
10
|
|
11
|
+
// Provides a default set of icons for the editor.
|
12
|
+
// Many of the icons were created with Inkscape.
|
13
|
+
|
11
14
|
type IconType = SVGSVGElement|HTMLImageElement;
|
12
15
|
|
13
16
|
const svgNamespace = 'http://www.w3.org/2000/svg';
|
@@ -138,7 +141,7 @@ export default class IconProvider {
|
|
138
141
|
const strokeColor = 'var(--icon-color)';
|
139
142
|
const strokeWidth = '3';
|
140
143
|
|
141
|
-
// Draw a cursor-like shape
|
144
|
+
// Draw a cursor-like shape
|
142
145
|
return this.makeIconFromPath(`
|
143
146
|
m 10,60
|
144
147
|
5,30
|
@@ -275,6 +278,51 @@ export default class IconProvider {
|
|
275
278
|
|
276
279
|
return icon;
|
277
280
|
}
|
281
|
+
|
282
|
+
public makeRotationLockIcon(): IconType {
|
283
|
+
const icon = this.makeIconFromPath(`
|
284
|
+
M 40.1 25.1
|
285
|
+
C 32.5 25 27.9 34.1 27.9 34.1
|
286
|
+
L 25.7 30
|
287
|
+
L 28 44.7
|
288
|
+
L 36.6 40.3
|
289
|
+
L 32.3 38.3
|
290
|
+
C 33.6 28 38.1 25.2 45.1 31.8
|
291
|
+
L 49.4 29.6
|
292
|
+
C 45.9 26.3 42.8 25.1 40.1 25.1
|
293
|
+
z
|
294
|
+
|
295
|
+
M 51.7 34.2
|
296
|
+
L 43.5 39.1
|
297
|
+
L 48 40.8
|
298
|
+
C 47.4 51.1 43.1 54.3 35.7 48.2
|
299
|
+
L 31.6 50.7
|
300
|
+
C 45.5 62.1 52.6 44.6 52.6 44.6
|
301
|
+
L 55.1 48.6
|
302
|
+
L 51.7 34.2
|
303
|
+
z
|
304
|
+
|
305
|
+
M 56.9 49.9
|
306
|
+
C 49.8 49.9 49.2 57.3 49.3 60.9
|
307
|
+
L 47.6 60.9
|
308
|
+
L 47.6 73.7
|
309
|
+
L 66.1 73.7
|
310
|
+
L 66.1 60.9
|
311
|
+
L 64.4 60.9
|
312
|
+
C 64.5 57.3 63.9 49.9 56.9 49.9
|
313
|
+
z
|
314
|
+
|
315
|
+
M 56.9 53.5
|
316
|
+
C 60.8 53.5 61 58.2 60.8 60.9
|
317
|
+
L 52.9 60.9
|
318
|
+
C 52.7 58.2 52.9 53.5 56.9 53.5
|
319
|
+
z
|
320
|
+
`);
|
321
|
+
|
322
|
+
icon.setAttribute('viewBox', '10 10 70 70');
|
323
|
+
|
324
|
+
return icon;
|
325
|
+
}
|
278
326
|
|
279
327
|
public makeTextIcon(textStyle: TextStyle): IconType {
|
280
328
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
@@ -3,6 +3,7 @@
|
|
3
3
|
export interface ToolbarLocalization {
|
4
4
|
fontLabel: string;
|
5
5
|
touchPanning: string;
|
6
|
+
lockRotation: string;
|
6
7
|
outlinedRectanglePen: string;
|
7
8
|
filledRectanglePen: string;
|
8
9
|
linePen: string;
|
@@ -61,6 +62,7 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
61
62
|
linePen: 'Line',
|
62
63
|
outlinedRectanglePen: 'Outlined rectangle',
|
63
64
|
filledRectanglePen: 'Filled rectangle',
|
65
|
+
lockRotation: 'Lock rotation',
|
64
66
|
|
65
67
|
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
66
68
|
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
@@ -4,10 +4,13 @@ import PipetteTool from '../tools/PipetteTool';
|
|
4
4
|
import { EditorEventType } from '../types';
|
5
5
|
|
6
6
|
type OnColorChangeListener = (color: Color4)=>void;
|
7
|
+
type SetColorCallback = (color: Color4|string) => void;
|
7
8
|
|
9
|
+
// Returns [ color input, input container, callback to change the color value ].
|
10
|
+
export const makeColorInput = (
|
11
|
+
editor: Editor, onColorChange: OnColorChangeListener
|
12
|
+
): [ HTMLInputElement, HTMLElement, SetColorCallback ] => {
|
8
13
|
|
9
|
-
// Returns [ color input, input container ].
|
10
|
-
export const makeColorInput = (editor: Editor, onColorChange: OnColorChangeListener): [ HTMLInputElement, HTMLElement ] => {
|
11
14
|
const colorInputContainer = document.createElement('span');
|
12
15
|
const colorInput = document.createElement('input');
|
13
16
|
|
@@ -31,6 +34,9 @@ export const makeColorInput = (editor: Editor, onColorChange: OnColorChangeListe
|
|
31
34
|
const handleColorInput = () => {
|
32
35
|
currentColor = Color4.fromHex(colorInput.value);
|
33
36
|
};
|
37
|
+
|
38
|
+
// Only change the pen color when we finish sending input (this limits the number of
|
39
|
+
// editor events triggered and accessibility announcements).
|
34
40
|
const onInputEnd = () => {
|
35
41
|
handleColorInput();
|
36
42
|
|
@@ -61,7 +67,19 @@ export const makeColorInput = (editor: Editor, onColorChange: OnColorChangeListe
|
|
61
67
|
onInputEnd();
|
62
68
|
});
|
63
69
|
|
64
|
-
|
70
|
+
const setColorInputValue = (color: Color4|string) => {
|
71
|
+
if (typeof color === 'object') {
|
72
|
+
color = color.toHexString();
|
73
|
+
}
|
74
|
+
|
75
|
+
colorInput.value = color;
|
76
|
+
|
77
|
+
// Fire all color event listeners. See
|
78
|
+
// https://github.com/mdbassit/Coloris#manually-updating-the-thumbnail
|
79
|
+
colorInput.dispatchEvent(new Event('input', { bubbles: true }));
|
80
|
+
};
|
81
|
+
|
82
|
+
return [ colorInput, colorInputContainer, setColorInputValue ];
|
65
83
|
};
|
66
84
|
|
67
85
|
const addPipetteTool = (editor: Editor, container: HTMLElement, onColorChange: OnColorChangeListener) => {
|
@@ -4,13 +4,16 @@ import BaseWidget from './BaseWidget';
|
|
4
4
|
|
5
5
|
export default class ActionButtonWidget extends BaseWidget {
|
6
6
|
public constructor(
|
7
|
-
editor: Editor,
|
7
|
+
editor: Editor,
|
8
|
+
id: string,
|
9
|
+
|
8
10
|
protected makeIcon: ()=> Element,
|
9
11
|
protected title: string,
|
10
|
-
|
11
12
|
protected clickAction: ()=>void,
|
13
|
+
|
14
|
+
localizationTable?: ToolbarLocalization,
|
12
15
|
) {
|
13
|
-
super(editor, localizationTable);
|
16
|
+
super(editor, id, localizationTable);
|
14
17
|
}
|
15
18
|
|
16
19
|
protected handleClick() {
|
@@ -8,9 +8,10 @@ export default abstract class BaseToolWidget extends BaseWidget {
|
|
8
8
|
public constructor(
|
9
9
|
protected editor: Editor,
|
10
10
|
protected targetTool: BaseTool,
|
11
|
-
|
11
|
+
id: string,
|
12
|
+
localizationTable?: ToolbarLocalization,
|
12
13
|
) {
|
13
|
-
super(editor, localizationTable);
|
14
|
+
super(editor, id, localizationTable);
|
14
15
|
|
15
16
|
editor.notifier.on(EditorEventType.ToolEnabled, toolEvt => {
|
16
17
|
if (toolEvt.kind !== EditorEventType.ToolEnabled) {
|
@@ -50,4 +51,4 @@ export default abstract class BaseToolWidget extends BaseWidget {
|
|
50
51
|
super.addTo(parent);
|
51
52
|
this.setSelected(this.targetTool.isEnabled());
|
52
53
|
}
|
53
|
-
}
|
54
|
+
}
|
@@ -4,6 +4,8 @@ import { EditorEventType, InputEvtType, KeyPressEvent } from '../../types';
|
|
4
4
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
5
5
|
import { ToolbarLocalization } from '../localization';
|
6
6
|
|
7
|
+
export type SavedToolbuttonState = Record<string, any>;
|
8
|
+
|
7
9
|
export default abstract class BaseWidget {
|
8
10
|
protected readonly container: HTMLElement;
|
9
11
|
private button: HTMLElement;
|
@@ -13,13 +15,20 @@ export default abstract class BaseWidget {
|
|
13
15
|
private label: HTMLLabelElement;
|
14
16
|
#hasDropdown: boolean;
|
15
17
|
private disabled: boolean = false;
|
16
|
-
|
18
|
+
|
19
|
+
// Maps subWidget IDs to subWidgets.
|
20
|
+
private subWidgets: Record<string, BaseWidget> = {};
|
21
|
+
|
17
22
|
private toplevel: boolean = true;
|
23
|
+
protected readonly localizationTable: ToolbarLocalization;
|
18
24
|
|
19
25
|
public constructor(
|
20
26
|
protected editor: Editor,
|
21
|
-
protected
|
27
|
+
protected id: string,
|
28
|
+
localizationTable?: ToolbarLocalization,
|
22
29
|
) {
|
30
|
+
this.localizationTable = localizationTable ?? editor.localization;
|
31
|
+
|
23
32
|
this.icon = null;
|
24
33
|
this.container = document.createElement('div');
|
25
34
|
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`);
|
@@ -43,17 +52,44 @@ export default abstract class BaseWidget {
|
|
43
52
|
}
|
44
53
|
}
|
45
54
|
|
55
|
+
public getId(): string {
|
56
|
+
return this.id;
|
57
|
+
}
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Returns the ID of this widget in `container`. Adds a suffix to this' ID
|
61
|
+
* if an item in `container` already has this' ID.
|
62
|
+
*
|
63
|
+
* For example, if `this` has ID `foo` and if
|
64
|
+
* `container = { 'foo': somethingNotThis, 'foo-1': somethingElseNotThis }`, this method
|
65
|
+
* returns `foo-2` because elements with IDs `foo` and `foo-1` are already present in
|
66
|
+
* `container`.
|
67
|
+
*/
|
68
|
+
public getUniqueIdIn(container: Record<string, BaseWidget>): string {
|
69
|
+
let id = this.getId();
|
70
|
+
let idCounter = 0;
|
71
|
+
|
72
|
+
while (id in container && container[id] !== this) {
|
73
|
+
id = this.getId() + '-' + idCounter.toString();
|
74
|
+
idCounter ++;
|
75
|
+
}
|
76
|
+
|
77
|
+
return id;
|
78
|
+
}
|
79
|
+
|
46
80
|
protected abstract getTitle(): string;
|
47
81
|
protected abstract createIcon(): Element;
|
48
82
|
|
49
83
|
// Add content to the widget's associated dropdown menu.
|
50
84
|
// Returns true if such a menu should be created, false otherwise.
|
51
85
|
protected fillDropdown(dropdown: HTMLElement): boolean {
|
52
|
-
if (this.subWidgets.length === 0) {
|
86
|
+
if (Object.keys(this.subWidgets).length === 0) {
|
53
87
|
return false;
|
54
88
|
}
|
55
89
|
|
56
|
-
for (const
|
90
|
+
for (const widgetId in this.subWidgets) {
|
91
|
+
const widget = this.subWidgets[widgetId];
|
92
|
+
|
57
93
|
widget.addTo(dropdown);
|
58
94
|
widget.setIsToplevel(false);
|
59
95
|
}
|
@@ -118,7 +154,10 @@ export default abstract class BaseWidget {
|
|
118
154
|
|
119
155
|
// Add a widget to this' dropdown. Must be called before this.addTo.
|
120
156
|
protected addSubWidget(widget: BaseWidget) {
|
121
|
-
|
157
|
+
// Generate a unique ID for the widget.
|
158
|
+
const id = widget.getUniqueIdIn(this.subWidgets);
|
159
|
+
|
160
|
+
this.subWidgets[id] = widget;
|
122
161
|
}
|
123
162
|
|
124
163
|
// Adds this to [parent]. This can only be called once for each ToolbarWidget.
|
@@ -251,4 +290,43 @@ export default abstract class BaseWidget {
|
|
251
290
|
icon.classList.add(`${toolbarCSSPrefix}showHideDropdownIcon`);
|
252
291
|
return icon;
|
253
292
|
}
|
293
|
+
|
294
|
+
/**
|
295
|
+
* Serialize state associated with this widget.
|
296
|
+
* Override this method to allow saving/restoring from state on application load.
|
297
|
+
*
|
298
|
+
* Overriders should call `super` and include the output of `super.serializeState` in
|
299
|
+
* the output dictionary.
|
300
|
+
*
|
301
|
+
* Clients should not rely on the output from `saveState` being in any particular
|
302
|
+
* format.
|
303
|
+
*/
|
304
|
+
public serializeState(): SavedToolbuttonState {
|
305
|
+
const subwidgetState: Record<string, any> = {};
|
306
|
+
|
307
|
+
// Save all subwidget state.
|
308
|
+
for (const subwidgetId in this.subWidgets) {
|
309
|
+
subwidgetState[subwidgetId] = this.subWidgets[subwidgetId].serializeState();
|
310
|
+
}
|
311
|
+
|
312
|
+
return {
|
313
|
+
subwidgetState,
|
314
|
+
};
|
315
|
+
}
|
316
|
+
|
317
|
+
/**
|
318
|
+
* Restore widget state from serialized data. See also `saveState`.
|
319
|
+
*
|
320
|
+
* Overriders must call `super`.
|
321
|
+
*/
|
322
|
+
public deserializeFrom(state: SavedToolbuttonState): void {
|
323
|
+
if (state.subwidgetState) {
|
324
|
+
// Deserialize all subwidgets.
|
325
|
+
for (const subwidgetId in state.subwidgetState) {
|
326
|
+
if (subwidgetId in this.subWidgets) {
|
327
|
+
this.subWidgets[subwidgetId].deserializeFrom(state.subwidgetState[subwidgetId]);
|
328
|
+
}
|
329
|
+
}
|
330
|
+
}
|
331
|
+
}
|
254
332
|
}
|
@@ -1,6 +1,17 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import Eraser from '../../tools/Eraser';
|
3
|
+
import { ToolbarLocalization } from '../localization';
|
1
4
|
import BaseToolWidget from './BaseToolWidget';
|
2
5
|
|
3
6
|
export default class EraserToolWidget extends BaseToolWidget {
|
7
|
+
public constructor(
|
8
|
+
editor: Editor,
|
9
|
+
tool: Eraser,
|
10
|
+
localizationTable?: ToolbarLocalization
|
11
|
+
) {
|
12
|
+
super(editor, tool, 'eraser-tool-widget', localizationTable);
|
13
|
+
}
|
14
|
+
|
4
15
|
protected getTitle(): string {
|
5
16
|
return this.localizationTable.eraser;
|
6
17
|
}
|
@@ -7,7 +7,7 @@ import Viewport from '../../Viewport';
|
|
7
7
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
8
8
|
import { ToolbarLocalization } from '../localization';
|
9
9
|
import BaseToolWidget from './BaseToolWidget';
|
10
|
-
import BaseWidget from './BaseWidget';
|
10
|
+
import BaseWidget, { SavedToolbuttonState } from './BaseWidget';
|
11
11
|
|
12
12
|
const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor) => {
|
13
13
|
const zoomLevelRow = document.createElement('div');
|
@@ -74,8 +74,8 @@ const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor)
|
|
74
74
|
};
|
75
75
|
|
76
76
|
class ZoomWidget extends BaseWidget {
|
77
|
-
public constructor(editor: Editor, localizationTable
|
78
|
-
super(editor, localizationTable);
|
77
|
+
public constructor(editor: Editor, localizationTable?: ToolbarLocalization) {
|
78
|
+
super(editor, 'zoom-widget', localizationTable);
|
79
79
|
|
80
80
|
// Make it possible to open the dropdown, even if this widget isn't selected.
|
81
81
|
this.container.classList.add('dropdownShowable');
|
@@ -101,12 +101,14 @@ class ZoomWidget extends BaseWidget {
|
|
101
101
|
|
102
102
|
class HandModeWidget extends BaseWidget {
|
103
103
|
public constructor(
|
104
|
-
editor: Editor,
|
104
|
+
editor: Editor,
|
105
105
|
|
106
106
|
protected tool: PanZoom, protected flag: PanZoomMode, protected makeIcon: ()=> Element,
|
107
107
|
private title: string,
|
108
|
+
|
109
|
+
localizationTable?: ToolbarLocalization,
|
108
110
|
) {
|
109
|
-
super(editor, localizationTable);
|
111
|
+
super(editor, `pan-mode-${flag}`, localizationTable);
|
110
112
|
|
111
113
|
editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
112
114
|
if (toolEvt.kind === EditorEventType.ToolUpdated && toolEvt.tool === tool) {
|
@@ -122,12 +124,7 @@ class HandModeWidget extends BaseWidget {
|
|
122
124
|
}
|
123
125
|
|
124
126
|
private setModeFlag(enabled: boolean) {
|
125
|
-
|
126
|
-
if (enabled) {
|
127
|
-
this.tool.setMode(mode | this.flag);
|
128
|
-
} else {
|
129
|
-
this.tool.setMode(mode & ~this.flag);
|
130
|
-
}
|
127
|
+
this.tool.setModeEnabled(this.flag, enabled);
|
131
128
|
}
|
132
129
|
|
133
130
|
protected handleClick() {
|
@@ -148,7 +145,6 @@ class HandModeWidget extends BaseWidget {
|
|
148
145
|
}
|
149
146
|
|
150
147
|
export default class HandToolWidget extends BaseToolWidget {
|
151
|
-
private touchPanningWidget: HandModeWidget;
|
152
148
|
private allowTogglingBaseTool: boolean;
|
153
149
|
|
154
150
|
public constructor(
|
@@ -162,7 +158,7 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
162
158
|
) {
|
163
159
|
const primaryHandTool = HandToolWidget.getPrimaryHandTool(editor.toolController);
|
164
160
|
const tool = primaryHandTool ?? overridePanZoomTool;
|
165
|
-
super(editor, tool, localizationTable);
|
161
|
+
super(editor, tool, 'hand-tool-widget', localizationTable);
|
166
162
|
|
167
163
|
// Only allow toggling a hand tool if we're using the primary hand tool and not the override
|
168
164
|
// hand tool for this button.
|
@@ -174,16 +170,29 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
174
170
|
}
|
175
171
|
|
176
172
|
// Controls for the overriding hand tool.
|
177
|
-
|
178
|
-
editor,
|
173
|
+
const touchPanningWidget = new HandModeWidget(
|
174
|
+
editor,
|
179
175
|
|
180
176
|
overridePanZoomTool, PanZoomMode.OneFingerTouchGestures,
|
181
177
|
() => this.editor.icons.makeTouchPanningIcon(),
|
182
178
|
|
183
|
-
localizationTable.touchPanning
|
179
|
+
localizationTable.touchPanning,
|
180
|
+
|
181
|
+
localizationTable,
|
184
182
|
);
|
183
|
+
|
184
|
+
const rotationLockWidget = new HandModeWidget(
|
185
|
+
editor,
|
185
186
|
|
186
|
-
|
187
|
+
overridePanZoomTool, PanZoomMode.RotationLocked,
|
188
|
+
() => this.editor.icons.makeRotationLockIcon(),
|
189
|
+
|
190
|
+
localizationTable.lockRotation,
|
191
|
+
localizationTable,
|
192
|
+
);
|
193
|
+
|
194
|
+
this.addSubWidget(touchPanningWidget);
|
195
|
+
this.addSubWidget(rotationLockWidget);
|
187
196
|
this.addSubWidget(
|
188
197
|
new ZoomWidget(editor, localizationTable)
|
189
198
|
);
|
@@ -216,4 +225,26 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
216
225
|
super.setSelected(selected);
|
217
226
|
}
|
218
227
|
}
|
228
|
+
|
229
|
+
public serializeState(): SavedToolbuttonState {
|
230
|
+
const toolMode = this.overridePanZoomTool.getMode();
|
231
|
+
|
232
|
+
return {
|
233
|
+
...super.serializeState(),
|
234
|
+
touchPanning: toolMode & PanZoomMode.OneFingerTouchGestures,
|
235
|
+
rotationLocked: toolMode & PanZoomMode.RotationLocked,
|
236
|
+
};
|
237
|
+
}
|
238
|
+
|
239
|
+
public deserializeFrom(state: SavedToolbuttonState): void {
|
240
|
+
if (state.touchPanning !== undefined) {
|
241
|
+
this.overridePanZoomTool.setModeEnabled(PanZoomMode.OneFingerTouchGestures, state.touchPanning);
|
242
|
+
}
|
243
|
+
|
244
|
+
if (state.rotationLocked !== undefined) {
|
245
|
+
this.overridePanZoomTool.setModeEnabled(PanZoomMode.RotationLocked, state.rotationLocked);
|
246
|
+
}
|
247
|
+
|
248
|
+
super.deserializeFrom(state);
|
249
|
+
}
|
219
250
|
}
|