js-draw 0.11.3 → 0.12.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/CHANGELOG.md +6 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +1 -0
- package/dist/src/Color4.js +1 -0
- package/dist/src/Editor.js +4 -5
- package/dist/src/SVGLoader.js +43 -35
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +15 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +51 -0
- package/dist/src/toolbar/HTMLToolbar.js +63 -5
- package/dist/src/toolbar/IconProvider.d.ts +1 -1
- package/dist/src/toolbar/IconProvider.js +33 -6
- package/dist/src/toolbar/widgets/EraserToolWidget.d.ts +8 -1
- package/dist/src/toolbar/widgets/EraserToolWidget.js +45 -4
- package/dist/src/toolbar/widgets/PenToolWidget.js +2 -2
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +12 -3
- package/dist/src/tools/Eraser.d.ts +10 -1
- package/dist/src/tools/Eraser.js +65 -13
- package/dist/src/tools/SelectionTool/Selection.d.ts +4 -1
- package/dist/src/tools/SelectionTool/Selection.js +64 -27
- package/dist/src/tools/SelectionTool/SelectionTool.js +3 -1
- package/dist/src/tools/TextTool.js +10 -6
- package/dist/src/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/Color4.ts +1 -0
- package/src/Editor.ts +3 -4
- package/src/SVGLoader.ts +14 -14
- package/src/components/AbstractComponent.ts +19 -0
- package/src/toolbar/HTMLToolbar.ts +81 -5
- package/src/toolbar/IconProvider.ts +34 -6
- package/src/toolbar/widgets/EraserToolWidget.ts +64 -5
- package/src/toolbar/widgets/PenToolWidget.ts +2 -2
- package/src/toolbar/widgets/SelectionToolWidget.ts +2 -2
- package/src/tools/Eraser.test.ts +79 -0
- package/src/tools/Eraser.ts +81 -17
- package/src/tools/SelectionTool/Selection.ts +73 -23
- package/src/tools/SelectionTool/SelectionTool.test.ts +138 -21
- package/src/tools/SelectionTool/SelectionTool.ts +3 -1
- package/src/tools/TextTool.ts +14 -8
- package/src/types.ts +2 -2
@@ -5,8 +5,11 @@ import EditorImage from '../../EditorImage';
|
|
5
5
|
import Path from '../../math/Path';
|
6
6
|
import { Vec2 } from '../../math/Vec2';
|
7
7
|
import { InputEvtType } from '../../types';
|
8
|
+
import Selection from './Selection';
|
8
9
|
import SelectionTool from './SelectionTool';
|
9
10
|
import createEditor from '../../testing/createEditor';
|
11
|
+
import Pointer from '../../Pointer';
|
12
|
+
import { Rect2 } from '../../lib';
|
10
13
|
|
11
14
|
const getSelectionTool = (editor: Editor): SelectionTool => {
|
12
15
|
return editor.toolController.getMatchingTools(SelectionTool)[0];
|
@@ -22,6 +25,33 @@ const createSquareStroke = (size: number = 1) => {
|
|
22
25
|
return { testStroke, addTestStrokeCommand };
|
23
26
|
};
|
24
27
|
|
28
|
+
const createEditorWithSingleObjectSelection = (objectSize: number = 50) => {
|
29
|
+
const { testStroke, addTestStrokeCommand } = createSquareStroke(objectSize);
|
30
|
+
const editor = createEditor();
|
31
|
+
editor.dispatch(addTestStrokeCommand);
|
32
|
+
|
33
|
+
// Select the object
|
34
|
+
const selectionTool = getSelectionTool(editor);
|
35
|
+
selectionTool.setEnabled(true);
|
36
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
37
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(10, 10));
|
38
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(5, 5));
|
39
|
+
|
40
|
+
return { editor, testStroke, selectionTool };
|
41
|
+
};
|
42
|
+
|
43
|
+
const dragSelection = (editor: Editor, selection: Selection, startPt: Vec2, endPt: Vec2) => {
|
44
|
+
const backgroundElem = selection.getBackgroundElem();
|
45
|
+
|
46
|
+
selection.onDragStart(Pointer.ofCanvasPoint(startPt, true, editor.viewport), backgroundElem);
|
47
|
+
jest.advanceTimersByTime(100);
|
48
|
+
|
49
|
+
selection.onDragUpdate(Pointer.ofCanvasPoint(endPt, true, editor.viewport));
|
50
|
+
jest.advanceTimersByTime(100);
|
51
|
+
|
52
|
+
selection.onDragEnd();
|
53
|
+
};
|
54
|
+
|
25
55
|
describe('SelectionTool', () => {
|
26
56
|
it('selection should shrink/grow to bounding box of selected objects', () => {
|
27
57
|
const { addTestStrokeCommand } = createSquareStroke();
|
@@ -47,17 +77,7 @@ describe('SelectionTool', () => {
|
|
47
77
|
});
|
48
78
|
|
49
79
|
it('sending keyboard events to the selected region should move selected items', () => {
|
50
|
-
const {
|
51
|
-
const editor = createEditor();
|
52
|
-
editor.dispatch(addTestStrokeCommand);
|
53
|
-
|
54
|
-
// Select the object
|
55
|
-
const selectionTool = getSelectionTool(editor);
|
56
|
-
selectionTool.setEnabled(true);
|
57
|
-
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
58
|
-
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(10, 10));
|
59
|
-
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(5, 5));
|
60
|
-
|
80
|
+
const { editor, selectionTool, testStroke } = createEditorWithSingleObjectSelection(50);
|
61
81
|
const selection = selectionTool.getSelection();
|
62
82
|
expect(selection).not.toBeNull();
|
63
83
|
|
@@ -78,16 +98,7 @@ describe('SelectionTool', () => {
|
|
78
98
|
});
|
79
99
|
|
80
100
|
it('moving the selection with a keyboard should move the view to keep the selection in view', () => {
|
81
|
-
const {
|
82
|
-
const editor = createEditor();
|
83
|
-
editor.dispatch(addTestStrokeCommand);
|
84
|
-
|
85
|
-
// Select the stroke
|
86
|
-
const selectionTool = getSelectionTool(editor);
|
87
|
-
selectionTool.setEnabled(true);
|
88
|
-
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
89
|
-
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(10, 10));
|
90
|
-
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(100, 100));
|
101
|
+
const { editor, selectionTool } = createEditorWithSingleObjectSelection(50);
|
91
102
|
|
92
103
|
const selection = selectionTool.getSelection();
|
93
104
|
if (selection === null) {
|
@@ -140,4 +151,110 @@ describe('SelectionTool', () => {
|
|
140
151
|
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(201, 201));
|
141
152
|
expect(selectionTool.getSelectedObjects()).toHaveLength(0);
|
142
153
|
});
|
154
|
+
|
155
|
+
it('should remove the selection from the document while dragging', () => {
|
156
|
+
const { editor, selectionTool } = createEditorWithSingleObjectSelection(50);
|
157
|
+
|
158
|
+
const selection = selectionTool.getSelection()!;
|
159
|
+
const backgroundElem = selection.getBackgroundElem();
|
160
|
+
selection.onDragStart(Pointer.ofCanvasPoint(Vec2.of(0, 0), true, editor.viewport), backgroundElem);
|
161
|
+
jest.advanceTimersByTime(100);
|
162
|
+
selection.onDragUpdate(Pointer.ofCanvasPoint(Vec2.of(20, 0), true, editor.viewport));
|
163
|
+
jest.advanceTimersByTime(100);
|
164
|
+
|
165
|
+
// Expect the selection to not be in the image while dragging
|
166
|
+
expect(editor.image.getAllElements()).toHaveLength(0);
|
167
|
+
|
168
|
+
selection.onDragEnd();
|
169
|
+
|
170
|
+
expect(editor.image.getAllElements()).toHaveLength(1);
|
171
|
+
});
|
172
|
+
|
173
|
+
it('should drag objects horizontally', () => {
|
174
|
+
const { editor, selectionTool, testStroke } = createEditorWithSingleObjectSelection(50);
|
175
|
+
|
176
|
+
expect(editor.image.findParent(testStroke)).not.toBeNull();
|
177
|
+
expect(testStroke.getBBox().topLeft).objEq(Vec2.of(0, 0));
|
178
|
+
|
179
|
+
const selection = selectionTool.getSelection()!;
|
180
|
+
dragSelection(editor, selection, Vec2.of(0, 0), Vec2.of(10, 0));
|
181
|
+
|
182
|
+
expect(editor.image.findParent(testStroke)).not.toBeNull();
|
183
|
+
expect(testStroke.getBBox().topLeft).objEq(Vec2.of(10, 0));
|
184
|
+
});
|
185
|
+
|
186
|
+
it('should round changes in objects positions when dragging', () => {
|
187
|
+
const { editor, selectionTool, testStroke } = createEditorWithSingleObjectSelection(50);
|
188
|
+
|
189
|
+
expect(editor.image.findParent(testStroke)).not.toBeNull();
|
190
|
+
expect(testStroke.getBBox().topLeft).objEq(Vec2.of(0, 0));
|
191
|
+
|
192
|
+
const selection = selectionTool.getSelection()!;
|
193
|
+
dragSelection(editor, selection, Vec2.of(0, 0), Vec2.of(9.999, 0));
|
194
|
+
|
195
|
+
expect(editor.image.findParent(testStroke)).not.toBeNull();
|
196
|
+
expect(testStroke.getBBox().topLeft).objEq(Vec2.of(10, 0));
|
197
|
+
});
|
198
|
+
|
199
|
+
it('dragCancel should return a selection to its original position', () => {
|
200
|
+
const { editor, selectionTool, testStroke } = createEditorWithSingleObjectSelection(150);
|
201
|
+
|
202
|
+
const selection = selectionTool.getSelection()!;
|
203
|
+
const dragBackground = selection.getBackgroundElem();
|
204
|
+
|
205
|
+
expect(testStroke.getBBox().topLeft).objEq(Vec2.zero);
|
206
|
+
|
207
|
+
selection.onDragStart(Pointer.ofCanvasPoint(Vec2.of(10, 0), true, editor.viewport), dragBackground);
|
208
|
+
jest.advanceTimersByTime(100);
|
209
|
+
selection.onDragUpdate(Pointer.ofCanvasPoint(Vec2.of(200, 10), true, editor.viewport));
|
210
|
+
jest.advanceTimersByTime(100);
|
211
|
+
selection.onDragCancel();
|
212
|
+
|
213
|
+
expect(testStroke.getBBox().topLeft).objEq(Vec2.zero);
|
214
|
+
expect(editor.image.findParent(testStroke)).not.toBeNull();
|
215
|
+
});
|
216
|
+
|
217
|
+
it('duplicateSelectedObjects should duplicate a selection while dragging', async () => {
|
218
|
+
const { editor, selectionTool, testStroke } = createEditorWithSingleObjectSelection(150);
|
219
|
+
|
220
|
+
const selection = selectionTool.getSelection()!;
|
221
|
+
const dragBackground = selection.getBackgroundElem();
|
222
|
+
|
223
|
+
selection.onDragStart(Pointer.ofCanvasPoint(Vec2.of(0, 0), true, editor.viewport), dragBackground);
|
224
|
+
jest.advanceTimersByTime(100);
|
225
|
+
selection.onDragUpdate(Pointer.ofCanvasPoint(Vec2.of(20, 0), true, editor.viewport));
|
226
|
+
|
227
|
+
// The selection should not be in the document while dragging
|
228
|
+
expect(editor.image.findParent(testStroke)).toBeNull();
|
229
|
+
|
230
|
+
await editor.dispatch(await selection.duplicateSelectedObjects());
|
231
|
+
jest.advanceTimersByTime(100);
|
232
|
+
|
233
|
+
// The duplicate stroke should be added to the document, but the original should not.
|
234
|
+
expect(editor.image.findParent(testStroke)).toBeNull();
|
235
|
+
|
236
|
+
const allObjectsInImage = editor.image.getAllElements();
|
237
|
+
expect(allObjectsInImage).toHaveLength(1);
|
238
|
+
|
239
|
+
const duplicateObject = allObjectsInImage[0];
|
240
|
+
|
241
|
+
// The duplicate stroke should be translated
|
242
|
+
expect(duplicateObject.getBBox()).objEq(new Rect2(20, 0, 150, 150));
|
243
|
+
|
244
|
+
// The duplicate stroke should be selected.
|
245
|
+
expect(selection.getSelectedObjects()).toHaveLength(1);
|
246
|
+
|
247
|
+
// The test stroke should not be added to the document
|
248
|
+
// (esp if we continue dragging)
|
249
|
+
selection.onDragUpdate(Pointer.ofCanvasPoint(Vec2.of(30, 10), true, editor.viewport));
|
250
|
+
jest.advanceTimersByTime(100);
|
251
|
+
|
252
|
+
expect(editor.image.findParent(testStroke)).toBeNull();
|
253
|
+
|
254
|
+
// The test stroke should be translated when we finish dragging.
|
255
|
+
selection.onDragEnd();
|
256
|
+
|
257
|
+
expect(editor.image.findParent(testStroke)).not.toBeNull();
|
258
|
+
expect(testStroke.getBBox()).objEq(new Rect2(30, 10, 150, 150));
|
259
|
+
});
|
143
260
|
});
|
@@ -296,7 +296,9 @@ export default class SelectionTool extends BaseTool {
|
|
296
296
|
}
|
297
297
|
else if (evt.ctrlKey) {
|
298
298
|
if (this.selectionBox && evt.key === 'd') {
|
299
|
-
this.
|
299
|
+
this.selectionBox.duplicateSelectedObjects().then(command => {
|
300
|
+
this.editor.dispatch(command);
|
301
|
+
});
|
300
302
|
return true;
|
301
303
|
}
|
302
304
|
else if (evt.key === 'a') {
|
package/src/tools/TextTool.ts
CHANGED
@@ -80,11 +80,14 @@ export default class TextTool extends BaseTool {
|
|
80
80
|
if (this.textInputElem && this.textTargetPosition) {
|
81
81
|
const content = this.textInputElem.value.trimEnd();
|
82
82
|
|
83
|
+
this.textInputElem.value = '';
|
84
|
+
|
83
85
|
if (removeInput) {
|
84
|
-
|
86
|
+
// In some browsers, .remove() triggers a .blur event (synchronously).
|
87
|
+
// Clear this.textInputElem before removal
|
88
|
+
const input = this.textInputElem;
|
85
89
|
this.textInputElem = null;
|
86
|
-
|
87
|
-
this.textInputElem.value = '';
|
90
|
+
input.remove();
|
88
91
|
}
|
89
92
|
|
90
93
|
if (content === '') {
|
@@ -100,14 +103,14 @@ export default class TextTool extends BaseTool {
|
|
100
103
|
).rightMul(
|
101
104
|
Mat33.zRotation(this.textRotation)
|
102
105
|
);
|
103
|
-
|
106
|
+
|
104
107
|
const textComponent = TextComponent.fromLines(content.split('\n'), textTransform, this.textStyle);
|
105
108
|
|
106
109
|
const action = EditorImage.addElement(textComponent);
|
107
110
|
if (this.removeExistingCommand) {
|
108
111
|
// Unapply so that `removeExistingCommand` can be added to the undo stack.
|
109
112
|
this.removeExistingCommand.unapply(this.editor);
|
110
|
-
|
113
|
+
|
111
114
|
this.editor.dispatch(uniteCommands([ this.removeExistingCommand, action ]));
|
112
115
|
this.removeExistingCommand = null;
|
113
116
|
} else {
|
@@ -177,10 +180,13 @@ export default class TextTool extends BaseTool {
|
|
177
180
|
// Delay removing the input -- flushInput may be called within a blur()
|
178
181
|
// event handler
|
179
182
|
const removeInput = false;
|
180
|
-
this.flushInput(removeInput);
|
181
|
-
|
182
183
|
const input = this.textInputElem;
|
183
|
-
|
184
|
+
|
185
|
+
this.flushInput(removeInput);
|
186
|
+
this.textInputElem = null;
|
187
|
+
setTimeout(() => {
|
188
|
+
input?.remove();
|
189
|
+
}, 0);
|
184
190
|
};
|
185
191
|
this.textInputElem.onkeyup = (evt) => {
|
186
192
|
if (evt.key === 'Enter' && !evt.shiftKey) {
|
package/src/types.ts
CHANGED
@@ -198,9 +198,9 @@ export type EditorEventDataType = EditorToolEvent | EditorObjectEvent
|
|
198
198
|
// Returns null to continue loading without pause.
|
199
199
|
// [totalToProcess] can be an estimate and may change if a better estimate becomes available.
|
200
200
|
export type OnProgressListener =
|
201
|
-
(amountProcessed: number, totalToProcess: number)=> Promise<void>|null;
|
201
|
+
(amountProcessed: number, totalToProcess: number)=> Promise<void>|null|void;
|
202
202
|
|
203
|
-
export type ComponentAddedListener = (component: AbstractComponent)=> void;
|
203
|
+
export type ComponentAddedListener = (component: AbstractComponent)=> Promise<void>|void;
|
204
204
|
|
205
205
|
// Called when a new estimate for the import/export rect has been generated. This can be called multiple times.
|
206
206
|
// Only the last call to this listener must be accurate.
|