js-draw 0.3.0 → 0.3.2

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 (113) hide show
  1. package/.github/ISSUE_TEMPLATE/translation.md +4 -1
  2. package/CHANGELOG.md +15 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +4 -1
  5. package/dist/src/Editor.js +117 -2
  6. package/dist/src/EditorImage.js +4 -1
  7. package/dist/src/SVGLoader.d.ts +4 -1
  8. package/dist/src/SVGLoader.js +78 -33
  9. package/dist/src/UndoRedoHistory.d.ts +1 -0
  10. package/dist/src/UndoRedoHistory.js +6 -0
  11. package/dist/src/Viewport.d.ts +1 -0
  12. package/dist/src/Viewport.js +12 -4
  13. package/dist/src/commands/lib.d.ts +2 -1
  14. package/dist/src/commands/lib.js +2 -1
  15. package/dist/src/commands/localization.d.ts +1 -0
  16. package/dist/src/commands/localization.js +1 -0
  17. package/dist/src/commands/uniteCommands.d.ts +4 -0
  18. package/dist/src/commands/uniteCommands.js +105 -0
  19. package/dist/src/components/AbstractComponent.d.ts +2 -0
  20. package/dist/src/components/AbstractComponent.js +41 -5
  21. package/dist/src/components/ImageComponent.d.ts +27 -0
  22. package/dist/src/components/ImageComponent.js +129 -0
  23. package/dist/src/components/Stroke.js +11 -6
  24. package/dist/src/components/builders/FreehandLineBuilder.js +7 -7
  25. package/dist/src/components/lib.d.ts +4 -2
  26. package/dist/src/components/lib.js +4 -2
  27. package/dist/src/components/localization.d.ts +2 -0
  28. package/dist/src/components/localization.js +2 -0
  29. package/dist/src/math/LineSegment2.d.ts +4 -0
  30. package/dist/src/math/LineSegment2.js +9 -0
  31. package/dist/src/math/Path.d.ts +5 -1
  32. package/dist/src/math/Path.js +89 -7
  33. package/dist/src/math/Rect2.js +1 -1
  34. package/dist/src/math/Triangle.d.ts +11 -0
  35. package/dist/src/math/Triangle.js +19 -0
  36. package/dist/src/rendering/Display.js +2 -2
  37. package/dist/src/rendering/localization.d.ts +3 -0
  38. package/dist/src/rendering/localization.js +3 -0
  39. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +9 -1
  40. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
  41. package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
  42. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
  43. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  44. package/dist/src/rendering/renderers/SVGRenderer.d.ts +14 -12
  45. package/dist/src/rendering/renderers/SVGRenderer.js +71 -87
  46. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
  47. package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
  48. package/dist/src/toolbar/HTMLToolbar.d.ts +1 -0
  49. package/dist/src/toolbar/HTMLToolbar.js +1 -0
  50. package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
  51. package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
  52. package/dist/src/tools/BaseTool.d.ts +4 -1
  53. package/dist/src/tools/BaseTool.js +12 -0
  54. package/dist/src/tools/PasteHandler.d.ts +16 -0
  55. package/dist/src/tools/PasteHandler.js +142 -0
  56. package/dist/src/tools/Pen.d.ts +2 -1
  57. package/dist/src/tools/Pen.js +16 -0
  58. package/dist/src/tools/SelectionTool.d.ts +7 -1
  59. package/dist/src/tools/SelectionTool.js +63 -5
  60. package/dist/src/tools/ToolController.d.ts +1 -0
  61. package/dist/src/tools/ToolController.js +45 -29
  62. package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
  63. package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
  64. package/dist/src/tools/lib.d.ts +2 -0
  65. package/dist/src/tools/lib.js +2 -0
  66. package/dist/src/tools/localization.d.ts +4 -0
  67. package/dist/src/tools/localization.js +4 -0
  68. package/dist/src/types.d.ts +21 -4
  69. package/dist/src/types.js +3 -0
  70. package/package.json +2 -2
  71. package/src/Editor.ts +131 -2
  72. package/src/EditorImage.ts +7 -1
  73. package/src/SVGLoader.ts +90 -36
  74. package/src/UndoRedoHistory.test.ts +33 -0
  75. package/src/UndoRedoHistory.ts +8 -0
  76. package/src/Viewport.ts +13 -4
  77. package/src/commands/lib.ts +2 -0
  78. package/src/commands/localization.ts +2 -0
  79. package/src/commands/uniteCommands.test.ts +23 -0
  80. package/src/commands/uniteCommands.ts +121 -0
  81. package/src/components/AbstractComponent.ts +55 -9
  82. package/src/components/ImageComponent.ts +153 -0
  83. package/src/components/Stroke.test.ts +5 -0
  84. package/src/components/Stroke.ts +13 -7
  85. package/src/components/builders/FreehandLineBuilder.ts +7 -7
  86. package/src/components/lib.ts +7 -2
  87. package/src/components/localization.ts +4 -0
  88. package/src/math/LineSegment2.test.ts +9 -0
  89. package/src/math/LineSegment2.ts +13 -0
  90. package/src/math/Path.test.ts +53 -0
  91. package/src/math/Path.toString.test.ts +4 -2
  92. package/src/math/Path.ts +109 -11
  93. package/src/math/Rect2.ts +1 -1
  94. package/src/math/Triangle.ts +29 -0
  95. package/src/rendering/Display.ts +2 -2
  96. package/src/rendering/localization.ts +6 -0
  97. package/src/rendering/renderers/AbstractRenderer.ts +17 -0
  98. package/src/rendering/renderers/CanvasRenderer.ts +10 -1
  99. package/src/rendering/renderers/DummyRenderer.ts +6 -1
  100. package/src/rendering/renderers/SVGRenderer.ts +76 -101
  101. package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
  102. package/src/toolbar/HTMLToolbar.ts +1 -1
  103. package/src/toolbar/types.ts +1 -1
  104. package/src/toolbar/widgets/BaseWidget.ts +27 -1
  105. package/src/tools/BaseTool.ts +17 -1
  106. package/src/tools/PasteHandler.ts +156 -0
  107. package/src/tools/Pen.ts +20 -1
  108. package/src/tools/SelectionTool.ts +80 -8
  109. package/src/tools/ToolController.ts +60 -46
  110. package/src/tools/ToolSwitcherShortcut.ts +34 -0
  111. package/src/tools/lib.ts +2 -0
  112. package/src/tools/localization.ts +10 -0
  113. package/src/types.ts +29 -3
