js-draw 0.5.0 → 0.7.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.
Files changed (92) hide show
  1. package/.firebase/hosting.ZG9jcw.cache +338 -0
  2. package/.github/ISSUE_TEMPLATE/translation.md +1 -1
  3. package/CHANGELOG.md +19 -0
  4. package/dist/bundle.js +1 -1
  5. package/dist/src/Editor.d.ts +8 -6
  6. package/dist/src/Editor.js +8 -4
  7. package/dist/src/EditorImage.d.ts +3 -0
  8. package/dist/src/EditorImage.js +7 -0
  9. package/dist/src/SVGLoader.js +7 -8
  10. package/dist/src/components/AbstractComponent.d.ts +1 -0
  11. package/dist/src/components/AbstractComponent.js +4 -0
  12. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
  13. package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
  14. package/dist/src/components/Stroke.js +1 -0
  15. package/dist/src/components/Text.d.ts +11 -8
  16. package/dist/src/components/Text.js +63 -20
  17. package/dist/src/components/UnknownSVGObject.d.ts +1 -0
  18. package/dist/src/components/UnknownSVGObject.js +3 -0
  19. package/dist/src/components/builders/FreehandLineBuilder.d.ts +9 -2
  20. package/dist/src/components/builders/FreehandLineBuilder.js +129 -30
  21. package/dist/src/components/lib.d.ts +2 -2
  22. package/dist/src/components/lib.js +2 -2
  23. package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
  24. package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -0
  25. package/dist/src/rendering/renderers/SVGRenderer.js +49 -22
  26. package/dist/src/testing/beforeEachFile.js +4 -0
  27. package/dist/src/toolbar/HTMLToolbar.js +2 -3
  28. package/dist/src/toolbar/IconProvider.d.ts +30 -0
  29. package/dist/src/toolbar/IconProvider.js +417 -0
  30. package/dist/src/toolbar/lib.d.ts +1 -1
  31. package/dist/src/toolbar/lib.js +1 -2
  32. package/dist/src/toolbar/localization.d.ts +0 -1
  33. package/dist/src/toolbar/localization.js +0 -1
  34. package/dist/src/toolbar/makeColorInput.js +1 -2
  35. package/dist/src/toolbar/widgets/BaseWidget.js +1 -2
  36. package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
  37. package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
  38. package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
  39. package/dist/src/toolbar/widgets/PenToolWidget.js +10 -8
  40. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
  41. package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
  42. package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
  43. package/dist/src/tools/PanZoom.d.ts +1 -1
  44. package/dist/src/tools/PanZoom.js +4 -1
  45. package/dist/src/tools/PasteHandler.js +2 -22
  46. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
  47. package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
  48. package/dist/src/tools/TextTool.d.ts +4 -0
  49. package/dist/src/tools/TextTool.js +73 -15
  50. package/dist/src/tools/ToolController.js +1 -0
  51. package/dist/src/tools/localization.d.ts +1 -0
  52. package/dist/src/tools/localization.js +1 -0
  53. package/package.json +1 -1
  54. package/src/Editor.toSVG.test.ts +27 -0
  55. package/src/Editor.ts +15 -9
  56. package/src/EditorImage.ts +9 -0
  57. package/src/SVGLoader.test.ts +57 -0
  58. package/src/SVGLoader.ts +9 -10
  59. package/src/components/AbstractComponent.ts +5 -0
  60. package/src/components/SVGGlobalAttributesObject.ts +4 -0
  61. package/src/components/Stroke.ts +1 -0
  62. package/src/components/Text.test.ts +3 -18
  63. package/src/components/Text.ts +78 -25
  64. package/src/components/UnknownSVGObject.ts +4 -0
  65. package/src/components/builders/FreehandLineBuilder.ts +162 -34
  66. package/src/components/lib.ts +3 -3
  67. package/src/rendering/renderers/CanvasRenderer.ts +2 -2
  68. package/src/rendering/renderers/SVGRenderer.ts +50 -24
  69. package/src/testing/beforeEachFile.ts +6 -1
  70. package/src/toolbar/HTMLToolbar.ts +2 -3
  71. package/src/toolbar/IconProvider.ts +480 -0
  72. package/src/toolbar/lib.ts +1 -1
  73. package/src/toolbar/localization.ts +0 -2
  74. package/src/toolbar/makeColorInput.ts +1 -2
  75. package/src/toolbar/widgets/BaseWidget.ts +1 -2
  76. package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
  77. package/src/toolbar/widgets/HandToolWidget.ts +42 -20
  78. package/src/toolbar/widgets/PenToolWidget.ts +11 -8
  79. package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
  80. package/src/toolbar/widgets/TextToolWidget.ts +1 -2
  81. package/src/tools/PanZoom.ts +4 -1
  82. package/src/tools/PasteHandler.ts +2 -24
  83. package/src/tools/SelectionTool/SelectionTool.css +1 -0
  84. package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
  85. package/src/tools/SelectionTool/SelectionTool.ts +73 -4
  86. package/src/tools/TextTool.ts +82 -17
  87. package/src/tools/ToolController.ts +1 -0
  88. package/src/tools/localization.ts +4 -0
  89. package/typedoc.json +5 -1
  90. package/dist/src/toolbar/icons.d.ts +0 -20
  91. package/dist/src/toolbar/icons.js +0 -385
  92. package/src/toolbar/icons.ts +0 -443
