js-draw 0.8.0 → 0.9.1
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/CHANGELOG.md +13 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +3 -0
- package/dist/src/Editor.d.ts +2 -0
- package/dist/src/Editor.js +31 -6
- package/dist/src/SVGLoader.js +5 -7
- package/dist/src/Viewport.js +2 -2
- package/dist/src/components/Stroke.js +2 -2
- package/dist/src/components/builders/LineBuilder.js +4 -0
- package/dist/src/components/util/StrokeSmoother.js +1 -1
- package/dist/src/math/Path.js +6 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +6 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +3 -0
- package/dist/src/toolbar/HTMLToolbar.js +24 -0
- package/dist/src/toolbar/IconProvider.d.ts +1 -0
- package/dist/src/toolbar/IconProvider.js +43 -1
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/toolbar/makeColorInput.d.ts +2 -1
- package/dist/src/toolbar/makeColorInput.js +13 -2
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +2 -2
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -2
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -3
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +32 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +67 -6
- package/dist/src/toolbar/widgets/EraserToolWidget.d.ts +4 -0
- package/dist/src/toolbar/widgets/EraserToolWidget.js +3 -0
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/HandToolWidget.js +22 -13
- package/dist/src/toolbar/widgets/PenToolWidget.d.ts +7 -1
- package/dist/src/toolbar/widgets/PenToolWidget.js +78 -12
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +7 -7
- package/dist/src/toolbar/widgets/TextToolWidget.d.ts +4 -1
- package/dist/src/toolbar/widgets/TextToolWidget.js +17 -3
- package/dist/src/tools/PanZoom.d.ts +4 -1
- package/dist/src/tools/PanZoom.js +24 -1
- package/dist/src/tools/SelectionTool/Selection.js +1 -1
- package/package.json +1 -1
- package/src/Color4.ts +2 -0
- package/src/Editor.ts +43 -9
- package/src/SVGLoader.ts +8 -8
- package/src/Viewport.ts +2 -2
- package/src/components/Stroke.ts +1 -1
- package/src/components/builders/LineBuilder.ts +4 -0
- package/src/components/util/StrokeSmoother.ts +1 -1
- package/src/math/Path.test.ts +24 -0
- package/src/math/Path.ts +7 -1
- package/src/rendering/renderers/SVGRenderer.ts +5 -1
- package/src/toolbar/HTMLToolbar.ts +33 -0
- package/src/toolbar/IconProvider.ts +49 -1
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/makeColorInput.ts +21 -3
- package/src/toolbar/widgets/ActionButtonWidget.ts +6 -3
- package/src/toolbar/widgets/BaseToolWidget.ts +4 -3
- package/src/toolbar/widgets/BaseWidget.ts +83 -5
- package/src/toolbar/widgets/EraserToolWidget.ts +11 -0
- package/src/toolbar/widgets/HandToolWidget.ts +48 -17
- package/src/toolbar/widgets/PenToolWidget.ts +105 -13
- package/src/toolbar/widgets/SelectionToolWidget.ts +8 -5
- package/src/toolbar/widgets/TextToolWidget.ts +29 -4
- package/src/tools/PanZoom.ts +28 -1
- package/src/tools/SelectionTool/Selection.ts +1 -1
- package/.firebase/hosting.ZG9jcw.cache +0 -338
@@ -11,12 +11,17 @@ import { toolbarCSSPrefix } from '../HTMLToolbar';
|
|
11
11
|
import { ToolbarLocalization } from '../localization';
|
12
12
|
import makeColorInput from '../makeColorInput';
|
13
13
|
import BaseToolWidget from './BaseToolWidget';
|
14
|
+
import Color4 from '../../Color4';
|
15
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
14
16
|
|
15
17
|
|
16
18
|
export interface PenTypeRecord {
|
17
19
|
// Description of the factory (e.g. 'Freehand line')
|
18
20
|
name: string;
|
19
21
|
|
22
|
+
// A unique ID for the facotory (e.g. 'chisel-tip-pen')
|
23
|
+
id: string;
|
24
|
+
|
20
25
|
// Creates an `AbstractComponent` from pen input.
|
21
26
|
factory: ComponentBuilderFactory;
|
22
27
|
}
|
@@ -26,34 +31,46 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
26
31
|
protected penTypes: PenTypeRecord[];
|
27
32
|
|
28
33
|
public constructor(
|
29
|
-
editor: Editor, private tool: Pen, localization
|
34
|
+
editor: Editor, private tool: Pen, localization?: ToolbarLocalization
|
30
35
|
) {
|
31
|
-
super(editor, tool, localization);
|
36
|
+
super(editor, tool, 'pen', localization);
|
32
37
|
|
33
38
|
// Default pen types
|
34
39
|
this.penTypes = [
|
35
40
|
{
|
36
|
-
name:
|
41
|
+
name: this.localizationTable.pressureSensitiveFreehandPen,
|
42
|
+
id: 'pressure-sensitive-pen',
|
43
|
+
|
37
44
|
factory: makePressureSensitiveFreehandLineBuilder,
|
38
45
|
},
|
39
46
|
{
|
40
|
-
name:
|
47
|
+
name: this.localizationTable.freehandPen,
|
48
|
+
id: 'freehand-pen',
|
49
|
+
|
41
50
|
factory: makeFreehandLineBuilder,
|
42
51
|
},
|
43
52
|
{
|
44
|
-
name:
|
53
|
+
name: this.localizationTable.arrowPen,
|
54
|
+
id: 'arrow',
|
55
|
+
|
45
56
|
factory: makeArrowBuilder,
|
46
57
|
},
|
47
58
|
{
|
48
|
-
name:
|
59
|
+
name: this.localizationTable.linePen,
|
60
|
+
id: 'line',
|
61
|
+
|
49
62
|
factory: makeLineBuilder,
|
50
63
|
},
|
51
64
|
{
|
52
|
-
name:
|
65
|
+
name: this.localizationTable.filledRectanglePen,
|
66
|
+
id: 'filled-rectangle',
|
67
|
+
|
53
68
|
factory: makeFilledRectangleBuilder,
|
54
69
|
},
|
55
70
|
{
|
56
|
-
name:
|
71
|
+
name: this.localizationTable.outlinedRectanglePen,
|
72
|
+
id: 'outlined-rectangle',
|
73
|
+
|
57
74
|
factory: makeOutlinedRectangleBuilder,
|
58
75
|
},
|
59
76
|
];
|
@@ -75,6 +92,30 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
75
92
|
return this.targetTool.description;
|
76
93
|
}
|
77
94
|
|
95
|
+
// Return the index of this tool's stroke factory in the list of
|
96
|
+
// all stroke factories.
|
97
|
+
//
|
98
|
+
// Returns -1 if the stroke factory is not in the list of all stroke factories.
|
99
|
+
private getCurrentPenTypeIdx(): number {
|
100
|
+
const currentFactory = this.tool.getStrokeFactory();
|
101
|
+
|
102
|
+
for (let i = 0; i < this.penTypes.length; i ++) {
|
103
|
+
if (this.penTypes[i].factory === currentFactory) {
|
104
|
+
return i;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
return -1;
|
108
|
+
}
|
109
|
+
|
110
|
+
private getCurrentPenType(): PenTypeRecord|null {
|
111
|
+
for (const penType of this.penTypes) {
|
112
|
+
if (penType.factory === this.tool.getStrokeFactory()) {
|
113
|
+
return penType;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
return null;
|
117
|
+
}
|
118
|
+
|
78
119
|
protected createIcon(): Element {
|
79
120
|
const strokeFactory = this.tool.getStrokeFactory();
|
80
121
|
if (strokeFactory === makeFreehandLineBuilder || strokeFactory === makePressureSensitiveFreehandLineBuilder) {
|
@@ -138,7 +179,7 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
138
179
|
|
139
180
|
const colorRow = document.createElement('div');
|
140
181
|
const colorLabel = document.createElement('label');
|
141
|
-
const [ colorInput, colorInputContainer ] = makeColorInput(this.editor, color => {
|
182
|
+
const [ colorInput, colorInputContainer, setColorInputValue ] = makeColorInput(this.editor, color => {
|
142
183
|
this.tool.setColor(color);
|
143
184
|
});
|
144
185
|
|
@@ -150,9 +191,10 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
150
191
|
colorRow.appendChild(colorInputContainer);
|
151
192
|
|
152
193
|
this.updateInputs = () => {
|
153
|
-
|
194
|
+
setColorInputValue(this.tool.getColor());
|
154
195
|
thicknessInput.value = inverseThicknessInputFn(this.tool.getThickness()).toString();
|
155
196
|
|
197
|
+
// Update the list of stroke factories
|
156
198
|
objectTypeSelect.replaceChildren();
|
157
199
|
for (let i = 0; i < this.penTypes.length; i ++) {
|
158
200
|
const penType = this.penTypes[i];
|
@@ -161,10 +203,14 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
161
203
|
option.innerText = penType.name;
|
162
204
|
|
163
205
|
objectTypeSelect.appendChild(option);
|
206
|
+
}
|
164
207
|
|
165
|
-
|
166
|
-
|
167
|
-
|
208
|
+
// Update the selected stroke factory.
|
209
|
+
const strokeFactoryIdx = this.getCurrentPenTypeIdx();
|
210
|
+
if (strokeFactoryIdx === -1) {
|
211
|
+
objectTypeSelect.value = '';
|
212
|
+
} else {
|
213
|
+
objectTypeSelect.value = strokeFactoryIdx.toString();
|
168
214
|
}
|
169
215
|
};
|
170
216
|
this.updateInputs();
|
@@ -190,4 +236,50 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
190
236
|
|
191
237
|
return false;
|
192
238
|
}
|
239
|
+
|
240
|
+
public serializeState(): SavedToolbuttonState {
|
241
|
+
return {
|
242
|
+
...super.serializeState(),
|
243
|
+
|
244
|
+
color: this.tool.getColor().toHexString(),
|
245
|
+
thickness: this.tool.getThickness(),
|
246
|
+
strokeFactoryId: this.getCurrentPenType()?.id,
|
247
|
+
};
|
248
|
+
}
|
249
|
+
|
250
|
+
public deserializeFrom(state: SavedToolbuttonState) {
|
251
|
+
super.deserializeFrom(state);
|
252
|
+
|
253
|
+
const verifyPropertyType = (propertyName: string, expectedType: 'string'|'number'|'object') => {
|
254
|
+
const actualType = typeof(state[propertyName]);
|
255
|
+
if (actualType !== expectedType) {
|
256
|
+
throw new Error(
|
257
|
+
`Deserializing property ${propertyName}: Invalid type. Expected ${expectedType},` +
|
258
|
+
` was ${actualType}.`
|
259
|
+
);
|
260
|
+
}
|
261
|
+
};
|
262
|
+
|
263
|
+
if (state.color) {
|
264
|
+
verifyPropertyType('color', 'string');
|
265
|
+
this.tool.setColor(Color4.fromHex(state.color));
|
266
|
+
}
|
267
|
+
|
268
|
+
if (state.thickness) {
|
269
|
+
verifyPropertyType('thickness', 'number');
|
270
|
+
this.tool.setThickness(state.thickness);
|
271
|
+
}
|
272
|
+
|
273
|
+
if (state.strokeFactoryId) {
|
274
|
+
verifyPropertyType('strokeFactoryId', 'string');
|
275
|
+
|
276
|
+
const factoryId: string = state.strokeFactoryId;
|
277
|
+
for (const penType of this.penTypes) {
|
278
|
+
if (factoryId === penType.id) {
|
279
|
+
this.tool.setStrokeFactory(penType.factory);
|
280
|
+
break;
|
281
|
+
}
|
282
|
+
}
|
283
|
+
}
|
284
|
+
}
|
193
285
|
}
|
@@ -7,20 +7,21 @@ import BaseToolWidget from './BaseToolWidget';
|
|
7
7
|
|
8
8
|
export default class SelectionToolWidget extends BaseToolWidget {
|
9
9
|
public constructor(
|
10
|
-
editor: Editor, private tool: SelectionTool, localization
|
10
|
+
editor: Editor, private tool: SelectionTool, localization?: ToolbarLocalization
|
11
11
|
) {
|
12
|
-
super(editor, tool, localization);
|
12
|
+
super(editor, tool, 'selection-tool-widget', localization);
|
13
13
|
|
14
14
|
const resizeButton = new ActionButtonWidget(
|
15
|
-
editor,
|
15
|
+
editor, 'resize-btn',
|
16
16
|
() => editor.icons.makeResizeViewportIcon(),
|
17
17
|
this.localizationTable.resizeImageToSelection,
|
18
18
|
() => {
|
19
19
|
this.resizeImageToSelection();
|
20
20
|
},
|
21
|
+
localization,
|
21
22
|
);
|
22
23
|
const deleteButton = new ActionButtonWidget(
|
23
|
-
editor,
|
24
|
+
editor, 'delete-btn',
|
24
25
|
() => editor.icons.makeDeleteSelectionIcon(),
|
25
26
|
this.localizationTable.deleteSelection,
|
26
27
|
() => {
|
@@ -28,15 +29,17 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
28
29
|
this.editor.dispatch(selection!.deleteSelectedObjects());
|
29
30
|
this.tool.clearSelection();
|
30
31
|
},
|
32
|
+
localization,
|
31
33
|
);
|
32
34
|
const duplicateButton = new ActionButtonWidget(
|
33
|
-
editor,
|
35
|
+
editor, 'duplicate-btn',
|
34
36
|
() => editor.icons.makeDuplicateSelectionIcon(),
|
35
37
|
this.localizationTable.duplicateSelection,
|
36
38
|
() => {
|
37
39
|
const selection = this.tool.getSelection();
|
38
40
|
this.editor.dispatch(selection!.duplicateSelectedObjects());
|
39
41
|
},
|
42
|
+
localization,
|
40
43
|
);
|
41
44
|
|
42
45
|
this.addSubWidget(resizeButton);
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import Color4 from '../../Color4';
|
1
2
|
import Editor from '../../Editor';
|
2
3
|
import TextTool from '../../tools/TextTool';
|
3
4
|
import { EditorEventType } from '../../types';
|
@@ -5,11 +6,12 @@ import { toolbarCSSPrefix } from '../HTMLToolbar';
|
|
5
6
|
import { ToolbarLocalization } from '../localization';
|
6
7
|
import makeColorInput from '../makeColorInput';
|
7
8
|
import BaseToolWidget from './BaseToolWidget';
|
9
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
8
10
|
|
9
11
|
export default class TextToolWidget extends BaseToolWidget {
|
10
12
|
private updateDropdownInputs: (()=>void)|null = null;
|
11
|
-
public constructor(editor: Editor, private tool: TextTool, localization
|
12
|
-
super(editor, tool, localization);
|
13
|
+
public constructor(editor: Editor, private tool: TextTool, localization?: ToolbarLocalization) {
|
14
|
+
super(editor, tool, 'text-tool-widget', localization);
|
13
15
|
|
14
16
|
editor.notifier.on(EditorEventType.ToolUpdated, evt => {
|
15
17
|
if (evt.kind === EditorEventType.ToolUpdated && evt.tool === tool) {
|
@@ -36,7 +38,7 @@ export default class TextToolWidget extends BaseToolWidget {
|
|
36
38
|
const fontInput = document.createElement('select');
|
37
39
|
const fontLabel = document.createElement('label');
|
38
40
|
|
39
|
-
const [ colorInput, colorInputContainer ] = makeColorInput(this.editor, color => {
|
41
|
+
const [ colorInput, colorInputContainer, setColorInputValue ] = makeColorInput(this.editor, color => {
|
40
42
|
this.tool.setColor(color);
|
41
43
|
});
|
42
44
|
const colorLabel = document.createElement('label');
|
@@ -74,7 +76,7 @@ export default class TextToolWidget extends BaseToolWidget {
|
|
74
76
|
|
75
77
|
this.updateDropdownInputs = () => {
|
76
78
|
const style = this.tool.getTextStyle();
|
77
|
-
|
79
|
+
setColorInputValue(style.renderingStyle.fill);
|
78
80
|
|
79
81
|
if (!fontsInInput.has(style.fontFamily)) {
|
80
82
|
addFontToInput(style.fontFamily);
|
@@ -86,4 +88,27 @@ export default class TextToolWidget extends BaseToolWidget {
|
|
86
88
|
dropdown.replaceChildren(colorRow, fontRow);
|
87
89
|
return true;
|
88
90
|
}
|
91
|
+
|
92
|
+
public serializeState(): SavedToolbuttonState {
|
93
|
+
const textStyle = this.tool.getTextStyle();
|
94
|
+
|
95
|
+
return {
|
96
|
+
...super.serializeState(),
|
97
|
+
|
98
|
+
fontFamily: textStyle.fontFamily,
|
99
|
+
color: textStyle.renderingStyle.fill.toHexString(),
|
100
|
+
};
|
101
|
+
}
|
102
|
+
|
103
|
+
public deserializeFrom(state: SavedToolbuttonState) {
|
104
|
+
if (state.fontFamily && typeof(state.fontFamily) === 'string') {
|
105
|
+
this.tool.setFontFamily(state.fontFamily);
|
106
|
+
}
|
107
|
+
|
108
|
+
if (state.color && typeof(state.color) === 'string') {
|
109
|
+
this.tool.setColor(Color4.fromHex(state.color));
|
110
|
+
}
|
111
|
+
|
112
|
+
super.deserializeFrom(state);
|
113
|
+
}
|
89
114
|
}
|
package/src/tools/PanZoom.ts
CHANGED
@@ -21,6 +21,8 @@ export enum PanZoomMode {
|
|
21
21
|
RightClickDrags = 0x1 << 2,
|
22
22
|
SinglePointerGestures = 0x1 << 3,
|
23
23
|
Keyboard = 0x1 << 4,
|
24
|
+
|
25
|
+
RotationLocked = 0x1 << 5,
|
24
26
|
}
|
25
27
|
|
26
28
|
export default class PanZoom extends BaseTool {
|
@@ -90,10 +92,15 @@ export default class PanZoom extends BaseTool {
|
|
90
92
|
const { screenCenter, canvasCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
|
91
93
|
|
92
94
|
const delta = this.getCenterDelta(screenCenter);
|
95
|
+
let rotation = angle - this.lastAngle;
|
96
|
+
|
97
|
+
if (this.isRotationLocked()) {
|
98
|
+
rotation = 0;
|
99
|
+
}
|
93
100
|
|
94
101
|
const transformUpdate = Mat33.translation(delta)
|
95
102
|
.rightMul(Mat33.scaling2D(dist / this.lastDist, canvasCenter))
|
96
|
-
.rightMul(Mat33.zRotation(
|
103
|
+
.rightMul(Mat33.zRotation(rotation, canvasCenter));
|
97
104
|
this.lastScreenCenter = screenCenter;
|
98
105
|
this.lastDist = dist;
|
99
106
|
this.lastAngle = angle;
|
@@ -250,6 +257,10 @@ export default class PanZoom extends BaseTool {
|
|
250
257
|
rotation += 0.0001;
|
251
258
|
}
|
252
259
|
|
260
|
+
if (this.isRotationLocked()) {
|
261
|
+
rotation = 0;
|
262
|
+
}
|
263
|
+
|
253
264
|
const toCanvas = this.editor.viewport.screenToCanvasTransform;
|
254
265
|
|
255
266
|
// Transform without translating (treat toCanvas as a linear instead of
|
@@ -270,6 +281,22 @@ export default class PanZoom extends BaseTool {
|
|
270
281
|
return true;
|
271
282
|
}
|
272
283
|
|
284
|
+
private isRotationLocked(): boolean {
|
285
|
+
return !!(this.mode & PanZoomMode.RotationLocked);
|
286
|
+
}
|
287
|
+
|
288
|
+
// Sets whether the given `mode` is enabled. `mode` should be a single
|
289
|
+
// mode from the `PanZoomMode` enum.
|
290
|
+
public setModeEnabled(mode: PanZoomMode, enabled: boolean) {
|
291
|
+
let newMode = this.mode;
|
292
|
+
if (enabled) {
|
293
|
+
newMode |= mode;
|
294
|
+
} else {
|
295
|
+
newMode &= ~mode;
|
296
|
+
}
|
297
|
+
this.setMode(newMode);
|
298
|
+
}
|
299
|
+
|
273
300
|
public setMode(mode: PanZoomMode) {
|
274
301
|
if (mode !== this.mode) {
|
275
302
|
this.mode = mode;
|
@@ -453,7 +453,7 @@ export default class Selection {
|
|
453
453
|
|
454
454
|
public setSelectedObjects(objects: AbstractComponent[], bbox: Rect2) {
|
455
455
|
this.originalRegion = bbox;
|
456
|
-
this.selectedElems = objects;
|
456
|
+
this.selectedElems = objects.filter(object => object.isSelectable());
|
457
457
|
this.updateUI();
|
458
458
|
}
|
459
459
|
|