@@ -0,0 +1,142 @@
1
+ /**
2
+ * A tool that handles paste events.
3
+ * @packageDocumentation
4
+ */
5
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
6
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
7
+ return new (P || (P = Promise))(function (resolve, reject) {
8
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
9
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
11
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
12
+ });
13
+ };
14
+ import { TextComponent } from '../components/lib';
15
+ import { uniteCommands } from '../commands/lib';
16
+ import SVGLoader from '../SVGLoader';
17
+ import { Mat33, Vec2 } from '../math/lib';
18
+ import BaseTool from './BaseTool';
19
+ import EditorImage from '../EditorImage';
20
+ import SelectionTool from './SelectionTool';
21
+ import TextTool from './TextTool';
22
+ import Color4 from '../Color4';
23
+ import ImageComponent from '../components/ImageComponent';
24
+ // { @inheritDoc PasteHandler! }
25
+ export default class PasteHandler extends BaseTool {
26
+ constructor(editor) {
27
+ super(editor.notifier, editor.localization.pasteHandler);
28
+ this.editor = editor;
29
+ }
30
+ onPaste(event) {
31
+ const mime = event.mime.toLowerCase();
32
+ if (mime === 'image/svg+xml') {
33
+ void this.doSVGPaste(event.data);
34
+ return true;
35
+ }
36
+ else if (mime === 'text/plain') {
37
+ void this.doTextPaste(event.data);
38
+ return true;
39
+ }
40
+ else if (mime === 'image/png' || mime === 'image/jpeg') {
41
+ void this.doImagePaste(event.data);
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+ addComponentsFromPaste(components) {
47
+ return __awaiter(this, void 0, void 0, function* () {
48
+ let bbox = null;
49
+ for (const component of components) {
50
+ if (bbox) {
51
+ bbox = bbox.union(component.getBBox());
52
+ }
53
+ else {
54
+ bbox = component.getBBox();
55
+ }
56
+ }
57
+ if (!bbox) {
58
+ return;
59
+ }
60
+ // Find a transform that scales/moves bbox onto the screen.
61
+ const visibleRect = this.editor.viewport.visibleRect;
62
+ const scaleRatioX = visibleRect.width / bbox.width;
63
+ const scaleRatioY = visibleRect.height / bbox.height;
64
+ let scaleRatio = scaleRatioX;
65
+ if (bbox.width * scaleRatio > visibleRect.width || bbox.height * scaleRatio > visibleRect.height) {
66
+ scaleRatio = scaleRatioY;
67
+ }
68
+ scaleRatio *= 2 / 3;
69
+ const transfm = Mat33.translation(visibleRect.center.minus(bbox.center)).rightMul(Mat33.scaling2D(scaleRatio, bbox.center));
70
+ const commands = [];
71
+ for (const component of components) {
72
+ // To allow deserialization, we need to add first, then transform.
73
+ commands.push(EditorImage.addElement(component));
74
+ commands.push(component.transformBy(transfm));
75
+ }
76
+ const applyChunkSize = 100;
77
+ this.editor.dispatch(uniteCommands(commands, applyChunkSize), true);
78
+ for (const selectionTool of this.editor.toolController.getMatchingTools(SelectionTool)) {
79
+ selectionTool.setEnabled(true);
80
+ selectionTool.setSelection(components);
81
+ }
82
+ });
83
+ }
84
+ doSVGPaste(data) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ const sanitize = true;
87
+ const loader = SVGLoader.fromString(data, sanitize);
88
+ const components = [];
89
+ yield loader.start((component) => {
90
+ components.push(component);
91
+ }, (_countProcessed, _totalToProcess) => null);
92
+ yield this.addComponentsFromPaste(components);
93
+ });
94
+ }
95
+ doTextPaste(text) {
96
+ var _a, _b;
97
+ return __awaiter(this, void 0, void 0, function* () {
98
+ const textTools = this.editor.toolController.getMatchingTools(TextTool);
99
+ textTools.sort((a, b) => {
100
+ if (!a.isEnabled() && b.isEnabled()) {
101
+ return -1;
102
+ }
103
+ if (!b.isEnabled() && a.isEnabled()) {
104
+ return 1;
105
+ }
106
+ return 0;
107
+ });
108
+ const defaultTextStyle = { size: 12, fontFamily: 'sans', renderingStyle: { fill: Color4.red } };
109
+ const pastedTextStyle = (_b = (_a = textTools[0]) === null || _a === void 0 ? void 0 : _a.getTextStyle()) !== null && _b !== void 0 ? _b : defaultTextStyle;
110
+ const lines = text.split('\n');
111
+ let lastComponent = null;
112
+ const components = [];
113
+ for (const line of lines) {
114
+ let position = Vec2.zero;
115
+ if (lastComponent) {
116
+ const lineMargin = Math.floor(pastedTextStyle.size);
117
+ position = lastComponent.getBBox().bottomLeft.plus(Vec2.unitY.times(lineMargin));
118
+ }
119
+ const component = new TextComponent([line], Mat33.translation(position), pastedTextStyle);
120
+ components.push(component);
121
+ lastComponent = component;
122
+ }
123
+ if (components.length === 1) {
124
+ yield this.addComponentsFromPaste([components[0]]);
125
+ }
126
+ else {
127
+ // Wrap the existing `TextComponent`s --- dragging one component should drag all.
128
+ yield this.addComponentsFromPaste([
129
+ new TextComponent(components, Mat33.identity, pastedTextStyle)
130
+ ]);
131
+ }
132
+ });
133
+ }
134
+ doImagePaste(dataURL) {
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ const image = new Image();
137
+ image.src = dataURL;
138
+ const component = yield ImageComponent.fromImage(image, Mat33.identity);
139
+ yield this.addComponentsFromPaste([component]);
140
+ });
141
+ }
142
+ }
@@ -1,7 +1,7 @@
1
1
  import Color4 from '../Color4';