@@ -0,0 +1,417 @@
1
+ import Color4 from '../Color4';
2
+ import EventDispatcher from '../EventDispatcher';
3
+ import { Vec2 } from '../math/Vec2';
4
+ import SVGRenderer from '../rendering/renderers/SVGRenderer';
5
+ import Viewport from '../Viewport';
6
+ const svgNamespace = 'http://www.w3.org/2000/svg';
7
+ const iconColorFill = `
8
+ style='fill: var(--icon-color);'
9
+ `;
10
+ const iconColorStrokeFill = `
11
+ style='fill: var(--icon-color); stroke: var(--icon-color);'
12
+ `;
13
+ const checkerboardPatternDef = `
14
+ <pattern
15
+ id='checkerboard'
16
+ viewBox='0,0,10,10'
17
+ width='20%'
18
+ height='20%'
19
+ patternUnits='userSpaceOnUse'
20
+ >
21
+ <rect x=0 y=0 width=10 height=10 fill='white'/>
22
+ <rect x=0 y=0 width=5 height=5 fill='gray'/>
23
+ <rect x=5 y=5 width=5 height=5 fill='gray'/>
24
+ </pattern>
25
+ `;
26
+ const checkerboardPatternRef = 'url(#checkerboard)';
27
+ // Provides icons that can be used in the toolbar, etc.
28
+ // Extend this class and override methods to customize icons.
29
+ export default class IconProvider {
30
+ makeUndoIcon() {
31
+ return this.makeRedoIcon(true);
32
+ }
33
+ // @param mirror - reflect across the x-axis @internal
34
+ // @returns a redo icon.
35
+ makeRedoIcon(mirror = false) {
36
+ const icon = document.createElementNS(svgNamespace, 'svg');
37
+ icon.innerHTML = `
38
+ <style>
39
+ .toolbar-svg-undo-redo-icon {
40
+ stroke: var(--icon-color);
41
+ stroke-width: 12;
42
+ stroke-linejoin: round;
43
+ stroke-linecap: round;
44
+ fill: none;
45
+
46
+ transform-origin: center;
47
+ }
48
+ </style>
49
+ <path
50
+ d='M20,20 A15,15 0 0 1 70,80 L80,90 L60,70 L65,90 L87,90 L65,80'
51
+ class='toolbar-svg-undo-redo-icon'
52
+ style='${mirror ? 'transform: scale(-1, 1);' : ''}'/>
53
+ `;
54
+ icon.setAttribute('viewBox', '0 0 100 100');
55
+ return icon;
56
+ }
57
+ makeDropdownIcon() {
58
+ const icon = document.createElementNS(svgNamespace, 'svg');
59
+ icon.innerHTML = `
60
+ <g>
61
+ <path
62
+ d='M5,10 L50,90 L95,10 Z'
63
+ ${iconColorFill}
64
+ />
65
+ </g>
66
+ `;
67
+ icon.setAttribute('viewBox', '0 0 100 100');
68
+ return icon;
69
+ }
70
+ makeEraserIcon() {
71
+ const icon = document.createElementNS(svgNamespace, 'svg');
72
+ // Draw an eraser-like shape
73
+ icon.innerHTML = `
74
+ <g>
75
+ <rect x=10 y=50 width=80 height=30 rx=10 fill='pink' />
76
+ <rect
77
+ x=10 y=10 width=80 height=50
78
+ ${iconColorFill}
79
+ />
80
+ </g>
81
+ `;
82
+ icon.setAttribute('viewBox', '0 0 100 100');
83
+ return icon;
84
+ }
85
+ makeSelectionIcon() {
86
+ const icon = document.createElementNS(svgNamespace, 'svg');
87
+ // Draw a cursor-like shape
88
+ icon.innerHTML = `
89
+ <g>
90
+ <rect x=10 y=10 width=70 height=70 fill='pink' stroke='black'/>
91
+ <rect x=75 y=75 width=10 height=10 fill='white' stroke='black'/>
92
+ </g>
93
+ `;
94
+ icon.setAttribute('viewBox', '0 0 100 100');
95
+ return icon;
96
+ }
97
+ /**
98
+ * @param pathData - SVG path data (e.g. `m10,10l30,30z`)
99
+ * @param fill - A valid CSS color (e.g. `var(--icon-color)` or `#f0f`). This can be `none`.
100
+ */
101
+ makeIconFromPath(pathData, fill = 'var(--icon-color)', strokeColor = 'none', strokeWidth = '0px') {
102
+ const icon = document.createElementNS(svgNamespace, 'svg');
103
+ const path = document.createElementNS(svgNamespace, 'path');
104
+ path.setAttribute('d', pathData);
105
+ path.style.fill = fill;
106
+ path.style.stroke = strokeColor;
107
+ path.style.strokeWidth = strokeWidth;
108
+ icon.appendChild(path);
109
+ icon.setAttribute('viewBox', '0 0 100 100');
110
+ return icon;
111
+ }
112
+ makeHandToolIcon() {
113
+ const fill = 'none';
114
+ const strokeColor = 'var(--icon-color)';
115
+ const strokeWidth = '3';
116
+ // Draw a cursor-like shape (like some of the other icons, made with Inkscape)
117
+ return this.makeIconFromPath(`
118
+ m 10,60
119
+ 5,30
120
+ H 90
121
+ V 30
122
+ C 90,20 75,20 75,30
123
+ V 60
124
+ 20
125
+ C 75,10 60,10 60,20
126
+ V 60
127
+ 15
128
+ C 60,5 45,5 45,15
129
+ V 60
130
+ 25
131
+ C 45,15 30,15 30,25
132
+ V 60
133
+ 75
134
+ L 25,60
135
+ C 20,45 10,50 10,60
136
+ Z
137
+ `, fill, strokeColor, strokeWidth);
138
+ }
139
+ makeTouchPanningIcon() {
140
+ const fill = 'none';
141
+ const strokeColor = 'var(--icon-color)';
142
+ const strokeWidth = '3';
143
+ return this.makeIconFromPath(`
144
+ M 5,5.5
145
+ V 17.2
146
+ L 16.25,5.46
147
+ Z
148
+
149
+ m 33.75,0
150
+ L 50,17
151
+ V 5.5
152
+ Z
153
+
154
+ M 5,40.7
155
+ v 11.7
156
+ h 11.25
157
+ z
158
+
159
+ M 26,19
160
+ C 19.8,19.4 17.65,30.4 21.9,34.8
161
+ L 50,70
162
+ H 27.5
163
+ c -11.25,0 -11.25,17.6 0,17.6
164
+ H 61.25
165
+ C 94.9,87.8 95,87.6 95,40.7 78.125,23 67,29 55.6,46.5
166
+ L 33.1,23
167
+ C 30.3125,20.128192 27.9,19 25.830078,19.119756
168
+ Z
169
+ `, fill, strokeColor, strokeWidth);
170
+ }
171
+ makeAllDevicePanningIcon() {
172
+ const fill = 'none';
173
+ const strokeColor = 'var(--icon-color)';
174
+ const strokeWidth = '3';
175
+ return this.makeIconFromPath(`
176
+ M 5 5
177
+ L 5 17.5
178
+ 17.5 5
179
+ 5 5
180
+ z
181
+
182
+ M 42.5 5
183
+ L 55 17.5
184
+ 55 5
185
+ 42.5 5
186
+ z
187
+
188
+ M 70 10
189
+ L 70 21
190
+ 61 15
191
+ 55.5 23
192
+ 66 30
193
+ 56 37
194
+ 61 45
195
+ 70 39
196
+ 70 50
197
+ 80 50
198
+ 80 39
199
+ 89 45
200
+ 95 36
201
+ 84 30
202
+ 95 23
203
+ 89 15
204
+ 80 21
205
+ 80 10
206
+ 70 10
207
+ z
208
+
209
+ M 27.5 26.25
210
+ L 27.5 91.25
211
+ L 43.75 83.125
212
+ L 52 99
213
+ L 68 91
214
+ L 60 75
215
+ L 76.25 66.875
216
+ L 27.5 26.25
217
+ z
218
+
219
+ M 5 42.5
220
+ L 5 55
221
+ L 17.5 55
222
+ L 5 42.5
223
+ z
224
+ `, fill, strokeColor, strokeWidth);
225
+ }
226
+ makeZoomIcon() {
227
+ const icon = document.createElementNS(svgNamespace, 'svg');
228
+ icon.setAttribute('viewBox', '0 0 100 100');
229
+ const addTextNode = (text, x, y) => {
230
+ const textNode = document.createElementNS(svgNamespace, 'text');
231
+ textNode.appendChild(document.createTextNode(text));
232
+ textNode.setAttribute('x', x.toString());
233
+ textNode.setAttribute('y', y.toString());
234
+ textNode.style.textAlign = 'center';
235
+ textNode.style.textAnchor = 'middle';
236
+ textNode.style.fontSize = '55px';
237
+ textNode.style.fill = 'var(--icon-color)';
238
+ textNode.style.fontFamily = 'monospace';
239
+ icon.appendChild(textNode);
240
+ };
241
+ addTextNode('+', 40, 45);
242
+ addTextNode('-', 70, 75);
243
+ return icon;
244
+ }
245
+ makeTextIcon(textStyle) {
246
+ var _a, _b;
247
+ const icon = document.createElementNS(svgNamespace, 'svg');
248
+ icon.setAttribute('viewBox', '0 0 100 100');
249
+ const textNode = document.createElementNS(svgNamespace, 'text');
250
+ textNode.appendChild(document.createTextNode('T'));
251
+ textNode.style.fontFamily = textStyle.fontFamily;
252
+ textNode.style.fontWeight = (_a = textStyle.fontWeight) !== null && _a !== void 0 ? _a : '';
253
+ textNode.style.fontVariant = (_b = textStyle.fontVariant) !== null && _b !== void 0 ? _b : '';
254
+ textNode.style.fill = textStyle.renderingStyle.fill.toHexString();
255
+ textNode.style.textAnchor = 'middle';
256
+ textNode.setAttribute('x', '50');
257
+ textNode.setAttribute('y', '75');
258
+ textNode.style.fontSize = '65px';
259
+ textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
260
+ icon.appendChild(textNode);
261
+ return icon;
262
+ }
263
+ makePenIcon(tipThickness, color) {
264
+ if (color instanceof Color4) {
265
+ color = color.toHexString();
266
+ }
267
+ const icon = document.createElementNS(svgNamespace, 'svg');
268
+ icon.setAttribute('viewBox', '0 0 100 100');
269
+ const halfThickness = tipThickness / 2;
270
+ // Draw a pen-like shape
271
+ const primaryStrokeTipPath = `M14,63 L${50 - halfThickness},95 L${50 + halfThickness},90 L88,60 Z`;
272
+ const backgroundStrokeTipPath = `M14,63 L${50 - halfThickness},85 L${50 + halfThickness},83 L88,60 Z`;
273
+ icon.innerHTML = `
274
+ <defs>
275
+ ${checkerboardPatternDef}
276
+ </defs>
277
+ <g>
278
+ <!-- Pen grip -->
279
+ <path
280
+ d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
281
+ ${iconColorStrokeFill}
282
+ />
283
+ </g>
284
+ <g>
285
+ <!-- Checkerboard background for slightly transparent pens -->
286
+ <path d='${backgroundStrokeTipPath}' fill='${checkerboardPatternRef}'/>
287
+
288
+ <!-- Actual pen tip -->
289
+ <path
290
+ d='${primaryStrokeTipPath}'
291
+ fill='${color}'
292
+ stroke='${color}'
293
+ />
294
+ </g>
295
+ `;
296
+ return icon;
297
+ }
298
+ makeIconFromFactory(pen, factory) {
299
+ const toolThickness = pen.getThickness();
300
+ const nowTime = (new Date()).getTime();
301
+ const startPoint = {
302
+ pos: Vec2.of(10, 10),
303
+ width: toolThickness / 5,
304
+ color: pen.getColor(),
305
+ time: nowTime - 100,
306
+ };
307
+ const endPoint = {
308
+ pos: Vec2.of(90, 90),
309
+ width: toolThickness / 5,
310
+ color: pen.getColor(),
311
+ time: nowTime,
312
+ };
313
+ const viewport = new Viewport(new EventDispatcher());
314
+ const builder = factory(startPoint, viewport);
315
+ builder.addPoint(endPoint);
316
+ const icon = document.createElementNS(svgNamespace, 'svg');
317
+ icon.setAttribute('viewBox', '0 0 100 100');
318
+ viewport.updateScreenSize(Vec2.of(100, 100));
319
+ const renderer = new SVGRenderer(icon, viewport);
320
+ builder.preview(renderer);
321
+ return icon;
322
+ }
323
+ makePipetteIcon(color) {
324
+ const icon = document.createElementNS(svgNamespace, 'svg');
325
+ const pipette = document.createElementNS(svgNamespace, 'path');
326
+ pipette.setAttribute('d', `
327
+ M 47,6
328
+ C 35,5 25,15 35,30
329
+ c -9.2,1.3 -15,0 -15,3
330
+ 0,2 5,5 15,7
331
+ V 81
332
+ L 40,90
333
+ h 6
334
+ L 40,80
335
+ V 40
336
+ h 15
337
+ v 40
338
+ l -6,10
339
+ h 6
340
+ l 5,-9.2
341
+ V 40
342
+ C 70,38 75,35 75,33
343
+ 75,30 69.2,31.2 60,30
344
+ 65,15 65,5 47,6
345
+ Z
346
+ `);
347
+ pipette.style.fill = 'var(--icon-color)';
348
+ if (color) {
349
+ const defs = document.createElementNS(svgNamespace, 'defs');
350
+ defs.innerHTML = checkerboardPatternDef;
351
+ icon.appendChild(defs);
352
+ const fluidBackground = document.createElementNS(svgNamespace, 'path');
353
+ const fluid = document.createElementNS(svgNamespace, 'path');
354
+ const fluidPathData = `
355
+ m 40,50 c 5,5 10,0 15,-5 V 80 L 50,90 H 45 L 40,80 Z
356
+ `;
357
+ fluid.setAttribute('d', fluidPathData);
358
+ fluidBackground.setAttribute('d', fluidPathData);
359
+ fluid.style.fill = color.toHexString();
360
+ fluidBackground.style.fill = checkerboardPatternRef;
361
+ icon.appendChild(fluidBackground);
362
+ icon.appendChild(fluid);
363
+ }
364
+ icon.appendChild(pipette);
365
+ icon.setAttribute('viewBox', '0 0 100 100');
366
+ return icon;
367
+ }
368
+ makeResizeViewportIcon() {
369
+ return this.makeIconFromPath(`
370
+ M 75 5 75 10 90 10 90 25 95 25 95 5 75 5 z
371
+ M 15 15 15 30 20 30 20 20 30 20 30 15 15 15 z
372
+ M 84 15 82 17 81 16 81 20 85 20 84 19 86 17 84 15 z
373
+ M 26 24 24 26 26 28 25 29 29 29 29 25 28 26 26 24 z
374
+ M 25 71 26 72 24 74 26 76 28 74 29 75 29 71 25 71 z
375
+ M 15 75 15 85 25 85 25 80 20 80 20 75 15 75 z
376
+ M 90 75 90 90 75 90 75 95 95 95 95 75 90 75 z
377
+ M 81 81 81 85 82 84 84 86 86 84 84 82 85 81 81 81 z
378
+ `);
379
+ }
380
+ makeDuplicateSelectionIcon() {
381
+ return this.makeIconFromPath(`
382
+ M 45,10 45,55 90,55 90,10 45,10 z
383
+ M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
384
+ `);
385
+ }
386
+ makeDeleteSelectionIcon() {
387
+ const strokeWidth = '5px';
388
+ const strokeColor = 'var(--icon-color)';
389
+ const fillColor = 'none';
390
+ return this.makeIconFromPath(`
391
+ M 10,10 90,90
392
+ M 10,90 90,10
393
+ `, fillColor, strokeColor, strokeWidth);
394
+ }
395
+ makeSaveIcon() {
396
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
397
+ svg.innerHTML = `
398
+ <style>
399
+ .toolbar-save-icon {
400
+ stroke: var(--icon-color);
401
+ stroke-width: 10;
402
+ stroke-linejoin: round;
403
+ stroke-linecap: round;
404
+ fill: none;
405
+ }
406
+ </style>
407
+ <path
408
+ d='
409
+ M 15,55 30,70 85,20
410
+ '
411
+ class='toolbar-save-icon'
412
+ />
413
+ `;
414
+ svg.setAttribute('viewBox', '0 0 100 100');
415
+ return svg;
416
+ }
417
+ }
@@ -1,3 +1,3 @@
1
1
  export * from './widgets/lib';
