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