js-draw 0.9.1 → 0.9.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 (59) hide show
  1. package/.github/ISSUE_TEMPLATE/translation.yml +742 -0
  2. package/CHANGELOG.md +3 -0
  3. package/build_tools/buildTranslationTemplate.ts +119 -0
  4. package/dist/build_tools/buildTranslationTemplate.d.ts +1 -0
  5. package/dist/build_tools/buildTranslationTemplate.js +93 -0
  6. package/dist/bundle.js +1 -1
  7. package/dist/src/Editor.d.ts +0 -1
  8. package/dist/src/Editor.js +7 -15
  9. package/dist/src/SVGLoader.js +1 -1
  10. package/dist/src/Viewport.js +1 -3
  11. package/dist/src/commands/Command.d.ts +2 -2
  12. package/dist/src/commands/uniteCommands.js +19 -10
  13. package/dist/src/components/{Text.d.ts → TextComponent.d.ts} +0 -0
  14. package/dist/src/components/{Text.js → TextComponent.js} +0 -0
  15. package/dist/src/components/lib.d.ts +1 -1
  16. package/dist/src/components/lib.js +1 -1
  17. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +1 -1
  18. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +1 -1
  19. package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
  20. package/dist/src/rendering/renderers/DummyRenderer.d.ts +1 -1
  21. package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
  22. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +1 -1
  23. package/dist/src/rendering/renderers/TextOnlyRenderer.js +2 -2
  24. package/dist/src/toolbar/IconProvider.d.ts +1 -1
  25. package/dist/src/tools/FindTool.d.ts +21 -0
  26. package/dist/src/tools/FindTool.js +113 -0
  27. package/dist/src/tools/SelectionTool/Selection.js +42 -11
  28. package/dist/src/tools/SelectionTool/SelectionTool.js +1 -1
  29. package/dist/src/tools/TextTool.d.ts +1 -1
  30. package/dist/src/tools/TextTool.js +1 -1
  31. package/dist/src/tools/ToolController.js +2 -0
  32. package/dist/src/tools/localization.d.ts +6 -0
  33. package/dist/src/tools/localization.js +6 -0
  34. package/package.json +2 -1
  35. package/src/Editor.css +1 -0
  36. package/src/Editor.toSVG.test.ts +1 -1
  37. package/src/Editor.ts +12 -22
  38. package/src/SVGLoader.ts +1 -1
  39. package/src/Viewport.ts +1 -3
  40. package/src/commands/Command.ts +2 -2
  41. package/src/commands/uniteCommands.ts +21 -10
  42. package/src/components/{Text.test.ts → TextComponent.test.ts} +1 -1
  43. package/src/components/{Text.ts → TextComponent.ts} +0 -0
  44. package/src/components/lib.ts +1 -1
  45. package/src/rendering/renderers/AbstractRenderer.ts +1 -1
  46. package/src/rendering/renderers/CanvasRenderer.ts +1 -1
  47. package/src/rendering/renderers/DummyRenderer.ts +1 -1
  48. package/src/rendering/renderers/SVGRenderer.ts +1 -1
  49. package/src/rendering/renderers/TextOnlyRenderer.ts +3 -3
  50. package/src/toolbar/IconProvider.ts +1 -1
  51. package/src/tools/FindTool.css +7 -0
  52. package/src/tools/FindTool.ts +151 -0
  53. package/src/tools/PasteHandler.ts +1 -1
  54. package/src/tools/SelectionTool/Selection.ts +51 -10
  55. package/src/tools/SelectionTool/SelectionTool.ts +1 -1
  56. package/src/tools/TextTool.ts +1 -1
  57. package/src/tools/ToolController.ts +2 -0
  58. package/src/tools/localization.ts +14 -0
  59. package/.github/ISSUE_TEMPLATE/translation.md +0 -100
