js-draw 0.9.0 → 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.
- package/.github/ISSUE_TEMPLATE/translation.yml +742 -0
- package/CHANGELOG.md +8 -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 +1 -0
- package/dist/src/Editor.js +20 -3
- package/dist/src/SVGLoader.js +1 -1
- package/dist/src/Viewport.js +3 -5
- package/dist/src/commands/Command.d.ts +2 -2
- package/dist/src/commands/uniteCommands.js +19 -10
- 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/LineBuilder.js +4 -0
- package/dist/src/components/lib.d.ts +1 -1
- package/dist/src/components/lib.js +1 -1
- package/dist/src/components/util/StrokeSmoother.js +1 -1
- 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 +6 -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 +1 -1
- package/dist/src/tools/FindTool.d.ts +21 -0
- package/dist/src/tools/FindTool.js +113 -0
- package/dist/src/tools/SelectionTool/Selection.js +43 -12
- package/dist/src/tools/SelectionTool/SelectionTool.js +1 -1
- package/dist/src/tools/TextTool.d.ts +1 -1
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/tools/ToolController.js +2 -0
- package/dist/src/tools/localization.d.ts +6 -0
- package/dist/src/tools/localization.js +6 -0
- package/package.json +2 -1
- package/src/Editor.css +1 -0
- package/src/Editor.toSVG.test.ts +1 -1
- package/src/Editor.ts +27 -3
- package/src/SVGLoader.ts +1 -1
- package/src/Viewport.ts +3 -5
- 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/LineBuilder.ts +4 -0
- package/src/components/lib.ts +1 -1
- package/src/components/util/StrokeSmoother.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 +6 -2
- package/src/rendering/renderers/TextOnlyRenderer.ts +3 -3
- package/src/toolbar/IconProvider.ts +1 -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 +52 -11
- package/src/tools/SelectionTool/SelectionTool.ts +1 -1
- package/src/tools/TextTool.ts +1 -1
- package/src/tools/ToolController.ts +2 -0
- package/src/tools/localization.ts +14 -0
- package/.firebase/hosting.ZG9jcw.cache +0 -338
- package/.github/ISSUE_TEMPLATE/translation.md +0 -100
package/dist/src/Editor.d.ts
CHANGED
@@ -197,6 +197,7 @@ export declare class Editor {
|
|
197
197
|
addStyleSheet(content: string): HTMLStyleElement;
|
198
198
|
sendKeyboardEvent(eventType: InputEvtType.KeyPressEvent | InputEvtType.KeyUpEvent, key: string, ctrlKey?: boolean, altKey?: boolean): void;
|
199
199
|
sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
|
200
|
+
toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp'): string;
|
200
201
|
toSVG(): SVGElement;
|
201
202
|
loadFrom(loader: ImageLoader): Promise<void>;
|
202
203
|
getImportExportRect(): Rect2;
|
package/dist/src/Editor.js
CHANGED
@@ -43,6 +43,7 @@ import Mat33 from './math/Mat33';
|
|
43
43
|
import getLocalizationTable from './localizations/getLocalizationTable';
|
44
44
|
import IconProvider from './toolbar/IconProvider';
|
45
45
|
import { toRoundedString } from './math/rounding';
|
46
|
+
import CanvasRenderer from './rendering/renderers/CanvasRenderer';
|
46
47
|
// { @inheritDoc Editor! }
|
47
48
|
export class Editor {
|
48
49
|
/**
|
@@ -626,15 +627,31 @@ export class Editor {
|
|
626
627
|
current: mainPointer,
|
627
628
|
});
|
628
629
|
}
|
630
|
+
// Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
|
631
|
+
// If `format` is not `image/png`, a PNG image URL may still be returned (as in the
|
632
|
+
// case of `HTMLCanvasElement::toDataURL`).
|
633
|
+
//
|
634
|
+
// The export resolution is the same as the size of the drawing canvas.
|
635
|
+
toDataURL(format = 'image/png') {
|
636
|
+
const canvas = document.createElement('canvas');
|
637
|
+
const resolution = this.importExportViewport.getResolution();
|
638
|
+
canvas.width = resolution.x;
|
639
|
+
canvas.height = resolution.y;
|
640
|
+
const ctx = canvas.getContext('2d');
|
641
|
+
const renderer = new CanvasRenderer(ctx, this.importExportViewport);
|
642
|
+
this.image.renderAll(renderer);
|
643
|
+
const dataURL = canvas.toDataURL(format);
|
644
|
+
return dataURL;
|
645
|
+
}
|
629
646
|
toSVG() {
|
630
647
|
const importExportViewport = this.importExportViewport;
|
631
648
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
632
649
|
const result = document.createElementNS(svgNameSpace, 'svg');
|
633
650
|
const renderer = new SVGRenderer(result, importExportViewport);
|
634
|
-
const origTransform = importExportViewport.canvasToScreenTransform;
|
635
|
-
//
|
651
|
+
const origTransform = this.importExportViewport.canvasToScreenTransform;
|
652
|
+
// Render with (0,0) at (0,0) — we'll handle translation with
|
653
|
+
// the viewBox property.
|
636
654
|
importExportViewport.resetTransform(Mat33.identity);
|
637
|
-
// Render **all** elements.
|
638
655
|
this.image.renderAll(renderer);
|
639
656
|
importExportViewport.resetTransform(origTransform);
|
640
657
|
// Just show the main region
|
package/dist/src/SVGLoader.js
CHANGED
@@ -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/
|
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';
|
package/dist/src/Viewport.js
CHANGED
@@ -103,8 +103,8 @@ export class Viewport {
|
|
103
103
|
}
|
104
104
|
// Represent as k 10ⁿ for some n, k ∈ ℤ.
|
105
105
|
const decimalComponent = Math.pow(10, Math.floor(Math.log10(Math.abs(scaleRatio))));
|
106
|
-
const
|
107
|
-
scaleRatio = Math.round(scaleRatio / decimalComponent *
|
106
|
+
const roundAmountFactor = Math.pow(2, roundAmount);
|
107
|
+
scaleRatio = Math.round(scaleRatio / decimalComponent * roundAmountFactor) / roundAmountFactor * decimalComponent;
|
108
108
|
return scaleRatio;
|
109
109
|
}
|
110
110
|
// Computes and returns an affine transformation that makes `toMakeVisible` visible and roughly centered on the screen.
|
@@ -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
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
22
|
-
|
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);
|
File without changes
|
File without changes
|
@@ -37,6 +37,10 @@ export default class LineBuilder {
|
|
37
37
|
kind: PathCommandType.LineTo,
|
38
38
|
point: endPoint.minus(scaledEndNormal),
|
39
39
|
},
|
40
|
+
{
|
41
|
+
kind: PathCommandType.LineTo,
|
42
|
+
point: startPoint.minus(scaledStartNormal),
|
43
|
+
},
|
40
44
|
],
|
41
45
|
style: {
|
42
46
|
fill: this.startPoint.color,
|
@@ -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 './
|
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 './
|
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, };
|
@@ -167,7 +167,7 @@ export class StrokeSmoother {
|
|
167
167
|
if (!controlPoint || segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
|
168
168
|
// Position the control point closer to the first -- the connecting
|
169
169
|
// segment will be roughly a line.
|
170
|
-
controlPoint = segmentStart.plus(enteringVec.times(startEndDist /
|
170
|
+
controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 4));
|
171
171
|
}
|
172
172
|
console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
|
173
173
|
console.assert(!controlPoint.eq(segmentEnd, 1e-11), 'Control and end points are equal!');
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
|
-
import { TextStyle } from '../../components/
|
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,5 +1,5 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
-
import TextComponent from '../../components/
|
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,5 +1,5 @@
|
|
1
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
|
-
import { TextStyle } from '../../components/
|
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';
|
@@ -79,7 +79,12 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
79
79
|
const pathElem = document.createElementNS(svgNameSpace, 'path');
|
80
80
|
pathElem.setAttribute('d', this.lastPathString.join(' '));
|
81
81
|
const style = this.lastPathStyle;
|
82
|
-
|
82
|
+
if (style.fill.a > 0) {
|
83
|
+
pathElem.setAttribute('fill', style.fill.toHexString());
|
84
|
+
}
|
85
|
+
else {
|
86
|
+
pathElem.setAttribute('fill', 'none');
|
87
|
+
}
|
83
88
|
if (style.stroke) {
|
84
89
|
pathElem.setAttribute('stroke', style.stroke.color.toHexString());
|
85
90
|
pathElem.setAttribute('stroke-width', style.stroke.width.toString());
|
@@ -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/
|
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
|
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() {
|
@@ -287,7 +287,7 @@ export default class Selection {
|
|
287
287
|
}
|
288
288
|
setSelectedObjects(objects, bbox) {
|
289
289
|
this.originalRegion = bbox;
|
290
|
-
this.selectedElems = objects;
|
290
|
+
this.selectedElems = objects.filter(object => object.isSelectable());
|
291
291
|
this.updateUI();
|
292
292
|
}
|
293
293
|
getSelectedObjects() {
|
@@ -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
|
};
|
@@ -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 {
|
@@ -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.
|
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
package/src/Editor.toSVG.test.ts
CHANGED