js-draw 0.1.5 → 0.1.8

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