js-draw 0.8.0 → 0.9.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/CHANGELOG.md +8 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +3 -0
- package/dist/src/SVGLoader.js +5 -7
- package/dist/src/components/Stroke.js +2 -2
- package/dist/src/math/Path.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/package.json +1 -1
- package/src/Color4.ts +2 -0
- package/src/SVGLoader.ts +8 -8
- package/src/components/Stroke.ts +1 -1
- package/src/math/Path.test.ts +24 -0
- package/src/math/Path.ts +7 -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/dist/src/Color4.js
CHANGED
@@ -63,6 +63,9 @@ export default class Color4 {
|
|
63
63
|
if (text.startsWith('#')) {
|
64
64
|
return Color4.fromHex(text);
|
65
65
|
}
|
66
|
+
else if (text === 'none' || text === 'transparent') {
|
67
|
+
return Color4.transparent;
|
68
|
+
}
|
66
69
|
else {
|
67
70
|
// Otherwise, try to use an HTML5Canvas to determine the color
|
68
71
|
const canvas = document.createElement('canvas');
|
package/dist/src/SVGLoader.js
CHANGED
@@ -22,6 +22,7 @@ export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
|
22
22
|
// Key to retrieve unrecognised attributes from an AbstractComponent
|
23
23
|
export const svgAttributesDataKey = 'svgAttrs';
|
24
24
|
export const svgStyleAttributesDataKey = 'svgStyleAttrs';
|
25
|
+
const supportedStrokeFillStyleAttrs = ['stroke', 'fill', 'stroke-width'];
|
25
26
|
export default class SVGLoader {
|
26
27
|
constructor(source, onFinish, storeUnknown = true) {
|
27
28
|
this.source = source;
|
@@ -123,8 +124,7 @@ export default class SVGLoader {
|
|
123
124
|
try {
|
124
125
|
const strokeData = this.strokeDataFromElem(node);
|
125
126
|
elem = new Stroke(strokeData);
|
126
|
-
|
127
|
-
this.attachUnrecognisedAttrs(elem, node, new Set([...supportedStyleAttrs, 'd']), new Set(supportedStyleAttrs));
|
127
|
+
this.attachUnrecognisedAttrs(elem, node, new Set([...supportedStrokeFillStyleAttrs, 'd']), new Set(supportedStrokeFillStyleAttrs));
|
128
128
|
}
|
129
129
|
catch (e) {
|
130
130
|
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
|
@@ -193,8 +193,8 @@ export default class SVGLoader {
|
|
193
193
|
const fontSizeMatch = /^([-0-9.e]+)px/i.exec(computedStyles.fontSize);
|
194
194
|
const supportedStyleAttrs = [
|
195
195
|
'fontFamily',
|
196
|
-
'
|
197
|
-
|
196
|
+
'transform',
|
197
|
+
...supportedStrokeFillStyleAttrs,
|
198
198
|
];
|
199
199
|
let fontSize = 12;
|
200
200
|
if (fontSizeMatch) {
|
@@ -204,9 +204,7 @@ export default class SVGLoader {
|
|
204
204
|
const style = {
|
205
205
|
size: fontSize,
|
206
206
|
fontFamily: computedStyles.fontFamily || elem.style.fontFamily || 'sans-serif',
|
207
|
-
renderingStyle:
|
208
|
-
fill: Color4.fromString(computedStyles.fill || elem.style.fill || '#000')
|
209
|
-
},
|
207
|
+
renderingStyle: this.getStyle(elem),
|
210
208
|
};
|
211
209
|
const supportedAttrs = [];
|
212
210
|
const transform = this.getTransform(elem, supportedAttrs, computedStyles);
|
@@ -36,7 +36,7 @@ export default class Stroke extends AbstractComponent {
|
|
36
36
|
return false;
|
37
37
|
}
|
38
38
|
render(canvas, visibleRect) {
|
39
|
-
var _a;
|
39
|
+
var _a, _b;
|
40
40
|
canvas.startObject(this.getBBox());
|
41
41
|
for (const part of this.parts) {
|
42
42
|
const bbox = this.bboxForPart(part.path.bbox, part.style);
|
@@ -45,7 +45,7 @@ export default class Stroke extends AbstractComponent {
|
|
45
45
|
continue;
|
46
46
|
}
|
47
47
|
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 2 || bbox.size.y > visibleRect.size.y * 2;
|
48
|
-
if (muchBiggerThanVisible && !part.path.roughlyIntersects(visibleRect, (_a = part.style.stroke) === null || _a === void 0 ? void 0 : _a.width)) {
|
48
|
+
if (muchBiggerThanVisible && !part.path.roughlyIntersects(visibleRect, (_b = (_a = part.style.stroke) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0)) {
|
49
49
|
continue;
|
50
50
|
}
|
51
51
|
}
|
package/dist/src/math/Path.js
CHANGED
@@ -212,7 +212,7 @@ export default class Path {
|
|
212
212
|
return rect.containsPoint(this.startPoint);
|
213
213
|
}
|
214
214
|
const isClosed = this.startPoint.eq(this.getEndPoint());
|
215
|
-
if (isClosed && strokeWidth
|
215
|
+
if (isClosed && strokeWidth === 0) {
|
216
216
|
return this.closedRoughlyIntersects(rect);
|
217
217
|
}
|
218
218
|
if (rect.containsRect(this.bbox)) {
|
@@ -305,6 +305,11 @@ export default class Path {
|
|
305
305
|
point: corner,
|
306
306
|
});
|
307
307
|
}
|
308
|
+
// Close the shape
|
309
|
+
commands.push({
|
310
|
+
kind: PathCommandType.LineTo,
|
311
|
+
point: startPoint,
|
312
|
+
});
|
308
313
|
return new Path(startPoint, commands);
|
309
314
|
}
|
310
315
|
static fromRenderable(renderable) {
|
@@ -7,12 +7,15 @@ export default class HTMLToolbar {
|
|
7
7
|
private editor;
|
8
8
|
private localizationTable;
|
9
9
|
private container;
|
10
|
+
private widgets;
|
10
11
|
private static colorisStarted;
|
11
12
|
private updateColoris;
|
12
13
|
/** @internal */
|
13
14
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
14
15
|
setupColorPickers(): void;
|
15
16
|
addWidget(widget: BaseWidget): void;
|
17
|
+
serializeState(): string;
|
18
|
+
deserializeState(state: string): void;
|
16
19
|
addActionButton(title: string | ActionButtonIcon, command: () => void, parent?: Element): HTMLButtonElement;
|
17
20
|
addUndoRedoButtons(): void;
|
18
21
|
addDefaultToolWidgets(): void;
|
@@ -18,6 +18,7 @@ export default class HTMLToolbar {
|
|
18
18
|
constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
|
19
19
|
this.editor = editor;
|
20
20
|
this.localizationTable = localizationTable;
|
21
|
+
this.widgets = {};
|
21
22
|
this.updateColoris = null;
|
22
23
|
this.container = document.createElement('div');
|
23
24
|
this.container.classList.add(`${toolbarCSSPrefix}root`);
|
@@ -95,9 +96,32 @@ export default class HTMLToolbar {
|
|
95
96
|
// Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
|
96
97
|
// (i.e. its `addTo` method should not have been called).
|
97
98
|
addWidget(widget) {
|
99
|
+
// Prevent name collisions
|
100
|
+
const id = widget.getUniqueIdIn(this.widgets);
|
101
|
+
// Add the widget
|
102
|
+
this.widgets[id] = widget;
|
103
|
+
// Add HTML elements.
|
98
104
|
widget.addTo(this.container);
|
99
105
|
this.setupColorPickers();
|
100
106
|
}
|
107
|
+
serializeState() {
|
108
|
+
const result = {};
|
109
|
+
for (const widgetId in this.widgets) {
|
110
|
+
result[widgetId] = this.widgets[widgetId].serializeState();
|
111
|
+
}
|
112
|
+
return JSON.stringify(result);
|
113
|
+
}
|
114
|
+
// Deserialize toolbar widgets from the given state.
|
115
|
+
// Assumes that toolbar widgets are in the same order as when state was serialized.
|
116
|
+
deserializeState(state) {
|
117
|
+
const data = JSON.parse(state);
|
118
|
+
for (const widgetId in data) {
|
119
|
+
if (!(widgetId in this.widgets)) {
|
120
|
+
console.warn(`Unable to deserialize widget ${widgetId} — no such widget.`);
|
121
|
+
}
|
122
|
+
this.widgets[widgetId].deserializeFrom(data[widgetId]);
|
123
|
+
}
|
124
|
+
}
|
101
125
|
addActionButton(title, command, parent) {
|
102
126
|
const button = document.createElement('button');
|
103
127
|
button.classList.add(`${toolbarCSSPrefix}button`);
|
@@ -18,6 +18,7 @@ export default class IconProvider {
|
|
18
18
|
makeTouchPanningIcon(): IconType;
|
19
19
|
makeAllDevicePanningIcon(): IconType;
|
20
20
|
makeZoomIcon(): IconType;
|
21
|
+
makeRotationLockIcon(): IconType;
|
21
22
|
makeTextIcon(textStyle: TextStyle): IconType;
|
22
23
|
makePenIcon(tipThickness: number, color: string | Color4): IconType;
|
23
24
|
makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType;
|
@@ -113,7 +113,7 @@ export default class IconProvider {
|
|
113
113
|
const fill = 'none';
|
114
114
|
const strokeColor = 'var(--icon-color)';
|
115
115
|
const strokeWidth = '3';
|
116
|
-
// Draw a cursor-like shape
|
116
|
+
// Draw a cursor-like shape
|
117
117
|
return this.makeIconFromPath(`
|
118
118
|
m 10,60
|
119
119
|
5,30
|
@@ -242,6 +242,48 @@ export default class IconProvider {
|
|
242
242
|
addTextNode('-', 70, 75);
|
243
243
|
return icon;
|
244
244
|
}
|
245
|
+
makeRotationLockIcon() {
|
246
|
+
const icon = this.makeIconFromPath(`
|
247
|
+
M 40.1 25.1
|
248
|
+
C 32.5 25 27.9 34.1 27.9 34.1
|
249
|
+
L 25.7 30
|
250
|
+
L 28 44.7
|
251
|
+
L 36.6 40.3
|
252
|
+
L 32.3 38.3
|
253
|
+
C 33.6 28 38.1 25.2 45.1 31.8
|
254
|
+
L 49.4 29.6
|
255
|
+
C 45.9 26.3 42.8 25.1 40.1 25.1
|
256
|
+
z
|
257
|
+
|
258
|
+
M 51.7 34.2
|
259
|
+
L 43.5 39.1
|
260
|
+
L 48 40.8
|
261
|
+
C 47.4 51.1 43.1 54.3 35.7 48.2
|
262
|
+
L 31.6 50.7
|
263
|
+
C 45.5 62.1 52.6 44.6 52.6 44.6
|
264
|
+
L 55.1 48.6
|
265
|
+
L 51.7 34.2
|
266
|
+
z
|
267
|
+
|
268
|
+
M 56.9 49.9
|
269
|
+
C 49.8 49.9 49.2 57.3 49.3 60.9
|
270
|
+
L 47.6 60.9
|
271
|
+
L 47.6 73.7
|
272
|
+
L 66.1 73.7
|
273
|
+
L 66.1 60.9
|
274
|
+
L 64.4 60.9
|
275
|
+
C 64.5 57.3 63.9 49.9 56.9 49.9
|
276
|
+
z
|
277
|
+
|
278
|
+
M 56.9 53.5
|
279
|
+
C 60.8 53.5 61 58.2 60.8 60.9
|
280
|
+
L 52.9 60.9
|
281
|
+
C 52.7 58.2 52.9 53.5 56.9 53.5
|
282
|
+
z
|
283
|
+
`);
|
284
|
+
icon.setAttribute('viewBox', '10 10 70 70');
|
285
|
+
return icon;
|
286
|
+
}
|
245
287
|
makeTextIcon(textStyle) {
|
246
288
|
var _a, _b;
|
247
289
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
@@ -24,6 +24,7 @@ export const defaultToolbarLocalization = {
|
|
24
24
|
linePen: 'Line',
|
25
25
|
outlinedRectanglePen: 'Outlined rectangle',
|
26
26
|
filledRectanglePen: 'Filled rectangle',
|
27
|
+
lockRotation: 'Lock rotation',
|
27
28
|
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
28
29
|
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
29
30
|
zoomLevel: (zoomPercent) => `Zoom: ${zoomPercent}%`,
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import Editor from '../Editor';
|
3
3
|
declare type OnColorChangeListener = (color: Color4) => void;
|
4
|
-
|
4
|
+
declare type SetColorCallback = (color: Color4 | string) => void;
|
5
|
+
export declare const makeColorInput: (editor: Editor, onColorChange: OnColorChangeListener) => [HTMLInputElement, HTMLElement, SetColorCallback];
|
5
6
|
export default makeColorInput;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import PipetteTool from '../tools/PipetteTool';
|
3
3
|
import { EditorEventType } from '../types';
|
4
|
-
// Returns [ color input, input container ].
|
4
|
+
// Returns [ color input, input container, callback to change the color value ].
|
5
5
|
export const makeColorInput = (editor, onColorChange) => {
|
6
6
|
const colorInputContainer = document.createElement('span');
|
7
7
|
const colorInput = document.createElement('input');
|
@@ -22,6 +22,8 @@ export const makeColorInput = (editor, onColorChange) => {
|
|
22
22
|
const handleColorInput = () => {
|
23
23
|
currentColor = Color4.fromHex(colorInput.value);
|
24
24
|
};
|
25
|
+
// Only change the pen color when we finish sending input (this limits the number of
|
26
|
+
// editor events triggered and accessibility announcements).
|
25
27
|
const onInputEnd = () => {
|
26
28
|
handleColorInput();
|
27
29
|
if (currentColor) {
|
@@ -47,7 +49,16 @@ export const makeColorInput = (editor, onColorChange) => {
|
|
47
49
|
});
|
48
50
|
onInputEnd();
|
49
51
|
});
|
50
|
-
|
52
|
+
const setColorInputValue = (color) => {
|
53
|
+
if (typeof color === 'object') {
|
54
|
+
color = color.toHexString();
|
55
|
+
}
|
56
|
+
colorInput.value = color;
|
57
|
+
// Fire all color event listeners. See
|
58
|
+
// https://github.com/mdbassit/Coloris#manually-updating-the-thumbnail
|
59
|
+
colorInput.dispatchEvent(new Event('input', { bubbles: true }));
|
60
|
+
};
|
61
|
+
return [colorInput, colorInputContainer, setColorInputValue];
|
51
62
|
};
|
52
63
|
const addPipetteTool = (editor, container, onColorChange) => {
|
53
64
|
const pipetteButton = document.createElement('button');
|
@@ -5,7 +5,7 @@ export default class ActionButtonWidget extends BaseWidget {
|
|
5
5
|
protected makeIcon: () => Element;
|
6
6
|
protected title: string;
|
7
7
|
protected clickAction: () => void;
|
8
|
-
constructor(editor: Editor,
|
8
|
+
constructor(editor: Editor, id: string, makeIcon: () => Element, title: string, clickAction: () => void, localizationTable?: ToolbarLocalization);
|
9
9
|
protected handleClick(): void;
|
10
10
|
protected getTitle(): string;
|
11
11
|
protected createIcon(): Element;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import BaseWidget from './BaseWidget';
|
2
2
|
export default class ActionButtonWidget extends BaseWidget {
|
3
|
-
constructor(editor,
|
4
|
-
super(editor, localizationTable);
|
3
|
+
constructor(editor, id, makeIcon, title, clickAction, localizationTable) {
|
4
|
+
super(editor, id, localizationTable);
|
5
5
|
this.makeIcon = makeIcon;
|
6
6
|
this.title = title;
|
7
7
|
this.clickAction = clickAction;
|
@@ -5,8 +5,7 @@ import BaseWidget from './BaseWidget';
|
|
5
5
|
export default abstract class BaseToolWidget extends BaseWidget {
|
6
6
|
protected editor: Editor;
|
7
7
|
protected targetTool: BaseTool;
|
8
|
-
|
9
|
-
constructor(editor: Editor, targetTool: BaseTool, localizationTable: ToolbarLocalization);
|
8
|
+
constructor(editor: Editor, targetTool: BaseTool, id: string, localizationTable?: ToolbarLocalization);
|
10
9
|
protected handleClick(): void;
|
11
10
|
addTo(parent: HTMLElement): void;
|
12
11
|
}
|
@@ -1,11 +1,10 @@
|
|
1
1
|
import { EditorEventType } from '../../types';
|
2
2
|
import BaseWidget from './BaseWidget';
|
3
3
|
export default class BaseToolWidget extends BaseWidget {
|
4
|
-
constructor(editor, targetTool, localizationTable) {
|
5
|
-
super(editor, localizationTable);
|
4
|
+
constructor(editor, targetTool, id, localizationTable) {
|
5
|
+
super(editor, id, localizationTable);
|
6
6
|
this.editor = editor;
|
7
7
|
this.targetTool = targetTool;
|
8
|
-
this.localizationTable = localizationTable;
|
9
8
|
editor.notifier.on(EditorEventType.ToolEnabled, toolEvt => {
|
10
9
|
if (toolEvt.kind !== EditorEventType.ToolEnabled) {
|
11
10
|
throw new Error('Incorrect event type! (Expected ToolEnabled)');
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
2
|
import { KeyPressEvent } from '../../types';
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
|
+
export declare type SavedToolbuttonState = Record<string, any>;
|
4
5
|
export default abstract class BaseWidget {
|
5
6
|
#private;
|
6
7
|
protected editor: Editor;
|
7
|
-
protected
|
8
|
+
protected id: string;
|
8
9
|
protected readonly container: HTMLElement;
|
9
10
|
private button;
|
10
11
|
private icon;
|
@@ -14,7 +15,19 @@ export default abstract class BaseWidget {
|
|
14
15
|
private disabled;
|
15
16
|
private subWidgets;
|
16
17
|
private toplevel;
|
17
|
-
|
18
|
+
protected readonly localizationTable: ToolbarLocalization;
|
19
|
+
constructor(editor: Editor, id: string, localizationTable?: ToolbarLocalization);
|
20
|
+
getId(): string;
|
21
|
+
/**
|
22
|
+
* Returns the ID of this widget in `container`. Adds a suffix to this' ID
|
23
|
+
* if an item in `container` already has this' ID.
|
24
|
+
*
|
25
|
+
* For example, if `this` has ID `foo` and if
|
26
|
+
* `container = { 'foo': somethingNotThis, 'foo-1': somethingElseNotThis }`, this method
|
27
|
+
* returns `foo-2` because elements with IDs `foo` and `foo-1` are already present in
|
28
|
+
* `container`.
|
29
|
+
*/
|
30
|
+
getUniqueIdIn(container: Record<string, BaseWidget>): string;
|
18
31
|
protected abstract getTitle(): string;
|
19
32
|
protected abstract createIcon(): Element;
|
20
33
|
protected fillDropdown(dropdown: HTMLElement): boolean;
|
@@ -34,4 +47,21 @@ export default abstract class BaseWidget {
|
|
34
47
|
protected isDropdownVisible(): boolean;
|
35
48
|
protected isSelected(): boolean;
|
36
49
|
private createDropdownIcon;
|
50
|
+
/**
|
51
|
+
* Serialize state associated with this widget.
|
52
|
+
* Override this method to allow saving/restoring from state on application load.
|
53
|
+
*
|
54
|
+
* Overriders should call `super` and include the output of `super.serializeState` in
|
55
|
+
* the output dictionary.
|
56
|
+
*
|
57
|
+
* Clients should not rely on the output from `saveState` being in any particular
|
58
|
+
* format.
|
59
|
+
*/
|
60
|
+
serializeState(): SavedToolbuttonState;
|
61
|
+
/**
|
62
|
+
* Restore widget state from serialized data. See also `saveState`.
|
63
|
+
*
|
64
|
+
* Overriders must call `super`.
|
65
|
+
*/
|
66
|
+
deserializeFrom(state: SavedToolbuttonState): void;
|
37
67
|
}
|
@@ -14,13 +14,15 @@ import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler';
|
|
14
14
|
import { EditorEventType, InputEvtType } from '../../types';
|
15
15
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
16
16
|
export default class BaseWidget {
|
17
|
-
constructor(editor, localizationTable) {
|
17
|
+
constructor(editor, id, localizationTable) {
|
18
18
|
this.editor = editor;
|
19
|
-
this.
|
19
|
+
this.id = id;
|
20
20
|
_BaseWidget_hasDropdown.set(this, void 0);
|
21
21
|
this.disabled = false;
|
22
|
-
|
22
|
+
// Maps subWidget IDs to subWidgets.
|
23
|
+
this.subWidgets = {};
|
23
24
|
this.toplevel = true;
|
25
|
+
this.localizationTable = localizationTable !== null && localizationTable !== void 0 ? localizationTable : editor.localization;
|
24
26
|
this.icon = null;
|
25
27
|
this.container = document.createElement('div');
|
26
28
|
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`);
|
@@ -40,13 +42,35 @@ export default class BaseWidget {
|
|
40
42
|
toolbarShortcutHandlers[0].registerListener(event => this.onKeyPress(event));
|
41
43
|
}
|
42
44
|
}
|
45
|
+
getId() {
|
46
|
+
return this.id;
|
47
|
+
}
|
48
|
+
/**
|
49
|
+
* Returns the ID of this widget in `container`. Adds a suffix to this' ID
|
50
|
+
* if an item in `container` already has this' ID.
|
51
|
+
*
|
52
|
+
* For example, if `this` has ID `foo` and if
|
53
|
+
* `container = { 'foo': somethingNotThis, 'foo-1': somethingElseNotThis }`, this method
|
54
|
+
* returns `foo-2` because elements with IDs `foo` and `foo-1` are already present in
|
55
|
+
* `container`.
|
56
|
+
*/
|
57
|
+
getUniqueIdIn(container) {
|
58
|
+
let id = this.getId();
|
59
|
+
let idCounter = 0;
|
60
|
+
while (id in container && container[id] !== this) {
|
61
|
+
id = this.getId() + '-' + idCounter.toString();
|
62
|
+
idCounter++;
|
63
|
+
}
|
64
|
+
return id;
|
65
|
+
}
|
43
66
|
// Add content to the widget's associated dropdown menu.
|
44
67
|
// Returns true if such a menu should be created, false otherwise.
|
45
68
|
fillDropdown(dropdown) {
|
46
|
-
if (this.subWidgets.length === 0) {
|
69
|
+
if (Object.keys(this.subWidgets).length === 0) {
|
47
70
|
return false;
|
48
71
|
}
|
49
|
-
for (const
|
72
|
+
for (const widgetId in this.subWidgets) {
|
73
|
+
const widget = this.subWidgets[widgetId];
|
50
74
|
widget.addTo(dropdown);
|
51
75
|
widget.setIsToplevel(false);
|
52
76
|
}
|
@@ -100,7 +124,9 @@ export default class BaseWidget {
|
|
100
124
|
}
|
101
125
|
// Add a widget to this' dropdown. Must be called before this.addTo.
|
102
126
|
addSubWidget(widget) {
|
103
|
-
|
127
|
+
// Generate a unique ID for the widget.
|
128
|
+
const id = widget.getUniqueIdIn(this.subWidgets);
|
129
|
+
this.subWidgets[id] = widget;
|
104
130
|
}
|
105
131
|
// Adds this to [parent]. This can only be called once for each ToolbarWidget.
|
106
132
|
// @internal
|
@@ -209,5 +235,40 @@ export default class BaseWidget {
|
|
209
235
|
icon.classList.add(`${toolbarCSSPrefix}showHideDropdownIcon`);
|
210
236
|
return icon;
|
211
237
|
}
|
238
|
+
/**
|
239
|
+
* Serialize state associated with this widget.
|
240
|
+
* Override this method to allow saving/restoring from state on application load.
|
241
|
+
*
|
242
|
+
* Overriders should call `super` and include the output of `super.serializeState` in
|
243
|
+
* the output dictionary.
|
244
|
+
*
|
245
|
+
* Clients should not rely on the output from `saveState` being in any particular
|
246
|
+
* format.
|
247
|
+
*/
|
248
|
+
serializeState() {
|
249
|
+
const subwidgetState = {};
|
250
|
+
// Save all subwidget state.
|
251
|
+
for (const subwidgetId in this.subWidgets) {
|
252
|
+
subwidgetState[subwidgetId] = this.subWidgets[subwidgetId].serializeState();
|
253
|
+
}
|
254
|
+
return {
|
255
|
+
subwidgetState,
|
256
|
+
};
|
257
|
+
}
|
258
|
+
/**
|
259
|
+
* Restore widget state from serialized data. See also `saveState`.
|
260
|
+
*
|
261
|
+
* Overriders must call `super`.
|
262
|
+
*/
|
263
|
+
deserializeFrom(state) {
|
264
|
+
if (state.subwidgetState) {
|
265
|
+
// Deserialize all subwidgets.
|
266
|
+
for (const subwidgetId in state.subwidgetState) {
|
267
|
+
if (subwidgetId in this.subWidgets) {
|
268
|
+
this.subWidgets[subwidgetId].deserializeFrom(state.subwidgetState[subwidgetId]);
|
269
|
+
}
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
212
273
|
}
|
213
274
|
_BaseWidget_hasDropdown = new WeakMap();
|
@@ -1,5 +1,9 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import Eraser from '../../tools/Eraser';
|
3
|
+
import { ToolbarLocalization } from '../localization';
|
1
4
|
import BaseToolWidget from './BaseToolWidget';
|
2
5
|
export default class EraserToolWidget extends BaseToolWidget {
|
6
|
+
constructor(editor: Editor, tool: Eraser, localizationTable?: ToolbarLocalization);
|
3
7
|
protected getTitle(): string;
|
4
8
|
protected createIcon(): Element;
|
5
9
|
protected fillDropdown(_dropdown: HTMLElement): boolean;
|
@@ -1,5 +1,8 @@
|
|
1
1
|
import BaseToolWidget from './BaseToolWidget';
|
2
2
|
export default class EraserToolWidget extends BaseToolWidget {
|
3
|
+
constructor(editor, tool, localizationTable) {
|
4
|
+
super(editor, tool, 'eraser-tool-widget', localizationTable);
|
5
|
+
}
|
3
6
|
getTitle() {
|
4
7
|
return this.localizationTable.eraser;
|
5
8
|
}
|
@@ -2,9 +2,9 @@ import Editor from '../../Editor';
|
|
2
2
|
import PanZoom from '../../tools/PanZoom';
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
4
|
import BaseToolWidget from './BaseToolWidget';
|
5
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
5
6
|
export default class HandToolWidget extends BaseToolWidget {
|
6
7
|
protected overridePanZoomTool: PanZoom;
|
7
|
-
private touchPanningWidget;
|
8
8
|
private allowTogglingBaseTool;
|
9
9
|
constructor(editor: Editor, overridePanZoomTool: PanZoom, localizationTable: ToolbarLocalization);
|
10
10
|
private static getPrimaryHandTool;
|
@@ -12,4 +12,6 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
12
12
|
protected createIcon(): Element;
|
13
13
|
protected handleClick(): void;
|
14
14
|
setSelected(selected: boolean): void;
|
15
|
+
serializeState(): SavedToolbuttonState;
|
16
|
+
deserializeFrom(state: SavedToolbuttonState): void;
|
15
17
|
}
|
@@ -57,7 +57,7 @@ const makeZoomControl = (localizationTable, editor) => {
|
|
57
57
|
};
|
58
58
|
class ZoomWidget extends BaseWidget {
|
59
59
|
constructor(editor, localizationTable) {
|
60
|
-
super(editor, localizationTable);
|
60
|
+
super(editor, 'zoom-widget', localizationTable);
|
61
61
|
// Make it possible to open the dropdown, even if this widget isn't selected.
|
62
62
|
this.container.classList.add('dropdownShowable');
|
63
63
|
}
|
@@ -76,8 +76,8 @@ class ZoomWidget extends BaseWidget {
|
|
76
76
|
}
|
77
77
|
}
|
78
78
|
class HandModeWidget extends BaseWidget {
|
79
|
-
constructor(editor,
|
80
|
-
super(editor, localizationTable);
|
79
|
+
constructor(editor, tool, flag, makeIcon, title, localizationTable) {
|
80
|
+
super(editor, `pan-mode-${flag}`, localizationTable);
|
81
81
|
this.tool = tool;
|
82
82
|
this.flag = flag;
|
83
83
|
this.makeIcon = makeIcon;
|
@@ -94,13 +94,7 @@ class HandModeWidget extends BaseWidget {
|
|
94
94
|
this.setSelected(false);
|
95
95
|
}
|
96
96
|
setModeFlag(enabled) {
|
97
|
-
|
98
|
-
if (enabled) {
|
99
|
-
this.tool.setMode(mode | this.flag);
|
100
|
-
}
|
101
|
-
else {
|
102
|
-
this.tool.setMode(mode & ~this.flag);
|
103
|
-
}
|
97
|
+
this.tool.setModeEnabled(this.flag, enabled);
|
104
98
|
}
|
105
99
|
handleClick() {
|
106
100
|
this.setModeFlag(!this.isSelected());
|
@@ -122,7 +116,7 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
122
116
|
overridePanZoomTool, localizationTable) {
|
123
117
|
const primaryHandTool = HandToolWidget.getPrimaryHandTool(editor.toolController);
|
124
118
|
const tool = primaryHandTool !== null && primaryHandTool !== void 0 ? primaryHandTool : overridePanZoomTool;
|
125
|
-
super(editor, tool, localizationTable);
|
119
|
+
super(editor, tool, 'hand-tool-widget', localizationTable);
|
126
120
|
this.overridePanZoomTool = overridePanZoomTool;
|
127
121
|
// Only allow toggling a hand tool if we're using the primary hand tool and not the override
|
128
122
|
// hand tool for this button.
|
@@ -132,8 +126,10 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
132
126
|
this.container.classList.add('dropdownShowable');
|
133
127
|
}
|
134
128
|
// Controls for the overriding hand tool.
|
135
|
-
|
136
|
-
|
129
|
+
const touchPanningWidget = new HandModeWidget(editor, overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning, localizationTable);
|
130
|
+
const rotationLockWidget = new HandModeWidget(editor, overridePanZoomTool, PanZoomMode.RotationLocked, () => this.editor.icons.makeRotationLockIcon(), localizationTable.lockRotation, localizationTable);
|
131
|
+
this.addSubWidget(touchPanningWidget);
|
132
|
+
this.addSubWidget(rotationLockWidget);
|
137
133
|
this.addSubWidget(new ZoomWidget(editor, localizationTable));
|
138
134
|
}
|
139
135
|
static getPrimaryHandTool(toolController) {
|
@@ -160,4 +156,17 @@ export default class HandToolWidget extends BaseToolWidget {
|
|
160
156
|
super.setSelected(selected);
|
161
157
|
}
|
162
158
|
}
|
159
|
+
serializeState() {
|
160
|
+
const toolMode = this.overridePanZoomTool.getMode();
|
161
|
+
return Object.assign(Object.assign({}, super.serializeState()), { touchPanning: toolMode & PanZoomMode.OneFingerTouchGestures, rotationLocked: toolMode & PanZoomMode.RotationLocked });
|
162
|
+
}
|
163
|
+
deserializeFrom(state) {
|
164
|
+
if (state.touchPanning !== undefined) {
|
165
|
+
this.overridePanZoomTool.setModeEnabled(PanZoomMode.OneFingerTouchGestures, state.touchPanning);
|
166
|
+
}
|
167
|
+
if (state.rotationLocked !== undefined) {
|
168
|
+
this.overridePanZoomTool.setModeEnabled(PanZoomMode.RotationLocked, state.rotationLocked);
|
169
|
+
}
|
170
|
+
super.deserializeFrom(state);
|
171
|
+
}
|
163
172
|
}
|
@@ -4,18 +4,24 @@ import Pen from '../../tools/Pen';
|
|
4
4
|
import { KeyPressEvent } from '../../types';
|
5
5
|
import { ToolbarLocalization } from '../localization';
|
6
6
|
import BaseToolWidget from './BaseToolWidget';
|
7
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
7
8
|
export interface PenTypeRecord {
|
8
9
|
name: string;
|
10
|
+
id: string;
|
9
11
|
factory: ComponentBuilderFactory;
|
10
12
|
}
|
11
13
|
export default class PenToolWidget extends BaseToolWidget {
|
12
14
|
private tool;
|
13
15
|
private updateInputs;
|
14
16
|
protected penTypes: PenTypeRecord[];
|
15
|
-
constructor(editor: Editor, tool: Pen, localization
|
17
|
+
constructor(editor: Editor, tool: Pen, localization?: ToolbarLocalization);
|
16
18
|
protected getTitle(): string;
|
19
|
+
private getCurrentPenTypeIdx;
|
20
|
+
private getCurrentPenType;
|
17
21
|
protected createIcon(): Element;
|
18
22
|
private static idCounter;
|
19
23
|
protected fillDropdown(dropdown: HTMLElement): boolean;
|
20
24
|
protected onKeyPress(event: KeyPressEvent): boolean;
|
25
|
+
serializeState(): SavedToolbuttonState;
|
26
|
+
deserializeFrom(state: SavedToolbuttonState): void;
|
21
27
|
}
|