js-draw 0.14.0 → 0.15.0
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 +8 -0
- package/CHANGELOG.md +8 -1
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +4 -0
- package/dist/src/Color4.js +22 -0
- package/dist/src/Editor.d.ts +2 -1
- package/dist/src/Editor.js +10 -1
- package/dist/src/EditorImage.d.ts +1 -0
- package/dist/src/EditorImage.js +11 -0
- package/dist/src/commands/UnresolvedCommand.d.ts +14 -0
- package/dist/src/commands/UnresolvedCommand.js +22 -0
- package/dist/src/commands/uniteCommands.js +4 -2
- package/dist/src/components/AbstractComponent.d.ts +0 -1
- package/dist/src/components/AbstractComponent.js +30 -50
- package/dist/src/components/RestylableComponent.d.ts +24 -0
- package/dist/src/components/RestylableComponent.js +80 -0
- package/dist/src/components/Stroke.d.ts +8 -1
- package/dist/src/components/Stroke.js +49 -1
- package/dist/src/components/TextComponent.d.ts +10 -10
- package/dist/src/components/TextComponent.js +46 -13
- package/dist/src/components/lib.d.ts +2 -1
- package/dist/src/components/lib.js +2 -1
- package/dist/src/components/localization.d.ts +1 -0
- package/dist/src/components/localization.js +1 -0
- package/dist/src/rendering/TextRenderingStyle.d.ts +23 -0
- package/dist/src/rendering/TextRenderingStyle.js +20 -0
- 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/DummyRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +1 -1
- package/dist/src/toolbar/IconProvider.d.ts +2 -1
- package/dist/src/toolbar/IconProvider.js +10 -0
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +10 -4
- package/dist/src/toolbar/widgets/InsertImageWidget.js +2 -1
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +77 -1
- package/dist/src/tools/Pen.js +2 -2
- package/dist/src/tools/SelectionTool/SelectAllShortcutHandler.d.ts +8 -0
- package/dist/src/tools/SelectionTool/SelectAllShortcutHandler.js +22 -0
- package/dist/src/tools/SelectionTool/Selection.js +1 -1
- package/dist/src/tools/SelectionTool/SelectionTool.js +4 -9
- package/dist/src/tools/TextTool.d.ts +1 -1
- package/dist/src/tools/ToolController.js +2 -0
- package/dist/src/tools/lib.d.ts +1 -0
- package/dist/src/tools/lib.js +1 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/package.json +1 -1
- package/src/Color4.test.ts +4 -0
- package/src/Color4.ts +26 -0
- package/src/Editor.toSVG.test.ts +1 -1
- package/src/Editor.ts +12 -1
- package/src/EditorImage.ts +13 -0
- package/src/SVGLoader.ts +2 -1
- package/src/commands/UnresolvedCommand.ts +37 -0
- package/src/commands/uniteCommands.ts +5 -2
- package/src/components/AbstractComponent.ts +36 -61
- package/src/components/RestylableComponent.ts +142 -0
- package/src/components/Stroke.test.ts +68 -0
- package/src/components/Stroke.ts +68 -2
- package/src/components/TextComponent.test.ts +56 -2
- package/src/components/TextComponent.ts +63 -25
- package/src/components/lib.ts +4 -1
- package/src/components/localization.ts +3 -0
- package/src/math/Rect2.test.ts +18 -6
- package/src/rendering/TextRenderingStyle.ts +38 -0
- package/src/rendering/renderers/AbstractRenderer.ts +1 -1
- package/src/rendering/renderers/CanvasRenderer.ts +2 -1
- package/src/rendering/renderers/DummyRenderer.ts +1 -1
- package/src/rendering/renderers/SVGRenderer.ts +1 -1
- package/src/rendering/renderers/TextOnlyRenderer.ts +1 -1
- package/src/toolbar/IconProvider.ts +12 -1
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/widgets/BaseWidget.ts +12 -4
- package/src/toolbar/widgets/InsertImageWidget.ts +2 -1
- package/src/toolbar/widgets/SelectionToolWidget.ts +95 -1
- package/src/tools/PanZoom.test.ts +2 -1
- package/src/tools/PasteHandler.ts +1 -1
- package/src/tools/Pen.ts +2 -2
- package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +28 -0
- package/src/tools/SelectionTool/Selection.ts +1 -1
- package/src/tools/SelectionTool/SelectionTool.ts +4 -9
- package/src/tools/TextTool.ts +2 -1
- package/src/tools/ToolController.ts +2 -0
- package/src/tools/lib.ts +1 -0
- package/src/tools/localization.ts +2 -0
@@ -1,24 +1,22 @@
|
|
1
|
+
import SerializableCommand from '../commands/SerializableCommand';
|
1
2
|
import LineSegment2 from '../math/LineSegment2';
|
2
3
|
import Mat33, { Mat33Array } from '../math/Mat33';
|
3
4
|
import Rect2 from '../math/Rect2';
|
5
|
+
import Editor from '../Editor';
|
4
6
|
import { Vec2 } from '../math/Vec2';
|
5
7
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
6
|
-
import
|
8
|
+
import { TextStyle, textStyleFromJSON, textStyleToJSON } from '../rendering/TextRenderingStyle';
|
7
9
|
import AbstractComponent from './AbstractComponent';
|
8
10
|
import { ImageComponentLocalization } from './localization';
|
9
|
-
|
10
|
-
export interface TextStyle {
|
11
|
-
size: number;
|
12
|
-
fontFamily: string;
|
13
|
-
fontWeight?: string;
|
14
|
-
fontVariant?: string;
|
15
|
-
renderingStyle: RenderingStyle;
|
16
|
-
}
|
11
|
+
import RestyleableComponent, { ComponentStyle, createRestyleComponentCommand } from './RestylableComponent';
|
17
12
|
|
18
13
|
const componentTypeId = 'text';
|
19
|
-
export default class TextComponent extends AbstractComponent {
|
14
|
+
export default class TextComponent extends AbstractComponent implements RestyleableComponent {
|
20
15
|
protected contentBBox: Rect2;
|
21
16
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/prefer-as-const
|
18
|
+
readonly isRestylableComponent: true = true;
|
19
|
+
|
22
20
|
public constructor(
|
23
21
|
protected readonly textObjects: Array<string|TextComponent>,
|
24
22
|
private transform: Mat33,
|
@@ -152,14 +150,57 @@ export default class TextComponent extends AbstractComponent {
|
|
152
150
|
return false;
|
153
151
|
}
|
154
152
|
|
155
|
-
public
|
156
|
-
return
|
153
|
+
public getStyle(): ComponentStyle {
|
154
|
+
return {
|
155
|
+
color: this.style.renderingStyle.fill,
|
156
|
+
|
157
|
+
// Make a copy
|
158
|
+
textStyle: {
|
159
|
+
...this.style,
|
160
|
+
renderingStyle: {
|
161
|
+
...this.style.renderingStyle,
|
162
|
+
},
|
163
|
+
},
|
164
|
+
};
|
165
|
+
}
|
166
|
+
|
167
|
+
public updateStyle(style: ComponentStyle): SerializableCommand {
|
168
|
+
return createRestyleComponentCommand(this.getStyle(), style, this);
|
169
|
+
}
|
170
|
+
|
171
|
+
public forceStyle(style: ComponentStyle, editor: Editor|null): void {
|
172
|
+
if (style.textStyle) {
|
173
|
+
this.style = style.textStyle;
|
174
|
+
} else if (style.color) {
|
175
|
+
this.style.renderingStyle = {
|
176
|
+
...this.style.renderingStyle,
|
177
|
+
fill: style.color,
|
178
|
+
};
|
179
|
+
} else {
|
180
|
+
return;
|
181
|
+
}
|
182
|
+
|
183
|
+
for (const child of this.textObjects) {
|
184
|
+
if (child instanceof TextComponent) {
|
185
|
+
child.forceStyle(style, editor);
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
if (editor) {
|
190
|
+
editor.image.queueRerenderOf(this);
|
191
|
+
editor.queueRerender();
|
192
|
+
}
|
157
193
|
}
|
158
194
|
|
195
|
+
// See this.getStyle
|
159
196
|
public getTextStyle() {
|
160
197
|
return this.style;
|
161
198
|
}
|
162
199
|
|
200
|
+
public getBaselinePos() {
|
201
|
+
return this.transform.transformVec2(Vec2.zero);
|
202
|
+
}
|
203
|
+
|
163
204
|
public getTransform(): Mat33 {
|
164
205
|
return this.transform;
|
165
206
|
}
|
@@ -191,13 +232,11 @@ export default class TextComponent extends AbstractComponent {
|
|
191
232
|
return localizationTable.text(this.getText());
|
192
233
|
}
|
193
234
|
|
235
|
+
// Do not rely on the output of `serializeToJSON` taking any particular format.
|
194
236
|
protected serializeToJSON(): Record<string, any> {
|
195
|
-
const serializableStyle =
|
196
|
-
...this.style,
|
197
|
-
renderingStyle: styleToJSON(this.style.renderingStyle),
|
198
|
-
};
|
237
|
+
const serializableStyle = textStyleToJSON(this.style);
|
199
238
|
|
200
|
-
const
|
239
|
+
const serializedTextObjects = this.textObjects.map(text => {
|
201
240
|
if (typeof text === 'string') {
|
202
241
|
return {
|
203
242
|
text,
|
@@ -210,20 +249,19 @@ export default class TextComponent extends AbstractComponent {
|
|
210
249
|
});
|
211
250
|
|
212
251
|
return {
|
213
|
-
textObjects,
|
252
|
+
textObjects: serializedTextObjects,
|
214
253
|
transform: this.transform.toArray(),
|
215
254
|
style: serializableStyle,
|
216
255
|
};
|
217
256
|
}
|
218
257
|
|
258
|
+
// @internal
|
219
259
|
public static deserializeFromString(json: any): TextComponent {
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
fontFamily: json.style.fontFamily,
|
226
|
-
};
|
260
|
+
if (typeof json === 'string') {
|
261
|
+
json = JSON.parse(json);
|
262
|
+
}
|
263
|
+
|
264
|
+
const style = textStyleFromJSON(json.style);
|
227
265
|
|
228
266
|
const textObjects: Array<string|TextComponent> = json.textObjects.map((data: any) => {
|
229
267
|
if ((data.text ?? null) !== null) {
|
package/src/components/lib.ts
CHANGED
@@ -8,12 +8,15 @@ export { default as AbstractComponent } from './AbstractComponent';
|
|
8
8
|
import Stroke from './Stroke';
|
9
9
|
import TextComponent from './TextComponent';
|
10
10
|
import ImageComponent from './ImageComponent';
|
11
|
+
import RestyleableComponent, { createRestyleComponentCommand } from './RestylableComponent';
|
11
12
|
|
12
13
|
export {
|
13
14
|
Stroke,
|
14
15
|
TextComponent as Text,
|
16
|
+
RestyleableComponent,
|
17
|
+
createRestyleComponentCommand,
|
15
18
|
|
16
|
-
TextComponent
|
19
|
+
TextComponent,
|
17
20
|
Stroke as StrokeComponent,
|
18
21
|
ImageComponent,
|
19
22
|
};
|
@@ -4,12 +4,15 @@ export interface ImageComponentLocalization {
|
|
4
4
|
imageNode: (description: string)=> string;
|
5
5
|
stroke: string;
|
6
6
|
svgObject: string;
|
7
|
+
|
8
|
+
restyledElements: string;
|
7
9
|
}
|
8
10
|
|
9
11
|
export const defaultComponentLocalization: ImageComponentLocalization = {
|
10
12
|
unlabeledImageNode: 'Unlabeled image node',
|
11
13
|
stroke: 'Stroke',
|
12
14
|
svgObject: 'SVG Object',
|
15
|
+
restyledElements: 'Restyled elements',
|
13
16
|
text: (text) => `Text object: ${text}`,
|
14
17
|
imageNode: (description: string) => `Image: ${description}`,
|
15
18
|
};
|
package/src/math/Rect2.test.ts
CHANGED
@@ -77,13 +77,23 @@ describe('Rect2', () => {
|
|
77
77
|
expect(new Rect2(-2, -2, 4, 4).containsRect(new Rect2(-1, 0, 10, 1))).toBe(false);
|
78
78
|
});
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
describe('containsRect', () => {
|
81
|
+
it('a rectangle should contain itself', () => {
|
82
|
+
const rect = new Rect2(1 / 3, 1 / 4, 1 / 5, 1 / 6);
|
83
|
+
expect(rect.containsRect(rect)).toBe(true);
|
84
|
+
});
|
84
85
|
|
85
|
-
|
86
|
-
|
86
|
+
it('empty rect should not contain a larger rect', () => {
|
87
|
+
expect(Rect2.empty.containsRect(new Rect2(-1, -1, 3, 3))).toBe(false);
|
88
|
+
});
|
89
|
+
|
90
|
+
it('should correctly contain rectangles', () => {
|
91
|
+
const testRect = new Rect2(4, -10, 50, 100);
|
92
|
+
expect(testRect.containsRect(new Rect2(4.1, 0, 1, 1))).toBe(true);
|
93
|
+
expect(testRect.containsRect(new Rect2(48, 0, 1, 1))).toBe(true);
|
94
|
+
expect(testRect.containsRect(new Rect2(48, -9, 1, 1))).toBe(true);
|
95
|
+
expect(testRect.containsRect(new Rect2(48, -9, 1, 91))).toBe(true);
|
96
|
+
});
|
87
97
|
});
|
88
98
|
|
89
99
|
it('intersecting rectangles should be identified as intersecting', () => {
|
@@ -92,6 +102,8 @@ describe('Rect2', () => {
|
|
92
102
|
expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0, 0, 10, 10))).toBe(true);
|
93
103
|
expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(3, 3, 10, 10))).toBe(false);
|
94
104
|
expect(new Rect2(-1, -1, 2, 2).intersects(new Rect2(0.2, 0.1, 0, 0))).toBe(true);
|
105
|
+
expect(new Rect2(-100, -1, 200, 2).intersects(new Rect2(-5, -5, 10, 30))).toBe(true);
|
106
|
+
expect(new Rect2(-100, -1, 200, 2).intersects(new Rect2(-5, 50, 10, 30))).toBe(false);
|
95
107
|
});
|
96
108
|
|
97
109
|
it('intersecting rectangles should have their intersections correctly computed', () => {
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import RenderingStyle, { styleFromJSON, styleToJSON } from './RenderingStyle';
|
2
|
+
|
3
|
+
export interface TextStyle {
|
4
|
+
size: number;
|
5
|
+
fontFamily: string;
|
6
|
+
fontWeight?: string;
|
7
|
+
fontVariant?: string;
|
8
|
+
renderingStyle: RenderingStyle;
|
9
|
+
}
|
10
|
+
|
11
|
+
export default TextStyle;
|
12
|
+
|
13
|
+
export const textStyleFromJSON = (json: any) => {
|
14
|
+
if (typeof json === 'string') {
|
15
|
+
json = JSON.parse(json);
|
16
|
+
}
|
17
|
+
|
18
|
+
if (typeof(json.fontFamily) !== 'string') {
|
19
|
+
throw new Error('Serialized textStyle missing string fontFamily attribute!');
|
20
|
+
}
|
21
|
+
|
22
|
+
const style: TextStyle = {
|
23
|
+
renderingStyle: styleFromJSON(json.renderingStyle),
|
24
|
+
size: json.size,
|
25
|
+
fontWeight: json.fontWeight,
|
26
|
+
fontVariant: json.fontVariant,
|
27
|
+
fontFamily: json.fontFamily,
|
28
|
+
};
|
29
|
+
|
30
|
+
return style;
|
31
|
+
};
|
32
|
+
|
33
|
+
export const textStyleToJSON = (style: TextStyle) => {
|
34
|
+
return {
|
35
|
+
...style,
|
36
|
+
renderingStyle: styleToJSON(style.renderingStyle),
|
37
|
+
};
|
38
|
+
};
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
|
-
import { TextStyle } from '../../components/TextComponent';
|
3
2
|
import Mat33 from '../../math/Mat33';
|
4
3
|
import Path, { PathCommand, PathCommandType } from '../../math/Path';
|
5
4
|
import Rect2 from '../../math/Rect2';
|
6
5
|
import { Point2, Vec2 } from '../../math/Vec2';
|
7
6
|
import Viewport from '../../Viewport';
|
8
7
|
import RenderingStyle, { stylesEqual } from '../RenderingStyle';
|
8
|
+
import TextStyle from '../TextRenderingStyle';
|
9
9
|
|
10
10
|
export interface RenderablePathSpec {
|
11
11
|
startPoint: Point2;
|
@@ -1,11 +1,12 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
-
import TextComponent
|
2
|
+
import TextComponent 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';
|
6
6
|
import Vec3 from '../../math/Vec3';
|
7
7
|
import Viewport from '../../Viewport';
|
8
8
|
import RenderingStyle from '../RenderingStyle';
|
9
|
+
import TextStyle from '../TextRenderingStyle';
|
9
10
|
import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
|
10
11
|
|
11
12
|
export default class CanvasRenderer extends AbstractRenderer {
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import { TextStyle } from '../../components/TextComponent';
|
2
1
|
import Mat33 from '../../math/Mat33';
|
3
2
|
import Rect2 from '../../math/Rect2';
|
4
3
|
import { Point2, Vec2 } from '../../math/Vec2';
|
5
4
|
import Vec3 from '../../math/Vec3';
|
6
5
|
import Viewport from '../../Viewport';
|
7
6
|
import RenderingStyle from '../RenderingStyle';
|
7
|
+
import TextStyle from '../TextRenderingStyle';
|
8
8
|
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
9
9
|
|
10
10
|
// Renderer that outputs almost nothing. Useful for automated tests.
|
@@ -1,6 +1,5 @@
|
|
1
1
|
|
2
2
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
3
|
-
import { TextStyle } from '../../components/TextComponent';
|
4
3
|
import Mat33 from '../../math/Mat33';
|
5
4
|
import Path from '../../math/Path';
|
6
5
|
import Rect2 from '../../math/Rect2';
|
@@ -9,6 +8,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
|
|
9
8
|
import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
|
10
9
|
import Viewport from '../../Viewport';
|
11
10
|
import RenderingStyle from '../RenderingStyle';
|
11
|
+
import TextStyle from '../TextRenderingStyle';
|
12
12
|
import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
|
13
13
|
|
14
14
|
export const renderedStylesheetId = 'js-draw-style-sheet';
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import { TextStyle } from '../../components/TextComponent';
|
2
1
|
import Mat33 from '../../math/Mat33';
|
3
2
|
import Rect2 from '../../math/Rect2';
|
4
3
|
import { Vec2 } from '../../math/Vec2';
|
@@ -6,6 +5,7 @@ import Vec3 from '../../math/Vec3';
|
|
6
5
|
import Viewport from '../../Viewport';
|
7
6
|
import { TextRendererLocalization } from '../localization';
|
8
7
|
import RenderingStyle from '../RenderingStyle';
|
8
|
+
import TextStyle from '../TextRenderingStyle';
|
9
9
|
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
10
10
|
|
11
11
|
// Outputs a description of what was rendered.
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import { ComponentBuilderFactory } from '../components/builders/types';
|
3
|
-
import { TextStyle } from '../components/TextComponent';
|
4
3
|
import EventDispatcher from '../EventDispatcher';
|
5
4
|
import { Vec2 } from '../math/Vec2';
|
6
5
|
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
6
|
+
import TextStyle from '../rendering/TextRenderingStyle';
|
7
7
|
import Pen from '../tools/Pen';
|
8
8
|
import { StrokeDataPoint } from '../types';
|
9
9
|
import Viewport from '../Viewport';
|
@@ -609,6 +609,17 @@ export default class IconProvider {
|
|
609
609
|
icon.setAttribute('viewBox', '0 0 100 100');
|
610
610
|
return icon;
|
611
611
|
}
|
612
|
+
|
613
|
+
public makeFormatSelectionIcon(): IconType {
|
614
|
+
return this.makeIconFromPath(`
|
615
|
+
M 5 10
|
616
|
+
L 5 20 L 10 20 L 10 15 L 20 15 L 20 40 L 15 40 L 15 45 L 35 45 L 35 40 L 30 40 L 30 15 L 40 15 L 40 20 L 45 20 L 45 15 L 45 10 L 5 10 z
|
617
|
+
M 90 10 C 90 10 86.5 13.8 86 14 C 86 14 76.2 24.8 76 25 L 60 25 L 60 65 C 75 70 85 70 90 65 L 90 25 L 80 25 L 76.7 25 L 90 10 z
|
618
|
+
M 60 25 L 55 25 L 50 30 L 60 25 z
|
619
|
+
M 10 55 L 10 90 L 41 90 L 41 86 L 45 86 L 45 55 L 10 55 z
|
620
|
+
M 42 87 L 42 93 L 48 93 L 48 87 L 42 87 z
|
621
|
+
`);
|
622
|
+
}
|
612
623
|
|
613
624
|
public makeResizeViewportIcon(): IconType {
|
614
625
|
return this.makeIconFromPath(`
|
@@ -28,6 +28,7 @@ export interface ToolbarLocalization {
|
|
28
28
|
duplicateSelection: string;
|
29
29
|
pickColorFromScreen: string;
|
30
30
|
clickToPickColorAnnouncement: string;
|
31
|
+
reformatSelection: string;
|
31
32
|
undo: string;
|
32
33
|
redo: string;
|
33
34
|
zoom: string;
|
@@ -52,6 +53,7 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
52
53
|
handTool: 'Pan',
|
53
54
|
zoom: 'Zoom',
|
54
55
|
image: 'Image',
|
56
|
+
reformatSelection: 'Format selection',
|
55
57
|
inputAltText: 'Alt text: ',
|
56
58
|
chooseFile: 'Choose file: ',
|
57
59
|
submit: 'Submit',
|
@@ -110,13 +110,17 @@ export default abstract class BaseWidget {
|
|
110
110
|
|
111
111
|
// If we didn't do anything with the event, send it to the editor.
|
112
112
|
if (!handled) {
|
113
|
-
this.editor.toolController.dispatchInputEvent({
|
113
|
+
handled = this.editor.toolController.dispatchInputEvent({
|
114
114
|
kind: InputEvtType.KeyPressEvent,
|
115
115
|
key: evt.key,
|
116
|
-
ctrlKey: evt.ctrlKey,
|
116
|
+
ctrlKey: evt.ctrlKey || evt.metaKey,
|
117
117
|
altKey: evt.altKey,
|
118
118
|
});
|
119
119
|
}
|
120
|
+
|
121
|
+
if (handled) {
|
122
|
+
evt.preventDefault();
|
123
|
+
}
|
120
124
|
};
|
121
125
|
|
122
126
|
button.onkeyup = evt => {
|
@@ -124,12 +128,16 @@ export default abstract class BaseWidget {
|
|
124
128
|
return;
|
125
129
|
}
|
126
130
|
|
127
|
-
this.editor.toolController.dispatchInputEvent({
|
131
|
+
const handled = this.editor.toolController.dispatchInputEvent({
|
128
132
|
kind: InputEvtType.KeyUpEvent,
|
129
133
|
key: evt.key,
|
130
|
-
ctrlKey: evt.ctrlKey,
|
134
|
+
ctrlKey: evt.ctrlKey || evt.metaKey,
|
131
135
|
altKey: evt.altKey,
|
132
136
|
});
|
137
|
+
|
138
|
+
if (handled) {
|
139
|
+
evt.preventDefault();
|
140
|
+
}
|
133
141
|
};
|
134
142
|
|
135
143
|
button.onclick = () => {
|
@@ -2,7 +2,8 @@ import ImageComponent from '../../components/ImageComponent';
|
|
2
2
|
import Editor from '../../Editor';
|
3
3
|
import Erase from '../../commands/Erase';
|
4
4
|
import EditorImage from '../../EditorImage';
|
5
|
-
import
|
5
|
+
import uniteCommands from '../../commands/uniteCommands';
|
6
|
+
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
|
6
7
|
import Mat33 from '../../math/Mat33';
|
7
8
|
import fileToBase64 from '../../util/fileToBase64';
|
8
9
|
import { ToolbarLocalization } from '../localization';
|
@@ -1,9 +1,96 @@
|
|
1
|
+
import Color4 from '../../Color4';
|
2
|
+
import { isRestylableComponent } from '../../components/RestylableComponent';
|
1
3
|
import Editor from '../../Editor';
|
4
|
+
import uniteCommands from '../../commands/uniteCommands';
|
2
5
|
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
|
3
6
|
import { EditorEventType, KeyPressEvent } from '../../types';
|
4
7
|
import { ToolbarLocalization } from '../localization';
|
8
|
+
import makeColorInput from '../makeColorInput';
|
5
9
|
import ActionButtonWidget from './ActionButtonWidget';
|
6
10
|
import BaseToolWidget from './BaseToolWidget';
|
11
|
+
import BaseWidget from './BaseWidget';
|
12
|
+
|
13
|
+
class RestyleSelectionWidget extends BaseWidget {
|
14
|
+
private updateFormatData: ()=>void = () => {};
|
15
|
+
|
16
|
+
public constructor(editor: Editor, private selectionTool: SelectionTool, localizationTable?: ToolbarLocalization) {
|
17
|
+
super(editor, 'restyle-selection', localizationTable);
|
18
|
+
|
19
|
+
// Allow showing the dropdown even if this widget isn't selected yet
|
20
|
+
this.container.classList.add('dropdownShowable');
|
21
|
+
|
22
|
+
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
23
|
+
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
24
|
+
throw new Error('Invalid event type!');
|
25
|
+
}
|
26
|
+
|
27
|
+
if (toolEvt.tool === this.selectionTool) {
|
28
|
+
this.updateFormatData();
|
29
|
+
}
|
30
|
+
});
|
31
|
+
}
|
32
|
+
|
33
|
+
protected getTitle(): string {
|
34
|
+
return this.localizationTable.reformatSelection;
|
35
|
+
}
|
36
|
+
|
37
|
+
protected createIcon(){
|
38
|
+
return this.editor.icons.makeFormatSelectionIcon();
|
39
|
+
}
|
40
|
+
|
41
|
+
protected handleClick(): void {
|
42
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
43
|
+
}
|
44
|
+
|
45
|
+
protected fillDropdown(dropdown: HTMLElement): boolean {
|
46
|
+
const container = document.createElement('div');
|
47
|
+
const colorRow = document.createElement('div');
|
48
|
+
const colorLabel = document.createElement('label');
|
49
|
+
const [ colorInput, colorInputContainer, setColorInputValue ] = makeColorInput(this.editor, color => {
|
50
|
+
const selection = this.selectionTool.getSelection();
|
51
|
+
|
52
|
+
if (selection) {
|
53
|
+
const updateStyleCommands = [];
|
54
|
+
|
55
|
+
for (const elem of selection.getSelectedObjects()) {
|
56
|
+
if (isRestylableComponent(elem)) {
|
57
|
+
updateStyleCommands.push(elem.updateStyle({ color }));
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
const unitedCommand = uniteCommands(updateStyleCommands);
|
62
|
+
this.editor.dispatch(unitedCommand);
|
63
|
+
}
|
64
|
+
});
|
65
|
+
|
66
|
+
colorLabel.innerText = this.localizationTable.colorLabel;
|
67
|
+
|
68
|
+
this.updateFormatData = () => {
|
69
|
+
const selection = this.selectionTool.getSelection();
|
70
|
+
if (selection) {
|
71
|
+
colorInput.disabled = false;
|
72
|
+
|
73
|
+
const colors = [];
|
74
|
+
for (const elem of selection.getSelectedObjects()) {
|
75
|
+
if (isRestylableComponent(elem)) {
|
76
|
+
const color = elem.getStyle().color;
|
77
|
+
if (color) {
|
78
|
+
colors.push(color);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
setColorInputValue(Color4.average(colors));
|
83
|
+
} else {
|
84
|
+
colorInput.disabled = true;
|
85
|
+
}
|
86
|
+
};
|
87
|
+
|
88
|
+
colorRow.replaceChildren(colorLabel, colorInputContainer);
|
89
|
+
container.replaceChildren(colorRow);
|
90
|
+
dropdown.replaceChildren(container);
|
91
|
+
return true;
|
92
|
+
}
|
93
|
+
}
|
7
94
|
|
8
95
|
export default class SelectionToolWidget extends BaseToolWidget {
|
9
96
|
public constructor(
|
@@ -41,15 +128,22 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
41
128
|
},
|
42
129
|
localization,
|
43
130
|
);
|
131
|
+
const restyleButton = new RestyleSelectionWidget(
|
132
|
+
editor,
|
133
|
+
this.tool,
|
134
|
+
localization,
|
135
|
+
);
|
44
136
|
|
45
137
|
this.addSubWidget(resizeButton);
|
46
138
|
this.addSubWidget(deleteButton);
|
47
139
|
this.addSubWidget(duplicateButton);
|
140
|
+
this.addSubWidget(restyleButton);
|
48
141
|
|
49
142
|
const updateDisabled = (disabled: boolean) => {
|
50
143
|
resizeButton.setDisabled(disabled);
|
51
144
|
deleteButton.setDisabled(disabled);
|
52
145
|
duplicateButton.setDisabled(disabled);
|
146
|
+
restyleButton.setDisabled(disabled);
|
53
147
|
};
|
54
148
|
updateDisabled(true);
|
55
149
|
|
@@ -61,7 +155,7 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
61
155
|
|
62
156
|
if (toolEvt.tool === this.tool) {
|
63
157
|
const selection = this.tool.getSelection();
|
64
|
-
const hasSelection = selection && selection.
|
158
|
+
const hasSelection = selection && selection.getSelectedItemCount() > 0;
|
65
159
|
|
66
160
|
updateDisabled(!hasSelection);
|
67
161
|
}
|
@@ -53,7 +53,8 @@ describe('PanZoom', () => {
|
|
53
53
|
sendTouchEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(100, 0));
|
54
54
|
|
55
55
|
const updatedTranslation = editor.viewport.canvasToScreen(Vec2.zero);
|
56
|
-
expect(updatedTranslation.minus(origTranslation).magnitude()).
|
56
|
+
expect(updatedTranslation.minus(origTranslation).magnitude()).toBeGreaterThanOrEqual(100);
|
57
|
+
expect(updatedTranslation.minus(origTranslation).magnitude()).toBeLessThan(110);
|
57
58
|
|
58
59
|
await waitForTimeout(600); // ms
|
59
60
|
jest.useFakeTimers();
|
@@ -6,8 +6,8 @@ import { Mat33 } from '../math/lib';
|
|
6
6
|
import BaseTool from './BaseTool';
|
7
7
|
import TextTool from './TextTool';
|
8
8
|
import Color4 from '../Color4';
|
9
|
-
import { TextStyle } from '../components/TextComponent';
|
10
9
|
import ImageComponent from '../components/ImageComponent';
|
10
|
+
import TextStyle from '../rendering/TextRenderingStyle';
|
11
11
|
|
12
12
|
/**
|
13
13
|
* A tool that handles paste events (e.g. as triggered by ctrl+V).
|
package/src/tools/Pen.ts
CHANGED
@@ -200,7 +200,7 @@ export default class Pen extends BaseTool {
|
|
200
200
|
return true;
|
201
201
|
}
|
202
202
|
|
203
|
-
if (key === 'control') {
|
203
|
+
if (key === 'control' || key === 'meta') {
|
204
204
|
this.ctrlKeyPressed = true;
|
205
205
|
return true;
|
206
206
|
}
|
@@ -216,7 +216,7 @@ export default class Pen extends BaseTool {
|
|
216
216
|
public onKeyUp({ key }: KeyUpEvent): boolean {
|
217
217
|
key = key.toLowerCase();
|
218
218
|
|
219
|
-
if (key === 'control') {
|
219
|
+
if (key === 'control' || key === 'meta') {
|
220
220
|
this.ctrlKeyPressed = false;
|
221
221
|
return true;
|
222
222
|
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import { KeyPressEvent } from '../../types';
|
3
|
+
import BaseTool from '../BaseTool';
|
4
|
+
import SelectionTool from './SelectionTool';
|
5
|
+
|
6
|
+
// Handles ctrl+a: Select all
|
7
|
+
export default class SelectAllShortcutHandler extends BaseTool {
|
8
|
+
public constructor(private editor: Editor) {
|
9
|
+
super(editor.notifier, editor.localization.selectAllTool);
|
10
|
+
}
|
11
|
+
|
12
|
+
// @internal
|
13
|
+
public onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean {
|
14
|
+
if (ctrlKey && key === 'a') {
|
15
|
+
const selectionTools = this.editor.toolController.getMatchingTools(SelectionTool);
|
16
|
+
|
17
|
+
if (selectionTools.length > 0) {
|
18
|
+
const selectionTool = selectionTools[0];
|
19
|
+
selectionTool.setEnabled(true);
|
20
|
+
selectionTool.setSelection(this.editor.image.getAllElements());
|
21
|
+
|
22
|
+
return true;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
return false;
|
27
|
+
}
|
28
|
+
}
|
@@ -265,7 +265,7 @@ export default class Selection {
|
|
265
265
|
this.selection?.setTransform(this.fullTransform.inverse(), false);
|
266
266
|
this.selection?.updateUI();
|
267
267
|
|
268
|
-
await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize);
|
268
|
+
await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
|
269
269
|
this.selection?.setTransform(Mat33.identity);
|
270
270
|
this.selection?.recomputeRegion();
|
271
271
|
this.selection?.updateUI();
|
@@ -212,10 +212,10 @@ export default class SelectionTool extends BaseTool {
|
|
212
212
|
'e', 'j', 'ArrowDown',
|
213
213
|
'r', 'R',
|
214
214
|
'i', 'I', 'o', 'O',
|
215
|
-
'Control',
|
215
|
+
'Control', 'Meta',
|
216
216
|
];
|
217
217
|
public onKeyPress(event: KeyPressEvent): boolean {
|
218
|
-
if (event.key === 'Control') {
|
218
|
+
if (event.key === 'Control' || event.key === 'Meta') {
|
219
219
|
this.ctrlKeyPressed = true;
|
220
220
|
return true;
|
221
221
|
}
|
@@ -226,8 +226,7 @@ export default class SelectionTool extends BaseTool {
|
|
226
226
|
return true;
|
227
227
|
}
|
228
228
|
else if (event.key === 'a' && event.ctrlKey) {
|
229
|
-
|
230
|
-
// Return early to prevent 'a' from moving the selection/view.
|
229
|
+
this.setSelection(this.editor.image.getAllElements());
|
231
230
|
return true;
|
232
231
|
}
|
233
232
|
else if (event.ctrlKey) {
|
@@ -335,7 +334,7 @@ export default class SelectionTool extends BaseTool {
|
|
335
334
|
}
|
336
335
|
|
337
336
|
public onKeyUp(evt: KeyUpEvent) {
|
338
|
-
if (evt.key === 'Control') {
|
337
|
+
if (evt.key === 'Control' || evt.key === 'Meta') {
|
339
338
|
this.ctrlKeyPressed = false;
|
340
339
|
return true;
|
341
340
|
}
|
@@ -351,10 +350,6 @@ export default class SelectionTool extends BaseTool {
|
|
351
350
|
});
|
352
351
|
return true;
|
353
352
|
}
|
354
|
-
else if (evt.key === 'a') {
|
355
|
-
this.setSelection(this.editor.image.getAllElements());
|
356
|
-
return true;
|
357
|
-
}
|
358
353
|
}
|
359
354
|
|
360
355
|
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
|