js-draw 0.4.0 → 0.5.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/.github/pull_request_template.md +15 -0
- package/.github/workflows/firebase-hosting-merge.yml +7 -0
- package/.github/workflows/firebase-hosting-pull-request.yml +10 -0
- package/.github/workflows/github-pages.yml +2 -0
- package/CHANGELOG.md +15 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +1 -1
- package/dist/src/Editor.js +8 -3
- package/dist/src/components/AbstractComponent.js +1 -0
- package/dist/src/components/Stroke.js +15 -9
- package/dist/src/components/Text.d.ts +1 -1
- package/dist/src/components/Text.js +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +33 -35
- package/dist/src/math/Vec3.d.ts +1 -1
- package/dist/src/math/Vec3.js +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +1 -1
- package/dist/src/testing/beforeEachFile.d.ts +1 -0
- package/dist/src/testing/beforeEachFile.js +3 -0
- package/dist/src/testing/createEditor.d.ts +1 -0
- package/dist/src/testing/createEditor.js +7 -1
- package/dist/src/testing/loadExpectExtensions.d.ts +0 -15
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +2 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +15 -0
- package/dist/src/toolbar/widgets/PenToolWidget.d.ts +2 -0
- package/dist/src/toolbar/widgets/PenToolWidget.js +14 -0
- package/dist/src/tools/SelectionTool/SelectionTool.js +8 -0
- package/dist/src/tools/ToolController.js +2 -0
- package/dist/src/tools/ToolbarShortcutHandler.d.ts +12 -0
- package/dist/src/tools/ToolbarShortcutHandler.js +23 -0
- package/dist/src/tools/lib.d.ts +1 -0
- package/dist/src/tools/lib.js +1 -0
- package/dist/src/types.d.ts +4 -2
- package/jest.config.js +5 -0
- package/package.json +15 -14
- package/src/Editor.ts +8 -2
- package/src/components/AbstractComponent.ts +2 -0
- package/src/components/Stroke.test.ts +0 -3
- package/src/components/Stroke.ts +14 -7
- package/src/components/Text.test.ts +0 -3
- package/src/components/Text.ts +2 -2
- package/src/components/builders/FreehandLineBuilder.ts +36 -42
- package/src/language/assertions.ts +2 -2
- package/src/math/LineSegment2.test.ts +8 -10
- package/src/math/Mat33.test.ts +0 -2
- package/src/math/Rect2.test.ts +0 -3
- package/src/math/Vec2.test.ts +0 -3
- package/src/math/Vec3.test.ts +0 -3
- package/src/math/Vec3.ts +1 -1
- package/src/rendering/renderers/SVGRenderer.ts +1 -1
- package/src/testing/beforeEachFile.ts +3 -0
- package/src/testing/createEditor.ts +8 -1
- package/src/testing/global.d.ts +17 -0
- package/src/testing/loadExpectExtensions.ts +0 -15
- package/src/toolbar/toolbar.css +3 -2
- package/src/toolbar/widgets/BaseWidget.ts +19 -1
- package/src/toolbar/widgets/PenToolWidget.ts +18 -1
- package/src/tools/Pen.test.ts +150 -0
- package/src/tools/SelectionTool/SelectionTool.css +1 -1
- package/src/tools/SelectionTool/SelectionTool.ts +9 -0
- package/src/tools/ToolController.ts +2 -0
- package/src/tools/ToolbarShortcutHandler.ts +34 -0
- package/src/tools/UndoRedoShortcut.test.ts +3 -0
- package/src/tools/lib.ts +1 -0
- package/src/types.ts +13 -8
- package/tsconfig.json +3 -1
@@ -0,0 +1,150 @@
|
|
1
|
+
|
2
|
+
import { Rect2 } from '../lib';
|
3
|
+
import { Vec2 } from '../math/Vec2';
|
4
|
+
import createEditor from '../testing/createEditor';
|
5
|
+
import { InputEvtType } from '../types';
|
6
|
+
|
7
|
+
describe('Pen', () => {
|
8
|
+
it('should draw horizontal lines', () => {
|
9
|
+
const editor = createEditor();
|
10
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
11
|
+
for (let i = 0; i < 10; i++) {
|
12
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(i, 0));
|
13
|
+
jest.advanceTimersByTime(200);
|
14
|
+
}
|
15
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(200, 0));
|
16
|
+
|
17
|
+
const elems = editor.image.getElementsIntersectingRegion(new Rect2(0, 10, 10, -10));
|
18
|
+
expect(elems).toHaveLength(1);
|
19
|
+
|
20
|
+
// Account for stroke width
|
21
|
+
const tolerableError = 8;
|
22
|
+
expect(elems[0].getBBox().topLeft).objEq(Vec2.of(0, 0), tolerableError);
|
23
|
+
expect(elems[0].getBBox().bottomRight).objEq(Vec2.of(200, 0), tolerableError);
|
24
|
+
});
|
25
|
+
|
26
|
+
it('should draw vertical line', () => {
|
27
|
+
const editor = createEditor();
|
28
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
29
|
+
for (let i = 0; i < 10; i++) {
|
30
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(0, i * 20));
|
31
|
+
jest.advanceTimersByTime(200);
|
32
|
+
}
|
33
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(0, 150));
|
34
|
+
|
35
|
+
const elems = editor.image.getElementsIntersectingRegion(Rect2.unitSquare);
|
36
|
+
expect(elems).toHaveLength(1);
|
37
|
+
|
38
|
+
expect(elems[0].getBBox().topLeft).objEq(Vec2.of(0, 0), 8); // ± 8
|
39
|
+
expect(elems[0].getBBox().bottomRight).objEq(Vec2.of(0, 175), 25); // ± 25
|
40
|
+
});
|
41
|
+
|
42
|
+
it('should draw vertical line with slight bend', () => {
|
43
|
+
const editor = createEditor();
|
44
|
+
|
45
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(417, 24));
|
46
|
+
jest.advanceTimersByTime(245);
|
47
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 197));
|
48
|
+
jest.advanceTimersByTime(20);
|
49
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 199));
|
50
|
+
jest.advanceTimersByTime(12);
|
51
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 201));
|
52
|
+
jest.advanceTimersByTime(40);
|
53
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 203));
|
54
|
+
jest.advanceTimersByTime(14);
|
55
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 206));
|
56
|
+
jest.advanceTimersByTime(35);
|
57
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 208));
|
58
|
+
jest.advanceTimersByTime(16);
|
59
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 211));
|
60
|
+
jest.advanceTimersByTime(51);
|
61
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 215));
|
62
|
+
jest.advanceTimersByTime(32);
|
63
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 218));
|
64
|
+
jest.advanceTimersByTime(30);
|
65
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 220));
|
66
|
+
jest.advanceTimersByTime(24);
|
67
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 222));
|
68
|
+
jest.advanceTimersByTime(14);
|
69
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 224));
|
70
|
+
jest.advanceTimersByTime(32);
|
71
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 227));
|
72
|
+
jest.advanceTimersByTime(17);
|
73
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 229));
|
74
|
+
jest.advanceTimersByTime(53);
|
75
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 234));
|
76
|
+
jest.advanceTimersByTime(34);
|
77
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 236));
|
78
|
+
jest.advanceTimersByTime(17);
|
79
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 238));
|
80
|
+
jest.advanceTimersByTime(39);
|
81
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 240));
|
82
|
+
jest.advanceTimersByTime(10);
|
83
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 243));
|
84
|
+
jest.advanceTimersByTime(34);
|
85
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 250));
|
86
|
+
jest.advanceTimersByTime(57);
|
87
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 252));
|
88
|
+
jest.advanceTimersByTime(8);
|
89
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(422, 256));
|
90
|
+
jest.advanceTimersByTime(28);
|
91
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(422, 258));
|
92
|
+
jest.advanceTimersByTime(21);
|
93
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(421, 262));
|
94
|
+
jest.advanceTimersByTime(34);
|
95
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 264));
|
96
|
+
jest.advanceTimersByTime(5);
|
97
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 266));
|
98
|
+
jest.advanceTimersByTime(22);
|
99
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 268));
|
100
|
+
jest.advanceTimersByTime(22);
|
101
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 271));
|
102
|
+
jest.advanceTimersByTime(18);
|
103
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 274));
|
104
|
+
jest.advanceTimersByTime(33);
|
105
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 277));
|
106
|
+
jest.advanceTimersByTime(16);
|
107
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 279));
|
108
|
+
jest.advanceTimersByTime(36);
|
109
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 282));
|
110
|
+
jest.advanceTimersByTime(15);
|
111
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 284));
|
112
|
+
jest.advanceTimersByTime(48);
|
113
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 289));
|
114
|
+
jest.advanceTimersByTime(16);
|
115
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 291));
|
116
|
+
jest.advanceTimersByTime(31);
|
117
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 295));
|
118
|
+
jest.advanceTimersByTime(23);
|
119
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 301));
|
120
|
+
jest.advanceTimersByTime(31);
|
121
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 306));
|
122
|
+
jest.advanceTimersByTime(18);
|
123
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 308));
|
124
|
+
jest.advanceTimersByTime(20);
|
125
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 310));
|
126
|
+
jest.advanceTimersByTime(13);
|
127
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 313));
|
128
|
+
jest.advanceTimersByTime(17);
|
129
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 317));
|
130
|
+
jest.advanceTimersByTime(33);
|
131
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 321));
|
132
|
+
jest.advanceTimersByTime(15);
|
133
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 324));
|
134
|
+
jest.advanceTimersByTime(23);
|
135
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 326));
|
136
|
+
jest.advanceTimersByTime(14);
|
137
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 329));
|
138
|
+
jest.advanceTimersByTime(36);
|
139
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 333));
|
140
|
+
jest.advanceTimersByTime(8);
|
141
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 340));
|
142
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(420, 340));
|
143
|
+
|
144
|
+
const elems = editor.image.getElementsIntersectingRegion(new Rect2(0, 0, 1000, 1000));
|
145
|
+
expect(elems).toHaveLength(1);
|
146
|
+
|
147
|
+
expect(elems[0].getBBox().topLeft).objEq(Vec2.of(420, 24), 8); // ± 8
|
148
|
+
expect(elems[0].getBBox().bottomRight).objEq(Vec2.of(420, 340), 25); // ± 25
|
149
|
+
});
|
150
|
+
});
|
@@ -12,6 +12,7 @@ import Viewport from '../../Viewport';
|
|
12
12
|
import BaseTool from '../BaseTool';
|
13
13
|
import SVGRenderer from '../../rendering/renderers/SVGRenderer';
|
14
14
|
import Selection from './Selection';
|
15
|
+
import TextComponent from '../../components/Text';
|
15
16
|
|
16
17
|
export const cssPrefix = 'selection-tool-';
|
17
18
|
|
@@ -272,11 +273,19 @@ export default class SelectionTool extends BaseTool {
|
|
272
273
|
const sanitize = true;
|
273
274
|
const renderer = new SVGRenderer(exportElem, exportViewport, sanitize);
|
274
275
|
|
276
|
+
const text: string[] = [];
|
275
277
|
for (const elem of selectedElems) {
|
276
278
|
elem.render(renderer);
|
279
|
+
|
280
|
+
if (elem instanceof TextComponent) {
|
281
|
+
text.push(elem.getText());
|
282
|
+
}
|
277
283
|
}
|
278
284
|
|
279
285
|
event.setData('image/svg+xml', exportElem.outerHTML);
|
286
|
+
if (text.length > 0) {
|
287
|
+
event.setData('text/plain', text.join('\n'));
|
288
|
+
}
|
280
289
|
return true;
|
281
290
|
}
|
282
291
|
|
@@ -13,6 +13,7 @@ import TextTool from './TextTool';
|
|
13
13
|
import PipetteTool from './PipetteTool';
|
14
14
|
import ToolSwitcherShortcut from './ToolSwitcherShortcut';
|
15
15
|
import PasteHandler from './PasteHandler';
|
16
|
+
import ToolbarShortcutHandler from './ToolbarShortcutHandler';
|
16
17
|
|
17
18
|
export default class ToolController {
|
18
19
|
private tools: BaseTool[];
|
@@ -45,6 +46,7 @@ export default class ToolController {
|
|
45
46
|
...primaryTools,
|
46
47
|
keyboardPanZoomTool,
|
47
48
|
new UndoRedoShortcut(editor),
|
49
|
+
new ToolbarShortcutHandler(editor),
|
48
50
|
new ToolSwitcherShortcut(editor),
|
49
51
|
new PasteHandler(editor),
|
50
52
|
];
|
@@ -0,0 +1,34 @@
|
|
1
|
+
// Allows the toolbar to register keyboard events.
|
2
|
+
// @packageDocumentation
|
3
|
+
|
4
|
+
import Editor from '../Editor';
|
5
|
+
import { KeyPressEvent } from '../types';
|
6
|
+
import BaseTool from './BaseTool';
|
7
|
+
|
8
|
+
// Returns true if the event was handled, false otherwise.
|
9
|
+
type KeyPressListener = (event: KeyPressEvent)=>boolean;
|
10
|
+
|
11
|
+
export default class ToolbarShortcutHandler extends BaseTool {
|
12
|
+
private listeners: Set<KeyPressListener> = new Set([]);
|
13
|
+
public constructor(editor: Editor) {
|
14
|
+
super(editor.notifier, editor.localization.changeTool);
|
15
|
+
}
|
16
|
+
|
17
|
+
public registerListener(listener: KeyPressListener) {
|
18
|
+
this.listeners.add(listener);
|
19
|
+
}
|
20
|
+
|
21
|
+
public removeListener(listener: KeyPressListener) {
|
22
|
+
this.listeners.delete(listener);
|
23
|
+
}
|
24
|
+
|
25
|
+
public onKeyPress(event: KeyPressEvent): boolean {
|
26
|
+
for (const listener of this.listeners) {
|
27
|
+
if (listener(event)) {
|
28
|
+
return true;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return false;
|
33
|
+
}
|
34
|
+
}
|
@@ -19,6 +19,7 @@ describe('UndoRedoShortcut', () => {
|
|
19
19
|
editor.toolController.dispatchInputEvent({
|
20
20
|
kind: InputEvtType.KeyPressEvent,
|
21
21
|
ctrlKey: true,
|
22
|
+
altKey: false,
|
22
23
|
key: 'z',
|
23
24
|
});
|
24
25
|
|
@@ -35,6 +36,7 @@ describe('UndoRedoShortcut', () => {
|
|
35
36
|
editor.toolController.dispatchInputEvent({
|
36
37
|
kind: InputEvtType.KeyPressEvent,
|
37
38
|
ctrlKey: true,
|
39
|
+
altKey: false,
|
38
40
|
key: 'z',
|
39
41
|
});
|
40
42
|
|
@@ -44,6 +46,7 @@ describe('UndoRedoShortcut', () => {
|
|
44
46
|
editor.toolController.dispatchInputEvent({
|
45
47
|
kind: InputEvtType.KeyPressEvent,
|
46
48
|
ctrlKey: true,
|
49
|
+
altKey: false,
|
47
50
|
key: 'Z',
|
48
51
|
});
|
49
52
|
|
package/src/tools/lib.ts
CHANGED
@@ -17,3 +17,4 @@ export { default as SelectionTool } from './SelectionTool/SelectionTool';
|
|
17
17
|
export { default as EraserTool } from './Eraser';
|
18
18
|
export { default as PasteHandler } from './PasteHandler';
|
19
19
|
|
20
|
+
export { default as ToolbarShortcutHandler } from './ToolbarShortcutHandler';
|
package/src/types.ts
CHANGED
@@ -52,14 +52,25 @@ export interface WheelEvt {
|
|
52
52
|
|
53
53
|
export interface KeyPressEvent {
|
54
54
|
readonly kind: InputEvtType.KeyPressEvent;
|
55
|
+
|
56
|
+
// key, as given by an HTML `KeyboardEvent`
|
55
57
|
readonly key: string;
|
56
|
-
|
58
|
+
|
59
|
+
// If `ctrlKey` is undefined, that is equivalent to `ctrlKey = false`.
|
60
|
+
readonly ctrlKey: boolean|undefined;
|
61
|
+
|
62
|
+
// If falsey, the `alt` key is not pressed.
|
63
|
+
readonly altKey: boolean|undefined;
|
57
64
|
}
|
58
65
|
|
59
66
|
export interface KeyUpEvent {
|
60
67
|
readonly kind: InputEvtType.KeyUpEvent;
|
61
68
|
readonly key: string;
|
62
|
-
|
69
|
+
|
70
|
+
// As in `KeyPressEvent, if `ctrlKey` is undefined, that is equivalent to
|
71
|
+
// `ctrlKey = false`.
|
72
|
+
readonly ctrlKey: boolean|undefined;
|
73
|
+
readonly altKey: boolean|undefined;
|
63
74
|
}
|
64
75
|
|
65
76
|
export interface CopyEvent {
|
@@ -101,12 +112,6 @@ export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt
|
|
101
112
|
export type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
|
102
113
|
|
103
114
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
115
|
export enum EditorEventType {
|
111
116
|
ToolEnabled,
|
112
117
|
ToolDisabled,
|
package/tsconfig.json
CHANGED
@@ -23,10 +23,12 @@
|
|
23
23
|
"**/node_modules",
|
24
24
|
|
25
25
|
// Files that don't need transpilation
|
26
|
-
"**/*.test.ts",
|
26
|
+
// "**/*.test.ts", <- vscode requires .test.ts files to be transpiled for other settings to apply.
|
27
27
|
"__mocks__/*",
|
28
28
|
|
29
29
|
// Output files
|
30
30
|
"./dist/**"
|
31
31
|
],
|
32
|
+
|
33
|
+
"files": [ "./src/testing/global.d.ts" ]
|
32
34
|
}
|