js-draw 0.5.0 → 0.6.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 (73) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Editor.d.ts +8 -5
  4. package/dist/src/Editor.js +4 -1
  5. package/dist/src/EditorImage.d.ts +3 -0
  6. package/dist/src/EditorImage.js +7 -0
  7. package/dist/src/SVGLoader.js +5 -6
  8. package/dist/src/components/AbstractComponent.d.ts +1 -0
  9. package/dist/src/components/AbstractComponent.js +4 -0
  10. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
  11. package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
  12. package/dist/src/components/Text.d.ts +3 -5
  13. package/dist/src/components/Text.js +19 -10
  14. package/dist/src/components/UnknownSVGObject.d.ts +1 -0
  15. package/dist/src/components/UnknownSVGObject.js +3 -0
  16. package/dist/src/components/builders/FreehandLineBuilder.js +2 -2
  17. package/dist/src/testing/beforeEachFile.js +4 -0
  18. package/dist/src/toolbar/HTMLToolbar.js +2 -3
  19. package/dist/src/toolbar/IconProvider.d.ts +24 -0
  20. package/dist/src/toolbar/IconProvider.js +415 -0
  21. package/dist/src/toolbar/lib.d.ts +1 -1
  22. package/dist/src/toolbar/lib.js +1 -2
  23. package/dist/src/toolbar/localization.d.ts +0 -1
  24. package/dist/src/toolbar/localization.js +0 -1
  25. package/dist/src/toolbar/makeColorInput.js +1 -2
  26. package/dist/src/toolbar/widgets/BaseWidget.js +1 -2
  27. package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
  28. package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
  29. package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
  30. package/dist/src/toolbar/widgets/PenToolWidget.js +2 -3
  31. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
  32. package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
  33. package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
  34. package/dist/src/tools/PanZoom.d.ts +1 -1
  35. package/dist/src/tools/PanZoom.js +4 -1
  36. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
  37. package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
  38. package/dist/src/tools/ToolController.js +1 -0
  39. package/dist/src/tools/localization.d.ts +1 -0
  40. package/dist/src/tools/localization.js +1 -0
  41. package/package.json +1 -1
  42. package/src/Editor.ts +11 -5
  43. package/src/EditorImage.ts +9 -0
  44. package/src/SVGLoader.test.ts +37 -0
  45. package/src/SVGLoader.ts +5 -6
  46. package/src/components/AbstractComponent.ts +5 -0
  47. package/src/components/SVGGlobalAttributesObject.ts +4 -0
  48. package/src/components/Text.test.ts +1 -16
  49. package/src/components/Text.ts +21 -11
  50. package/src/components/UnknownSVGObject.ts +4 -0
  51. package/src/components/builders/FreehandLineBuilder.ts +2 -2
  52. package/src/testing/beforeEachFile.ts +6 -1
  53. package/src/toolbar/HTMLToolbar.ts +2 -3
  54. package/src/toolbar/IconProvider.ts +476 -0
  55. package/src/toolbar/lib.ts +1 -1
  56. package/src/toolbar/localization.ts +0 -2
  57. package/src/toolbar/makeColorInput.ts +1 -2
  58. package/src/toolbar/widgets/BaseWidget.ts +1 -2
  59. package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
  60. package/src/toolbar/widgets/HandToolWidget.ts +42 -20
  61. package/src/toolbar/widgets/PenToolWidget.ts +2 -3
  62. package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
  63. package/src/toolbar/widgets/TextToolWidget.ts +1 -2
  64. package/src/tools/PanZoom.ts +4 -1
  65. package/src/tools/SelectionTool/SelectionTool.css +1 -0
  66. package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
  67. package/src/tools/SelectionTool/SelectionTool.ts +73 -4
  68. package/src/tools/ToolController.ts +1 -0
  69. package/src/tools/localization.ts +4 -0
  70. package/typedoc.json +5 -1
  71. package/dist/src/toolbar/icons.d.ts +0 -20
  72. package/dist/src/toolbar/icons.js +0 -385
  73. package/src/toolbar/icons.ts +0 -443
