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.
- package/.github/ISSUE_TEMPLATE/translation.md +4 -1
- package/CHANGELOG.md +15 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +4 -1
- package/dist/src/Editor.js +117 -2
- package/dist/src/EditorImage.js +4 -1
- package/dist/src/SVGLoader.d.ts +4 -1
- package/dist/src/SVGLoader.js +78 -33
- package/dist/src/UndoRedoHistory.d.ts +1 -0
- package/dist/src/UndoRedoHistory.js +6 -0
- package/dist/src/Viewport.d.ts +1 -0
- package/dist/src/Viewport.js +12 -4
- package/dist/src/commands/lib.d.ts +2 -1
- package/dist/src/commands/lib.js +2 -1
- package/dist/src/commands/localization.d.ts +1 -0
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/commands/uniteCommands.d.ts +4 -0
- package/dist/src/commands/uniteCommands.js +105 -0
- package/dist/src/components/AbstractComponent.d.ts +2 -0
- package/dist/src/components/AbstractComponent.js +41 -5
- package/dist/src/components/ImageComponent.d.ts +27 -0
- package/dist/src/components/ImageComponent.js +129 -0
- package/dist/src/components/Stroke.js +11 -6
- package/dist/src/components/builders/FreehandLineBuilder.js +7 -7
- package/dist/src/components/lib.d.ts +4 -2
- package/dist/src/components/lib.js +4 -2
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/math/LineSegment2.d.ts +4 -0
- package/dist/src/math/LineSegment2.js +9 -0
- package/dist/src/math/Path.d.ts +5 -1
- package/dist/src/math/Path.js +89 -7
- package/dist/src/math/Rect2.js +1 -1
- package/dist/src/math/Triangle.d.ts +11 -0
- package/dist/src/math/Triangle.js +19 -0
- package/dist/src/rendering/Display.js +2 -2
- package/dist/src/rendering/localization.d.ts +3 -0
- package/dist/src/rendering/localization.js +3 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +9 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +14 -12
- package/dist/src/rendering/renderers/SVGRenderer.js +71 -87
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -0
- package/dist/src/toolbar/HTMLToolbar.js +1 -0
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
- package/dist/src/tools/BaseTool.d.ts +4 -1
- package/dist/src/tools/BaseTool.js +12 -0
- package/dist/src/tools/PasteHandler.d.ts +16 -0
- package/dist/src/tools/PasteHandler.js +142 -0
- package/dist/src/tools/Pen.d.ts +2 -1
- package/dist/src/tools/Pen.js +16 -0
- package/dist/src/tools/SelectionTool.d.ts +7 -1
- package/dist/src/tools/SelectionTool.js +63 -5
- package/dist/src/tools/ToolController.d.ts +1 -0
- package/dist/src/tools/ToolController.js +45 -29
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
- package/dist/src/tools/lib.d.ts +2 -0
- package/dist/src/tools/lib.js +2 -0
- package/dist/src/tools/localization.d.ts +4 -0
- package/dist/src/tools/localization.js +4 -0
- package/dist/src/types.d.ts +21 -4
- package/dist/src/types.js +3 -0
- package/package.json +2 -2
- package/src/Editor.ts +131 -2
- package/src/EditorImage.ts +7 -1
- package/src/SVGLoader.ts +90 -36
- package/src/UndoRedoHistory.test.ts +33 -0
- package/src/UndoRedoHistory.ts +8 -0
- package/src/Viewport.ts +13 -4
- package/src/commands/lib.ts +2 -0
- package/src/commands/localization.ts +2 -0
- package/src/commands/uniteCommands.test.ts +23 -0
- package/src/commands/uniteCommands.ts +121 -0
- package/src/components/AbstractComponent.ts +55 -9
- package/src/components/ImageComponent.ts +153 -0
- package/src/components/Stroke.test.ts +5 -0
- package/src/components/Stroke.ts +13 -7
- package/src/components/builders/FreehandLineBuilder.ts +7 -7
- package/src/components/lib.ts +7 -2
- package/src/components/localization.ts +4 -0
- package/src/math/LineSegment2.test.ts +9 -0
- package/src/math/LineSegment2.ts +13 -0
- package/src/math/Path.test.ts +53 -0
- package/src/math/Path.toString.test.ts +4 -2
- package/src/math/Path.ts +109 -11
- package/src/math/Rect2.ts +1 -1
- package/src/math/Triangle.ts +29 -0
- package/src/rendering/Display.ts +2 -2
- package/src/rendering/localization.ts +6 -0
- package/src/rendering/renderers/AbstractRenderer.ts +17 -0
- package/src/rendering/renderers/CanvasRenderer.ts +10 -1
- package/src/rendering/renderers/DummyRenderer.ts +6 -1
- package/src/rendering/renderers/SVGRenderer.ts +76 -101
- package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
- package/src/toolbar/HTMLToolbar.ts +1 -1
- package/src/toolbar/types.ts +1 -1
- package/src/toolbar/widgets/BaseWidget.ts +27 -1
- package/src/tools/BaseTool.ts +17 -1
- package/src/tools/PasteHandler.ts +156 -0
- package/src/tools/Pen.ts +20 -1
- package/src/tools/SelectionTool.ts +80 -8
- package/src/tools/ToolController.ts +60 -46
- package/src/tools/ToolSwitcherShortcut.ts +34 -0
- package/src/tools/lib.ts +2 -0
- package/src/tools/localization.ts +10 -0
- 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
|
+
}
|
package/dist/src/tools/Pen.d.ts
CHANGED
@@ -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
|
}
|
package/dist/src/tools/Pen.js
CHANGED
@@ -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.
|
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.
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
+
}
|
package/dist/src/tools/lib.d.ts
CHANGED
@@ -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';
|
package/dist/src/tools/lib.js
CHANGED
@@ -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
|
};
|
package/dist/src/types.d.ts
CHANGED
@@ -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
|
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.
|
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": {
|