js-draw 0.13.1 → 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 +15 -0
- package/README.md +1 -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 +14 -5
- package/dist/src/EditorImage.d.ts +1 -0
- package/dist/src/EditorImage.js +11 -0
- package/dist/src/SVGLoader.js +8 -2
- package/dist/src/Viewport.d.ts +1 -0
- package/dist/src/Viewport.js +6 -3
- 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/math/Path.js +10 -3
- 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 +30 -3
- package/dist/src/toolbar/IconProvider.js +37 -2
- 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.d.ts +6 -0
- package/dist/src/tools/SelectionTool/Selection.js +13 -4
- package/dist/src/tools/SelectionTool/SelectionTool.js +9 -12
- package/dist/src/tools/SelectionTool/TransformMode.js +1 -1
- 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 +16 -5
- package/src/EditorImage.ts +13 -0
- package/src/SVGLoader.ts +11 -3
- package/src/Viewport.ts +7 -3
- 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/Path.toString.test.ts +10 -0
- package/src/math/Path.ts +11 -3
- 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 +40 -7
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/toolbar.css +3 -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 +17 -6
- package/src/tools/SelectionTool/SelectionTool.ts +9 -13
- package/src/tools/SelectionTool/TransformMode.ts +1 -1
- 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,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,17 +1,14 @@
|
|
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';
|
10
10
|
|
11
|
-
|
12
|
-
// Many of the icons were created with Inkscape.
|
13
|
-
|
14
|
-
type IconType = SVGSVGElement|HTMLImageElement;
|
11
|
+
export type IconType = HTMLImageElement|SVGElement;
|
15
12
|
|
16
13
|
const svgNamespace = 'http://www.w3.org/2000/svg';
|
17
14
|
const iconColorFill = `
|
@@ -35,8 +32,33 @@ const checkerboardPatternDef = `
|
|
35
32
|
`;
|
36
33
|
const checkerboardPatternRef = 'url(#checkerboard)';
|
37
34
|
|
38
|
-
|
39
|
-
|
35
|
+
/**
|
36
|
+
* Provides icons that can be used in the toolbar, etc.
|
37
|
+
* Extend this class and override methods to customize icons.
|
38
|
+
*
|
39
|
+
* @example
|
40
|
+
* ```ts
|
41
|
+
* class CustomIconProvider extends jsdraw.IconProvider {
|
42
|
+
* // Use '☺' instead of the default dropdown symbol.
|
43
|
+
* public makeDropdownIcon() {
|
44
|
+
* const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
45
|
+
* icon.innerHTML = `
|
46
|
+
* <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
|
47
|
+
* `;
|
48
|
+
* icon.setAttribute('viewBox', '0 0 100 100');
|
49
|
+
* return icon;
|
50
|
+
* }
|
51
|
+
* }
|
52
|
+
*
|
53
|
+
* const icons = new CustomIconProvider();
|
54
|
+
* const editor = new jsdraw.Editor(document.body, {
|
55
|
+
* iconProvider: icons,
|
56
|
+
* });
|
57
|
+
*
|
58
|
+
* // Add a toolbar that uses these icons
|
59
|
+
* editor.addToolbar();
|
60
|
+
* ```
|
61
|
+
*/
|
40
62
|
export default class IconProvider {
|
41
63
|
|
42
64
|
public makeUndoIcon(): IconType {
|
@@ -587,6 +609,17 @@ export default class IconProvider {
|
|
587
609
|
icon.setAttribute('viewBox', '0 0 100 100');
|
588
610
|
return icon;
|
589
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
|
+
}
|
590
623
|
|
591
624
|
public makeResizeViewportIcon(): IconType {
|
592
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',
|
package/src/toolbar/toolbar.css
CHANGED
@@ -33,6 +33,7 @@
|
|
33
33
|
|
34
34
|
.toolbar-dropdown .toolbar-button > .toolbar-icon {
|
35
35
|
max-width: 50px;
|
36
|
+
width: 100%;
|
36
37
|
}
|
37
38
|
|
38
39
|
.toolbar-button.disabled {
|
@@ -89,6 +90,8 @@
|
|
89
90
|
|
90
91
|
.toolbar-root .toolbar-icon {
|
91
92
|
flex-shrink: 1;
|
93
|
+
|
94
|
+
width: 100%;
|
92
95
|
min-width: 30px;
|
93
96
|
min-height: 30px;
|
94
97
|
}
|
@@ -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
|
+
}
|
@@ -121,6 +121,21 @@ export default class Selection {
|
|
121
121
|
return this.originalRegion.transformedBoundingBox(scaleAndTranslateMat);
|
122
122
|
}
|
123
123
|
|
124
|
+
/**
|
125
|
+
* Computes and returns the bounding box of the selection without
|
126
|
+
* any additional padding. Computes directly from the elements that are selected.
|
127
|
+
* @internal
|
128
|
+
*/
|
129
|
+
public computeTightBoundingBox() {
|
130
|
+
const bbox = this.selectedElems.reduce((
|
131
|
+
accumulator: Rect2|null, elem: AbstractComponent
|
132
|
+
): Rect2 => {
|
133
|
+
return (accumulator ?? elem.getBBox()).union(elem.getBBox());
|
134
|
+
}, null);
|
135
|
+
|
136
|
+
return bbox ?? Rect2.empty;
|
137
|
+
}
|
138
|
+
|
124
139
|
public get regionRotation(): number {
|
125
140
|
return this.transform.transformVec3(Vec2.unitX).angle();
|
126
141
|
}
|
@@ -250,7 +265,7 @@ export default class Selection {
|
|
250
265
|
this.selection?.setTransform(this.fullTransform.inverse(), false);
|
251
266
|
this.selection?.updateUI();
|
252
267
|
|
253
|
-
await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize);
|
268
|
+
await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
|
254
269
|
this.selection?.setTransform(Mat33.identity);
|
255
270
|
this.selection?.recomputeRegion();
|
256
271
|
this.selection?.updateUI();
|
@@ -328,11 +343,7 @@ export default class Selection {
|
|
328
343
|
// Recompute this' region from the selected elements.
|
329
344
|
// Returns false if the selection is empty.
|
330
345
|
public recomputeRegion(): boolean {
|
331
|
-
const newRegion = this.
|
332
|
-
accumulator: Rect2|null, elem: AbstractComponent
|
333
|
-
): Rect2 => {
|
334
|
-
return (accumulator ?? elem.getBBox()).union(elem.getBBox());
|
335
|
-
}, null);
|
346
|
+
const newRegion = this.computeTightBoundingBox();
|
336
347
|
|
337
348
|
if (!newRegion) {
|
338
349
|
this.cancelSelection();
|
@@ -62,12 +62,13 @@ export default class SelectionTool extends BaseTool {
|
|
62
62
|
private snapSelectionToGrid() {
|
63
63
|
if (!this.selectionBox) throw new Error('No selection to snap!');
|
64
64
|
|
65
|
-
|
66
|
-
const
|
67
|
-
|
65
|
+
// Snap the top left corner of what we have selected.
|
66
|
+
const topLeftOfBBox = this.selectionBox.computeTightBoundingBox().topLeft;
|
67
|
+
const snappedTopLeft = this.editor.viewport.snapToGrid(topLeftOfBBox);
|
68
|
+
const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
|
68
69
|
|
69
70
|
const oldTransform = this.selectionBox.getTransform();
|
70
|
-
this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(
|
71
|
+
this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDelta)));
|
71
72
|
this.selectionBox.finalizeTransform();
|
72
73
|
}
|
73
74
|
|
@@ -211,10 +212,10 @@ export default class SelectionTool extends BaseTool {
|
|
211
212
|
'e', 'j', 'ArrowDown',
|
212
213
|
'r', 'R',
|
213
214
|
'i', 'I', 'o', 'O',
|
214
|
-
'Control',
|
215
|
+
'Control', 'Meta',
|
215
216
|
];
|
216
217
|
public onKeyPress(event: KeyPressEvent): boolean {
|
217
|
-
if (event.key === 'Control') {
|
218
|
+
if (event.key === 'Control' || event.key === 'Meta') {
|
218
219
|
this.ctrlKeyPressed = true;
|
219
220
|
return true;
|
220
221
|
}
|
@@ -225,8 +226,7 @@ export default class SelectionTool extends BaseTool {
|
|
225
226
|
return true;
|
226
227
|
}
|
227
228
|
else if (event.key === 'a' && event.ctrlKey) {
|
228
|
-
|
229
|
-
// Return early to prevent 'a' from moving the selection/view.
|
229
|
+
this.setSelection(this.editor.image.getAllElements());
|
230
230
|
return true;
|
231
231
|
}
|
232
232
|
else if (event.ctrlKey) {
|
@@ -334,7 +334,7 @@ export default class SelectionTool extends BaseTool {
|
|
334
334
|
}
|
335
335
|
|
336
336
|
public onKeyUp(evt: KeyUpEvent) {
|
337
|
-
if (evt.key === 'Control') {
|
337
|
+
if (evt.key === 'Control' || evt.key === 'Meta') {
|
338
338
|
this.ctrlKeyPressed = false;
|
339
339
|
return true;
|
340
340
|
}
|
@@ -350,10 +350,6 @@ export default class SelectionTool extends BaseTool {
|
|
350
350
|
});
|
351
351
|
return true;
|
352
352
|
}
|
353
|
-
else if (evt.key === 'a') {
|
354
|
-
this.setSelection(this.editor.image.getAllElements());
|
355
|
-
return true;
|
356
|
-
}
|
357
353
|
}
|
358
354
|
|
359
355
|
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
|
@@ -62,7 +62,7 @@ export class ResizeTransformer {
|
|
62
62
|
// long decimal representations => large file sizes.
|
63
63
|
scale = scale.map(component => Viewport.roundScaleRatio(component, 2));
|
64
64
|
|
65
|
-
if (scale.x
|
65
|
+
if (scale.x !== 0 && scale.y !== 0) {
|
66
66
|
const origin = this.editor.viewport.roundPoint(this.selection.preTransformRegion.topLeft);
|
67
67
|
this.selection.setTransform(Mat33.scaling2D(scale, origin));
|
68
68
|
}
|
package/src/tools/TextTool.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
|
-
import TextComponent
|
2
|
+
import TextComponent from '../components/TextComponent';
|
3
3
|
import Editor from '../Editor';
|
4
4
|
import EditorImage from '../EditorImage';
|
5
5
|
import Rect2 from '../math/Rect2';
|
@@ -11,6 +11,7 @@ import BaseTool from './BaseTool';
|
|
11
11
|
import { ToolLocalization } from './localization';
|
12
12
|
import Erase from '../commands/Erase';
|
13
13
|
import uniteCommands from '../commands/uniteCommands';
|
14
|
+
import TextStyle from '../rendering/TextRenderingStyle';
|
14
15
|
|
15
16
|
const overlayCssClass = 'textEditorOverlay';
|
16
17
|
export default class TextTool extends BaseTool {
|
@@ -16,6 +16,7 @@ import PasteHandler from './PasteHandler';
|
|
16
16
|
import ToolbarShortcutHandler from './ToolbarShortcutHandler';
|
17
17
|
import { makePressureSensitiveFreehandLineBuilder } from '../components/builders/PressureSensitiveFreehandLineBuilder';
|
18
18
|
import FindTool from './FindTool';
|
19
|
+
import SelectAllShortcutHandler from './SelectionTool/SelectAllShortcutHandler';
|
19
20
|
|
20
21
|
export default class ToolController {
|
21
22
|
private tools: BaseTool[];
|
@@ -53,6 +54,7 @@ export default class ToolController {
|
|
53
54
|
new ToolSwitcherShortcut(editor),
|
54
55
|
new FindTool(editor),
|
55
56
|
new PasteHandler(editor),
|
57
|
+
new SelectAllShortcutHandler(editor),
|
56
58
|
];
|
57
59
|
primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
|
58
60
|
panZoomTool.setEnabled(true);
|
package/src/tools/lib.ts
CHANGED
@@ -14,6 +14,7 @@ export { default as PanZoomTool, PanZoomMode } from './PanZoom';
|
|
14
14
|
export { default as PenTool, PenStyle } from './Pen';
|
15
15
|
export { default as TextTool } from './TextTool';
|
16
16
|
export { default as SelectionTool } from './SelectionTool/SelectionTool';
|
17
|
+
export { default as SelectAllShortcutHandler } from './SelectionTool/SelectAllShortcutHandler';
|
17
18
|
export { default as EraserTool } from './Eraser';
|
18
19
|
export { default as PasteHandler } from './PasteHandler';
|
19
20
|
|
@@ -3,6 +3,7 @@ export interface ToolLocalization {
|
|
3
3
|
keyboardPanZoom: string;
|
4
4
|
penTool: (penId: number)=>string;
|
5
5
|
selectionTool: string;
|
6
|
+
selectAllTool: string;
|
6
7
|
eraserTool: string;
|
7
8
|
touchPanTool: string;
|
8
9
|
twoFingerPanZoomTool: string;
|
@@ -34,6 +35,7 @@ export interface ToolLocalization {
|
|
34
35
|
export const defaultToolLocalization: ToolLocalization = {
|
35
36
|
penTool: (penId) => `Pen ${penId}`,
|
36
37
|
selectionTool: 'Selection',
|
38
|
+
selectAllTool: 'Select all shortcut',
|
37
39
|
eraserTool: 'Eraser',
|
38
40
|
touchPanTool: 'Touch panning',
|
39
41
|
twoFingerPanZoomTool: 'Panning and zooming',
|