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.
Files changed (66) hide show
  1. package/.github/pull_request_template.md +15 -0
  2. package/.github/workflows/firebase-hosting-merge.yml +7 -0
  3. package/.github/workflows/firebase-hosting-pull-request.yml +10 -0
  4. package/.github/workflows/github-pages.yml +2 -0
  5. package/CHANGELOG.md +15 -0
  6. package/dist/bundle.js +1 -1
  7. package/dist/src/Editor.d.ts +1 -1
  8. package/dist/src/Editor.js +8 -3
  9. package/dist/src/components/AbstractComponent.js +1 -0
  10. package/dist/src/components/Stroke.js +15 -9
  11. package/dist/src/components/Text.d.ts +1 -1
  12. package/dist/src/components/Text.js +1 -1
  13. package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -0
  14. package/dist/src/components/builders/FreehandLineBuilder.js +33 -35
  15. package/dist/src/math/Vec3.d.ts +1 -1
  16. package/dist/src/math/Vec3.js +1 -1
  17. package/dist/src/rendering/renderers/SVGRenderer.js +1 -1
  18. package/dist/src/testing/beforeEachFile.d.ts +1 -0
  19. package/dist/src/testing/beforeEachFile.js +3 -0
  20. package/dist/src/testing/createEditor.d.ts +1 -0
  21. package/dist/src/testing/createEditor.js +7 -1
  22. package/dist/src/testing/loadExpectExtensions.d.ts +0 -15
  23. package/dist/src/toolbar/widgets/BaseWidget.d.ts +2 -0
  24. package/dist/src/toolbar/widgets/BaseWidget.js +15 -0
  25. package/dist/src/toolbar/widgets/PenToolWidget.d.ts +2 -0
  26. package/dist/src/toolbar/widgets/PenToolWidget.js +14 -0
  27. package/dist/src/tools/SelectionTool/SelectionTool.js +8 -0
  28. package/dist/src/tools/ToolController.js +2 -0
  29. package/dist/src/tools/ToolbarShortcutHandler.d.ts +12 -0
  30. package/dist/src/tools/ToolbarShortcutHandler.js +23 -0
  31. package/dist/src/tools/lib.d.ts +1 -0
  32. package/dist/src/tools/lib.js +1 -0
  33. package/dist/src/types.d.ts +4 -2
  34. package/jest.config.js +5 -0
  35. package/package.json +15 -14
  36. package/src/Editor.ts +8 -2
  37. package/src/components/AbstractComponent.ts +2 -0
  38. package/src/components/Stroke.test.ts +0 -3
  39. package/src/components/Stroke.ts +14 -7
  40. package/src/components/Text.test.ts +0 -3
  41. package/src/components/Text.ts +2 -2
  42. package/src/components/builders/FreehandLineBuilder.ts +36 -42
  43. package/src/language/assertions.ts +2 -2
  44. package/src/math/LineSegment2.test.ts +8 -10
  45. package/src/math/Mat33.test.ts +0 -2
  46. package/src/math/Rect2.test.ts +0 -3
  47. package/src/math/Vec2.test.ts +0 -3
  48. package/src/math/Vec3.test.ts +0 -3
  49. package/src/math/Vec3.ts +1 -1
  50. package/src/rendering/renderers/SVGRenderer.ts +1 -1
  51. package/src/testing/beforeEachFile.ts +3 -0
  52. package/src/testing/createEditor.ts +8 -1
  53. package/src/testing/global.d.ts +17 -0
  54. package/src/testing/loadExpectExtensions.ts +0 -15
  55. package/src/toolbar/toolbar.css +3 -2
  56. package/src/toolbar/widgets/BaseWidget.ts +19 -1
  57. package/src/toolbar/widgets/PenToolWidget.ts +18 -1
  58. package/src/tools/Pen.test.ts +150 -0
  59. package/src/tools/SelectionTool/SelectionTool.css +1 -1
  60. package/src/tools/SelectionTool/SelectionTool.ts +9 -0
  61. package/src/tools/ToolController.ts +2 -0
  62. package/src/tools/ToolbarShortcutHandler.ts +34 -0
  63. package/src/tools/UndoRedoShortcut.test.ts +3 -0
  64. package/src/tools/lib.ts +1 -0
  65. package/src/types.ts +13 -8
  66. 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
+ });
@@ -1,7 +1,7 @@
1
1
 
2
2
  .selection-tool-selection-background {
3
3
  background-color: var(--secondary-background-color);
4
- opacity: 0.8;
4
+ opacity: 0.5;
5
5
  overflow: visible;
6
6
  }
7
7
 
@@ -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
- readonly ctrlKey: boolean;
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
- readonly ctrlKey: boolean;
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
  }