@@ -1,7 +1,6 @@
1
1
  import Editor from '../../Editor';
2
2
  import SelectionTool from '../../tools/SelectionTool/SelectionTool';
3
- import { EditorEventType } from '../../types';
4
- import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
3
+ import { EditorEventType, KeyPressEvent } from '../../types';
5
4
  import { ToolbarLocalization } from '../localization';
6
5
  import ActionButtonWidget from './ActionButtonWidget';
7
6
  import BaseToolWidget from './BaseToolWidget';
@@ -14,16 +13,15 @@ export default class SelectionToolWidget extends BaseToolWidget {
14
13
 
15
14
  const resizeButton = new ActionButtonWidget(
16
15
  editor, localization,
17
- makeResizeViewportIcon,
16
+ () => editor.icons.makeResizeViewportIcon(),
18
17
  this.localizationTable.resizeImageToSelection,
19
18
  () => {
20
- const selection = this.tool.getSelection();
21
- this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
19
+ this.resizeImageToSelection();
22
20
  },
23
21
  );
24
22
  const deleteButton = new ActionButtonWidget(
25
23
  editor, localization,
26
- makeDeleteSelectionIcon,
24
+ () => editor.icons.makeDeleteSelectionIcon(),
27
25
  this.localizationTable.deleteSelection,
28
26
  () => {
29
27
  const selection = this.tool.getSelection();
@@ -33,7 +31,7 @@ export default class SelectionToolWidget extends BaseToolWidget {
33
31
  );
34
32
  const duplicateButton = new ActionButtonWidget(
35
33
  editor, localization,
36
- makeDuplicateSelectionIcon,
34
+ () => editor.icons.makeDuplicateSelectionIcon(),
37
35
  this.localizationTable.duplicateSelection,
38
36
  () => {
39
37
  const selection = this.tool.getSelection();
@@ -67,11 +65,29 @@ export default class SelectionToolWidget extends BaseToolWidget {
67
65
  });
68
66
  }
69
67
 
68
+ private resizeImageToSelection() {
69
+ const selection = this.tool.getSelection();
70
+ if (selection) {
71
+ this.editor.dispatch(this.editor.setImportExportRect(selection.region));
72
+ }
73
+ }
74
+
75
+ protected onKeyPress(event: KeyPressEvent): boolean {
76
+ // Resize image to selection:
77
+ // Other keys are handled directly by the selection tool.
78
+ if (event.ctrlKey && event.key === 'r') {
79
+ this.resizeImageToSelection();
80
+ return true;
81
+ }
82
+
83
+ return false;
84
+ }
85
+
70
86
  protected getTitle(): string {
71
87
  return this.localizationTable.select;
72
88
  }
73
89
 
74
90
  protected createIcon(): Element {
75
- return makeSelectionIcon();
91
+ return this.editor.icons.makeSelectionIcon();
76
92
  }
77
93
  }
@@ -2,7 +2,6 @@ import Editor from '../../Editor';
2
2
  import TextTool from '../../tools/TextTool';
3
3
  import { EditorEventType } from '../../types';
4
4
  import { toolbarCSSPrefix } from '../HTMLToolbar';
5
- import { makeTextIcon } from '../icons';
6
5
  import { ToolbarLocalization } from '../localization';
7
6
  import makeColorInput from '../makeColorInput';
8
7
  import BaseToolWidget from './BaseToolWidget';
@@ -26,7 +25,7 @@ export default class TextToolWidget extends BaseToolWidget {
26
25
 
27
26
  protected createIcon(): Element {
28
27
  const textStyle = this.tool.getTextStyle();
29
- return makeTextIcon(textStyle);
28
+ return this.editor.icons.makeTextIcon(textStyle);
30
29
  }
31
30
 
32
31
  private static idCounter: number = 0;
@@ -182,10 +182,13 @@ export default class PanZoom extends BaseTool {
182
182
  return true;
183
183
  }
184
184
 
185
- public onKeyPress({ key }: KeyPressEvent): boolean {
185
+ public onKeyPress({ key, ctrlKey, altKey }: KeyPressEvent): boolean {
186
186
  if (!(this.mode & PanZoomMode.Keyboard)) {
187
187
  return false;
188
188
  }
189
+ if (ctrlKey || altKey) {
190
+ return false;
191
+ }
189
192
 
190
193
  // No need to keep the same the transform for keyboard events.
191
194
  this.transform = Viewport.transformBy(Mat33.identity);
@@ -3,6 +3,7 @@
3
3
  background-color: var(--secondary-background-color);
4
4
  opacity: 0.5;
5
5
  overflow: visible;
6
+ position: absolute;
6
7
  }
7
8
 
8
9
  .selection-tool-handle {
@@ -100,4 +100,44 @@ describe('SelectionTool', () => {
100
100
  editor.sendKeyboardEvent(InputEvtType.KeyUpEvent, 'a');
101
101
  expect(editor.viewport.visibleRect.containsPoint(selection.region.center)).toBe(true);
102
102
  });
103
+
104
+ it('shift+click should expand an existing selection', () => {
105
+ const { addTestStrokeCommand: stroke1Command } = createSquareStroke(50);
106
+ const { addTestStrokeCommand: stroke2Command } = createSquareStroke(500);
107
+
108
+ const editor = createEditor();
109
+ editor.dispatch(stroke1Command);
110
+ editor.dispatch(stroke2Command);
111
+
112
+ // Select the first stroke
113
+ const selectionTool = getSelectionTool(editor);
114
+ selectionTool.setEnabled(true);
115
+
116
+ // Select the smaller rectangle
117
+ editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(40, 40));
118
+ editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(100, 100));
119
+
120
+ expect(selectionTool.getSelectedObjects()).toHaveLength(1);
121
+
122
+ // Shift key down.
123
+ editor.sendKeyboardEvent(InputEvtType.KeyPressEvent, 'Shift');
124
+
125
+ // Select the larger stroke.
126
+ editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(200, 200));
127
+ editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(600, 600));
128
+
129
+ expect(selectionTool.getSelectedObjects()).toHaveLength(2);
130
+
131
+ editor.sendKeyboardEvent(InputEvtType.KeyUpEvent, 'Shift');
132
+
133
+ // Select the larger stroke without shift pressed
134
+ editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(200, 200));
135
+ editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(600, 600));
136
+ expect(selectionTool.getSelectedObjects()).toHaveLength(1);
137
+
138
+ // Select nothing
139
+ editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(200, 200));
140
+ editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(201, 201));
141
+ expect(selectionTool.getSelectedObjects()).toHaveLength(0);
142
+ });
103
143
  });
