js-draw 0.9.1 → 0.9.3
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.yml +742 -0
- package/CHANGELOG.md +7 -0
- package/build_tools/buildTranslationTemplate.ts +119 -0
- package/dist/build_tools/buildTranslationTemplate.d.ts +1 -0
- package/dist/build_tools/buildTranslationTemplate.js +93 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -3
- package/dist/src/Editor.js +7 -15
- package/dist/src/EditorImage.d.ts +1 -1
- package/dist/src/EventDispatcher.d.ts +1 -1
- package/dist/src/SVGLoader.d.ts +2 -2
- package/dist/src/SVGLoader.js +1 -1
- package/dist/src/UndoRedoHistory.d.ts +2 -2
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/Viewport.js +1 -3
- package/dist/src/commands/Command.d.ts +2 -2
- package/dist/src/commands/SerializableCommand.d.ts +1 -1
- package/dist/src/commands/uniteCommands.js +19 -10
- package/dist/src/components/AbstractComponent.d.ts +3 -3
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/src/components/{Text.d.ts → TextComponent.d.ts} +0 -0
- package/dist/src/components/{Text.js → TextComponent.js} +0 -0
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +2 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +10 -2
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/components/lib.d.ts +1 -1
- package/dist/src/components/lib.js +1 -1
- package/dist/src/components/util/StrokeSmoother.d.ts +1 -1
- package/dist/src/math/Mat33.d.ts +1 -1
- package/dist/src/math/Path.d.ts +1 -1
- package/dist/src/math/Vec2.d.ts +2 -2
- package/dist/src/rendering/caching/testUtils.d.ts +1 -1
- package/dist/src/rendering/caching/types.d.ts +2 -2
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +1 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +2 -2
- package/dist/src/toolbar/IconProvider.d.ts +2 -2
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/toolbar/makeColorInput.d.ts +2 -2
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/TextToolWidget.js +23 -2
- package/dist/src/tools/BaseTool.js +4 -4
- package/dist/src/tools/FindTool.d.ts +21 -0
- package/dist/src/tools/FindTool.js +113 -0
- package/dist/src/tools/PipetteTool.d.ts +1 -1
- package/dist/src/tools/SelectionTool/Selection.js +42 -11
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +3 -3
- package/dist/src/tools/SelectionTool/SelectionTool.js +1 -1
- package/dist/src/tools/TextTool.d.ts +1 -1
- package/dist/src/tools/TextTool.js +9 -3
- package/dist/src/tools/ToolController.js +2 -0
- package/dist/src/tools/ToolbarShortcutHandler.d.ts +1 -1
- package/dist/src/tools/localization.d.ts +6 -0
- package/dist/src/tools/localization.js +6 -0
- package/dist/src/types.d.ts +8 -8
- package/package.json +17 -16
- package/src/Editor.css +1 -0
- package/src/Editor.toSVG.test.ts +1 -1
- package/src/Editor.ts +12 -22
- package/src/SVGLoader.ts +1 -1
- package/src/Viewport.ts +1 -3
- package/src/commands/Command.ts +2 -2
- package/src/commands/uniteCommands.ts +21 -10
- package/src/components/{Text.test.ts → TextComponent.test.ts} +1 -1
- package/src/components/{Text.ts → TextComponent.ts} +0 -0
- package/src/components/builders/FreehandLineBuilder.ts +12 -2
- package/src/components/lib.ts +1 -1
- package/src/rendering/renderers/AbstractRenderer.ts +1 -1
- package/src/rendering/renderers/CanvasRenderer.ts +1 -1
- package/src/rendering/renderers/DummyRenderer.ts +1 -1
- package/src/rendering/renderers/SVGRenderer.ts +2 -2
- package/src/rendering/renderers/TextOnlyRenderer.ts +3 -3
- package/src/toolbar/IconProvider.ts +1 -1
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/widgets/TextToolWidget.ts +29 -1
- package/src/tools/FindTool.css +7 -0
- package/src/tools/FindTool.ts +151 -0
- package/src/tools/PasteHandler.ts +1 -1
- package/src/tools/SelectionTool/Selection.ts +51 -10
- package/src/tools/SelectionTool/SelectionTool.ts +1 -1
- package/src/tools/TextTool.ts +11 -3
- package/src/tools/ToolController.ts +2 -0
- package/src/tools/localization.ts +14 -0
- package/.github/ISSUE_TEMPLATE/translation.md +0 -100
@@ -0,0 +1,113 @@
|
|
1
|
+
// Displays a find dialog that allows the user to search for and focus text.
|
2
|
+
//
|
3
|
+
// @packageDocumentation
|
4
|
+
import TextComponent from '../components/TextComponent';
|
5
|
+
import BaseTool from './BaseTool';
|
6
|
+
export const cssPrefix = 'find-tool';
|
7
|
+
export default class FindTool extends BaseTool {
|
8
|
+
constructor(editor) {
|
9
|
+
super(editor.notifier, editor.localization.findLabel);
|
10
|
+
this.editor = editor;
|
11
|
+
this.currentMatchIdx = 0;
|
12
|
+
this.overlay = document.createElement('div');
|
13
|
+
this.fillOverlay();
|
14
|
+
editor.createHTMLOverlay(this.overlay);
|
15
|
+
this.overlay.style.display = 'none';
|
16
|
+
this.overlay.classList.add(`${cssPrefix}-overlay`);
|
17
|
+
}
|
18
|
+
getMatches(searchFor) {
|
19
|
+
searchFor = searchFor.toLocaleLowerCase();
|
20
|
+
const allTextComponents = this.editor.image.getAllElements()
|
21
|
+
.filter(elem => elem instanceof TextComponent);
|
22
|
+
const matches = allTextComponents.filter(text => text.getText().toLocaleLowerCase().indexOf(searchFor) !== -1);
|
23
|
+
return matches.map(match => match.getBBox());
|
24
|
+
}
|
25
|
+
focusCurrentMatch() {
|
26
|
+
const matches = this.getMatches(this.searchInput.value);
|
27
|
+
let matchIdx = this.currentMatchIdx % matches.length;
|
28
|
+
if (matchIdx < 0) {
|
29
|
+
matchIdx = matches.length + matchIdx;
|
30
|
+
}
|
31
|
+
if (matchIdx < matches.length) {
|
32
|
+
this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true));
|
33
|
+
this.editor.announceForAccessibility(this.editor.localization.focusedFoundText(matchIdx + 1, matches.length));
|
34
|
+
}
|
35
|
+
}
|
36
|
+
toNextMatch() {
|
37
|
+
this.currentMatchIdx++;
|
38
|
+
this.focusCurrentMatch();
|
39
|
+
}
|
40
|
+
toPrevMatch() {
|
41
|
+
this.currentMatchIdx--;
|
42
|
+
this.focusCurrentMatch();
|
43
|
+
}
|
44
|
+
fillOverlay() {
|
45
|
+
const label = document.createElement('label');
|
46
|
+
this.searchInput = document.createElement('input');
|
47
|
+
const nextBtn = document.createElement('button');
|
48
|
+
const closeBtn = document.createElement('button');
|
49
|
+
// Math.random() ensures that the ID is unique (to allow us to refer to it
|
50
|
+
// with an htmlFor).
|
51
|
+
this.searchInput.setAttribute('id', `${cssPrefix}-searchInput-${Math.random()}`);
|
52
|
+
label.htmlFor = this.searchInput.getAttribute('id');
|
53
|
+
label.innerText = this.editor.localization.findLabel;
|
54
|
+
nextBtn.innerText = this.editor.localization.toNextMatch;
|
55
|
+
closeBtn.innerText = this.editor.localization.closeFindDialog;
|
56
|
+
this.searchInput.onkeydown = (ev) => {
|
57
|
+
if (ev.key === 'Enter') {
|
58
|
+
if (ev.shiftKey) {
|
59
|
+
this.toPrevMatch();
|
60
|
+
}
|
61
|
+
else {
|
62
|
+
this.toNextMatch();
|
63
|
+
}
|
64
|
+
}
|
65
|
+
else if (ev.key === 'Escape') {
|
66
|
+
this.setVisible(false);
|
67
|
+
}
|
68
|
+
else if (ev.key === 'f' && ev.ctrlKey) {
|
69
|
+
ev.preventDefault();
|
70
|
+
this.toggleVisible();
|
71
|
+
}
|
72
|
+
};
|
73
|
+
nextBtn.onclick = () => {
|
74
|
+
this.toNextMatch();
|
75
|
+
};
|
76
|
+
closeBtn.onclick = () => {
|
77
|
+
this.setVisible(false);
|
78
|
+
};
|
79
|
+
this.overlay.replaceChildren(label, this.searchInput, nextBtn, closeBtn);
|
80
|
+
}
|
81
|
+
isVisible() {
|
82
|
+
return this.overlay.style.display !== 'none';
|
83
|
+
}
|
84
|
+
setVisible(visible) {
|
85
|
+
if (visible !== this.isVisible()) {
|
86
|
+
this.overlay.style.display = visible ? 'block' : 'none';
|
87
|
+
if (visible) {
|
88
|
+
this.searchInput.focus();
|
89
|
+
this.editor.announceForAccessibility(this.editor.localization.findDialogShown);
|
90
|
+
}
|
91
|
+
else {
|
92
|
+
this.editor.focus();
|
93
|
+
this.editor.announceForAccessibility(this.editor.localization.findDialogHidden);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
97
|
+
toggleVisible() {
|
98
|
+
this.setVisible(!this.isVisible());
|
99
|
+
}
|
100
|
+
onKeyPress(event) {
|
101
|
+
if (event.ctrlKey && event.key === 'f') {
|
102
|
+
this.toggleVisible();
|
103
|
+
return true;
|
104
|
+
}
|
105
|
+
return false;
|
106
|
+
}
|
107
|
+
setEnabled(enabled) {
|
108
|
+
super.setEnabled(enabled);
|
109
|
+
if (enabled) {
|
110
|
+
this.setVisible(false);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
}
|
@@ -2,7 +2,7 @@ import Color4 from '../Color4';
|
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import { PointerEvt } from '../types';
|
4
4
|
import BaseTool from './BaseTool';
|
5
|
-
|
5
|
+
type ColorListener = (color: Color4 | null) => void;
|
6
6
|
export default class PipetteTool extends BaseTool {
|
7
7
|
private editor;
|
8
8
|
private colorPreviewListener;
|
@@ -108,13 +108,13 @@ export default class Selection {
|
|
108
108
|
cmd.unapply(this.editor);
|
109
109
|
});
|
110
110
|
const fullTransform = this.transform;
|
111
|
-
const
|
111
|
+
const selectedElems = this.selectedElems;
|
112
112
|
// Reset for the next drag
|
113
113
|
this.transformCommands = [];
|
114
114
|
this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
|
115
115
|
this.transform = Mat33.identity;
|
116
116
|
// Make the commands undo-able
|
117
|
-
this.editor.dispatch(new Selection.ApplyTransformationCommand(this,
|
117
|
+
this.editor.dispatch(new Selection.ApplyTransformationCommand(this, selectedElems, fullTransform));
|
118
118
|
}
|
119
119
|
// Preview the effects of the current transformation on the selection
|
120
120
|
previewTransformCmds() {
|
@@ -296,26 +296,56 @@ export default class Selection {
|
|
296
296
|
}
|
297
297
|
_a = Selection;
|
298
298
|
(() => {
|
299
|
-
SerializableCommand.register('selection-tool-transform', (json,
|
299
|
+
SerializableCommand.register('selection-tool-transform', (json, _editor) => {
|
300
|
+
var _b;
|
300
301
|
// The selection box is lost when serializing/deserializing. No need to store box rotation
|
301
302
|
const fullTransform = new Mat33(...json.transform);
|
302
|
-
const
|
303
|
-
return new _a.ApplyTransformationCommand(null,
|
303
|
+
const elemIds = ((_b = json.elems) !== null && _b !== void 0 ? _b : []);
|
304
|
+
return new _a.ApplyTransformationCommand(null, elemIds, fullTransform);
|
304
305
|
});
|
305
306
|
})();
|
306
307
|
Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
307
|
-
constructor(selection,
|
308
|
+
constructor(selection,
|
309
|
+
// If a `string[]`, selectedElems is a list of element IDs.
|
310
|
+
selectedElems,
|
311
|
+
// Full transformation used to transform elements.
|
312
|
+
fullTransform) {
|
308
313
|
super('selection-tool-transform');
|
309
314
|
this.selection = selection;
|
310
|
-
this.currentTransfmCommands = currentTransfmCommands;
|
311
315
|
this.fullTransform = fullTransform;
|
316
|
+
const isIDList = (arr) => {
|
317
|
+
return typeof arr[0] === 'string';
|
318
|
+
};
|
319
|
+
// If a list of element IDs,
|
320
|
+
if (isIDList(selectedElems)) {
|
321
|
+
this.selectedElemIds = selectedElems;
|
322
|
+
}
|
323
|
+
else {
|
324
|
+
this.selectedElemIds = selectedElems.map(elem => elem.getId());
|
325
|
+
this.transformCommands = selectedElems.map(elem => {
|
326
|
+
return elem.transformBy(this.fullTransform);
|
327
|
+
});
|
328
|
+
}
|
329
|
+
}
|
330
|
+
resolveToElems(editor) {
|
331
|
+
if (this.transformCommands) {
|
332
|
+
return;
|
333
|
+
}
|
334
|
+
this.transformCommands = this.selectedElemIds.map(id => {
|
335
|
+
const elem = editor.image.lookupElement(id);
|
336
|
+
if (!elem) {
|
337
|
+
throw new Error(`Unable to find element with ID, ${id}.`);
|
338
|
+
}
|
339
|
+
return elem.transformBy(this.fullTransform);
|
340
|
+
});
|
312
341
|
}
|
313
342
|
apply(editor) {
|
314
343
|
var _b, _c, _d, _e, _f;
|
315
344
|
return __awaiter(this, void 0, void 0, function* () {
|
345
|
+
this.resolveToElems(editor);
|
316
346
|
(_b = this.selection) === null || _b === void 0 ? void 0 : _b.setTransform(this.fullTransform, false);
|
317
347
|
(_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
|
318
|
-
yield editor.asyncApplyCommands(this.
|
348
|
+
yield editor.asyncApplyCommands(this.transformCommands, updateChunkSize);
|
319
349
|
(_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33.identity, false);
|
320
350
|
(_e = this.selection) === null || _e === void 0 ? void 0 : _e.recomputeRegion();
|
321
351
|
(_f = this.selection) === null || _f === void 0 ? void 0 : _f.updateUI();
|
@@ -324,9 +354,10 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
324
354
|
unapply(editor) {
|
325
355
|
var _b, _c, _d, _e, _f;
|
326
356
|
return __awaiter(this, void 0, void 0, function* () {
|
357
|
+
this.resolveToElems(editor);
|
327
358
|
(_b = this.selection) === null || _b === void 0 ? void 0 : _b.setTransform(this.fullTransform.inverse(), false);
|
328
359
|
(_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
|
329
|
-
yield editor.asyncUnapplyCommands(this.
|
360
|
+
yield editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize);
|
330
361
|
(_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33.identity);
|
331
362
|
(_e = this.selection) === null || _e === void 0 ? void 0 : _e.recomputeRegion();
|
332
363
|
(_f = this.selection) === null || _f === void 0 ? void 0 : _f.updateUI();
|
@@ -334,11 +365,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
334
365
|
}
|
335
366
|
serializeToJSON() {
|
336
367
|
return {
|
337
|
-
|
368
|
+
elems: this.selectedElemIds,
|
338
369
|
transform: this.fullTransform.toArray(),
|
339
370
|
};
|
340
371
|
}
|
341
372
|
description(_editor, localizationTable) {
|
342
|
-
return localizationTable.transformedElements(this.
|
373
|
+
return localizationTable.transformedElements(this.selectedElemIds.length);
|
343
374
|
}
|
344
375
|
};
|
@@ -6,9 +6,9 @@ export declare enum HandleShape {
|
|
6
6
|
Square = 1
|
7
7
|
}
|
8
8
|
export declare const handleSize = 30;
|
9
|
-
export
|
10
|
-
export
|
11
|
-
export
|
9
|
+
export type DragStartCallback = (startPoint: Point2) => void;
|
10
|
+
export type DragUpdateCallback = (canvasPoint: Point2) => void;
|
11
|
+
export type DragEndCallback = () => void;
|
12
12
|
export default class SelectionHandle {
|
13
13
|
readonly shape: HandleShape;
|
14
14
|
private readonly parentSide;
|
@@ -8,7 +8,7 @@ import Viewport from '../../Viewport';
|
|
8
8
|
import BaseTool from '../BaseTool';
|
9
9
|
import SVGRenderer from '../../rendering/renderers/SVGRenderer';
|
10
10
|
import Selection from './Selection';
|
11
|
-
import TextComponent from '../../components/
|
11
|
+
import TextComponent from '../../components/TextComponent';
|
12
12
|
export const cssPrefix = 'selection-tool-';
|
13
13
|
// {@inheritDoc SelectionTool!}
|
14
14
|
export default class SelectionTool extends BaseTool {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
|
-
import TextComponent from '../components/
|
2
|
+
import TextComponent from '../components/TextComponent';
|
3
3
|
import EditorImage from '../EditorImage';
|
4
4
|
import Rect2 from '../math/Rect2';
|
5
5
|
import Mat33 from '../math/Mat33';
|
@@ -108,8 +108,10 @@ export default class TextTool extends BaseTool {
|
|
108
108
|
this.textInputElem.style.margin = '0';
|
109
109
|
this.textInputElem.style.width = `${this.textInputElem.scrollWidth}px`;
|
110
110
|
this.textInputElem.style.height = `${this.textInputElem.scrollHeight}px`;
|
111
|
+
// Get the ascent based on the font, using a character that is tall in most fonts.
|
112
|
+
const tallCharacter = '⎢';
|
113
|
+
const ascent = this.getTextAscent(tallCharacter, this.textStyle);
|
111
114
|
const rotation = this.textRotation + viewport.getRotationAngle();
|
112
|
-
const ascent = this.getTextAscent(this.textInputElem.value || 'W', this.textStyle);
|
113
115
|
const scale = this.getTextScaleMatrix();
|
114
116
|
this.textInputElem.style.transform = `${scale.toCSSMatrix()} rotate(${rotation * 180 / Math.PI}deg) translate(0, ${-ascent}px)`;
|
115
117
|
this.textInputElem.style.transformOrigin = 'top left';
|
@@ -171,7 +173,11 @@ export default class TextTool extends BaseTool {
|
|
171
173
|
const halfTestRegionSize = Vec2.of(2.5, 2.5).times(this.editor.viewport.getSizeOfPixelOnCanvas());
|
172
174
|
const testRegion = Rect2.fromCorners(canvasPos.minus(halfTestRegionSize), canvasPos.plus(halfTestRegionSize));
|
173
175
|
const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
|
174
|
-
|
176
|
+
let targetTextNodes = targetNodes.filter(node => node instanceof TextComponent);
|
177
|
+
// Don't try to edit text nodes that contain the viewport (this allows us
|
178
|
+
// to zoom in on text nodes and add text on top of them.)
|
179
|
+
const visibleRect = this.editor.viewport.visibleRect;
|
180
|
+
targetTextNodes = targetTextNodes.filter(node => !node.getBBox().containsRect(visibleRect));
|
175
181
|
// End any TextNodes we're currently editing.
|
176
182
|
this.flushInput();
|
177
183
|
if (targetTextNodes.length > 0) {
|
@@ -12,6 +12,7 @@ import ToolSwitcherShortcut from './ToolSwitcherShortcut';
|
|
12
12
|
import PasteHandler from './PasteHandler';
|
13
13
|
import ToolbarShortcutHandler from './ToolbarShortcutHandler';
|
14
14
|
import { makePressureSensitiveFreehandLineBuilder } from '../components/builders/PressureSensitiveFreehandLineBuilder';
|
15
|
+
import FindTool from './FindTool';
|
15
16
|
export default class ToolController {
|
16
17
|
/** @internal */
|
17
18
|
constructor(editor, localization) {
|
@@ -40,6 +41,7 @@ export default class ToolController {
|
|
40
41
|
new UndoRedoShortcut(editor),
|
41
42
|
new ToolbarShortcutHandler(editor),
|
42
43
|
new ToolSwitcherShortcut(editor),
|
44
|
+
new FindTool(editor),
|
43
45
|
new PasteHandler(editor),
|
44
46
|
];
|
45
47
|
primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import { KeyPressEvent } from '../types';
|
3
3
|
import BaseTool from './BaseTool';
|
4
|
-
|
4
|
+
type KeyPressListener = (event: KeyPressEvent) => boolean;
|
5
5
|
export default class ToolbarShortcutHandler extends BaseTool {
|
6
6
|
private listeners;
|
7
7
|
constructor(editor: Editor);
|
@@ -12,6 +12,12 @@ export interface ToolLocalization {
|
|
12
12
|
enterTextToInsert: string;
|
13
13
|
changeTool: string;
|
14
14
|
pasteHandler: string;
|
15
|
+
findLabel: string;
|
16
|
+
toNextMatch: string;
|
17
|
+
closeFindDialog: string;
|
18
|
+
findDialogShown: string;
|
19
|
+
findDialogHidden: string;
|
20
|
+
focusedFoundText: (currentMatchNumber: number, totalMatches: number) => string;
|
15
21
|
anyDevicePanning: string;
|
16
22
|
copied: (count: number, description: string) => string;
|
17
23
|
pasted: (count: number, description: string) => string;
|
@@ -12,6 +12,12 @@ export const defaultToolLocalization = {
|
|
12
12
|
enterTextToInsert: 'Text to insert',
|
13
13
|
changeTool: 'Change tool',
|
14
14
|
pasteHandler: 'Copy paste handler',
|
15
|
+
findLabel: 'Find',
|
16
|
+
toNextMatch: 'Next',
|
17
|
+
closeFindDialog: 'Close',
|
18
|
+
findDialogShown: 'Find dialog shown',
|
19
|
+
findDialogHidden: 'Find dialog hidden',
|
20
|
+
focusedFoundText: (matchIdx, totalMatches) => `Viewing match ${matchIdx} of ${totalMatches}`,
|
15
21
|
anyDevicePanning: 'Any device panning',
|
16
22
|
copied: (count, description) => `Copied ${count} ${description}`,
|
17
23
|
pasted: (count, description) => `Pasted ${count} ${description}`,
|
package/dist/src/types.d.ts
CHANGED
@@ -68,9 +68,9 @@ export interface PointerMoveEvt extends PointerEvtBase {
|
|
68
68
|
export interface PointerUpEvt extends PointerEvtBase {
|
69
69
|
readonly kind: InputEvtType.PointerUpEvt;
|
70
70
|
}
|
71
|
-
export
|
72
|
-
export
|
73
|
-
export
|
71
|
+
export type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
|
72
|
+
export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt | CopyEvent | PasteEvent;
|
73
|
+
export type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
|
74
74
|
export declare enum EditorEventType {
|
75
75
|
ToolEnabled = 0,
|
76
76
|
ToolDisabled = 1,
|
@@ -85,7 +85,7 @@ export declare enum EditorEventType {
|
|
85
85
|
ColorPickerColorSelected = 10,
|
86
86
|
ToolbarDropdownShown = 11
|
87
87
|
}
|
88
|
-
|
88
|
+
type EditorToolEventType = EditorEventType.ToolEnabled | EditorEventType.ToolDisabled | EditorEventType.ToolUpdated;
|
89
89
|
export interface EditorToolEvent {
|
90
90
|
readonly kind: EditorToolEventType;
|
91
91
|
readonly tool: BaseTool;
|
@@ -128,10 +128,10 @@ export interface ToolbarDropdownShownEvent {
|
|
128
128
|
readonly kind: EditorEventType.ToolbarDropdownShown;
|
129
129
|
readonly parentWidget: BaseWidget;
|
130
130
|
}
|
131
|
-
export
|
132
|
-
export
|
133
|
-
export
|
134
|
-
export
|
131
|
+
export type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
|
132
|
+
export type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
|
133
|
+
export type ComponentAddedListener = (component: AbstractComponent) => void;
|
134
|
+
export type OnDetermineExportRectListener = (exportRect: Rect2) => void;
|
135
135
|
export interface ImageLoader {
|
136
136
|
start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener): Promise<void>;
|
137
137
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "0.9.
|
3
|
+
"version": "0.9.3",
|
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,6 +72,7 @@
|
|
72
72
|
"linter-precommit": "eslint --fix --ext .js --ext .ts",
|
73
73
|
"lint-staged": "lint-staged",
|
74
74
|
"lint-ci": "eslint . --max-warnings=0 --ext .js --ext .ts",
|
75
|
+
"build-translation-template": "ts-node ./build_tools/buildTranslationTemplate.ts",
|
75
76
|
"prepare": "husky install && yarn build",
|
76
77
|
"prepack": "yarn build && yarn test && pinst --disable",
|
77
78
|
"postpack": "pinst --enable"
|
@@ -82,27 +83,27 @@
|
|
82
83
|
},
|
83
84
|
"devDependencies": {
|
84
85
|
"@types/bezier-js": "^4.1.0",
|
85
|
-
"@types/jest": "^29.
|
86
|
-
"@types/jsdom": "^20.0.
|
87
|
-
"@types/node": "^18.
|
88
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
89
|
-
"@typescript-eslint/parser": "^5.
|
90
|
-
"css-loader": "^6.7.
|
91
|
-
"eslint": "^8.
|
92
|
-
"husky": "^8.0.
|
93
|
-
"jest": "^29.
|
94
|
-
"jest-environment-jsdom": "^29.
|
95
|
-
"jsdom": "^20.0.
|
86
|
+
"@types/jest": "^29.2.3",
|
87
|
+
"@types/jsdom": "^20.0.1",
|
88
|
+
"@types/node": "^18.11.9",
|
89
|
+
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
90
|
+
"@typescript-eslint/parser": "^5.44.0",
|
91
|
+
"css-loader": "^6.7.2",
|
92
|
+
"eslint": "^8.28.0",
|
93
|
+
"husky": "^8.0.2",
|
94
|
+
"jest": "^29.2.3",
|
95
|
+
"jest-environment-jsdom": "^29.3.1",
|
96
|
+
"jsdom": "^20.0.3",
|
96
97
|
"lint-staged": "^13.0.3",
|
97
98
|
"pinst": "^3.0.0",
|
98
99
|
"style-loader": "^3.3.1",
|
99
100
|
"terser-webpack-plugin": "^5.3.6",
|
100
|
-
"ts-jest": "^29.0.
|
101
|
+
"ts-jest": "^29.0.3",
|
101
102
|
"ts-loader": "^9.4.1",
|
102
103
|
"ts-node": "^10.9.1",
|
103
|
-
"typedoc": "^0.23.
|
104
|
-
"typescript": "^4.
|
105
|
-
"webpack": "^5.
|
104
|
+
"typedoc": "^0.23.21",
|
105
|
+
"typescript": "^4.9.3",
|
106
|
+
"webpack": "^5.75.0"
|
106
107
|
},
|
107
108
|
"bugs": {
|
108
109
|
"url": "https://github.com/personalizedrefrigerator/js-draw/issues"
|
package/src/Editor.css
CHANGED
package/src/Editor.toSVG.test.ts
CHANGED
package/src/Editor.ts
CHANGED
@@ -27,7 +27,7 @@ import EventDispatcher from './EventDispatcher';
|
|
27
27
|
import { Point2, Vec2 } from './math/Vec2';
|
28
28
|
import Vec3 from './math/Vec3';
|
29
29
|
import HTMLToolbar from './toolbar/HTMLToolbar';
|
30
|
-
import
|
30
|
+
import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';
|
31
31
|
import Display, { RenderingMode } from './rendering/Display';
|
32
32
|
import SVGRenderer from './rendering/renderers/SVGRenderer';
|
33
33
|
import Color4 from './Color4';
|
@@ -831,7 +831,7 @@ export class Editor {
|
|
831
831
|
// The export resolution is the same as the size of the drawing canvas.
|
832
832
|
public toDataURL(format: 'image/png'|'image/jpeg'|'image/webp' = 'image/png'): string {
|
833
833
|
const canvas = document.createElement('canvas');
|
834
|
-
|
834
|
+
|
835
835
|
const resolution = this.importExportViewport.getResolution();
|
836
836
|
|
837
837
|
canvas.width = resolution.x;
|
@@ -840,8 +840,7 @@ export class Editor {
|
|
840
840
|
const ctx = canvas.getContext('2d')!;
|
841
841
|
const renderer = new CanvasRenderer(ctx, this.importExportViewport);
|
842
842
|
|
843
|
-
|
844
|
-
this.renderAllWithTransform(renderer, this.importExportViewport, Mat33.identity);
|
843
|
+
this.image.renderAll(renderer);
|
845
844
|
|
846
845
|
const dataURL = canvas.toDataURL(format);
|
847
846
|
return dataURL;
|
@@ -853,7 +852,15 @@ export class Editor {
|
|
853
852
|
const result = document.createElementNS(svgNameSpace, 'svg');
|
854
853
|
const renderer = new SVGRenderer(result, importExportViewport);
|
855
854
|
|
856
|
-
this.
|
855
|
+
const origTransform = this.importExportViewport.canvasToScreenTransform;
|
856
|
+
// Render with (0,0) at (0,0) — we'll handle translation with
|
857
|
+
// the viewBox property.
|
858
|
+
importExportViewport.resetTransform(Mat33.identity);
|
859
|
+
|
860
|
+
this.image.renderAll(renderer);
|
861
|
+
|
862
|
+
importExportViewport.resetTransform(origTransform);
|
863
|
+
|
857
864
|
|
858
865
|
// Just show the main region
|
859
866
|
const rect = importExportViewport.visibleRect;
|
@@ -871,23 +878,6 @@ export class Editor {
|
|
871
878
|
return result;
|
872
879
|
}
|
873
880
|
|
874
|
-
// Renders everything in this' image to `renderer`, but first transforming the given `viewport`
|
875
|
-
// such that its transform is `transform`. The given `viewport`'s transform is restored before this method
|
876
|
-
// returns.
|
877
|
-
//
|
878
|
-
// For example, rendering with `transform = Mat33.identity` *sets* `viewport`'s transform to `Mat33.identity`,
|
879
|
-
// renders everything in this' image to `renderer`, then restores `viewport`'s transform to whatever it was before.
|
880
|
-
private renderAllWithTransform(
|
881
|
-
renderer: AbstractRenderer, viewport: Viewport, transform: Mat33 = Mat33.identity
|
882
|
-
): void {
|
883
|
-
const origTransform = this.importExportViewport.canvasToScreenTransform;
|
884
|
-
viewport.resetTransform(transform);
|
885
|
-
|
886
|
-
this.image.renderAll(renderer);
|
887
|
-
|
888
|
-
viewport.resetTransform(origTransform);
|
889
|
-
}
|
890
|
-
|
891
881
|
public async loadFrom(loader: ImageLoader) {
|
892
882
|
this.showLoadingWarning(0);
|
893
883
|
this.display.setDraftMode(true);
|
package/src/SVGLoader.ts
CHANGED
@@ -3,7 +3,7 @@ import AbstractComponent from './components/AbstractComponent';
|
|
3
3
|
import ImageComponent from './components/ImageComponent';
|
4
4
|
import Stroke from './components/Stroke';
|
5
5
|
import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
|
6
|
-
import TextComponent, { TextStyle } from './components/
|
6
|
+
import TextComponent, { TextStyle } from './components/TextComponent';
|
7
7
|
import UnknownSVGObject from './components/UnknownSVGObject';
|
8
8
|
import Mat33 from './math/Mat33';
|
9
9
|
import Path from './math/Path';
|
package/src/Viewport.ts
CHANGED
@@ -223,9 +223,7 @@ export class Viewport {
|
|
223
223
|
const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension < 1/3;
|
224
224
|
|
225
225
|
if ((largerThanTarget && allowZoomOut) || (muchSmallerThanTarget && allowZoomIn)) {
|
226
|
-
|
227
|
-
// If smaller, shrink the visible rectangle as much as possible
|
228
|
-
const multiplier = (largerThanTarget ? Math.max : Math.min)(
|
226
|
+
const multiplier = Math.max(
|
229
227
|
toMakeVisible.w / targetRect.w, toMakeVisible.h / targetRect.h
|
230
228
|
);
|
231
229
|
const visibleRectTransform = Mat33.scaling2D(multiplier, targetRect.topLeft);
|
package/src/commands/Command.ts
CHANGED
@@ -2,8 +2,8 @@ import Editor from '../Editor';
|
|
2
2
|
import { EditorLocalization } from '../localization';
|
3
3
|
|
4
4
|
export abstract class Command {
|
5
|
-
public abstract apply(editor: Editor): void;
|
6
|
-
public abstract unapply(editor: Editor): void;
|
5
|
+
public abstract apply(editor: Editor): Promise<void>|void;
|
6
|
+
public abstract unapply(editor: Editor): Promise<void>|void;
|
7
7
|
|
8
8
|
// Called when the command is being deleted
|
9
9
|
public onDrop(_editor: Editor) { }
|
@@ -9,23 +9,34 @@ class NonSerializableUnion extends Command {
|
|
9
9
|
super();
|
10
10
|
}
|
11
11
|
|
12
|
+
private static waitForAll(commands: (Promise<void>|void)[]): Promise<void>|void {
|
13
|
+
// If any are Promises...
|
14
|
+
if (commands.some(command => command && command['then'])) {
|
15
|
+
console.log('waiting...');
|
16
|
+
// Wait for all commands to finish.
|
17
|
+
return Promise.all(commands)
|
18
|
+
// Ensure we return a Promise<void> and not a Promise<void[]>
|
19
|
+
.then(() => {});
|
20
|
+
}
|
21
|
+
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
12
25
|
public apply(editor: Editor) {
|
13
26
|
if (this.applyChunkSize === undefined) {
|
14
|
-
|
15
|
-
|
16
|
-
}
|
27
|
+
const results = this.commands.map(cmd => cmd.apply(editor));
|
28
|
+
return NonSerializableUnion.waitForAll(results);
|
17
29
|
} else {
|
18
|
-
editor.asyncApplyCommands(this.commands, this.applyChunkSize);
|
30
|
+
return editor.asyncApplyCommands(this.commands, this.applyChunkSize);
|
19
31
|
}
|
20
32
|
}
|
21
33
|
|
22
34
|
public unapply(editor: Editor) {
|
23
35
|
if (this.applyChunkSize === undefined) {
|
24
|
-
|
25
|
-
|
26
|
-
}
|
36
|
+
const results = this.commands.map(cmd => cmd.unapply(editor));
|
37
|
+
return NonSerializableUnion.waitForAll(results);
|
27
38
|
} else {
|
28
|
-
editor.asyncUnapplyCommands(this.commands, this.applyChunkSize);
|
39
|
+
return editor.asyncUnapplyCommands(this.commands, this.applyChunkSize);
|
29
40
|
}
|
30
41
|
}
|
31
42
|
|
@@ -71,11 +82,11 @@ class SerializableUnion extends SerializableCommand {
|
|
71
82
|
}
|
72
83
|
|
73
84
|
public apply(editor: Editor) {
|
74
|
-
this.nonserializableCommand.apply(editor);
|
85
|
+
return this.nonserializableCommand.apply(editor);
|
75
86
|
}
|
76
87
|
|
77
88
|
public unapply(editor: Editor) {
|
78
|
-
this.nonserializableCommand.unapply(editor);
|
89
|
+
return this.nonserializableCommand.unapply(editor);
|
79
90
|
}
|
80
91
|
|
81
92
|
public description(editor: Editor, localizationTable: EditorLocalization): string {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import Mat33 from '../math/Mat33';
|
3
3
|
import AbstractComponent from './AbstractComponent';
|
4
|
-
import TextComponent, { TextStyle } from './
|
4
|
+
import TextComponent, { TextStyle } from './TextComponent';
|
5
5
|
|
6
6
|
|
7
7
|
describe('Text', () => {
|
File without changes
|