2
- export * as icons from './icons';
3
2
  export * from './makeColorInput';
3
+ export { default as IconProvider } from './IconProvider';
@@ -1,4 +1,3 @@
1
1
  export * from './widgets/lib';
2
- import * as icons_1 from './icons';
3
- export { icons_1 as icons };
4
2
  export * from './makeColorInput';
3
+ export { default as IconProvider } from './IconProvider';
@@ -1,6 +1,5 @@
1
1
  export interface ToolbarLocalization {
2
2
  fontLabel: string;
3
- anyDevicePanning: string;
4
3
  touchPanning: string;
5
4
  outlinedRectanglePen: string;
6
5
  filledRectanglePen: string;
@@ -18,7 +18,6 @@ export const defaultToolbarLocalization = {
18
18
  clickToPickColorAnnouncement: 'Click on the screen to pick a color',
19
19
  selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
20
20
  touchPanning: 'Touchscreen panning',
21
- anyDevicePanning: 'Any device panning',
22
21
  freehandPen: 'Freehand',
23
22
  arrowPen: 'Arrow',
24
23
  linePen: 'Line',
@@ -1,7 +1,6 @@
1
1
  import Color4 from '../Color4';
2
2
  import PipetteTool from '../tools/PipetteTool';
3
3
  import { EditorEventType } from '../types';
4
- import { makePipetteIcon } from './icons';
5
4
  // Returns [ color input, input container ].
6
5
  export const makeColorInput = (editor, onColorChange) => {
7
6
  const colorInputContainer = document.createElement('span');
@@ -56,7 +55,7 @@ const addPipetteTool = (editor, container, onColorChange) => {
56
55
  pipetteButton.title = editor.localization.pickColorFromScreen;
57
56
  pipetteButton.setAttribute('alt', pipetteButton.title);
58
57
  const updatePipetteIcon = (color) => {
59
- pipetteButton.replaceChildren(makePipetteIcon(color));
58
+ pipetteButton.replaceChildren(editor.icons.makePipetteIcon(color));
60
59
  };
61
60
  updatePipetteIcon();
62
61
  const pipetteTool = editor.toolController.getMatchingTools(PipetteTool)[0];
@@ -13,7 +13,6 @@ var _BaseWidget_hasDropdown;
13
13
  import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler';
14
14
  import { EditorEventType, InputEvtType } from '../../types';
15
15
  import { toolbarCSSPrefix } from '../HTMLToolbar';
16
- import { makeDropdownIcon } from '../icons';
17
16
  export default class BaseWidget {
18
17
  constructor(editor, localizationTable) {
19
18
  this.editor = editor;
@@ -206,7 +205,7 @@ export default class BaseWidget {
206
205
  return this.container.classList.contains('selected');
207
206
  }
208
207
  createDropdownIcon() {
209
- const icon = makeDropdownIcon();
208
+ const icon = this.editor.icons.makeDropdownIcon();
210
209
  icon.classList.add(`${toolbarCSSPrefix}showHideDropdownIcon`);
211
210
  return icon;
212
211
  }
@@ -1,11 +1,10 @@
1
- import { makeEraserIcon } from '../icons';
2
1
  import BaseToolWidget from './BaseToolWidget';
3
2
  export default class EraserToolWidget extends BaseToolWidget {
4
3
  getTitle() {
5
4
  return this.localizationTable.eraser;
6
5
  }
7
6
  createIcon() {
8
- return makeEraserIcon();
7
+ return this.editor.icons.makeEraserIcon();
9
8
  }
10
9
  fillDropdown(_dropdown) {
11
10
  // No dropdown associated with the eraser
@@ -3,11 +3,13 @@ import PanZoom from '../../tools/PanZoom';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import BaseToolWidget from './BaseToolWidget';
5
5
  export default class HandToolWidget extends BaseToolWidget {
6
- protected tool: PanZoom;
6
+ protected overridePanZoomTool: PanZoom;
7
7
  private touchPanningWidget;
8
- constructor(editor: Editor, tool: PanZoom, localizationTable: ToolbarLocalization);
8
+ private allowTogglingBaseTool;
9
+ constructor(editor: Editor, overridePanZoomTool: PanZoom, localizationTable: ToolbarLocalization);
10
+ private static getPrimaryHandTool;
9
11
  protected getTitle(): string;
10
12
  protected createIcon(): Element;
11
- setSelected(_selected: boolean): void;
12
13
  protected handleClick(): void;
14
+ setSelected(selected: boolean): void;
13
15
  }
@@ -1,9 +1,8 @@
1
1
  import Mat33 from '../../math/Mat33';
2
- import { PanZoomMode } from '../../tools/PanZoom';
2
+ import PanZoom, { PanZoomMode } from '../../tools/PanZoom';
3
3
  import { EditorEventType } from '../../types';
4
4
  import Viewport from '../../Viewport';
5
5
  import { toolbarCSSPrefix } from '../HTMLToolbar';
6
- import { makeAllDevicePanningIcon, makeHandToolIcon, makeTouchPanningIcon, makeZoomIcon } from '../icons';
7
6
  import BaseToolWidget from './BaseToolWidget';
8
7
  import BaseWidget from './BaseWidget';
9
8
  const makeZoomControl = (localizationTable, editor) => {
@@ -66,7 +65,7 @@ class ZoomWidget extends BaseWidget {
66
65
  return this.localizationTable.zoom;
67
66
  }
68
67
  createIcon() {
69
- return makeZoomIcon();
68
+ return this.editor.icons.makeZoomIcon();
70
69
  }
71
70
  handleClick() {
72
71
  this.setDropdownVisible(!this.isDropdownVisible());
@@ -117,24 +116,48 @@ class HandModeWidget extends BaseWidget {
117
116
  }
118
117
  }
119
118
  export default class HandToolWidget extends BaseToolWidget {
120
- constructor(editor, tool, localizationTable) {
119
+ constructor(editor,
120
+ // Pan zoom tool that overrides all other tools (enabling this tool for a device
121
+ // causes that device to pan/zoom instead of interact with the primary tools)
122
+ overridePanZoomTool, localizationTable) {
123
+ const primaryHandTool = HandToolWidget.getPrimaryHandTool(editor.toolController);
124
+ const tool = primaryHandTool !== null && primaryHandTool !== void 0 ? primaryHandTool : overridePanZoomTool;
121
125
  super(editor, tool, localizationTable);
122
- this.tool = tool;
123
- this.container.classList.add('dropdownShowable');
124
- this.touchPanningWidget = new HandModeWidget(editor, localizationTable, tool, PanZoomMode.OneFingerTouchGestures, makeTouchPanningIcon, localizationTable.touchPanning);
126
+ this.overridePanZoomTool = overridePanZoomTool;
127
+ // Only allow toggling a hand tool if we're using the primary hand tool and not the override
128
+ // hand tool for this button.
129
+ this.allowTogglingBaseTool = primaryHandTool !== null;
130
+ // Allow showing/hiding the dropdown, even if `overridePanZoomTool` isn't enabled.
131
+ if (!this.allowTogglingBaseTool) {
132
+ this.container.classList.add('dropdownShowable');
133
+ }
134
+ // Controls for the overriding hand tool.
135
+ this.touchPanningWidget = new HandModeWidget(editor, localizationTable, overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning);
125
136
  this.addSubWidget(this.touchPanningWidget);
126
- this.addSubWidget(new HandModeWidget(editor, localizationTable, tool, PanZoomMode.SinglePointerGestures, makeAllDevicePanningIcon, localizationTable.anyDevicePanning));
127
137
  this.addSubWidget(new ZoomWidget(editor, localizationTable));
128
138
  }
139
+ static getPrimaryHandTool(toolController) {
140
+ const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom);
141
+ const primaryPanZoomTool = primaryPanZoomToolList[0];
142
+ return primaryPanZoomTool;
143
+ }
129
144
  getTitle() {
130
145
  return this.localizationTable.handTool;
131
146
  }
132
147
  createIcon() {
133
- return makeHandToolIcon();
134
- }
135
- setSelected(_selected) {
148
+ return this.editor.icons.makeHandToolIcon();
136
149
  }
137
150
  handleClick() {
138
- this.setDropdownVisible(!this.isDropdownVisible());
151
+ if (this.allowTogglingBaseTool) {
152
+ super.handleClick();
153
+ }
154
+ else {
155
+ this.setDropdownVisible(!this.isDropdownVisible());
156
+ }
157
+ }
158
+ setSelected(selected) {
159
+ if (this.allowTogglingBaseTool) {
160
+ super.setSelected(selected);
161
+ }
139
162
  }
140
163
  }
@@ -4,7 +4,6 @@ import { makeLineBuilder } from '../../components/builders/LineBuilder';
4
4
  import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../../components/builders/RectangleBuilder';
5
5
  import { EditorEventType } from '../../types';
6
6
  import { toolbarCSSPrefix } from '../HTMLToolbar';
7
- import { makeIconFromFactory, makePenIcon } from '../icons';
8
7
  import makeColorInput from '../makeColorInput';
9
8
  import BaseToolWidget from './BaseToolWidget';
10
9
  export default class PenToolWidget extends BaseToolWidget {
@@ -55,11 +54,11 @@ export default class PenToolWidget extends BaseToolWidget {
55
54
  // Use a square-root scale to prevent the pen's tip from overflowing.
56
55
  const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 4);
57
56
  const color = this.tool.getColor();
58
- return makePenIcon(scale, color.toHexString());
57
+ return this.editor.icons.makePenIcon(scale, color.toHexString());
59
58
  }
60
59
  else {
61
60
  const strokeFactory = this.tool.getStrokeFactory();
62
- return makeIconFromFactory(this.tool, strokeFactory);
61
+ return this.editor.icons.makeIconFromFactory(this.tool, strokeFactory);
63
62
  }
64
63
  }
65
64
  fillDropdown(dropdown) {
@@ -78,12 +77,15 @@ export default class PenToolWidget extends BaseToolWidget {
78
77
  thicknessLabel.setAttribute('for', thicknessInput.id);
79
78
  objectSelectLabel.innerText = this.localizationTable.selectObjectType;
80
79
  objectSelectLabel.setAttribute('for', objectTypeSelect.id);
80
+ // Use a logarithmic scale for thicknessInput (finer control over thinner strokewidths.)
81
+ const inverseThicknessInputFn = (t) => Math.log10(t);
82
+ const thicknessInputFn = (t) => Math.pow(10, t);
81
83
  thicknessInput.type = 'range';
82
- thicknessInput.min = '2';
83
- thicknessInput.max = '20';
84
- thicknessInput.step = '1';
84
+ thicknessInput.min = `${inverseThicknessInputFn(2)}`;
85
+ thicknessInput.max = `${inverseThicknessInputFn(400)}`;
86
+ thicknessInput.step = '0.1';
85
87
  thicknessInput.oninput = () => {
86
- this.tool.setThickness(Math.pow(parseFloat(thicknessInput.value), 2));
88
+ this.tool.setThickness(thicknessInputFn(parseFloat(thicknessInput.value)));
87
89
  };
88
90
  thicknessRow.appendChild(thicknessLabel);
89
91
  thicknessRow.appendChild(thicknessInput);
@@ -109,7 +111,7 @@ export default class PenToolWidget extends BaseToolWidget {
109
111
  colorRow.appendChild(colorInputContainer);
110
112
  this.updateInputs = () => {
111
113
  colorInput.value = this.tool.getColor().toHexString();
112
- thicknessInput.value = Math.sqrt(this.tool.getThickness()).toString();
114
+ thicknessInput.value = inverseThicknessInputFn(this.tool.getThickness()).toString();
113
115
  objectTypeSelect.replaceChildren();
114
116
  for (let i = 0; i < this.penTypes.length; i++) {
115
117
  const penType = this.penTypes[i];
@@ -1,10 +1,13 @@
1
1
  import Editor from '../../Editor';
2
2
  import SelectionTool from '../../tools/SelectionTool/SelectionTool';
3
+ import { KeyPressEvent } from '../../types';
3
4
  import { ToolbarLocalization } from '../localization';
4
5
  import BaseToolWidget from './BaseToolWidget';
5
6
  export default class SelectionToolWidget extends BaseToolWidget {
6
7
  private tool;
7
8
  constructor(editor: Editor, tool: SelectionTool, localization: ToolbarLocalization);
9
+ private resizeImageToSelection;
10
+ protected onKeyPress(event: KeyPressEvent): boolean;
8
11
  protected getTitle(): string;
9
12
  protected createIcon(): Element;
10
13
  }