js-draw 0.1.6 → 0.1.7
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +6 -2
- package/dist/src/Editor.d.ts +1 -0
- package/dist/src/Editor.js +19 -8
- package/dist/src/EditorImage.d.ts +8 -13
- package/dist/src/EditorImage.js +51 -29
- package/dist/src/Viewport.d.ts +9 -1
- package/dist/src/Viewport.js +3 -1
- package/dist/src/commands/Command.d.ts +9 -8
- package/dist/src/commands/Command.js +15 -14
- package/dist/src/commands/Duplicate.d.ts +14 -0
- package/dist/src/commands/Duplicate.js +34 -0
- package/dist/src/commands/Erase.d.ts +5 -2
- package/dist/src/commands/Erase.js +28 -9
- package/dist/src/commands/SerializableCommand.d.ts +13 -0
- package/dist/src/commands/SerializableCommand.js +28 -0
- package/dist/src/commands/localization.d.ts +2 -0
- package/dist/src/commands/localization.js +2 -0
- package/dist/src/components/AbstractComponent.d.ts +15 -2
- package/dist/src/components/AbstractComponent.js +122 -26
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +6 -1
- package/dist/src/components/SVGGlobalAttributesObject.js +23 -1
- package/dist/src/components/Stroke.d.ts +5 -0
- package/dist/src/components/Stroke.js +32 -1
- package/dist/src/components/Text.d.ts +11 -4
- package/dist/src/components/Text.js +57 -3
- package/dist/src/components/UnknownSVGObject.d.ts +2 -0
- package/dist/src/components/UnknownSVGObject.js +12 -1
- package/dist/src/components/util/describeComponentList.d.ts +4 -0
- package/dist/src/components/util/describeComponentList.js +14 -0
- package/dist/src/geometry/Path.d.ts +4 -1
- package/dist/src/geometry/Path.js +4 -0
- package/dist/src/rendering/Display.d.ts +3 -0
- package/dist/src/rendering/Display.js +13 -0
- package/dist/src/rendering/RenderingStyle.d.ts +24 -0
- package/dist/src/rendering/RenderingStyle.js +32 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +1 -8
- package/dist/src/rendering/renderers/AbstractRenderer.js +1 -6
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -1
- package/dist/src/toolbar/HTMLToolbar.js +52 -534
- package/dist/src/toolbar/icons.d.ts +5 -0
- package/dist/src/toolbar/icons.js +186 -13
- package/dist/src/toolbar/localization.d.ts +4 -0
- package/dist/src/toolbar/localization.js +4 -0
- package/dist/src/toolbar/makeColorInput.d.ts +5 -0
- package/dist/src/toolbar/makeColorInput.js +81 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +12 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.js +44 -0
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +32 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +148 -0
- package/dist/src/toolbar/widgets/EraserWidget.d.ts +6 -0
- package/dist/src/toolbar/widgets/EraserWidget.js +14 -0
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +133 -0
- package/dist/src/toolbar/widgets/PenWidget.d.ts +20 -0
- package/dist/src/toolbar/widgets/PenWidget.js +131 -0
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +11 -0
- package/dist/src/toolbar/widgets/SelectionWidget.js +56 -0
- package/dist/src/toolbar/widgets/TextToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/TextToolWidget.js +72 -0
- package/dist/src/tools/Pen.js +1 -1
- package/dist/src/tools/PipetteTool.d.ts +20 -0
- package/dist/src/tools/PipetteTool.js +40 -0
- package/dist/src/tools/SelectionTool.d.ts +2 -0
- package/dist/src/tools/SelectionTool.js +41 -23
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/tools/ToolController.d.ts +3 -1
- package/dist/src/tools/ToolController.js +4 -0
- package/dist/src/tools/localization.d.ts +2 -1
- package/dist/src/tools/localization.js +3 -2
- package/dist/src/types.d.ts +7 -2
- package/dist/src/types.js +1 -0
- package/jest.config.js +2 -0
- package/package.json +6 -6
- package/src/Color4.ts +9 -3
- package/src/Editor.ts +23 -11
- package/src/EditorImage.test.ts +4 -4
- package/src/EditorImage.ts +61 -20
- package/src/SVGLoader.ts +2 -1
- package/src/Viewport.ts +2 -1
- package/src/commands/Command.ts +21 -19
- package/src/commands/Duplicate.ts +49 -0
- package/src/commands/Erase.ts +34 -13
- package/src/commands/SerializableCommand.ts +41 -0
- package/src/commands/localization.ts +5 -0
- package/src/components/AbstractComponent.ts +168 -26
- package/src/components/SVGGlobalAttributesObject.ts +34 -2
- package/src/components/Stroke.test.ts +53 -0
- package/src/components/Stroke.ts +37 -2
- package/src/components/Text.test.ts +38 -0
- package/src/components/Text.ts +80 -5
- package/src/components/UnknownSVGObject.test.ts +10 -0
- package/src/components/UnknownSVGObject.ts +15 -1
- package/src/components/builders/FreehandLineBuilder.ts +2 -1
- package/src/components/util/describeComponentList.ts +18 -0
- package/src/geometry/Path.ts +8 -1
- package/src/rendering/Display.ts +17 -1
- package/src/rendering/RenderingStyle.test.ts +68 -0
- package/src/rendering/RenderingStyle.ts +46 -0
- package/src/rendering/caching/RenderingCache.test.ts +1 -1
- package/src/rendering/renderers/AbstractRenderer.ts +1 -15
- package/src/rendering/renderers/CanvasRenderer.ts +2 -1
- package/src/rendering/renderers/DummyRenderer.ts +2 -1
- package/src/rendering/renderers/SVGRenderer.ts +2 -1
- package/src/rendering/renderers/TextOnlyRenderer.ts +2 -1
- package/src/toolbar/HTMLToolbar.ts +58 -660
- package/src/toolbar/icons.ts +205 -13
- package/src/toolbar/localization.ts +10 -2
- package/src/toolbar/makeColorInput.ts +105 -0
- package/src/toolbar/toolbar.css +116 -78
- package/src/toolbar/widgets/BaseToolWidget.ts +53 -0
- package/src/toolbar/widgets/BaseWidget.ts +175 -0
- package/src/toolbar/widgets/EraserWidget.ts +16 -0
- package/src/toolbar/widgets/HandToolWidget.ts +186 -0
- package/src/toolbar/widgets/PenWidget.ts +165 -0
- package/src/toolbar/widgets/SelectionWidget.ts +72 -0
- package/src/toolbar/widgets/TextToolWidget.ts +90 -0
- package/src/tools/Pen.ts +1 -1
- package/src/tools/PipetteTool.ts +56 -0
- package/src/tools/SelectionTool.test.ts +2 -4
- package/src/tools/SelectionTool.ts +47 -27
- package/src/tools/TextTool.ts +1 -1
- package/src/tools/ToolController.ts +10 -6
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/tools/localization.ts +6 -3
- package/src/types.ts +12 -1
@@ -6,635 +6,24 @@ import { coloris, init as colorisInit } from '@melloware/coloris';
|
|
6
6
|
import Color4 from '../Color4';
|
7
7
|
import Pen from '../tools/Pen';
|
8
8
|
import Eraser from '../tools/Eraser';
|
9
|
-
import BaseTool from '../tools/BaseTool';
|
10
9
|
import SelectionTool from '../tools/SelectionTool';
|
11
|
-
import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
|
12
|
-
import { ComponentBuilderFactory } from '../components/builders/types';
|
13
|
-
import { makeArrowBuilder } from '../components/builders/ArrowBuilder';
|
14
|
-
import { makeLineBuilder } from '../components/builders/LineBuilder';
|
15
|
-
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../components/builders/RectangleBuilder';
|
16
10
|
import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
|
17
11
|
import { ActionButtonIcon } from './types';
|
18
|
-
import {
|
19
|
-
import PanZoom
|
20
|
-
import Mat33 from '../geometry/Mat33';
|
21
|
-
import Viewport from '../Viewport';
|
12
|
+
import { makeRedoIcon, makeUndoIcon } from './icons';
|
13
|
+
import PanZoom from '../tools/PanZoom';
|
22
14
|
import TextTool from '../tools/TextTool';
|
15
|
+
import PenWidget from './widgets/PenWidget';
|
16
|
+
import EraserWidget from './widgets/EraserWidget';
|
17
|
+
import { SelectionWidget } from './widgets/SelectionWidget';
|
18
|
+
import TextToolWidget from './widgets/TextToolWidget';
|
19
|
+
import HandToolWidget from './widgets/HandToolWidget';
|
23
20
|
|
24
21
|
|
25
|
-
const toolbarCSSPrefix = 'toolbar-';
|
22
|
+
export const toolbarCSSPrefix = 'toolbar-';
|
26
23
|
|
27
|
-
abstract class ToolbarWidget {
|
28
|
-
protected readonly container: HTMLElement;
|
29
|
-
private button: HTMLElement;
|
30
|
-
private icon: Element|null;
|
31
|
-
private dropdownContainer: HTMLElement;
|
32
|
-
private dropdownIcon: Element;
|
33
|
-
private label: HTMLLabelElement;
|
34
|
-
private hasDropdown: boolean;
|
35
|
-
|
36
|
-
public constructor(
|
37
|
-
protected editor: Editor,
|
38
|
-
protected targetTool: BaseTool,
|
39
|
-
protected localizationTable: ToolbarLocalization,
|
40
|
-
) {
|
41
|
-
this.icon = null;
|
42
|
-
this.container = document.createElement('div');
|
43
|
-
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`);
|
44
|
-
this.dropdownContainer = document.createElement('div');
|
45
|
-
this.dropdownContainer.classList.add(`${toolbarCSSPrefix}dropdown`);
|
46
|
-
this.dropdownContainer.classList.add('hidden');
|
47
|
-
this.hasDropdown = false;
|
48
|
-
|
49
|
-
this.button = document.createElement('div');
|
50
|
-
this.button.classList.add(`${toolbarCSSPrefix}button`);
|
51
|
-
this.label = document.createElement('label');
|
52
|
-
this.button.setAttribute('role', 'button');
|
53
|
-
this.button.tabIndex = 0;
|
54
|
-
|
55
|
-
editor.notifier.on(EditorEventType.ToolEnabled, toolEvt => {
|
56
|
-
if (toolEvt.kind !== EditorEventType.ToolEnabled) {
|
57
|
-
throw new Error('Incorrect event type! (Expected ToolEnabled)');
|
58
|
-
}
|
59
|
-
|
60
|
-
if (toolEvt.tool === targetTool) {
|
61
|
-
this.updateSelected(true);
|
62
|
-
}
|
63
|
-
});
|
64
|
-
|
65
|
-
editor.notifier.on(EditorEventType.ToolDisabled, toolEvt => {
|
66
|
-
if (toolEvt.kind !== EditorEventType.ToolDisabled) {
|
67
|
-
throw new Error('Incorrect event type! (Expected ToolDisabled)');
|
68
|
-
}
|
69
|
-
|
70
|
-
if (toolEvt.tool === targetTool) {
|
71
|
-
this.updateSelected(false);
|
72
|
-
this.setDropdownVisible(false);
|
73
|
-
}
|
74
|
-
});
|
75
|
-
}
|
76
|
-
|
77
|
-
protected abstract getTitle(): string;
|
78
|
-
protected abstract createIcon(): Element;
|
79
|
-
|
80
|
-
// Add content to the widget's associated dropdown menu.
|
81
|
-
// Returns true if such a menu should be created, false otherwise.
|
82
|
-
protected abstract fillDropdown(dropdown: HTMLElement): boolean;
|
83
|
-
|
84
|
-
protected setupActionBtnClickListener(button: HTMLElement) {
|
85
|
-
button.onclick = () => {
|
86
|
-
this.handleClick();
|
87
|
-
};
|
88
|
-
}
|
89
|
-
|
90
|
-
protected handleClick() {
|
91
|
-
if (this.hasDropdown) {
|
92
|
-
if (!this.targetTool.isEnabled()) {
|
93
|
-
this.targetTool.setEnabled(true);
|
94
|
-
} else {
|
95
|
-
this.setDropdownVisible(!this.isDropdownVisible());
|
96
|
-
}
|
97
|
-
} else {
|
98
|
-
this.targetTool.setEnabled(!this.targetTool.isEnabled());
|
99
|
-
}
|
100
|
-
}
|
101
|
-
|
102
|
-
// Adds this to [parent]. This can only be called once for each ToolbarWidget.
|
103
|
-
public addTo(parent: HTMLElement) {
|
104
|
-
this.label.innerText = this.getTitle();
|
105
|
-
|
106
|
-
this.setupActionBtnClickListener(this.button);
|
107
|
-
|
108
|
-
this.icon = null;
|
109
|
-
this.updateIcon();
|
110
|
-
|
111
|
-
this.updateSelected(this.targetTool.isEnabled());
|
112
|
-
|
113
|
-
this.button.replaceChildren(this.icon!, this.label);
|
114
|
-
this.container.appendChild(this.button);
|
115
|
-
|
116
|
-
this.hasDropdown = this.fillDropdown(this.dropdownContainer);
|
117
|
-
if (this.hasDropdown) {
|
118
|
-
this.dropdownIcon = this.createDropdownIcon();
|
119
|
-
this.button.appendChild(this.dropdownIcon);
|
120
|
-
this.container.appendChild(this.dropdownContainer);
|
121
|
-
}
|
122
|
-
|
123
|
-
this.setDropdownVisible(false);
|
124
|
-
parent.appendChild(this.container);
|
125
|
-
}
|
126
|
-
|
127
|
-
protected updateIcon() {
|
128
|
-
const newIcon = this.createIcon();
|
129
|
-
this.icon?.replaceWith(newIcon);
|
130
|
-
this.icon = newIcon;
|
131
|
-
this.icon.classList.add(`${toolbarCSSPrefix}icon`);
|
132
|
-
}
|
133
|
-
|
134
|
-
protected updateSelected(selected: boolean) {
|
135
|
-
const currentlySelected = this.container.classList.contains('selected');
|
136
|
-
if (currentlySelected === selected) {
|
137
|
-
return;
|
138
|
-
}
|
139
|
-
|
140
|
-
if (selected) {
|
141
|
-
this.container.classList.add('selected');
|
142
|
-
this.button.ariaSelected = 'true';
|
143
|
-
} else {
|
144
|
-
this.container.classList.remove('selected');
|
145
|
-
this.button.ariaSelected = 'false';
|
146
|
-
}
|
147
|
-
}
|
148
|
-
|
149
|
-
protected setDropdownVisible(visible: boolean) {
|
150
|
-
const currentlyVisible = this.container.classList.contains('dropdownVisible');
|
151
|
-
if (currentlyVisible === visible) {
|
152
|
-
return;
|
153
|
-
}
|
154
|
-
|
155
|
-
if (visible) {
|
156
|
-
this.dropdownContainer.classList.remove('hidden');
|
157
|
-
this.container.classList.add('dropdownVisible');
|
158
|
-
this.editor.announceForAccessibility(
|
159
|
-
this.localizationTable.dropdownShown(this.targetTool.description)
|
160
|
-
);
|
161
|
-
} else {
|
162
|
-
this.dropdownContainer.classList.add('hidden');
|
163
|
-
this.container.classList.remove('dropdownVisible');
|
164
|
-
this.editor.announceForAccessibility(
|
165
|
-
this.localizationTable.dropdownHidden(this.targetTool.description)
|
166
|
-
);
|
167
|
-
}
|
168
|
-
|
169
|
-
this.repositionDropdown();
|
170
|
-
}
|
171
|
-
|
172
|
-
protected repositionDropdown() {
|
173
|
-
const dropdownBBox = this.dropdownContainer.getBoundingClientRect();
|
174
|
-
const screenWidth = document.body.clientWidth;
|
175
|
-
|
176
|
-
if (dropdownBBox.left > screenWidth / 2) {
|
177
|
-
this.dropdownContainer.style.marginLeft = this.button.clientWidth + 'px';
|
178
|
-
this.dropdownContainer.style.transform = 'translate(-100%, 0)';
|
179
|
-
} else {
|
180
|
-
this.dropdownContainer.style.marginLeft = '';
|
181
|
-
this.dropdownContainer.style.transform = '';
|
182
|
-
}
|
183
|
-
}
|
184
|
-
|
185
|
-
protected isDropdownVisible(): boolean {
|
186
|
-
return !this.dropdownContainer.classList.contains('hidden');
|
187
|
-
}
|
188
|
-
|
189
|
-
private createDropdownIcon(): Element {
|
190
|
-
const icon = makeDropdownIcon();
|
191
|
-
icon.classList.add(`${toolbarCSSPrefix}showHideDropdownIcon`);
|
192
|
-
return icon;
|
193
|
-
}
|
194
|
-
}
|
195
|
-
|
196
|
-
class EraserWidget extends ToolbarWidget {
|
197
|
-
protected getTitle(): string {
|
198
|
-
return this.localizationTable.eraser;
|
199
|
-
}
|
200
|
-
protected createIcon(): Element {
|
201
|
-
return makeEraserIcon();
|
202
|
-
}
|
203
|
-
|
204
|
-
protected fillDropdown(_dropdown: HTMLElement): boolean {
|
205
|
-
// No dropdown associated with the eraser
|
206
|
-
return false;
|
207
|
-
}
|
208
|
-
}
|
209
|
-
|
210
|
-
class SelectionWidget extends ToolbarWidget {
|
211
|
-
public constructor(
|
212
|
-
editor: Editor, private tool: SelectionTool, localization: ToolbarLocalization
|
213
|
-
) {
|
214
|
-
super(editor, tool, localization);
|
215
|
-
}
|
216
|
-
|
217
|
-
protected getTitle(): string {
|
218
|
-
return this.localizationTable.select;
|
219
|
-
}
|
220
|
-
|
221
|
-
protected createIcon(): Element {
|
222
|
-
return makeSelectionIcon();
|
223
|
-
}
|
224
|
-
|
225
|
-
protected fillDropdown(dropdown: HTMLElement): boolean {
|
226
|
-
const container = document.createElement('div');
|
227
|
-
const resizeButton = document.createElement('button');
|
228
|
-
const deleteButton = document.createElement('button');
|
229
|
-
|
230
|
-
resizeButton.innerText = this.localizationTable.resizeImageToSelection;
|
231
|
-
resizeButton.disabled = true;
|
232
|
-
deleteButton.innerText = this.localizationTable.deleteSelection;
|
233
|
-
deleteButton.disabled = true;
|
234
|
-
|
235
|
-
resizeButton.onclick = () => {
|
236
|
-
const selection = this.tool.getSelection();
|
237
|
-
this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
|
238
|
-
};
|
239
|
-
|
240
|
-
deleteButton.onclick = () => {
|
241
|
-
const selection = this.tool.getSelection();
|
242
|
-
this.editor.dispatch(selection!.deleteSelectedObjects());
|
243
|
-
this.tool.clearSelection();
|
244
|
-
};
|
245
|
-
|
246
|
-
// Enable/disable actions based on whether items are selected
|
247
|
-
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
248
|
-
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
249
|
-
throw new Error('Invalid event type!');
|
250
|
-
}
|
251
|
-
|
252
|
-
if (toolEvt.tool === this.tool) {
|
253
|
-
const selection = this.tool.getSelection();
|
254
|
-
const hasSelection = selection && selection.region.area > 0;
|
255
|
-
|
256
|
-
resizeButton.disabled = !hasSelection;
|
257
|
-
deleteButton.disabled = resizeButton.disabled;
|
258
|
-
}
|
259
|
-
});
|
260
|
-
|
261
|
-
container.replaceChildren(resizeButton, deleteButton);
|
262
|
-
dropdown.appendChild(container);
|
263
|
-
return true;
|
264
|
-
}
|
265
|
-
}
|
266
|
-
|
267
|
-
const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor) => {
|
268
|
-
const zoomLevelRow = document.createElement('div');
|
269
|
-
|
270
|
-
const increaseButton = document.createElement('button');
|
271
|
-
const decreaseButton = document.createElement('button');
|
272
|
-
const zoomLevelDisplay = document.createElement('span');
|
273
|
-
increaseButton.innerText = '+';
|
274
|
-
decreaseButton.innerText = '-';
|
275
|
-
zoomLevelRow.replaceChildren(zoomLevelDisplay, increaseButton, decreaseButton);
|
276
|
-
|
277
|
-
zoomLevelRow.classList.add(`${toolbarCSSPrefix}zoomLevelEditor`);
|
278
|
-
zoomLevelDisplay.classList.add('zoomDisplay');
|
279
|
-
|
280
|
-
let lastZoom: number|undefined;
|
281
|
-
const updateZoomDisplay = () => {
|
282
|
-
let zoomLevel = editor.viewport.getScaleFactor() * 100;
|
283
|
-
|
284
|
-
if (zoomLevel > 0.1) {
|
285
|
-
zoomLevel = Math.round(zoomLevel * 10) / 10;
|
286
|
-
} else {
|
287
|
-
zoomLevel = Math.round(zoomLevel * 1000) / 1000;
|
288
|
-
}
|
289
|
-
|
290
|
-
if (zoomLevel !== lastZoom) {
|
291
|
-
zoomLevelDisplay.innerText = localizationTable.zoomLevel(zoomLevel);
|
292
|
-
lastZoom = zoomLevel;
|
293
|
-
}
|
294
|
-
};
|
295
|
-
updateZoomDisplay();
|
296
|
-
|
297
|
-
editor.notifier.on(EditorEventType.ViewportChanged, (event) => {
|
298
|
-
if (event.kind === EditorEventType.ViewportChanged) {
|
299
|
-
updateZoomDisplay();
|
300
|
-
}
|
301
|
-
});
|
302
|
-
|
303
|
-
const zoomBy = (factor: number) => {
|
304
|
-
const screenCenter = editor.viewport.visibleRect.center;
|
305
|
-
const transformUpdate = Mat33.scaling2D(factor, screenCenter);
|
306
|
-
editor.dispatch(new Viewport.ViewportTransform(transformUpdate), false);
|
307
|
-
};
|
308
|
-
|
309
|
-
increaseButton.onclick = () => {
|
310
|
-
zoomBy(5.0/4);
|
311
|
-
};
|
312
|
-
|
313
|
-
decreaseButton.onclick = () => {
|
314
|
-
zoomBy(4.0/5);
|
315
|
-
};
|
316
|
-
|
317
|
-
return zoomLevelRow;
|
318
|
-
};
|
319
|
-
|
320
|
-
class HandToolWidget extends ToolbarWidget {
|
321
|
-
public constructor(
|
322
|
-
editor: Editor, protected tool: PanZoom, localizationTable: ToolbarLocalization
|
323
|
-
) {
|
324
|
-
super(editor, tool, localizationTable);
|
325
|
-
this.container.classList.add('dropdownShowable');
|
326
|
-
}
|
327
|
-
protected getTitle(): string {
|
328
|
-
return this.localizationTable.handTool;
|
329
|
-
}
|
330
|
-
|
331
|
-
protected createIcon(): Element {
|
332
|
-
return makeHandToolIcon();
|
333
|
-
}
|
334
|
-
|
335
|
-
protected fillDropdown(dropdown: HTMLElement): boolean {
|
336
|
-
type OnToggle = (checked: boolean)=>void;
|
337
|
-
let idCounter = 0;
|
338
|
-
const addCheckbox = (label: string, onToggle: OnToggle) => {
|
339
|
-
const rowContainer = document.createElement('div');
|
340
|
-
const labelElem = document.createElement('label');
|
341
|
-
const checkboxElem = document.createElement('input');
|
342
|
-
|
343
|
-
checkboxElem.type = 'checkbox';
|
344
|
-
checkboxElem.id = `${toolbarCSSPrefix}hand-tool-option-${idCounter++}`;
|
345
|
-
labelElem.setAttribute('for', checkboxElem.id);
|
346
|
-
|
347
|
-
checkboxElem.oninput = () => {
|
348
|
-
onToggle(checkboxElem.checked);
|
349
|
-
};
|
350
|
-
labelElem.innerText = label;
|
351
|
-
|
352
|
-
rowContainer.replaceChildren(checkboxElem, labelElem);
|
353
|
-
dropdown.appendChild(rowContainer);
|
354
|
-
|
355
|
-
return checkboxElem;
|
356
|
-
};
|
357
|
-
|
358
|
-
const setModeFlag = (enabled: boolean, flag: PanZoomMode) => {
|
359
|
-
const mode = this.tool.getMode();
|
360
|
-
if (enabled) {
|
361
|
-
this.tool.setMode(mode | flag);
|
362
|
-
} else {
|
363
|
-
this.tool.setMode(mode & ~flag);
|
364
|
-
}
|
365
|
-
};
|
366
|
-
|
367
|
-
const touchPanningCheckbox = addCheckbox(this.localizationTable.touchPanning, checked => {
|
368
|
-
setModeFlag(checked, PanZoomMode.OneFingerTouchGestures);
|
369
|
-
});
|
370
|
-
|
371
|
-
const anyDevicePanningCheckbox = addCheckbox(this.localizationTable.anyDevicePanning, checked => {
|
372
|
-
setModeFlag(checked, PanZoomMode.SinglePointerGestures);
|
373
|
-
});
|
374
|
-
|
375
|
-
dropdown.appendChild(makeZoomControl(this.localizationTable, this.editor));
|
376
|
-
|
377
|
-
const updateInputs = () => {
|
378
|
-
const mode = this.tool.getMode();
|
379
|
-
anyDevicePanningCheckbox.checked = !!(mode & PanZoomMode.SinglePointerGestures);
|
380
|
-
if (anyDevicePanningCheckbox.checked) {
|
381
|
-
touchPanningCheckbox.checked = true;
|
382
|
-
touchPanningCheckbox.disabled = true;
|
383
|
-
} else {
|
384
|
-
touchPanningCheckbox.checked = !!(mode & PanZoomMode.OneFingerTouchGestures);
|
385
|
-
touchPanningCheckbox.disabled = false;
|
386
|
-
}
|
387
|
-
};
|
388
|
-
|
389
|
-
updateInputs();
|
390
|
-
this.editor.notifier.on(EditorEventType.ToolUpdated, event => {
|
391
|
-
if (event.kind === EditorEventType.ToolUpdated && event.tool === this.tool) {
|
392
|
-
updateInputs();
|
393
|
-
}
|
394
|
-
});
|
395
|
-
|
396
|
-
return true;
|
397
|
-
}
|
398
|
-
|
399
|
-
protected updateSelected(_active: boolean) {
|
400
|
-
}
|
401
|
-
|
402
|
-
protected handleClick() {
|
403
|
-
this.setDropdownVisible(!this.isDropdownVisible());
|
404
|
-
}
|
405
|
-
}
|
406
|
-
|
407
|
-
class TextToolWidget extends ToolbarWidget {
|
408
|
-
private updateDropdownInputs: (()=>void)|null = null;
|
409
|
-
public constructor(editor: Editor, private tool: TextTool, localization: ToolbarLocalization) {
|
410
|
-
super(editor, tool, localization);
|
411
|
-
|
412
|
-
editor.notifier.on(EditorEventType.ToolUpdated, evt => {
|
413
|
-
if (evt.kind === EditorEventType.ToolUpdated && evt.tool === tool) {
|
414
|
-
this.updateIcon();
|
415
|
-
this.updateDropdownInputs?.();
|
416
|
-
}
|
417
|
-
});
|
418
|
-
}
|
419
|
-
|
420
|
-
protected getTitle(): string {
|
421
|
-
return this.targetTool.description;
|
422
|
-
}
|
423
|
-
|
424
|
-
protected createIcon(): Element {
|
425
|
-
const textStyle = this.tool.getTextStyle();
|
426
|
-
return makeTextIcon(textStyle);
|
427
|
-
}
|
428
|
-
|
429
|
-
private static idCounter: number = 0;
|
430
|
-
protected fillDropdown(dropdown: HTMLElement): boolean {
|
431
|
-
const fontRow = document.createElement('div');
|
432
|
-
const colorRow = document.createElement('div');
|
433
|
-
|
434
|
-
const fontInput = document.createElement('select');
|
435
|
-
const fontLabel = document.createElement('label');
|
436
|
-
|
437
|
-
const colorInput = document.createElement('input');
|
438
|
-
const colorLabel = document.createElement('label');
|
439
|
-
|
440
|
-
const fontsInInput = new Set();
|
441
|
-
const addFontToInput = (fontName: string) => {
|
442
|
-
const option = document.createElement('option');
|
443
|
-
option.value = fontName;
|
444
|
-
option.textContent = fontName;
|
445
|
-
fontInput.appendChild(option);
|
446
|
-
fontsInInput.add(fontName);
|
447
|
-
};
|
448
|
-
|
449
|
-
fontLabel.innerText = this.localizationTable.fontLabel;
|
450
|
-
colorLabel.innerText = this.localizationTable.colorLabel;
|
451
|
-
|
452
|
-
colorInput.classList.add('coloris_input');
|
453
|
-
colorInput.type = 'button';
|
454
|
-
colorInput.id = `${toolbarCSSPrefix}-text-color-input-${TextToolWidget.idCounter++}`;
|
455
|
-
colorLabel.setAttribute('for', colorInput.id);
|
456
|
-
|
457
|
-
addFontToInput('monospace');
|
458
|
-
addFontToInput('serif');
|
459
|
-
addFontToInput('sans-serif');
|
460
|
-
fontInput.id = `${toolbarCSSPrefix}-text-font-input-${TextToolWidget.idCounter++}`;
|
461
|
-
fontLabel.setAttribute('for', fontInput.id);
|
462
|
-
|
463
|
-
fontInput.onchange = () => {
|
464
|
-
this.tool.setFontFamily(fontInput.value);
|
465
|
-
};
|
466
|
-
|
467
|
-
colorInput.oninput = () => {
|
468
|
-
this.tool.setColor(Color4.fromString(colorInput.value));
|
469
|
-
};
|
470
|
-
|
471
|
-
colorRow.appendChild(colorLabel);
|
472
|
-
colorRow.appendChild(colorInput);
|
473
|
-
|
474
|
-
fontRow.appendChild(fontLabel);
|
475
|
-
fontRow.appendChild(fontInput);
|
476
|
-
|
477
|
-
this.updateDropdownInputs = () => {
|
478
|
-
const style = this.tool.getTextStyle();
|
479
|
-
colorInput.value = style.renderingStyle.fill.toHexString();
|
480
|
-
|
481
|
-
if (!fontsInInput.has(style.fontFamily)) {
|
482
|
-
addFontToInput(style.fontFamily);
|
483
|
-
}
|
484
|
-
fontInput.value = style.fontFamily;
|
485
|
-
};
|
486
|
-
this.updateDropdownInputs();
|
487
|
-
|
488
|
-
dropdown.replaceChildren(colorRow, fontRow);
|
489
|
-
return true;
|
490
|
-
}
|
491
|
-
}
|
492
|
-
|
493
|
-
class PenWidget extends ToolbarWidget {
|
494
|
-
private updateInputs: ()=> void = () => {};
|
495
|
-
|
496
|
-
public constructor(
|
497
|
-
editor: Editor, private tool: Pen, localization: ToolbarLocalization, private penTypes: PenTypeRecord[]
|
498
|
-
) {
|
499
|
-
super(editor, tool, localization);
|
500
|
-
|
501
|
-
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
502
|
-
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
503
|
-
throw new Error('Invalid event type!');
|
504
|
-
}
|
505
|
-
|
506
|
-
// The button icon may depend on tool properties.
|
507
|
-
if (toolEvt.tool === this.tool) {
|
508
|
-
this.updateIcon();
|
509
|
-
this.updateInputs();
|
510
|
-
}
|
511
|
-
});
|
512
|
-
}
|
513
|
-
|
514
|
-
protected getTitle(): string {
|
515
|
-
return this.targetTool.description;
|
516
|
-
}
|
517
|
-
|
518
|
-
protected createIcon(): Element {
|
519
|
-
const strokeFactory = this.tool.getStrokeFactory();
|
520
|
-
if (strokeFactory === makeFreehandLineBuilder) {
|
521
|
-
// Use a square-root scale to prevent the pen's tip from overflowing.
|
522
|
-
const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 4);
|
523
|
-
const color = this.tool.getColor();
|
524
|
-
return makePenIcon(scale, color.toHexString());
|
525
|
-
} else {
|
526
|
-
const strokeFactory = this.tool.getStrokeFactory();
|
527
|
-
return makeIconFromFactory(this.tool, strokeFactory);
|
528
|
-
}
|
529
|
-
}
|
530
|
-
|
531
|
-
private static idCounter: number = 0;
|
532
|
-
protected fillDropdown(dropdown: HTMLElement): boolean {
|
533
|
-
const container = document.createElement('div');
|
534
|
-
|
535
|
-
const thicknessRow = document.createElement('div');
|
536
|
-
const objectTypeRow = document.createElement('div');
|
537
|
-
|
538
|
-
// Thickness: Value of the input is squared to allow for finer control/larger values.
|
539
|
-
const thicknessLabel = document.createElement('label');
|
540
|
-
const thicknessInput = document.createElement('input');
|
541
|
-
const objectSelectLabel = document.createElement('label');
|
542
|
-
const objectTypeSelect = document.createElement('select');
|
543
|
-
|
544
|
-
// Give inputs IDs so we can label them with a <label for=...>Label text</label>
|
545
|
-
thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${PenWidget.idCounter++}`;
|
546
|
-
objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${PenWidget.idCounter++}`;
|
547
|
-
|
548
|
-
thicknessLabel.innerText = this.localizationTable.thicknessLabel;
|
549
|
-
thicknessLabel.setAttribute('for', thicknessInput.id);
|
550
|
-
objectSelectLabel.innerText = this.localizationTable.selectObjectType;
|
551
|
-
objectSelectLabel.setAttribute('for', objectTypeSelect.id);
|
552
|
-
|
553
|
-
thicknessInput.type = 'range';
|
554
|
-
thicknessInput.min = '1';
|
555
|
-
thicknessInput.max = '20';
|
556
|
-
thicknessInput.step = '1';
|
557
|
-
thicknessInput.oninput = () => {
|
558
|
-
this.tool.setThickness(parseFloat(thicknessInput.value) ** 2);
|
559
|
-
};
|
560
|
-
thicknessRow.appendChild(thicknessLabel);
|
561
|
-
thicknessRow.appendChild(thicknessInput);
|
562
|
-
|
563
|
-
objectTypeSelect.oninput = () => {
|
564
|
-
const penTypeIdx = parseInt(objectTypeSelect.value);
|
565
|
-
if (penTypeIdx < 0 || penTypeIdx >= this.penTypes.length) {
|
566
|
-
console.error('Invalid pen type index', penTypeIdx);
|
567
|
-
return;
|
568
|
-
}
|
569
|
-
|
570
|
-
this.tool.setStrokeFactory(this.penTypes[penTypeIdx].factory);
|
571
|
-
};
|
572
|
-
objectTypeRow.appendChild(objectSelectLabel);
|
573
|
-
objectTypeRow.appendChild(objectTypeSelect);
|
574
|
-
|
575
|
-
const colorRow = document.createElement('div');
|
576
|
-
const colorLabel = document.createElement('label');
|
577
|
-
const colorInput = document.createElement('input');
|
578
|
-
|
579
|
-
colorInput.id = `${toolbarCSSPrefix}colorInput${PenWidget.idCounter++}`;
|
580
|
-
colorLabel.innerText = this.localizationTable.colorLabel;
|
581
|
-
colorLabel.setAttribute('for', colorInput.id);
|
582
|
-
|
583
|
-
colorInput.className = 'coloris_input';
|
584
|
-
colorInput.type = 'button';
|
585
|
-
colorInput.oninput = () => {
|
586
|
-
this.tool.setColor(Color4.fromHex(colorInput.value));
|
587
|
-
};
|
588
|
-
colorInput.addEventListener('open', () => {
|
589
|
-
this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
|
590
|
-
kind: EditorEventType.ColorPickerToggled,
|
591
|
-
open: true,
|
592
|
-
});
|
593
|
-
});
|
594
|
-
colorInput.addEventListener('close', () => {
|
595
|
-
this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
|
596
|
-
kind: EditorEventType.ColorPickerToggled,
|
597
|
-
open: false,
|
598
|
-
});
|
599
|
-
});
|
600
|
-
|
601
|
-
colorRow.appendChild(colorLabel);
|
602
|
-
colorRow.appendChild(colorInput);
|
603
|
-
|
604
|
-
this.updateInputs = () => {
|
605
|
-
colorInput.value = this.tool.getColor().toHexString();
|
606
|
-
thicknessInput.value = Math.sqrt(this.tool.getThickness()).toString();
|
607
|
-
|
608
|
-
objectTypeSelect.replaceChildren();
|
609
|
-
for (let i = 0; i < this.penTypes.length; i ++) {
|
610
|
-
const penType = this.penTypes[i];
|
611
|
-
const option = document.createElement('option');
|
612
|
-
option.value = i.toString();
|
613
|
-
option.innerText = penType.name;
|
614
|
-
|
615
|
-
objectTypeSelect.appendChild(option);
|
616
|
-
|
617
|
-
if (penType.factory === this.tool.getStrokeFactory()) {
|
618
|
-
objectTypeSelect.value = i.toString();
|
619
|
-
}
|
620
|
-
}
|
621
|
-
};
|
622
|
-
this.updateInputs();
|
623
|
-
|
624
|
-
container.replaceChildren(colorRow, thicknessRow, objectTypeRow);
|
625
|
-
dropdown.replaceChildren(container);
|
626
|
-
return true;
|
627
|
-
}
|
628
|
-
}
|
629
|
-
|
630
|
-
interface PenTypeRecord {
|
631
|
-
name: string;
|
632
|
-
factory: ComponentBuilderFactory;
|
633
|
-
}
|
634
24
|
|
635
25
|
export default class HTMLToolbar {
|
636
26
|
private container: HTMLElement;
|
637
|
-
private penTypes: PenTypeRecord[];
|
638
27
|
|
639
28
|
public constructor(
|
640
29
|
private editor: Editor, parent: HTMLElement,
|
@@ -647,30 +36,6 @@ export default class HTMLToolbar {
|
|
647
36
|
|
648
37
|
colorisInit();
|
649
38
|
this.setupColorPickers();
|
650
|
-
|
651
|
-
// Default pen types
|
652
|
-
this.penTypes = [
|
653
|
-
{
|
654
|
-
name: localizationTable.freehandPen,
|
655
|
-
factory: makeFreehandLineBuilder,
|
656
|
-
},
|
657
|
-
{
|
658
|
-
name: localizationTable.arrowPen,
|
659
|
-
factory: makeArrowBuilder,
|
660
|
-
},
|
661
|
-
{
|
662
|
-
name: localizationTable.linePen,
|
663
|
-
factory: makeLineBuilder,
|
664
|
-
},
|
665
|
-
{
|
666
|
-
name: localizationTable.filledRectanglePen,
|
667
|
-
factory: makeFilledRectangleBuilder,
|
668
|
-
},
|
669
|
-
{
|
670
|
-
name: localizationTable.outlinedRectanglePen,
|
671
|
-
factory: makeOutlinedRectangleBuilder,
|
672
|
-
},
|
673
|
-
];
|
674
39
|
}
|
675
40
|
|
676
41
|
public setupColorPickers() {
|
@@ -678,22 +43,48 @@ export default class HTMLToolbar {
|
|
678
43
|
closePickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
|
679
44
|
this.editor.createHTMLOverlay(closePickerOverlay);
|
680
45
|
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
46
|
+
const maxSwatchLen = 12;
|
47
|
+
const swatches = [
|
48
|
+
Color4.red.toHexString(),
|
49
|
+
Color4.purple.toHexString(),
|
50
|
+
Color4.blue.toHexString(),
|
51
|
+
Color4.clay.toHexString(),
|
52
|
+
Color4.black.toHexString(),
|
53
|
+
Color4.white.toHexString(),
|
54
|
+
];
|
55
|
+
const presetColorEnd = swatches.length;
|
56
|
+
|
57
|
+
// (Re)init Coloris -- update the swatches list.
|
58
|
+
const initColoris = () => {
|
59
|
+
coloris({
|
60
|
+
el: '.coloris_input',
|
61
|
+
format: 'hex',
|
62
|
+
selectInput: false,
|
63
|
+
focusInput: false,
|
64
|
+
themeMode: 'auto',
|
65
|
+
|
66
|
+
swatches
|
67
|
+
});
|
68
|
+
};
|
69
|
+
initColoris();
|
70
|
+
|
71
|
+
const addColorToSwatch = (newColor: string) => {
|
72
|
+
let alreadyPresent = false;
|
73
|
+
|
74
|
+
for (const color of swatches) {
|
75
|
+
if (color === newColor) {
|
76
|
+
alreadyPresent = true;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
if (!alreadyPresent) {
|
81
|
+
swatches.push(newColor);
|
82
|
+
if (swatches.length > maxSwatchLen) {
|
83
|
+
swatches.splice(presetColorEnd, 1);
|
84
|
+
}
|
85
|
+
initColoris();
|
86
|
+
}
|
87
|
+
};
|
697
88
|
|
698
89
|
this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
|
699
90
|
if (event.kind !== EditorEventType.ColorPickerToggled) {
|
@@ -704,6 +95,13 @@ export default class HTMLToolbar {
|
|
704
95
|
// on that shows/hides the color picker.
|
705
96
|
closePickerOverlay.style.display = event.open ? 'block' : 'none';
|
706
97
|
});
|
98
|
+
|
99
|
+
// Add newly-selected colors to the swatch.
|
100
|
+
this.editor.notifier.on(EditorEventType.ColorPickerColorSelected, event => {
|
101
|
+
if (event.kind === EditorEventType.ColorPickerColorSelected) {
|
102
|
+
addColorToSwatch(event.color.toHexString());
|
103
|
+
}
|
104
|
+
});
|
707
105
|
}
|
708
106
|
|
709
107
|
public addActionButton(title: string|ActionButtonIcon, command: ()=> void, parent?: Element) {
|
@@ -768,7 +166,7 @@ export default class HTMLToolbar {
|
|
768
166
|
}
|
769
167
|
|
770
168
|
const widget = new PenWidget(
|
771
|
-
this.editor, tool, this.localizationTable,
|
169
|
+
this.editor, tool, this.localizationTable,
|
772
170
|
);
|
773
171
|
widget.addTo(this.container);
|
774
172
|
}
|