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,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
|
+
}
|
package/dist/src/toolbar/lib.js
CHANGED
@@ -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
|
6
|
+
protected overridePanZoomTool: PanZoom;
|
7
7
|
private touchPanningWidget;
|
8
|
-
|
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,
|
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.
|
123
|
-
|
124
|
-
|
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
|
-
|
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 =
|
83
|
-
thicknessInput.max =
|
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(
|
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 =
|
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
|
}
|