@@ -199,7 +199,6 @@ export declare class Editor {
199
199
  sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
200
200
  toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp'): string;
201
201
  toSVG(): SVGElement;
202
- private renderAllWithTransform;
203
202
  loadFrom(loader: ImageLoader): Promise<void>;
204
203
  getImportExportRect(): Rect2;
205
204
  setImportExportRect(imageRect: Rect2): Command;
@@ -639,8 +639,7 @@ export class Editor {
639
639
  canvas.height = resolution.y;
640
640
  const ctx = canvas.getContext('2d');
641
641
  const renderer = new CanvasRenderer(ctx, this.importExportViewport);
642
- // Render everything with no transform (0,0) should be (0,0) in the output image
643
- this.renderAllWithTransform(renderer, this.importExportViewport, Mat33.identity);
642
+ this.image.renderAll(renderer);
644
643
  const dataURL = canvas.toDataURL(format);
645
644
  return dataURL;
646
645
  }
@@ -649,7 +648,12 @@ export class Editor {
649
648
  const svgNameSpace = 'http://www.w3.org/2000/svg';
650
649
  const result = document.createElementNS(svgNameSpace, 'svg');
651
650
  const renderer = new SVGRenderer(result, importExportViewport);
652
- this.renderAllWithTransform(renderer, importExportViewport);
651
+ const origTransform = this.importExportViewport.canvasToScreenTransform;
652
+ // Render with (0,0) at (0,0) — we'll handle translation with
653
+ // the viewBox property.
654
+ importExportViewport.resetTransform(Mat33.identity);
655
+ this.image.renderAll(renderer);
656
+ importExportViewport.resetTransform(origTransform);
653
657
  // Just show the main region
654
658
  const rect = importExportViewport.visibleRect;
655
659
  result.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
@@ -662,18 +666,6 @@ export class Editor {
662
666
  result.setAttribute('xmlns', svgNameSpace);
663
667
  return result;
664
668
  }
665
- // Renders everything in this' image to `renderer`, but first transforming the given `viewport`
666
- // such that its transform is `transform`. The given `viewport`'s transform is restored before this method
667
- // returns.
668
- //
669
- // For example, rendering with `transform = Mat33.identity` *sets* `viewport`'s transform to `Mat33.identity`,
670
- // renders everything in this' image to `renderer`, then restores `viewport`'s transform to whatever it was before.
671
- renderAllWithTransform(renderer, viewport, transform = Mat33.identity) {
672
- const origTransform = this.importExportViewport.canvasToScreenTransform;
673
- viewport.resetTransform(transform);
674
- this.image.renderAll(renderer);
675
- viewport.resetTransform(origTransform);
676
- }
677
669
  loadFrom(loader) {
678
670
  return __awaiter(this, void 0, void 0, function* () {
679
671
  this.showLoadingWarning(0);
@@ -11,7 +11,7 @@ import Color4 from './Color4';
11
11
  import ImageComponent from './components/ImageComponent';
12
12
  import Stroke from './components/Stroke';
13
13
  import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
14
- import TextComponent from './components/Text';
14
+ import TextComponent from './components/TextComponent';
15
15
  import UnknownSVGObject from './components/UnknownSVGObject';
16
16
  import Mat33 from './math/Mat33';
17
17
  import Path from './math/Path';
@@ -128,9 +128,7 @@ export class Viewport {
128
128
  // Ensure that toMakeVisible is at least 1/3rd of the visible region.
129
129
  const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension < 1 / 3;
130
130
  if ((largerThanTarget && allowZoomOut) || (muchSmallerThanTarget && allowZoomIn)) {
131
- // If larger than the target, ensure that the longest axis is visible.
132
- // If smaller, shrink the visible rectangle as much as possible
133
- const multiplier = (largerThanTarget ? Math.max : Math.min)(toMakeVisible.w / targetRect.w, toMakeVisible.h / targetRect.h);
131
+ const multiplier = Math.max(toMakeVisible.w / targetRect.w, toMakeVisible.h / targetRect.h);
134
132
  const visibleRectTransform = Mat33.scaling2D(multiplier, targetRect.topLeft);
135
133
  const viewportContentTransform = visibleRectTransform.inverse();
136
134
  transform = transform.rightMul(viewportContentTransform);
@@ -1,8 +1,8 @@
1
1
  import Editor from '../Editor';
2
2
  import { EditorLocalization } from '../localization';
3
3
  export declare abstract class Command {
4
- abstract apply(editor: Editor): void;
5
- abstract unapply(editor: Editor): void;
4
+ abstract apply(editor: Editor): Promise<void> | void;
5
+ abstract unapply(editor: Editor): Promise<void> | void;
6
6
  onDrop(_editor: Editor): void;
7
7
  abstract description(editor: Editor, localizationTable: EditorLocalization): string;
8
8
  static union(a: Command, b: Command): Command;
@@ -6,24 +6,33 @@ class NonSerializableUnion extends Command {
6
6
  this.commands = commands;
7
7
  this.applyChunkSize = applyChunkSize;
8
8
  }
9
+ static waitForAll(commands) {
10
+ // If any are Promises...
11
+ if (commands.some(command => command && command['then'])) {
12
+ console.log('waiting...');
13
+ // Wait for all commands to finish.
14
+ return Promise.all(commands)
15
+ // Ensure we return a Promise<void> and not a Promise<void[]>
16
+ .then(() => { });
17
+ }
18
+ return;
19
+ }
9
20
  apply(editor) {
10
21
  if (this.applyChunkSize === undefined) {
11
- for (const command of this.commands) {
12
- command.apply(editor);
13
- }
22
+ const results = this.commands.map(cmd => cmd.apply(editor));
23
+ return NonSerializableUnion.waitForAll(results);
14
24
  }
15
25
  else {
16
- editor.asyncApplyCommands(this.commands, this.applyChunkSize);
26
+ return editor.asyncApplyCommands(this.commands, this.applyChunkSize);
17
27
  }
18
28
  }
19
29
  unapply(editor) {
20
30
  if (this.applyChunkSize === undefined) {
21
- for (const command of this.commands) {
22
- command.unapply(editor);
23
- }
31
+ const results = this.commands.map(cmd => cmd.unapply(editor));
32
+ return NonSerializableUnion.waitForAll(results);
24
33
  }
25
34
  else {
26
- editor.asyncUnapplyCommands(this.commands, this.applyChunkSize);
35
+ return editor.asyncUnapplyCommands(this.commands, this.applyChunkSize);
27
36
  }
28
37
  }
29
38
  description(editor, localizationTable) {
@@ -63,10 +72,10 @@ class SerializableUnion extends SerializableCommand {
63
72
  };
64
73
  }
65
74
  apply(editor) {
66
- this.nonserializableCommand.apply(editor);
75
+ return this.nonserializableCommand.apply(editor);
67
76
  }
68
77
  unapply(editor) {
69
- this.nonserializableCommand.unapply(editor);
78
+ return this.nonserializableCommand.unapply(editor);
70
79
  }
71
80
  description(editor, localizationTable) {
72
81
  return this.nonserializableCommand.description(editor, localizationTable);
@@ -5,6 +5,6 @@ export { default as StrokeSmoother, Curve as StrokeSmootherCurve } from './util/
5
5
  export * from './AbstractComponent';
6
6
  export { default as AbstractComponent } from './AbstractComponent';
7
7
  import Stroke from './Stroke';
8
- import TextComponent from './Text';
8
+ import TextComponent from './TextComponent';
9
9
  import ImageComponent from './ImageComponent';
10
10
  export { Stroke, TextComponent as Text, TextComponent as TextComponent, Stroke as StrokeComponent, ImageComponent, };
@@ -5,6 +5,6 @@ export { default as StrokeSmoother } from './util/StrokeSmoother';
5
5
  export * from './AbstractComponent';
6
6
  export { default as AbstractComponent } from './AbstractComponent';
7
7
  import Stroke from './Stroke';
8
- import TextComponent from './Text';
8
+ import TextComponent from './TextComponent';
9
9
  import ImageComponent from './ImageComponent';
10
10
  export { Stroke, TextComponent as Text, TextComponent as TextComponent, Stroke as StrokeComponent, ImageComponent, };
@@ -1,5 +1,5 @@
1
1
  import { LoadSaveDataTable } from '../../components/AbstractComponent';
2
- import { TextStyle } from '../../components/Text';
2
+ import { TextStyle } from '../../components/TextComponent';
3
3
  import Mat33 from '../../math/Mat33';
4
4
  import Path, { PathCommand } from '../../math/Path';
5
5
  import Rect2 from '../../math/Rect2';
@@ -1,4 +1,4 @@
1
- import { TextStyle } from '../../components/Text';
1
+ import { TextStyle } from '../../components/TextComponent';
2
2
  import Mat33 from '../../math/Mat33';
3
3
  import Rect2 from '../../math/Rect2';
4
4
  import { Point2, Vec2 } from '../../math/Vec2';
@@ -1,5 +1,5 @@
1
1
  import Color4 from '../../Color4';
2
- import TextComponent from '../../components/Text';
2
+ import TextComponent from '../../components/TextComponent';
3
3
  import { Vec2 } from '../../math/Vec2';
4
4
  import AbstractRenderer from './AbstractRenderer';
5
5
  export default class CanvasRenderer extends AbstractRenderer {
@@ -1,4 +1,4 @@
1
- import { TextStyle } from '../../components/Text';
1
+ import { TextStyle } from '../../components/TextComponent';
2
2
  import Mat33 from '../../math/Mat33';
3
3
  import Rect2 from '../../math/Rect2';
4
4
  import { Point2, Vec2 } from '../../math/Vec2';
@@ -1,5 +1,5 @@
1
1
  import { LoadSaveDataTable } from '../../components/AbstractComponent';
2
- import { TextStyle } from '../../components/Text';
2
+ import { TextStyle } from '../../components/TextComponent';
3
3
  import Mat33 from '../../math/Mat33';
4
4
  import Rect2 from '../../math/Rect2';
5
5
  import { Point2, Vec2 } from '../../math/Vec2';
@@ -1,4 +1,4 @@
1
- import { TextStyle } from '../../components/Text';
1
+ import { TextStyle } from '../../components/TextComponent';
2
2
  import Mat33 from '../../math/Mat33';
3
3
  import Rect2 from '../../math/Rect2';
4
4
  import Vec3 from '../../math/Vec3';
@@ -22,8 +22,8 @@ export default class TextOnlyRenderer extends AbstractRenderer {
22
22
  getDescription() {
23
23
  return [
24
24
  this.localizationTable.pathNodeCount(this.pathCount),
25
- ...(this.textNodeCount > 0 ? this.localizationTable.textNodeCount(this.textNodeCount) : []),
26
- ...(this.imageNodeCount > 0 ? this.localizationTable.imageNodeCount(this.imageNodeCount) : []),
25
+ ...(this.textNodeCount > 0 ? [this.localizationTable.textNodeCount(this.textNodeCount)] : []),
26
+ ...(this.imageNodeCount > 0 ? [this.localizationTable.imageNodeCount(this.imageNodeCount)] : []),
27
27
  ...this.descriptionBuilder
28
28
  ].join('\n');
29
29
  }
@@ -1,6 +1,6 @@
1
1
  import Color4 from '../Color4';
2
2
  import { ComponentBuilderFactory } from '../components/builders/types';
3
- import { TextStyle } from '../components/Text';
3
+ import { TextStyle } from '../components/TextComponent';
4
4
  import Pen from '../tools/Pen';
5
5
  declare type IconType = SVGSVGElement | HTMLImageElement;
6
6
  export default class IconProvider {
@@ -0,0 +1,21 @@
1
+ import Editor from '../Editor';
2
+ import { KeyPressEvent } from '../types';
3
+ import BaseTool from './BaseTool';
4
+ export declare const cssPrefix = "find-tool";
5
+ export default class FindTool extends BaseTool {
6
+ private editor;
7
+ private overlay;
8
+ private searchInput;
9
+ private currentMatchIdx;
10
+ constructor(editor: Editor);
11
+ private getMatches;
12
+ private focusCurrentMatch;
13
+ private toNextMatch;
14
+ private toPrevMatch;
15
+ private fillOverlay;
16
+ private isVisible;
17
+ private setVisible;
18
+ private toggleVisible;
19
+ onKeyPress(event: KeyPressEvent): boolean;
20
+ setEnabled(enabled: boolean): void;
21
+ }
@@ -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
+ }
@@ -108,13 +108,13 @@ export default class Selection {
108
108
  cmd.unapply(this.editor);
109
109
  });
110
110
  const fullTransform = this.transform;
111
- const currentTransfmCommands = this.computeTransformCommands();
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, currentTransfmCommands, fullTransform));
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, editor) => {
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 commands = json.commands.map(data => SerializableCommand.deserialize(data, editor));
303
- return new _a.ApplyTransformationCommand(null, commands, fullTransform);
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, currentTransfmCommands, fullTransform) {
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.currentTransfmCommands, updateChunkSize);
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.currentTransfmCommands, updateChunkSize);
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
- commands: this.currentTransfmCommands.map(command => command.serialize()),
368
+ elems: this.selectedElemIds,
338
369
  transform: this.fullTransform.toArray(),
339
370
  };
340
371
  }
341
372
  description(_editor, localizationTable) {
342
- return localizationTable.transformedElements(this.currentTransfmCommands.length);
373
+ return localizationTable.transformedElements(this.selectedElemIds.length);
343
374
  }
344
375
  };
@@ -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/Text';
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 { TextStyle } from '../components/Text';
2
+ import { TextStyle } from '../components/TextComponent';
3
3
  import Editor from '../Editor';
4
4
  import { PointerEvt } from '../types';
5
5
  import BaseTool from './BaseTool';
@@ -1,5 +1,5 @@
1
1
  import Color4 from '../Color4';
2
- import TextComponent from '../components/Text';
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';
@@ -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));
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.9.1",
3
+ "version": "0.9.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,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"
package/src/Editor.css CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  @import url('./toolbar/toolbar.css');
3
3
  @import url('./tools/SelectionTool/SelectionTool.css');
4
+ @import url('./tools/FindTool.css');
4
5
 
5
6
  .imageEditorContainer {
6
7
  /* Deafult colors for the editor */
@@ -1,4 +1,4 @@
1
- import { TextStyle } from './components/Text';
1
+ import { TextStyle } from './components/TextComponent';
2
2
  import { Color4, Mat33, Rect2, TextComponent, EditorImage, Vec2 } from './lib';
3
3
  import createEditor from './testing/createEditor';
4
4
 
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 AbstractRenderer, { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';
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
- // Render everything with no transform (0,0) should be (0,0) in the output image
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.renderAllWithTransform(renderer, importExportViewport);
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);