2
2
  import Editor from '../Editor';
3
3
  import Pointer from '../Pointer';
4
- import { PointerEvt, StrokeDataPoint } from '../types';
4
+ import { KeyPressEvent, PointerEvt, StrokeDataPoint } from '../types';
5
5
  import BaseTool from './BaseTool';
6
6
  import { ComponentBuilder, ComponentBuilderFactory } from '../components/builders/types';
7
7
  export interface PenStyle {
@@ -30,4 +30,5 @@ export default class Pen extends BaseTool {
30
30
  getThickness(): number;
31
31
  getColor(): Color4;
32
32
  getStrokeFactory(): ComponentBuilderFactory;
33
+ onKeyPress({ key }: KeyPressEvent): boolean;
33
34
  }
@@ -121,4 +121,20 @@ export default class Pen extends BaseTool {
121
121
  getThickness() { return this.style.thickness; }
122
122
  getColor() { return this.style.color; }
123
123
  getStrokeFactory() { return this.builderFactory; }
124
+ onKeyPress({ key }) {
125
+ key = key.toLowerCase();
126
+ let newThickness;
127
+ if (key === '-' || key === '_') {
128
+ newThickness = this.getThickness() * 2 / 3;
129
+ }
130
+ else if (key === '+' || key === '=') {
131
+ newThickness = this.getThickness() * 3 / 2;
132
+ }
133
+ if (newThickness !== undefined) {
134
+ newThickness = Math.min(Math.max(1, newThickness), 128);
135
+ this.setThickness(newThickness);
136
+ return true;
137
+ }
138
+ return false;
139
+ }
124
140
  }
@@ -1,9 +1,10 @@
1
1
  import Command from '../commands/Command';
2
+ import AbstractComponent from '../components/AbstractComponent';
2
3
  import Editor from '../Editor';
3
4
  import Mat33 from '../math/Mat33';
4
5
  import Rect2 from '../math/Rect2';
5
6
  import { Point2, Vec2 } from '../math/Vec2';
6
- import { KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
7
+ import { CopyEvent, KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
7
8
  import BaseTool from './BaseTool';
8
9
  declare class Selection {
9
10
  startPoint: Point2;
@@ -27,6 +28,8 @@ declare class Selection {
27
28
  appendBackgroundBoxTo(elem: HTMLElement): void;
28
29
  setToPoint(point: Point2): void;
29
30
  cancelSelection(): void;
31
+ setSelectedObjects(objects: AbstractComponent[], bbox: Rect2): void;
32
+ getSelectedObjects(): AbstractComponent[];
30
33
  resolveToObjects(): boolean;
31
34
  recomputeRegion(): boolean;
32
35
  getMinCanvasSize(): number;
@@ -43,6 +46,7 @@ export default class SelectionTool extends BaseTool {
43
46
  private prevSelectionBox;
44
47
  private selectionBox;
45
48
  constructor(editor: Editor, description: string);
49
+ private makeSelectionBox;
46
50
  onPointerDown(event: PointerEvt): boolean;
47
51
  onPointerMove(event: PointerEvt): void;
48
52
  private onGestureEnd;
@@ -52,8 +56,10 @@ export default class SelectionTool extends BaseTool {
52
56
  private static handleableKeys;
53
57
  onKeyPress(event: KeyPressEvent): boolean;
54
58
  onKeyUp(evt: KeyUpEvent): boolean;
59
+ onCopy(event: CopyEvent): boolean;
55
60
  setEnabled(enabled: boolean): void;
56
61
  getSelection(): Selection | null;
62
+ setSelection(objects: AbstractComponent[]): void;
57
63
  clearSelection(): void;
58
64
  }
59
65
  export {};
@@ -20,6 +20,7 @@ import { EditorEventType } from '../types';
20
20
  import Viewport from '../Viewport';
21
21
  import BaseTool from './BaseTool';
22
22
  import SerializableCommand from '../commands/SerializableCommand';
23
+ import SVGRenderer from '../rendering/renderers/SVGRenderer';
23
24
  const handleScreenSize = 30;
24
25
  const styles = `
25
26
  .handleOverlay {
@@ -262,6 +263,14 @@ class Selection {
262
263
  }
263
264
  this.region = Rect2.empty;
264
265
  }
266
+ setSelectedObjects(objects, bbox) {
267
+ this.region = bbox;
268
+ this.selectedElems = objects;
269
+ this.updateUI();
270
+ }
271
+ getSelectedObjects() {
272
+ return this.selectedElems;
273
+ }
265
274
  // Find the objects corresponding to this in the document,
266
275
  // select them.
267
276
  // Returns false iff nothing was selected.
@@ -427,13 +436,16 @@ export default class SelectionTool extends BaseTool {
427
436
  });
428
437
  this.editor.handleKeyEventsFrom(this.handleOverlay);
429
438
  }
439
+ makeSelectionBox(selectionStartPos) {
440
+ this.prevSelectionBox = this.selectionBox;
441
+ this.selectionBox = new Selection(selectionStartPos, this.editor);
442
+ // Remove any previous selection rects
443
+ this.handleOverlay.replaceChildren();
444
+ this.selectionBox.appendBackgroundBoxTo(this.handleOverlay);
445
+ }
430
446
  onPointerDown(event) {
431
447
  if (event.allPointers.length === 1 && event.current.isPrimary) {
432
- this.prevSelectionBox = this.selectionBox;
433
- this.selectionBox = new Selection(event.current.canvasPos, this.editor);
434
- // Remove any previous selection rects
435
- this.handleOverlay.replaceChildren();
436
- this.selectionBox.appendBackgroundBoxTo(this.handleOverlay);
448
+ this.makeSelectionBox(event.current.canvasPos);
437
449
  return true;
438
450
  }
439
451
  return false;
@@ -542,6 +554,11 @@ export default class SelectionTool extends BaseTool {
542
554
  Math.max(0.5, scaledSize.x / region.size.x), Math.max(0.5, scaledSize.y / region.size.y)), region.topLeft).rightMul(Mat33.zRotation(rotationSteps * rotateStepSize, region.center)).rightMul(Mat33.translation(Vec2.of(xTranslateSteps, yTranslateSteps).times(translateStepSize)));
543
555
  this.selectionBox.transformPreview(transform);
544
556
  }
557
+ if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
558
+ this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
559
+ this.clearSelection();
560
+ handled = true;
561
+ }
545
562
  return handled;
546
563
  }
547
564
  onKeyUp(evt) {
@@ -551,6 +568,28 @@ export default class SelectionTool extends BaseTool {
551
568
  }
552
569
  return false;
553
570
  }
571
+ onCopy(event) {
572
+ if (!this.selectionBox) {
573
+ return false;
574
+ }
575
+ const selectedElems = this.selectionBox.getSelectedObjects();
576
+ const bbox = this.selectionBox.region;
577
+ if (selectedElems.length === 0) {
578
+ return false;
579
+ }
580
+ const exportViewport = new Viewport(this.editor.notifier);
581
+ exportViewport.updateScreenSize(Vec2.of(bbox.w, bbox.h));
582
+ exportViewport.resetTransform(Mat33.translation(bbox.topLeft));
583
+ const svgNameSpace = 'http://www.w3.org/2000/svg';
584
+ const exportElem = document.createElementNS(svgNameSpace, 'svg');
585
+ const sanitize = true;
586
+ const renderer = new SVGRenderer(exportElem, exportViewport, sanitize);
587
+ for (const elem of selectedElems) {
588
+ elem.render(renderer);
589
+ }
590
+ event.setData('image/svg+xml', exportElem.outerHTML);
591
+ return true;
592
+ }
554
593
  setEnabled(enabled) {
555
594
  super.setEnabled(enabled);
556
595
  // Clear the selection
@@ -569,6 +608,25 @@ export default class SelectionTool extends BaseTool {
569
608
  getSelection() {
570
609
  return this.selectionBox;
571
610
  }
611
+ setSelection(objects) {
612
+ let bbox = null;
613
+ for (const object of objects) {
614
+ if (bbox) {
615
+ bbox = bbox.union(object.getBBox());
616
+ }
617
+ else {
618
+ bbox = object.getBBox();
619
+ }
620
+ }
621
+ if (!bbox) {
622
+ return;
623
+ }
624
+ this.clearSelection();
625
+ if (!this.selectionBox) {
626
+ this.makeSelectionBox(bbox.topLeft);
627
+ }
628
+ this.selectionBox.setSelectedObjects(objects, bbox);
629
+ }
572
630
  clearSelection() {
573
631
  this.handleOverlay.replaceChildren();
574
632
  this.prevSelectionBox = this.selectionBox;
@@ -11,6 +11,7 @@ export default class ToolController {
11
11
  constructor(editor: Editor, localization: ToolLocalization);
12
12
  setTools(tools: BaseTool[], primaryToolGroup?: ToolEnabledGroup): void;
13
13
  addPrimaryTool(tool: BaseTool): void;
14
+ getPrimaryTools(): BaseTool[];
14
15
  addTool(tool: BaseTool): void;
15
16
  dispatchInputEvent(event: InputEvt): boolean;
16
17
  getMatchingTools<Type extends BaseTool>(type: new (...args: any[]) => Type): Type[];
@@ -8,6 +8,8 @@ import Color4 from '../Color4';
8
8
  import UndoRedoShortcut from './UndoRedoShortcut';
9
9
  import TextTool from './TextTool';
10
10
  import PipetteTool from './PipetteTool';
11
+ import ToolSwitcherShortcut from './ToolSwitcherShortcut';
12
+ import PasteHandler from './PasteHandler';
11
13
  export default class ToolController {
12
14
  /** @internal */
13
15
  constructor(editor, localization) {
@@ -18,13 +20,13 @@ export default class ToolController {
18
20
  const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
19
21
  const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
20
22
  const primaryTools = [
21
- new SelectionTool(editor, localization.selectionTool),
22
- new Eraser(editor, localization.eraserTool),
23
23
  // Three pens
24
24
  primaryPenTool,
25
25
  new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
26
26
  // Highlighter-like pen with width=64
27
27
  new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
28
+ new Eraser(editor, localization.eraserTool),
29
+ new SelectionTool(editor, localization.selectionTool),
28
30
  new TextTool(editor, localization.textTool, localization),
29
31
  ];
30
32
  this.tools = [
@@ -33,6 +35,8 @@ export default class ToolController {
33
35
  ...primaryTools,
34
36
  keyboardPanZoomTool,
35
37
  new UndoRedoShortcut(editor),
38
+ new ToolSwitcherShortcut(editor),
39
+ new PasteHandler(editor),
36
40
  ];
37
41
  primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
38
42
  panZoomTool.setEnabled(true);
@@ -64,6 +68,11 @@ export default class ToolController {
64
68
  }
65
69
  this.addTool(tool);
66
70
  }
71
+ getPrimaryTools() {
72
+ return this.tools.filter(tool => {
73
+ return tool.getToolGroup() === this.primaryToolGroup;
74
+ });
75
+ }
67
76
  // Add a tool to the end of this' tool list (the added tool receives events after tools already added to this).
68
77
  // This should be called before creating the app's toolbar.
69
78
  addTool(tool) {
@@ -90,42 +99,49 @@ export default class ToolController {
90
99
  this.activeTool = null;
91
100
  handled = true;
92
101
  }
93
- else if (event.kind === InputEvtType.WheelEvt || event.kind === InputEvtType.KeyPressEvent || event.kind === InputEvtType.KeyUpEvent) {
94
- const isKeyPressEvt = event.kind === InputEvtType.KeyPressEvent;
95
- const isKeyReleaseEvt = event.kind === InputEvtType.KeyUpEvent;
96
- const isWheelEvt = event.kind === InputEvtType.WheelEvt;
102
+ else if (event.kind === InputEvtType.PointerMoveEvt) {
103
+ if (this.activeTool !== null) {
104
+ this.activeTool.onPointerMove(event);
105
+ handled = true;
106
+ }
107
+ }
108
+ else if (event.kind === InputEvtType.GestureCancelEvt) {
109
+ if (this.activeTool !== null) {
110
+ this.activeTool.onGestureCancel();
111
+ this.activeTool = null;
112
+ }
113
+ }
114
+ else {
115
+ let allCasesHandledGuard;
97
116
  for (const tool of this.tools) {
98
117
  if (!tool.isEnabled()) {
99
118
  continue;
100
119
  }
101
- const wheelResult = isWheelEvt && tool.onWheel(event);
102
- const keyPressResult = isKeyPressEvt && tool.onKeyPress(event);
103
- const keyReleaseResult = isKeyReleaseEvt && tool.onKeyUp(event);
104
- handled = keyPressResult || wheelResult || keyReleaseResult;
120
+ switch (event.kind) {
121
+ case InputEvtType.KeyPressEvent:
122
+ handled = tool.onKeyPress(event);
123
+ break;
124
+ case InputEvtType.KeyUpEvent:
125
+ handled = tool.onKeyUp(event);
126
+ break;
127
+ case InputEvtType.WheelEvt:
128
+ handled = tool.onWheel(event);
129
+ break;
130
+ case InputEvtType.CopyEvent:
131
+ handled = tool.onCopy(event);
132
+ break;
133
+ case InputEvtType.PasteEvent:
134
+ handled = tool.onPaste(event);
135
+ break;
136
+ default:
137
+ allCasesHandledGuard = event;
138
+ return allCasesHandledGuard;
139
+ }
105
140
  if (handled) {
106
141
  break;
107
142
  }
108
143
  }
109
144
  }
110
- else if (this.activeTool !== null) {
111
- let allCasesHandledGuard;
112
- switch (event.kind) {
113
- case InputEvtType.PointerMoveEvt:
114
- this.activeTool.onPointerMove(event);
115
- break;
116
- case InputEvtType.GestureCancelEvt:
117
- this.activeTool.onGestureCancel();
118
- this.activeTool = null;
119
- break;
120
- default:
121
- allCasesHandledGuard = event;
122
- return allCasesHandledGuard;
123
- }
124
- handled = true;
125
- }
126
- else {
127
- handled = false;
128
- }
129
145
  return handled;
130
146
  }
131
147
  getMatchingTools(type) {
@@ -0,0 +1,8 @@
1
+ import Editor from '../Editor';
2
+ import { KeyPressEvent } from '../types';
3
+ import BaseTool from './BaseTool';
4
+ export default class ToolSwitcherShortcut extends BaseTool {
5
+ private editor;
6
+ constructor(editor: Editor);
7
+ onKeyPress({ key }: KeyPressEvent): boolean;
8
+ }
@@ -0,0 +1,26 @@
1
+ // Handles ctrl+1, ctrl+2, ctrl+3, ..., shortcuts for switching tools.
2
+ // @packageDocumentation
3
+ import BaseTool from './BaseTool';
4
+ // {@inheritDoc ToolSwitcherShortcut!}
5
+ export default class ToolSwitcherShortcut extends BaseTool {
6
+ constructor(editor) {
7
+ super(editor.notifier, editor.localization.changeTool);
8
+ this.editor = editor;
9
+ }
10
+ onKeyPress({ key }) {
11
+ const toolController = this.editor.toolController;
12
+ const primaryTools = toolController.getPrimaryTools();
13
+ // Map keys 0-9 to primary tools.
14
+ const keyMatch = /^[0-9]$/.exec(key);
15
+ let targetTool;
16
+ if (keyMatch) {
17
+ const targetIdx = parseInt(keyMatch[0], 10) - 1;
18
+ targetTool = primaryTools[targetIdx];
19
+ }
20
+ if (targetTool) {
21
+ targetTool.setEnabled(true);
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ }
@@ -5,8 +5,10 @@ export { default as BaseTool } from './BaseTool';
5
5
  export { default as ToolController } from './ToolController';
6
6
  export { default as ToolEnabledGroup } from './ToolEnabledGroup';
7
7
  export { default as UndoRedoShortcut } from './UndoRedoShortcut';
8
+ export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
8
9
  export { default as PanZoomTool, PanZoomMode } from './PanZoom';
9
10
  export { default as PenTool, PenStyle } from './Pen';
10
11
  export { default as TextTool } from './TextTool';
11
12
  export { default as SelectionTool } from './SelectionTool';
12
13
  export { default as EraserTool } from './Eraser';
14
+ export { default as PasteHandler } from './PasteHandler';
@@ -5,8 +5,10 @@ export { default as BaseTool } from './BaseTool';
5
5
  export { default as ToolController } from './ToolController';
6
6
  export { default as ToolEnabledGroup } from './ToolEnabledGroup';
7
7
  export { default as UndoRedoShortcut } from './UndoRedoShortcut';
8
+ export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
8
9
  export { default as PanZoomTool, PanZoomMode } from './PanZoom';
9
10
  export { default as PenTool } from './Pen';
10
11
  export { default as TextTool } from './TextTool';
11
12
  export { default as SelectionTool } from './SelectionTool';
12
13
  export { default as EraserTool } from './Eraser';
14
+ export { default as PasteHandler } from './PasteHandler';
@@ -10,6 +10,10 @@ export interface ToolLocalization {
10
10
  rightClickDragPanTool: string;
11
11
  textTool: string;
12
12
  enterTextToInsert: string;
13
+ changeTool: string;
14
+ pasteHandler: string;
15
+ copied: (count: number, description: string) => string;
16
+ pasted: (count: number, description: string) => string;
13
17
  toolEnabledAnnouncement: (toolName: string) => string;
14
18
  toolDisabledAnnouncement: (toolName: string) => string;
15
19
  }
@@ -10,6 +10,10 @@ export const defaultToolLocalization = {
10
10
  keyboardPanZoom: 'Keyboard pan/zoom shortcuts',
11
11
  textTool: 'Text',
12
12
  enterTextToInsert: 'Text to insert',
13
+ changeTool: 'Change tool',
14
+ pasteHandler: 'Copy paste handler',
15
+ copied: (count, description) => `Copied ${count} ${description}`,
16
+ pasted: (count, description) => `Pasted ${count} ${description}`,
13
17
  toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
14
18
  toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
15
19
  };
@@ -8,6 +8,7 @@ import Rect2 from './math/Rect2';
8
8
  import Pointer from './Pointer';
9
9
  import Color4 from './Color4';
10
10
  import Command from './commands/Command';
11
+ import { BaseWidget } from './lib';
11
12
  export interface PointerEvtListener {
12
13
  onPointerDown(event: PointerEvt): boolean;
13
14
  onPointerMove(event: PointerEvt): void;
@@ -21,7 +22,9 @@ export declare enum InputEvtType {
21
22
  GestureCancelEvt = 3,
22
23
  WheelEvt = 4,
23
24
  KeyPressEvent = 5,
24
- KeyUpEvent = 6
25
+ KeyUpEvent = 6,
26
+ CopyEvent = 7,
27
+ PasteEvent = 8
25
28
  }
26
29
  export interface WheelEvt {
27
30
  readonly kind: InputEvtType.WheelEvt;
@@ -38,6 +41,15 @@ export interface KeyUpEvent {
38
41
  readonly key: string;
39
42
  readonly ctrlKey: boolean;
40
43
  }
44
+ export interface CopyEvent {
45
+ readonly kind: InputEvtType.CopyEvent;
46
+ setData(mime: string, data: string): void;
47
+ }
48
+ export interface PasteEvent {
49
+ readonly kind: InputEvtType.PasteEvent;
50
+ readonly data: string;
51
+ readonly mime: string;
52
+ }
41
53
  export interface GestureCancelEvt {
42
54
  readonly kind: InputEvtType.GestureCancelEvt;
43
55
  }
@@ -55,7 +67,7 @@ export interface PointerUpEvt extends PointerEvtBase {
55
67
  readonly kind: InputEvtType.PointerUpEvt;
56
68
  }
57
69
  export declare type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
58
- export declare type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt;
70
+ export declare type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt | CopyEvent | PasteEvent;
59
71
  export declare type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
60
72
  export declare enum EditorEventType {
61
73
  ToolEnabled = 0,
@@ -68,7 +80,8 @@ export declare enum EditorEventType {
68
80
  ViewportChanged = 7,
69
81
  DisplayResized = 8,
70
82
  ColorPickerToggled = 9,
71
- ColorPickerColorSelected = 10
83
+ ColorPickerColorSelected = 10,
84
+ ToolbarDropdownShown = 11
72
85
  }
73
86
  declare type EditorToolEventType = EditorEventType.ToolEnabled | EditorEventType.ToolDisabled | EditorEventType.ToolUpdated;
74
87
  export interface EditorToolEvent {
@@ -109,7 +122,11 @@ export interface ColorPickerColorSelected {
109
122
  readonly kind: EditorEventType.ColorPickerColorSelected;
110
123
  readonly color: Color4;
111
124
  }
112
- export declare type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | ColorPickerToggled | ColorPickerColorSelected;
125
+ export interface ToolbarDropdownShownEvent {
126
+ readonly kind: EditorEventType.ToolbarDropdownShown;
127
+ readonly parentWidget: BaseWidget;
128
+ }
129
+ export declare type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
113
130
  export declare type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
114
131
  export declare type ComponentAddedListener = (component: AbstractComponent) => void;
115
132
  export declare type OnDetermineExportRectListener = (exportRect: Rect2) => void;
package/dist/src/types.js CHANGED
@@ -8,6 +8,8 @@ export var InputEvtType;
8
8
  InputEvtType[InputEvtType["WheelEvt"] = 4] = "WheelEvt";
9
9
  InputEvtType[InputEvtType["KeyPressEvent"] = 5] = "KeyPressEvent";
10
10
  InputEvtType[InputEvtType["KeyUpEvent"] = 6] = "KeyUpEvent";
11
+ InputEvtType[InputEvtType["CopyEvent"] = 7] = "CopyEvent";
12
+ InputEvtType[InputEvtType["PasteEvent"] = 8] = "PasteEvent";
11
13
  })(InputEvtType || (InputEvtType = {}));
12
14
  export var EditorEventType;
13
15
  (function (EditorEventType) {
@@ -22,4 +24,5 @@ export var EditorEventType;
22
24
  EditorEventType[EditorEventType["DisplayResized"] = 8] = "DisplayResized";
23
25
  EditorEventType[EditorEventType["ColorPickerToggled"] = 9] = "ColorPickerToggled";
24
26
  EditorEventType[EditorEventType["ColorPickerColorSelected"] = 10] = "ColorPickerColorSelected";
27
+ EditorEventType[EditorEventType["ToolbarDropdownShown"] = 11] = "ToolbarDropdownShown";
25
28
  })(EditorEventType || (EditorEventType = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "./dist/src/lib.d.ts",
6
6
  "types": "./dist/src/lib.js",
@@ -72,7 +72,7 @@
72
72
  "linter-precommit": "eslint --fix --ext .js --ext .ts",
73
73
  "lint-staged": "lint-staged",
74
74
  "prepare": "husky install && yarn build",
75
- "prepack": "yarn build && pinst --disable",
75
+ "prepack": "yarn build && yarn test && pinst --disable",
76
76
  "postpack": "pinst --enable"
77
77
  },
78
78
  "dependencies": {