@@ -23,6 +23,9 @@ export default class SelectionTool extends BaseTool {
23
23
  private selectionBox: Selection|null;
24
24
  private lastEvtTarget: EventTarget|null = null;
25
25
 
26
+ private expandingSelectionBox: boolean = false;
27
+ private shiftKeyPressed: boolean = false;
28
+
26
29
  public constructor(private editor: Editor, description: string) {
27
30
  super(editor.notifier, description);
28
31
 
@@ -50,8 +53,11 @@ export default class SelectionTool extends BaseTool {
50
53
  this.selectionBox = new Selection(
51
54
  selectionStartPos, this.editor
52
55
  );
53
- // Remove any previous selection rects
54
- this.handleOverlay.replaceChildren();
56
+
57
+ if (!this.expandingSelectionBox) {
58
+ // Remove any previous selection rects
59
+ this.prevSelectionBox?.cancelSelection();
60
+ }
55
61
  this.selectionBox.addTo(this.handleOverlay);
56
62
  }
57
63
 
@@ -60,7 +66,11 @@ export default class SelectionTool extends BaseTool {
60
66
  if (event.allPointers.length === 1 && event.current.isPrimary) {
61
67
  if (this.lastEvtTarget && this.selectionBox?.onDragStart(event.current, this.lastEvtTarget)) {
62
68
  this.selectionBoxHandlingEvt = true;
63
- } else {
69
+ this.expandingSelectionBox = false;
70
+ }
71
+ else {
72
+ // Shift key: Combine the new and old selection boxes at the end of the gesture.
73
+ this.expandingSelectionBox = this.shiftKeyPressed;
64
74
  this.makeSelectionBox(event.current.canvasPos);
65
75
  }
66
76
 
@@ -99,6 +109,7 @@ export default class SelectionTool extends BaseTool {
99
109
  }
100
110
  }
101
111
 
112
+ // Called after a gestureCancel and a pointerUp
102
113
  private onGestureEnd() {
103
114
  this.lastEvtTarget = null;
104
115
 
@@ -127,7 +138,19 @@ export default class SelectionTool extends BaseTool {
127
138
  if (!this.selectionBox) return;
128
139
 
129
140
  this.selectionBox.setToPoint(event.current.canvasPos);
130
- this.onGestureEnd();
141
+
142
+ // Were we expanding the previous selection?
143
+ if (this.expandingSelectionBox && this.prevSelectionBox) {
144
+ // If so, finish expanding.
145
+ this.expandingSelectionBox = false;
146
+ this.selectionBox.resolveToObjects();
147
+ this.setSelection([
148
+ ...this.selectionBox.getSelectedObjects(),
149
+ ...this.prevSelectionBox.getSelectedObjects(),
150
+ ]);
151
+ } else {
152
+ this.onGestureEnd();
153
+ }
131
154
  }
132
155
 
133
156
  public onGestureCancel(): void {
@@ -139,6 +162,8 @@ export default class SelectionTool extends BaseTool {
139
162
  this.selectionBox = this.prevSelectionBox;
140
163
  this.selectionBox?.addTo(this.handleOverlay);
141
164
  }
165
+
166
+ this.expandingSelectionBox = false;
142
167
  }
143
168
 
144
169
  private static handleableKeys = [
@@ -150,6 +175,26 @@ export default class SelectionTool extends BaseTool {
150
175
  'i', 'I', 'o', 'O',
151
176
  ];
152
177
  public onKeyPress(event: KeyPressEvent): boolean {
178
+ if (this.selectionBox && event.ctrlKey && event.key === 'd') {
179
+ // Handle duplication on key up — we don't want to accidentally duplicate
180
+ // many times.
181
+ return true;
182
+ }
183
+ else if (event.key === 'a' && event.ctrlKey) {
184
+ // Handle ctrl+A on key up.
185
+ // Return early to prevent 'a' from moving the selection/view.
186
+ return true;
187
+ }
188
+ else if (event.ctrlKey) {
189
+ // Don't transform the selection with, for example, ctrl+i.
190
+ // Pass it to another tool, if apliccable.
191
+ return false;
192
+ }
193
+ else if (event.key === 'Shift') {
194
+ this.shiftKeyPressed = true;
195
+ return true;
196
+ }
197
+
153
198
  let rotationSteps = 0;
154
199
  let xTranslateSteps = 0;
155
200
  let yTranslateSteps = 0;
@@ -245,6 +290,21 @@ export default class SelectionTool extends BaseTool {
245
290
  }
246
291
 
247
292
  public onKeyUp(evt: KeyUpEvent) {
293
+ if (evt.key === 'Shift') {
294
+ this.shiftKeyPressed = false;
295
+ return true;
296
+ }
297
+ else if (evt.ctrlKey) {
298
+ if (this.selectionBox && evt.key === 'd') {
299
+ this.editor.dispatch(this.selectionBox.duplicateSelectedObjects());
300
+ return true;
301
+ }
302
+ else if (evt.key === 'a') {
303
+ this.setSelection(this.editor.image.getAllElements());
304
+ return true;
305
+ }
306
+ }
307
+
248
308
  if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
249
309
  this.selectionBox.finalizeTransform();
250
310
  return true;
@@ -307,11 +367,20 @@ export default class SelectionTool extends BaseTool {
307
367
  }
308
368
 
309
369
  // Get the object responsible for displaying this' selection.
370
+ // @internal
310
371
  public getSelection(): Selection|null {
311
372
  return this.selectionBox;
312
373
  }
313
374
 
375
+ public getSelectedObjects(): AbstractComponent[] {
376
+ return this.selectionBox?.getSelectedObjects() ?? [];
377
+ }
378
+
379
+ // Select the given `objects`. Any non-selectable objects in `objects` are ignored.
314
380
  public setSelection(objects: AbstractComponent[]) {
381
+ // Only select selectable objects.
382
+ objects = objects.filter(obj => obj.isSelectable());
383
+
315
384
  let bbox: Rect2|null = null;
316
385
  for (const object of objects) {
317
386
  if (bbox) {
@@ -39,6 +39,7 @@ export default class ToolController {
39
39
  new Eraser(editor, localization.eraserTool),
40
40
  new SelectionTool(editor, localization.selectionTool),
41
41
  new TextTool(editor, localization.textTool, localization),
42
+ new PanZoom(editor, PanZoomMode.SinglePointerGestures, localization.anyDevicePanning)
42
43
  ];
43
44
  this.tools = [
44
45
  new PipetteTool(editor, localization.pipetteTool),
@@ -15,6 +15,8 @@ export interface ToolLocalization {
15
15
  changeTool: string;
16
16
  pasteHandler: string;
17
17
 
18
+ anyDevicePanning: string;
19
+
18
20
  copied: (count: number, description: string) => string;
19
21
  pasted: (count: number, description: string) => string;
20
22
 
@@ -38,6 +40,8 @@ export const defaultToolLocalization: ToolLocalization = {
38
40
  changeTool: 'Change tool',
39
41
  pasteHandler: 'Copy paste handler',
40
42
 
43
+ anyDevicePanning: 'Any device panning',
44
+
41
45
  copied: (count: number, description: string) => `Copied ${count} ${description}`,
42
46
  pasted: (count: number, description: string) => `Pasted ${count} ${description}`,
43
47
 
package/typedoc.json CHANGED
@@ -3,7 +3,11 @@
3
3
  "./src/"
4
4
  ],
5
5
  "exclude": [
6
- "**/*.test.ts"
6
+ "**/*.test.ts",
7
+ "node_modules/**",
8
+ "dist/",
9
+ "dist-test/",
10
+ "src/testing/"
7
11
  ],
8
12
  "excludePrivate": true,
9
13
  "excludeInternal": true,
@@ -1,20 +0,0 @@
1
- import Color4 from '../Color4';
2
- import { ComponentBuilderFactory } from '../components/builders/types';
3
- import { TextStyle } from '../components/Text';
4
- import Pen from '../tools/Pen';
5
- export declare const makeUndoIcon: () => SVGSVGElement;
6
- export declare const makeRedoIcon: (mirror?: boolean) => SVGSVGElement;
7
- export declare const makeDropdownIcon: () => SVGSVGElement;
8
- export declare const makeEraserIcon: () => SVGSVGElement;
9
- export declare const makeSelectionIcon: () => SVGSVGElement;
10
- export declare const makeHandToolIcon: () => SVGSVGElement;
11
- export declare const makeTouchPanningIcon: () => SVGSVGElement;
12
- export declare const makeAllDevicePanningIcon: () => SVGSVGElement;
13
- export declare const makeZoomIcon: () => SVGSVGElement;
14
- export declare const makeTextIcon: (textStyle: TextStyle) => SVGSVGElement;
15
- export declare const makePenIcon: (tipThickness: number, color: string | Color4) => SVGSVGElement;
16
- export declare const makeIconFromFactory: (pen: Pen, factory: ComponentBuilderFactory) => SVGSVGElement;
17
- export declare const makePipetteIcon: (color?: Color4) => SVGSVGElement;
18
- export declare const makeResizeViewportIcon: () => SVGSVGElement;
19
- export declare const makeDuplicateSelectionIcon: () => SVGSVGElement;
20
- export declare const makeDeleteSelectionIcon: () => SVGSVGElement;