js-draw 0.11.3 → 0.12.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 +6 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +1 -0
- package/dist/src/Color4.js +1 -0
- package/dist/src/Editor.js +4 -5
- package/dist/src/SVGLoader.js +43 -35
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +15 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +51 -0
- package/dist/src/toolbar/HTMLToolbar.js +63 -5
- package/dist/src/toolbar/IconProvider.d.ts +1 -1
- package/dist/src/toolbar/IconProvider.js +33 -6
- package/dist/src/toolbar/widgets/EraserToolWidget.d.ts +8 -1
- package/dist/src/toolbar/widgets/EraserToolWidget.js +45 -4
- package/dist/src/toolbar/widgets/PenToolWidget.js +2 -2
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +12 -3
- package/dist/src/tools/Eraser.d.ts +10 -1
- package/dist/src/tools/Eraser.js +65 -13
- package/dist/src/tools/SelectionTool/Selection.d.ts +4 -1
- package/dist/src/tools/SelectionTool/Selection.js +64 -27
- package/dist/src/tools/SelectionTool/SelectionTool.js +3 -1
- package/dist/src/tools/TextTool.js +10 -6
- package/dist/src/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/Color4.ts +1 -0
- package/src/Editor.ts +3 -4
- package/src/SVGLoader.ts +14 -14
- package/src/components/AbstractComponent.ts +19 -0
- package/src/toolbar/HTMLToolbar.ts +81 -5
- package/src/toolbar/IconProvider.ts +34 -6
- package/src/toolbar/widgets/EraserToolWidget.ts +64 -5
- package/src/toolbar/widgets/PenToolWidget.ts +2 -2
- package/src/toolbar/widgets/SelectionToolWidget.ts +2 -2
- package/src/tools/Eraser.test.ts +79 -0
- package/src/tools/Eraser.ts +81 -17
- package/src/tools/SelectionTool/Selection.ts +73 -23
- package/src/tools/SelectionTool/SelectionTool.test.ts +138 -21
- package/src/tools/SelectionTool/SelectionTool.ts +3 -1
- package/src/tools/TextTool.ts +14 -8
- package/src/types.ts +2 -2
package/dist/src/Color4.d.ts
CHANGED
package/dist/src/Color4.js
CHANGED
@@ -146,4 +146,5 @@ Color4.purple = Color4.ofRGB(0.5, 0.2, 0.5);
|
|
146
146
|
Color4.yellow = Color4.ofRGB(1, 1, 0.1);
|
147
147
|
Color4.clay = Color4.ofRGB(0.8, 0.4, 0.2);
|
148
148
|
Color4.black = Color4.ofRGB(0, 0, 0);
|
149
|
+
Color4.gray = Color4.ofRGB(0.5, 0.5, 0.5);
|
149
150
|
Color4.white = Color4.ofRGB(1, 1, 1);
|
package/dist/src/Editor.js
CHANGED
@@ -188,8 +188,7 @@ export class Editor {
|
|
188
188
|
addToolbar(defaultLayout = true) {
|
189
189
|
const toolbar = new HTMLToolbar(this, this.container, this.localization);
|
190
190
|
if (defaultLayout) {
|
191
|
-
toolbar.
|
192
|
-
toolbar.addDefaultActionButtons();
|
191
|
+
toolbar.addDefaults();
|
193
192
|
}
|
194
193
|
return toolbar;
|
195
194
|
}
|
@@ -718,9 +717,9 @@ export class Editor {
|
|
718
717
|
return __awaiter(this, void 0, void 0, function* () {
|
719
718
|
this.showLoadingWarning(0);
|
720
719
|
this.display.setDraftMode(true);
|
721
|
-
yield loader.start((component) => {
|
722
|
-
this.dispatchNoAnnounce(EditorImage.addElement(component));
|
723
|
-
}, (countProcessed, totalToProcess) => {
|
720
|
+
yield loader.start((component) => __awaiter(this, void 0, void 0, function* () {
|
721
|
+
yield this.dispatchNoAnnounce(EditorImage.addElement(component));
|
722
|
+
}), (countProcessed, totalToProcess) => {
|
724
723
|
if (countProcessed % 500 === 0) {
|
725
724
|
this.showLoadingWarning(countProcessed / totalToProcess);
|
726
725
|
this.rerender();
|
package/dist/src/SVGLoader.js
CHANGED
@@ -125,22 +125,24 @@ export default class SVGLoader {
|
|
125
125
|
// Adds a stroke with a single path
|
126
126
|
addPath(node) {
|
127
127
|
var _a;
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
catch (e) {
|
135
|
-
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
|
136
|
-
if (this.storeUnknown) {
|
137
|
-
elem = new UnknownSVGObject(node);
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
129
|
+
let elem;
|
130
|
+
try {
|
131
|
+
const strokeData = this.strokeDataFromElem(node);
|
132
|
+
elem = new Stroke(strokeData);
|
133
|
+
this.attachUnrecognisedAttrs(elem, node, new Set([...supportedStrokeFillStyleAttrs, 'd']), new Set(supportedStrokeFillStyleAttrs));
|
138
134
|
}
|
139
|
-
|
140
|
-
|
135
|
+
catch (e) {
|
136
|
+
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
|
137
|
+
if (this.storeUnknown) {
|
138
|
+
elem = new UnknownSVGObject(node);
|
139
|
+
}
|
140
|
+
else {
|
141
|
+
return;
|
142
|
+
}
|
141
143
|
}
|
142
|
-
|
143
|
-
|
144
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, elem));
|
145
|
+
});
|
144
146
|
}
|
145
147
|
// If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
|
146
148
|
// to prevent storing duplicate transform information when saving the component.
|
@@ -223,14 +225,16 @@ export default class SVGLoader {
|
|
223
225
|
}
|
224
226
|
addText(elem) {
|
225
227
|
var _a;
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
228
|
+
return __awaiter(this, void 0, void 0, function* () {
|
229
|
+
try {
|
230
|
+
const textElem = this.makeText(elem);
|
231
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, textElem));
|
232
|
+
}
|
233
|
+
catch (e) {
|
234
|
+
console.error('Invalid text object in node', elem, '. Continuing.... Error:', e);
|
235
|
+
this.addUnknownNode(elem);
|
236
|
+
}
|
237
|
+
});
|
234
238
|
}
|
235
239
|
addImage(elem) {
|
236
240
|
var _a, _b, _c;
|
@@ -243,20 +247,22 @@ export default class SVGLoader {
|
|
243
247
|
const transform = this.getTransform(elem, supportedAttrs);
|
244
248
|
const imageElem = yield ImageComponent.fromImage(image, transform);
|
245
249
|
this.attachUnrecognisedAttrs(imageElem, elem, new Set(supportedAttrs), new Set(['transform']));
|
246
|
-
(_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, imageElem);
|
250
|
+
yield ((_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, imageElem));
|
247
251
|
}
|
248
252
|
catch (e) {
|
249
253
|
console.error('Error loading image:', e, '. Element: ', elem, '. Continuing...');
|
250
|
-
this.addUnknownNode(elem);
|
254
|
+
yield this.addUnknownNode(elem);
|
251
255
|
}
|
252
256
|
});
|
253
257
|
}
|
254
258
|
addUnknownNode(node) {
|
255
259
|
var _a;
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
+
return __awaiter(this, void 0, void 0, function* () {
|
261
|
+
if (this.storeUnknown) {
|
262
|
+
const component = new UnknownSVGObject(node);
|
263
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, component));
|
264
|
+
}
|
265
|
+
});
|
260
266
|
}
|
261
267
|
updateViewBox(node) {
|
262
268
|
var _a;
|
@@ -278,9 +284,11 @@ export default class SVGLoader {
|
|
278
284
|
}
|
279
285
|
updateSVGAttrs(node) {
|
280
286
|
var _a;
|
281
|
-
|
282
|
-
(
|
283
|
-
|
287
|
+
return __awaiter(this, void 0, void 0, function* () {
|
288
|
+
if (this.storeUnknown) {
|
289
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, new SVGGlobalAttributesObject(this.getSourceAttrs(node))));
|
290
|
+
}
|
291
|
+
});
|
284
292
|
}
|
285
293
|
visit(node) {
|
286
294
|
var _a;
|
@@ -292,10 +300,10 @@ export default class SVGLoader {
|
|
292
300
|
// Continue -- visit the node's children.
|
293
301
|
break;
|
294
302
|
case 'path':
|
295
|
-
this.addPath(node);
|
303
|
+
yield this.addPath(node);
|
296
304
|
break;
|
297
305
|
case 'text':
|
298
|
-
this.addText(node);
|
306
|
+
yield this.addText(node);
|
299
307
|
visitChildren = false;
|
300
308
|
break;
|
301
309
|
case 'image':
|
@@ -308,14 +316,14 @@ export default class SVGLoader {
|
|
308
316
|
this.updateSVGAttrs(node);
|
309
317
|
break;
|
310
318
|
case 'style':
|
311
|
-
this.addUnknownNode(node);
|
319
|
+
yield this.addUnknownNode(node);
|
312
320
|
break;
|
313
321
|
default:
|
314
322
|
console.warn('Unknown SVG element,', node);
|
315
323
|
if (!(node instanceof SVGElement)) {
|
316
324
|
console.warn('Element', node, 'is not an SVGElement!', this.storeUnknown ? 'Continuing anyway.' : 'Skipping.');
|
317
325
|
}
|
318
|
-
this.addUnknownNode(node);
|
326
|
+
yield this.addUnknownNode(node);
|
319
327
|
return;
|
320
328
|
}
|
321
329
|
if (visitChildren) {
|
@@ -25,6 +25,7 @@ export default abstract class AbstractComponent {
|
|
25
25
|
getBBox(): Rect2;
|
26
26
|
abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
27
27
|
abstract intersects(lineSegment: LineSegment2): boolean;
|
28
|
+
intersectsRect(rect: Rect2): boolean;
|
28
29
|
protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
|
29
30
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
30
31
|
transformBy(affineTransfm: Mat33): SerializableCommand;
|
@@ -43,6 +43,21 @@ export default class AbstractComponent {
|
|
43
43
|
getBBox() {
|
44
44
|
return this.contentBBox;
|
45
45
|
}
|
46
|
+
intersectsRect(rect) {
|
47
|
+
// If this component intersects rect,
|
48
|
+
// it is either contained entirely within rect or intersects one of rect's edges.
|
49
|
+
// If contained within,
|
50
|
+
if (rect.containsRect(this.getBBox())) {
|
51
|
+
return true;
|
52
|
+
}
|
53
|
+
// Calculated bounding boxes can be slightly larger than their actual contents' bounding box.
|
54
|
+
// As such, test with more lines than just the rect's edges.
|
55
|
+
const testLines = [];
|
56
|
+
for (const subregion of rect.divideIntoGrid(2, 2)) {
|
57
|
+
testLines.push(...subregion.getEdges());
|
58
|
+
}
|
59
|
+
return testLines.some(edge => this.intersects(edge));
|
60
|
+
}
|
46
61
|
// Returns a command that, when applied, transforms this by [affineTransfm] and
|
47
62
|
// updates the editor.
|
48
63
|
transformBy(affineTransfm) {
|
@@ -3,6 +3,11 @@ import { ToolbarLocalization } from './localization';
|
|
3
3
|
import { ActionButtonIcon } from './types';
|
4
4
|
import BaseWidget from './widgets/BaseWidget';
|
5
5
|
export declare const toolbarCSSPrefix = "toolbar-";
|
6
|
+
interface SpacerOptions {
|
7
|
+
grow: number;
|
8
|
+
minSize: string;
|
9
|
+
maxSize: string;
|
10
|
+
}
|
6
11
|
export default class HTMLToolbar {
|
7
12
|
private editor;
|
8
13
|
private localizationTable;
|
@@ -13,8 +18,45 @@ export default class HTMLToolbar {
|
|
13
18
|
/** @internal */
|
14
19
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
15
20
|
setupColorPickers(): void;
|
21
|
+
/**
|
22
|
+
* Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
|
23
|
+
* (i.e. its `addTo` method should not have been called).
|
24
|
+
*
|
25
|
+
* @example
|
26
|
+
* ```ts
|
27
|
+
* const toolbar = editor.addToolbar();
|
28
|
+
* const insertImageWidget = new InsertImageWidget(editor);
|
29
|
+
* toolbar.addWidget(insertImageWidget);
|
30
|
+
* ```
|
31
|
+
*/
|
16
32
|
addWidget(widget: BaseWidget): void;
|
33
|
+
/**
|
34
|
+
* Adds a spacer.
|
35
|
+
*
|
36
|
+
* @example
|
37
|
+
* Adding a save button that moves to the very right edge of the toolbar
|
38
|
+
* while keeping the other buttons centered:
|
39
|
+
* ```ts
|
40
|
+
* const toolbar = editor.addToolbar(false);
|
41
|
+
*
|
42
|
+
* toolbar.addSpacer({ grow: 1 });
|
43
|
+
* toolbar.addDefaults();
|
44
|
+
* toolbar.addSpacer({ grow: 1 });
|
45
|
+
*
|
46
|
+
* toolbar.addActionButton({
|
47
|
+
* label: 'Save',
|
48
|
+
* icon: editor.icons.makeSaveIcon(),
|
49
|
+
* }, () => {
|
50
|
+
* saveCallback();
|
51
|
+
* });
|
52
|
+
* ```
|
53
|
+
*/
|
54
|
+
addSpacer(options?: Partial<SpacerOptions>): void;
|
17
55
|
serializeState(): string;
|
56
|
+
/**
|
57
|
+
* Deserialize toolbar widgets from the given state.
|
58
|
+
* Assumes that toolbar widgets are in the same order as when state was serialized.
|
59
|
+
*/
|
18
60
|
deserializeState(state: string): void;
|
19
61
|
/**
|
20
62
|
* Adds an action button with `title` to this toolbar (or to the given `parent` element).
|
@@ -25,4 +67,13 @@ export default class HTMLToolbar {
|
|
25
67
|
addUndoRedoButtons(): void;
|
26
68
|
addDefaultToolWidgets(): void;
|
27
69
|
addDefaultActionButtons(): void;
|
70
|
+
/**
|
71
|
+
* Adds both the default tool widgets and action buttons. Equivalent to
|
72
|
+
* ```ts
|
73
|
+
* toolbar.addDefaultToolWidgets();
|
74
|
+
* toolbar.addDefaultActionButtons();
|
75
|
+
* ```
|
76
|
+
*/
|
77
|
+
addDefaults(): void;
|
28
78
|
}
|
79
|
+
export {};
|
@@ -12,7 +12,8 @@ import EraserWidget from './widgets/EraserToolWidget';
|
|
12
12
|
import SelectionToolWidget from './widgets/SelectionToolWidget';
|
13
13
|
import TextToolWidget from './widgets/TextToolWidget';
|
14
14
|
import HandToolWidget from './widgets/HandToolWidget';
|
15
|
-
import
|
15
|
+
import ActionButtonWidget from './widgets/ActionButtonWidget';
|
16
|
+
import InsertImageWidget from './widgets/InsertImageWidget';
|
16
17
|
export const toolbarCSSPrefix = 'toolbar-';
|
17
18
|
export default class HTMLToolbar {
|
18
19
|
/** @internal */
|
@@ -94,8 +95,17 @@ export default class HTMLToolbar {
|
|
94
95
|
}
|
95
96
|
});
|
96
97
|
}
|
97
|
-
|
98
|
-
|
98
|
+
/**
|
99
|
+
* Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
|
100
|
+
* (i.e. its `addTo` method should not have been called).
|
101
|
+
*
|
102
|
+
* @example
|
103
|
+
* ```ts
|
104
|
+
* const toolbar = editor.addToolbar();
|
105
|
+
* const insertImageWidget = new InsertImageWidget(editor);
|
106
|
+
* toolbar.addWidget(insertImageWidget);
|
107
|
+
* ```
|
108
|
+
*/
|
99
109
|
addWidget(widget) {
|
100
110
|
// Prevent name collisions
|
101
111
|
const id = widget.getUniqueIdIn(this.widgets);
|
@@ -105,6 +115,41 @@ export default class HTMLToolbar {
|
|
105
115
|
widget.addTo(this.container);
|
106
116
|
this.setupColorPickers();
|
107
117
|
}
|
118
|
+
/**
|
119
|
+
* Adds a spacer.
|
120
|
+
*
|
121
|
+
* @example
|
122
|
+
* Adding a save button that moves to the very right edge of the toolbar
|
123
|
+
* while keeping the other buttons centered:
|
124
|
+
* ```ts
|
125
|
+
* const toolbar = editor.addToolbar(false);
|
126
|
+
*
|
127
|
+
* toolbar.addSpacer({ grow: 1 });
|
128
|
+
* toolbar.addDefaults();
|
129
|
+
* toolbar.addSpacer({ grow: 1 });
|
130
|
+
*
|
131
|
+
* toolbar.addActionButton({
|
132
|
+
* label: 'Save',
|
133
|
+
* icon: editor.icons.makeSaveIcon(),
|
134
|
+
* }, () => {
|
135
|
+
* saveCallback();
|
136
|
+
* });
|
137
|
+
* ```
|
138
|
+
*/
|
139
|
+
addSpacer(options = {}) {
|
140
|
+
const spacer = document.createElement('div');
|
141
|
+
spacer.classList.add(`${toolbarCSSPrefix}spacer`);
|
142
|
+
if (options.grow) {
|
143
|
+
spacer.style.flexGrow = `${options.grow}`;
|
144
|
+
}
|
145
|
+
if (options.minSize) {
|
146
|
+
spacer.style.minWidth = options.minSize;
|
147
|
+
}
|
148
|
+
if (options.maxSize) {
|
149
|
+
spacer.style.maxWidth = options.maxSize;
|
150
|
+
}
|
151
|
+
this.container.appendChild(spacer);
|
152
|
+
}
|
108
153
|
serializeState() {
|
109
154
|
const result = {};
|
110
155
|
for (const widgetId in this.widgets) {
|
@@ -112,8 +157,10 @@ export default class HTMLToolbar {
|
|
112
157
|
}
|
113
158
|
return JSON.stringify(result);
|
114
159
|
}
|
115
|
-
|
116
|
-
|
160
|
+
/**
|
161
|
+
* Deserialize toolbar widgets from the given state.
|
162
|
+
* Assumes that toolbar widgets are in the same order as when state was serialized.
|
163
|
+
*/
|
117
164
|
deserializeState(state) {
|
118
165
|
const data = JSON.parse(state);
|
119
166
|
for (const widgetId in data) {
|
@@ -188,5 +235,16 @@ export default class HTMLToolbar {
|
|
188
235
|
addDefaultActionButtons() {
|
189
236
|
this.addUndoRedoButtons();
|
190
237
|
}
|
238
|
+
/**
|
239
|
+
* Adds both the default tool widgets and action buttons. Equivalent to
|
240
|
+
* ```ts
|
241
|
+
* toolbar.addDefaultToolWidgets();
|
242
|
+
* toolbar.addDefaultActionButtons();
|
243
|
+
* ```
|
244
|
+
*/
|
245
|
+
addDefaults() {
|
246
|
+
this.addDefaultToolWidgets();
|
247
|
+
this.addDefaultActionButtons();
|
248
|
+
}
|
191
249
|
}
|
192
250
|
HTMLToolbar.colorisStarted = false;
|
@@ -7,7 +7,7 @@ export default class IconProvider {
|
|
7
7
|
makeUndoIcon(): IconType;
|
8
8
|
makeRedoIcon(mirror?: boolean): IconType;
|
9
9
|
makeDropdownIcon(): IconType;
|
10
|
-
makeEraserIcon(): IconType;
|
10
|
+
makeEraserIcon(eraserSize?: number): IconType;
|
11
11
|
makeSelectionIcon(): IconType;
|
12
12
|
/**
|
13
13
|
* @param pathData - SVG path data (e.g. `m10,10l30,30z`)
|
@@ -67,19 +67,46 @@ export default class IconProvider {
|
|
67
67
|
icon.setAttribute('viewBox', '0 0 100 100');
|
68
68
|
return icon;
|
69
69
|
}
|
70
|
-
makeEraserIcon() {
|
70
|
+
makeEraserIcon(eraserSize) {
|
71
71
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
72
|
-
|
72
|
+
eraserSize !== null && eraserSize !== void 0 ? eraserSize : (eraserSize = 10);
|
73
|
+
const scaledSize = eraserSize / 4;
|
74
|
+
const eraserColor = '#ff70af';
|
75
|
+
// Draw an eraser-like shape. Created with Inkscape
|
73
76
|
icon.innerHTML = `
|
74
77
|
<g>
|
75
|
-
<
|
78
|
+
<path
|
79
|
+
style="fill:${eraserColor}"
|
80
|
+
stroke="black"
|
81
|
+
transform="rotate(41.35)"
|
82
|
+
d="M 52.5 27
|
83
|
+
C 50 28.9 48.9 31.7 48.9 34.8
|
84
|
+
L 48.9 39.8
|
85
|
+
C 48.9 45.3 53.4 49.8 58.9 49.8
|
86
|
+
L 103.9 49.8
|
87
|
+
C 105.8 49.8 107.6 49.2 109.1 48.3
|
88
|
+
L 110.2 ${scaledSize + 49.5} L 159.7 ${scaledSize + 5}
|
89
|
+
L 157.7 ${-scaledSize + 5.2} L 112.4 ${49.5 - scaledSize}
|
90
|
+
C 113.4 43.5 113.9 41.7 113.9 39.8
|
91
|
+
L 113.9 34.8
|
92
|
+
C 113.9 29.3 109.4 24.8 103.9 24.8
|
93
|
+
L 58.9 24.8
|
94
|
+
C 56.5 24.8 54.3 25.7 52.5 27
|
95
|
+
z "
|
96
|
+
id="path438" />
|
97
|
+
|
76
98
|
<rect
|
77
|
-
|
99
|
+
stroke="#cc8077"
|
78
100
|
${iconColorFill}
|
79
|
-
|
101
|
+
id="rect218"
|
102
|
+
width="65"
|
103
|
+
height="75"
|
104
|
+
x="48.9"
|
105
|
+
y="-38.7"
|
106
|
+
transform="rotate(41.35)" />
|
80
107
|
</g>
|
81
108
|
`;
|
82
|
-
icon.setAttribute('viewBox', '0 0
|
109
|
+
icon.setAttribute('viewBox', '0 0 120 120');
|
83
110
|
return icon;
|
84
111
|
}
|
85
112
|
makeSelectionIcon() {
|
@@ -2,9 +2,16 @@ import Editor from '../../Editor';
|
|
2
2
|
import Eraser from '../../tools/Eraser';
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
4
|
import BaseToolWidget from './BaseToolWidget';
|
5
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
5
6
|
export default class EraserToolWidget extends BaseToolWidget {
|
7
|
+
private tool;
|
8
|
+
private thicknessInput;
|
6
9
|
constructor(editor: Editor, tool: Eraser, localizationTable?: ToolbarLocalization);
|
7
10
|
protected getTitle(): string;
|
8
11
|
protected createIcon(): Element;
|
9
|
-
|
12
|
+
private updateInputs;
|
13
|
+
private static nextThicknessInputId;
|
14
|
+
protected fillDropdown(dropdown: HTMLElement): boolean;
|
15
|
+
serializeState(): SavedToolbuttonState;
|
16
|
+
deserializeFrom(state: SavedToolbuttonState): void;
|
10
17
|
}
|
@@ -1,16 +1,57 @@
|
|
1
|
+
import { EditorEventType } from '../../types';
|
2
|
+
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
1
3
|
import BaseToolWidget from './BaseToolWidget';
|
2
4
|
export default class EraserToolWidget extends BaseToolWidget {
|
3
5
|
constructor(editor, tool, localizationTable) {
|
4
6
|
super(editor, tool, 'eraser-tool-widget', localizationTable);
|
7
|
+
this.tool = tool;
|
8
|
+
this.thicknessInput = null;
|
9
|
+
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
10
|
+
if (toolEvt.kind === EditorEventType.ToolUpdated && toolEvt.tool === this.tool) {
|
11
|
+
this.updateInputs();
|
12
|
+
this.updateIcon();
|
13
|
+
}
|
14
|
+
});
|
5
15
|
}
|
6
16
|
getTitle() {
|
7
17
|
return this.localizationTable.eraser;
|
8
18
|
}
|
9
19
|
createIcon() {
|
10
|
-
return this.editor.icons.makeEraserIcon();
|
20
|
+
return this.editor.icons.makeEraserIcon(this.tool.getThickness());
|
11
21
|
}
|
12
|
-
|
13
|
-
|
14
|
-
|
22
|
+
updateInputs() {
|
23
|
+
if (this.thicknessInput) {
|
24
|
+
this.thicknessInput.value = `${this.tool.getThickness()}`;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
fillDropdown(dropdown) {
|
28
|
+
const thicknessLabel = document.createElement('label');
|
29
|
+
this.thicknessInput = document.createElement('input');
|
30
|
+
this.thicknessInput.type = 'range';
|
31
|
+
this.thicknessInput.min = '4';
|
32
|
+
this.thicknessInput.max = '40';
|
33
|
+
this.thicknessInput.oninput = () => {
|
34
|
+
this.tool.setThickness(parseFloat(this.thicknessInput.value));
|
35
|
+
};
|
36
|
+
this.thicknessInput.id = `${toolbarCSSPrefix}eraserThicknessInput${EraserToolWidget.nextThicknessInputId++}`;
|
37
|
+
thicknessLabel.innerText = this.localizationTable.thicknessLabel;
|
38
|
+
thicknessLabel.htmlFor = this.thicknessInput.id;
|
39
|
+
this.updateInputs();
|
40
|
+
dropdown.replaceChildren(thicknessLabel, this.thicknessInput);
|
41
|
+
return true;
|
42
|
+
}
|
43
|
+
serializeState() {
|
44
|
+
return Object.assign(Object.assign({}, super.serializeState()), { thickness: this.tool.getThickness() });
|
45
|
+
}
|
46
|
+
deserializeFrom(state) {
|
47
|
+
super.deserializeFrom(state);
|
48
|
+
if (state.thickness) {
|
49
|
+
const parsedThickness = parseFloat(state.thickness);
|
50
|
+
if (typeof parsedThickness !== 'number' || !isFinite(parsedThickness)) {
|
51
|
+
throw new Error(`Deserializing property ${parsedThickness} is not a number or is not finite.`);
|
52
|
+
}
|
53
|
+
this.tool.setThickness(parsedThickness);
|
54
|
+
}
|
15
55
|
}
|
16
56
|
}
|
57
|
+
EraserToolWidget.nextThicknessInputId = 0;
|
@@ -105,8 +105,8 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
105
105
|
const objectSelectLabel = document.createElement('label');
|
106
106
|
const objectTypeSelect = document.createElement('select');
|
107
107
|
// Give inputs IDs so we can label them with a <label for=...>Label text</label>
|
108
|
-
thicknessInput.id = `${toolbarCSSPrefix}
|
109
|
-
objectTypeSelect.id = `${toolbarCSSPrefix}
|
108
|
+
thicknessInput.id = `${toolbarCSSPrefix}penThicknessInput${PenToolWidget.idCounter++}`;
|
109
|
+
objectTypeSelect.id = `${toolbarCSSPrefix}penBuilderSelect${PenToolWidget.idCounter++}`;
|
110
110
|
thicknessLabel.innerText = this.localizationTable.thicknessLabel;
|
111
111
|
thicknessLabel.setAttribute('for', thicknessInput.id);
|
112
112
|
objectSelectLabel.innerText = this.localizationTable.selectObjectType;
|
@@ -1,3 +1,12 @@
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
|
+
});
|
9
|
+
};
|
1
10
|
import { EditorEventType } from '../../types';
|
2
11
|
import ActionButtonWidget from './ActionButtonWidget';
|
3
12
|
import BaseToolWidget from './BaseToolWidget';
|
@@ -13,10 +22,10 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
13
22
|
this.editor.dispatch(selection.deleteSelectedObjects());
|
14
23
|
this.tool.clearSelection();
|
15
24
|
}, localization);
|
16
|
-
const duplicateButton = new ActionButtonWidget(editor, 'duplicate-btn', () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, () => {
|
25
|
+
const duplicateButton = new ActionButtonWidget(editor, 'duplicate-btn', () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, () => __awaiter(this, void 0, void 0, function* () {
|
17
26
|
const selection = this.tool.getSelection();
|
18
|
-
this.editor.dispatch(selection.duplicateSelectedObjects());
|
19
|
-
}, localization);
|
27
|
+
this.editor.dispatch(yield selection.duplicateSelectedObjects());
|
28
|
+
}), localization);
|
20
29
|
this.addSubWidget(resizeButton);
|
21
30
|
this.addSubWidget(deleteButton);
|
22
31
|
this.addSubWidget(duplicateButton);
|
@@ -4,11 +4,20 @@ import Editor from '../Editor';
|
|
4
4
|
export default class Eraser extends BaseTool {
|
5
5
|
private editor;
|
6
6
|
private lastPoint;
|
7
|
+
private isFirstEraseEvt;
|
7
8
|
private toRemove;
|
9
|
+
private thickness;
|
8
10
|
private partialCommands;
|
9
11
|
constructor(editor: Editor, description: string);
|
12
|
+
private clearPreview;
|
13
|
+
private getSizeOnCanvas;
|
14
|
+
private drawPreviewAt;
|
15
|
+
private getEraserRect;
|
16
|
+
private eraseTo;
|
10
17
|
onPointerDown(event: PointerEvt): boolean;
|
11
18
|
onPointerMove(event: PointerEvt): void;
|
12
|
-
onPointerUp(
|
19
|
+
onPointerUp(event: PointerEvt): void;
|
13
20
|
onGestureCancel(): void;
|
21
|
+
getThickness(): number;
|
22
|
+
setThickness(thickness: number): void;
|
14
23
|
}
|