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
@@ -2,8 +2,9 @@ import LineSegment2 from '../math/LineSegment2';
|
|
2
2
|
import Mat33 from '../math/Mat33';
|
3
3
|
import Rect2 from '../math/Rect2';
|
4
4
|
import { Vec2 } from '../math/Vec2';
|
5
|
-
import {
|
5
|
+
import { textStyleFromJSON, textStyleToJSON } from '../rendering/TextRenderingStyle';
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
|
+
import { createRestyleComponentCommand } from './RestylableComponent';
|
7
8
|
const componentTypeId = 'text';
|
8
9
|
export default class TextComponent extends AbstractComponent {
|
9
10
|
constructor(textObjects, transform, style) {
|
@@ -11,6 +12,8 @@ export default class TextComponent extends AbstractComponent {
|
|
11
12
|
this.textObjects = textObjects;
|
12
13
|
this.transform = transform;
|
13
14
|
this.style = style;
|
15
|
+
// eslint-disable-next-line @typescript-eslint/prefer-as-const
|
16
|
+
this.isRestylableComponent = true;
|
14
17
|
this.recomputeBBox();
|
15
18
|
// If this has no direct children, choose a style representative of this' content
|
16
19
|
// (useful for estimating the style of the TextComponent).
|
@@ -117,12 +120,43 @@ export default class TextComponent extends AbstractComponent {
|
|
117
120
|
}
|
118
121
|
return false;
|
119
122
|
}
|
120
|
-
|
121
|
-
return
|
123
|
+
getStyle() {
|
124
|
+
return {
|
125
|
+
color: this.style.renderingStyle.fill,
|
126
|
+
// Make a copy
|
127
|
+
textStyle: Object.assign(Object.assign({}, this.style), { renderingStyle: Object.assign({}, this.style.renderingStyle) }),
|
128
|
+
};
|
129
|
+
}
|
130
|
+
updateStyle(style) {
|
131
|
+
return createRestyleComponentCommand(this.getStyle(), style, this);
|
132
|
+
}
|
133
|
+
forceStyle(style, editor) {
|
134
|
+
if (style.textStyle) {
|
135
|
+
this.style = style.textStyle;
|
136
|
+
}
|
137
|
+
else if (style.color) {
|
138
|
+
this.style.renderingStyle = Object.assign(Object.assign({}, this.style.renderingStyle), { fill: style.color });
|
139
|
+
}
|
140
|
+
else {
|
141
|
+
return;
|
142
|
+
}
|
143
|
+
for (const child of this.textObjects) {
|
144
|
+
if (child instanceof TextComponent) {
|
145
|
+
child.forceStyle(style, editor);
|
146
|
+
}
|
147
|
+
}
|
148
|
+
if (editor) {
|
149
|
+
editor.image.queueRerenderOf(this);
|
150
|
+
editor.queueRerender();
|
151
|
+
}
|
122
152
|
}
|
153
|
+
// See this.getStyle
|
123
154
|
getTextStyle() {
|
124
155
|
return this.style;
|
125
156
|
}
|
157
|
+
getBaselinePos() {
|
158
|
+
return this.transform.transformVec2(Vec2.zero);
|
159
|
+
}
|
126
160
|
getTransform() {
|
127
161
|
return this.transform;
|
128
162
|
}
|
@@ -148,9 +182,10 @@ export default class TextComponent extends AbstractComponent {
|
|
148
182
|
description(localizationTable) {
|
149
183
|
return localizationTable.text(this.getText());
|
150
184
|
}
|
185
|
+
// Do not rely on the output of `serializeToJSON` taking any particular format.
|
151
186
|
serializeToJSON() {
|
152
|
-
const serializableStyle =
|
153
|
-
const
|
187
|
+
const serializableStyle = textStyleToJSON(this.style);
|
188
|
+
const serializedTextObjects = this.textObjects.map(text => {
|
154
189
|
if (typeof text === 'string') {
|
155
190
|
return {
|
156
191
|
text,
|
@@ -163,19 +198,17 @@ export default class TextComponent extends AbstractComponent {
|
|
163
198
|
}
|
164
199
|
});
|
165
200
|
return {
|
166
|
-
textObjects,
|
201
|
+
textObjects: serializedTextObjects,
|
167
202
|
transform: this.transform.toArray(),
|
168
203
|
style: serializableStyle,
|
169
204
|
};
|
170
205
|
}
|
206
|
+
// @internal
|
171
207
|
static deserializeFromString(json) {
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
fontVariant: json.style.fontVariant,
|
177
|
-
fontFamily: json.style.fontFamily,
|
178
|
-
};
|
208
|
+
if (typeof json === 'string') {
|
209
|
+
json = JSON.parse(json);
|
210
|
+
}
|
211
|
+
const style = textStyleFromJSON(json.style);
|
179
212
|
const textObjects = json.textObjects.map((data) => {
|
180
213
|
var _a;
|
181
214
|
if (((_a = data.text) !== null && _a !== void 0 ? _a : null) !== null) {
|
@@ -7,4 +7,5 @@ export { default as AbstractComponent } from './AbstractComponent';
|
|
7
7
|
import Stroke from './Stroke';
|
8
8
|
import TextComponent from './TextComponent';
|
9
9
|
import ImageComponent from './ImageComponent';
|
10
|
-
|
10
|
+
import RestyleableComponent, { createRestyleComponentCommand } from './RestylableComponent';
|
11
|
+
export { Stroke, TextComponent as Text, RestyleableComponent, createRestyleComponentCommand, TextComponent, Stroke as StrokeComponent, ImageComponent, };
|
@@ -7,4 +7,5 @@ export { default as AbstractComponent } from './AbstractComponent';
|
|
7
7
|
import Stroke from './Stroke';
|
8
8
|
import TextComponent from './TextComponent';
|
9
9
|
import ImageComponent from './ImageComponent';
|
10
|
-
|
10
|
+
import { createRestyleComponentCommand } from './RestylableComponent';
|
11
|
+
export { Stroke, TextComponent as Text, createRestyleComponentCommand, TextComponent, Stroke as StrokeComponent, ImageComponent, };
|
@@ -2,6 +2,7 @@ export const defaultComponentLocalization = {
|
|
2
2
|
unlabeledImageNode: 'Unlabeled image node',
|
3
3
|
stroke: 'Stroke',
|
4
4
|
svgObject: 'SVG Object',
|
5
|
+
restyledElements: 'Restyled elements',
|
5
6
|
text: (text) => `Text object: ${text}`,
|
6
7
|
imageNode: (description) => `Image: ${description}`,
|
7
8
|
};
|
package/dist/src/math/Path.js
CHANGED
@@ -347,6 +347,7 @@ export default class Path {
|
|
347
347
|
// @param onlyAbsCommands - True if we should avoid converting absolute coordinates to relative offsets -- such
|
348
348
|
// conversions can lead to smaller output strings, but also take time.
|
349
349
|
static toString(startPoint, parts, onlyAbsCommands) {
|
350
|
+
var _a;
|
350
351
|
const result = [];
|
351
352
|
let prevPoint;
|
352
353
|
const addCommand = (command, ...points) => {
|
@@ -382,7 +383,7 @@ export default class Path {
|
|
382
383
|
commandString = `${command.toLowerCase()}${relativeCommandParts.join(' ')}`;
|
383
384
|
}
|
384
385
|
// Don't add no-ops.
|
385
|
-
if (commandString === 'l0,0') {
|
386
|
+
if (commandString === 'l0,0' || commandString === 'm0,0') {
|
386
387
|
return;
|
387
388
|
}
|
388
389
|
result.push(commandString);
|
@@ -390,9 +391,15 @@ export default class Path {
|
|
390
391
|
prevPoint = points[points.length - 1];
|
391
392
|
}
|
392
393
|
};
|
393
|
-
|
394
|
+
// Don't add two moveTos in a row (this can happen if
|
395
|
+
// the start point corresponds to a moveTo _and_ the first command is
|
396
|
+
// also a moveTo)
|
397
|
+
if (((_a = parts[0]) === null || _a === void 0 ? void 0 : _a.kind) !== PathCommandType.MoveTo) {
|
398
|
+
addCommand('M', startPoint);
|
399
|
+
}
|
394
400
|
let exhaustivenessCheck;
|
395
|
-
for (
|
401
|
+
for (let i = 0; i < parts.length; i++) {
|
402
|
+
const part = parts[i];
|
396
403
|
switch (part.kind) {
|
397
404
|
case PathCommandType.MoveTo:
|
398
405
|
addCommand('M', part.point);
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import RenderingStyle from './RenderingStyle';
|
2
|
+
export interface TextStyle {
|
3
|
+
size: number;
|
4
|
+
fontFamily: string;
|
5
|
+
fontWeight?: string;
|
6
|
+
fontVariant?: string;
|
7
|
+
renderingStyle: RenderingStyle;
|
8
|
+
}
|
9
|
+
export default TextStyle;
|
10
|
+
export declare const textStyleFromJSON: (json: any) => TextStyle;
|
11
|
+
export declare const textStyleToJSON: (style: TextStyle) => {
|
12
|
+
renderingStyle: {
|
13
|
+
fill: string;
|
14
|
+
stroke: {
|
15
|
+
color: string;
|
16
|
+
width: number;
|
17
|
+
} | undefined;
|
18
|
+
};
|
19
|
+
size: number;
|
20
|
+
fontFamily: string;
|
21
|
+
fontWeight?: string | undefined;
|
22
|
+
fontVariant?: string | undefined;
|
23
|
+
};
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { styleFromJSON, styleToJSON } from './RenderingStyle';
|
2
|
+
export const textStyleFromJSON = (json) => {
|
3
|
+
if (typeof json === 'string') {
|
4
|
+
json = JSON.parse(json);
|
5
|
+
}
|
6
|
+
if (typeof (json.fontFamily) !== 'string') {
|
7
|
+
throw new Error('Serialized textStyle missing string fontFamily attribute!');
|
8
|
+
}
|
9
|
+
const style = {
|
10
|
+
renderingStyle: styleFromJSON(json.renderingStyle),
|
11
|
+
size: json.size,
|
12
|
+
fontWeight: json.fontWeight,
|
13
|
+
fontVariant: json.fontVariant,
|
14
|
+
fontFamily: json.fontFamily,
|
15
|
+
};
|
16
|
+
return style;
|
17
|
+
};
|
18
|
+
export const textStyleToJSON = (style) => {
|
19
|
+
return Object.assign(Object.assign({}, style), { renderingStyle: styleToJSON(style.renderingStyle) });
|
20
|
+
};
|
@@ -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 } 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 from '../RenderingStyle';
|
8
|
+
import TextStyle from '../TextRenderingStyle';
|
9
9
|
export interface RenderablePathSpec {
|
10
10
|
startPoint: Point2;
|
11
11
|
commands: PathCommand[];
|
@@ -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, RenderablePathSpec } from './AbstractRenderer';
|
9
9
|
export default class CanvasRenderer extends AbstractRenderer {
|
10
10
|
private ctx;
|
@@ -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
|
export default class DummyRenderer extends AbstractRenderer {
|
10
10
|
clearedCount: number;
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
|
-
import { TextStyle } from '../../components/TextComponent';
|
3
2
|
import Mat33 from '../../math/Mat33';
|
4
3
|
import Rect2 from '../../math/Rect2';
|
5
4
|
import { Point2, Vec2 } from '../../math/Vec2';
|
6
5
|
import Viewport from '../../Viewport';
|
7
6
|
import RenderingStyle from '../RenderingStyle';
|
7
|
+
import TextStyle from '../TextRenderingStyle';
|
8
8
|
import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
|
9
9
|
export declare const renderedStylesheetId = "js-draw-style-sheet";
|
10
10
|
export default class SVGRenderer 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 Vec3 from '../../math/Vec3';
|
5
4
|
import Viewport from '../../Viewport';
|
6
5
|
import { TextRendererLocalization } from '../localization';
|
7
6
|
import RenderingStyle from '../RenderingStyle';
|
7
|
+
import TextStyle from '../TextRenderingStyle';
|
8
8
|
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
9
9
|
export default class TextOnlyRenderer extends AbstractRenderer {
|
10
10
|
private localizationTable;
|
@@ -1,8 +1,35 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import { ComponentBuilderFactory } from '../components/builders/types';
|
3
|
-
import
|
3
|
+
import TextStyle from '../rendering/TextRenderingStyle';
|
4
4
|
import Pen from '../tools/Pen';
|
5
|
-
type IconType =
|
5
|
+
export type IconType = HTMLImageElement | SVGElement;
|
6
|
+
/**
|
7
|
+
* Provides icons that can be used in the toolbar, etc.
|
8
|
+
* Extend this class and override methods to customize icons.
|
9
|
+
*
|
10
|
+
* @example
|
11
|
+
* ```ts
|
12
|
+
* class CustomIconProvider extends jsdraw.IconProvider {
|
13
|
+
* // Use '☺' instead of the default dropdown symbol.
|
14
|
+
* public makeDropdownIcon() {
|
15
|
+
* const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
16
|
+
* icon.innerHTML = `
|
17
|
+
* <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
|
18
|
+
* `;
|
19
|
+
* icon.setAttribute('viewBox', '0 0 100 100');
|
20
|
+
* return icon;
|
21
|
+
* }
|
22
|
+
* }
|
23
|
+
*
|
24
|
+
* const icons = new CustomIconProvider();
|
25
|
+
* const editor = new jsdraw.Editor(document.body, {
|
26
|
+
* iconProvider: icons,
|
27
|
+
* });
|
28
|
+
*
|
29
|
+
* // Add a toolbar that uses these icons
|
30
|
+
* editor.addToolbar();
|
31
|
+
* ```
|
32
|
+
*/
|
6
33
|
export default class IconProvider {
|
7
34
|
makeUndoIcon(): IconType;
|
8
35
|
makeRedoIcon(mirror?: boolean): IconType;
|
@@ -24,10 +51,10 @@ export default class IconProvider {
|
|
24
51
|
makePenIcon(strokeSize: number, color: string | Color4, rounded?: boolean): IconType;
|
25
52
|
makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType;
|
26
53
|
makePipetteIcon(color?: Color4): IconType;
|
54
|
+
makeFormatSelectionIcon(): IconType;
|
27
55
|
makeResizeViewportIcon(): IconType;
|
28
56
|
makeDuplicateSelectionIcon(): IconType;
|
29
57
|
makePasteIcon(): IconType;
|
30
58
|
makeDeleteSelectionIcon(): IconType;
|
31
59
|
makeSaveIcon(): IconType;
|
32
60
|
}
|
33
|
-
export {};
|
@@ -24,8 +24,33 @@ const checkerboardPatternDef = `
|
|
24
24
|
</pattern>
|
25
25
|
`;
|
26
26
|
const checkerboardPatternRef = 'url(#checkerboard)';
|
27
|
-
|
28
|
-
|
27
|
+
/**
|
28
|
+
* Provides icons that can be used in the toolbar, etc.
|
29
|
+
* Extend this class and override methods to customize icons.
|
30
|
+
*
|
31
|
+
* @example
|
32
|
+
* ```ts
|
33
|
+
* class CustomIconProvider extends jsdraw.IconProvider {
|
34
|
+
* // Use '☺' instead of the default dropdown symbol.
|
35
|
+
* public makeDropdownIcon() {
|
36
|
+
* const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
37
|
+
* icon.innerHTML = `
|
38
|
+
* <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
|
39
|
+
* `;
|
40
|
+
* icon.setAttribute('viewBox', '0 0 100 100');
|
41
|
+
* return icon;
|
42
|
+
* }
|
43
|
+
* }
|
44
|
+
*
|
45
|
+
* const icons = new CustomIconProvider();
|
46
|
+
* const editor = new jsdraw.Editor(document.body, {
|
47
|
+
* iconProvider: icons,
|
48
|
+
* });
|
49
|
+
*
|
50
|
+
* // Add a toolbar that uses these icons
|
51
|
+
* editor.addToolbar();
|
52
|
+
* ```
|
53
|
+
*/
|
29
54
|
export default class IconProvider {
|
30
55
|
makeUndoIcon() {
|
31
56
|
return this.makeRedoIcon(true);
|
@@ -512,6 +537,16 @@ export default class IconProvider {
|
|
512
537
|
icon.setAttribute('viewBox', '0 0 100 100');
|
513
538
|
return icon;
|
514
539
|
}
|
540
|
+
makeFormatSelectionIcon() {
|
541
|
+
return this.makeIconFromPath(`
|
542
|
+
M 5 10
|
543
|
+
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
|
544
|
+
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
|
545
|
+
M 60 25 L 55 25 L 50 30 L 60 25 z
|
546
|
+
M 10 55 L 10 90 L 41 90 L 41 86 L 45 86 L 45 55 L 10 55 z
|
547
|
+
M 42 87 L 42 93 L 48 93 L 48 87 L 42 87 z
|
548
|
+
`);
|
549
|
+
}
|
515
550
|
makeResizeViewportIcon() {
|
516
551
|
return this.makeIconFromPath(`
|
517
552
|
M 75 5 75 10 90 10 90 25 95 25 95 5 75 5 z
|
@@ -88,24 +88,30 @@ export default class BaseWidget {
|
|
88
88
|
}
|
89
89
|
// If we didn't do anything with the event, send it to the editor.
|
90
90
|
if (!handled) {
|
91
|
-
this.editor.toolController.dispatchInputEvent({
|
91
|
+
handled = this.editor.toolController.dispatchInputEvent({
|
92
92
|
kind: InputEvtType.KeyPressEvent,
|
93
93
|
key: evt.key,
|
94
|
-
ctrlKey: evt.ctrlKey,
|
94
|
+
ctrlKey: evt.ctrlKey || evt.metaKey,
|
95
95
|
altKey: evt.altKey,
|
96
96
|
});
|
97
97
|
}
|
98
|
+
if (handled) {
|
99
|
+
evt.preventDefault();
|
100
|
+
}
|
98
101
|
};
|
99
102
|
button.onkeyup = evt => {
|
100
103
|
if (evt.key in clickTriggers) {
|
101
104
|
return;
|
102
105
|
}
|
103
|
-
this.editor.toolController.dispatchInputEvent({
|
106
|
+
const handled = this.editor.toolController.dispatchInputEvent({
|
104
107
|
kind: InputEvtType.KeyUpEvent,
|
105
108
|
key: evt.key,
|
106
|
-
ctrlKey: evt.ctrlKey,
|
109
|
+
ctrlKey: evt.ctrlKey || evt.metaKey,
|
107
110
|
altKey: evt.altKey,
|
108
111
|
});
|
112
|
+
if (handled) {
|
113
|
+
evt.preventDefault();
|
114
|
+
}
|
109
115
|
};
|
110
116
|
button.onclick = () => {
|
111
117
|
if (!this.disabled) {
|
@@ -10,7 +10,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
10
10
|
import ImageComponent from '../../components/ImageComponent';
|
11
11
|
import Erase from '../../commands/Erase';
|
12
12
|
import EditorImage from '../../EditorImage';
|
13
|
-
import
|
13
|
+
import uniteCommands from '../../commands/uniteCommands';
|
14
|
+
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
|
14
15
|
import Mat33 from '../../math/Mat33';
|
15
16
|
import fileToBase64 from '../../util/fileToBase64';
|
16
17
|
import ActionButtonWidget from './ActionButtonWidget';
|
@@ -7,9 +7,82 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
8
|
});
|
9
9
|
};
|
10
|
+
import Color4 from '../../Color4';
|
11
|
+
import { isRestylableComponent } from '../../components/RestylableComponent';
|
12
|
+
import uniteCommands from '../../commands/uniteCommands';
|
10
13
|
import { EditorEventType } from '../../types';
|
14
|
+
import makeColorInput from '../makeColorInput';
|
11
15
|
import ActionButtonWidget from './ActionButtonWidget';
|
12
16
|
import BaseToolWidget from './BaseToolWidget';
|
17
|
+
import BaseWidget from './BaseWidget';
|
18
|
+
class RestyleSelectionWidget extends BaseWidget {
|
19
|
+
constructor(editor, selectionTool, localizationTable) {
|
20
|
+
super(editor, 'restyle-selection', localizationTable);
|
21
|
+
this.selectionTool = selectionTool;
|
22
|
+
this.updateFormatData = () => { };
|
23
|
+
// Allow showing the dropdown even if this widget isn't selected yet
|
24
|
+
this.container.classList.add('dropdownShowable');
|
25
|
+
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
26
|
+
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
27
|
+
throw new Error('Invalid event type!');
|
28
|
+
}
|
29
|
+
if (toolEvt.tool === this.selectionTool) {
|
30
|
+
this.updateFormatData();
|
31
|
+
}
|
32
|
+
});
|
33
|
+
}
|
34
|
+
getTitle() {
|
35
|
+
return this.localizationTable.reformatSelection;
|
36
|
+
}
|
37
|
+
createIcon() {
|
38
|
+
return this.editor.icons.makeFormatSelectionIcon();
|
39
|
+
}
|
40
|
+
handleClick() {
|
41
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
42
|
+
}
|
43
|
+
fillDropdown(dropdown) {
|
44
|
+
const container = document.createElement('div');
|
45
|
+
const colorRow = document.createElement('div');
|
46
|
+
const colorLabel = document.createElement('label');
|
47
|
+
const [colorInput, colorInputContainer, setColorInputValue] = makeColorInput(this.editor, color => {
|
48
|
+
const selection = this.selectionTool.getSelection();
|
49
|
+
if (selection) {
|
50
|
+
const updateStyleCommands = [];
|
51
|
+
for (const elem of selection.getSelectedObjects()) {
|
52
|
+
if (isRestylableComponent(elem)) {
|
53
|
+
updateStyleCommands.push(elem.updateStyle({ color }));
|
54
|
+
}
|
55
|
+
}
|
56
|
+
const unitedCommand = uniteCommands(updateStyleCommands);
|
57
|
+
this.editor.dispatch(unitedCommand);
|
58
|
+
}
|
59
|
+
});
|
60
|
+
colorLabel.innerText = this.localizationTable.colorLabel;
|
61
|
+
this.updateFormatData = () => {
|
62
|
+
const selection = this.selectionTool.getSelection();
|
63
|
+
if (selection) {
|
64
|
+
colorInput.disabled = false;
|
65
|
+
const colors = [];
|
66
|
+
for (const elem of selection.getSelectedObjects()) {
|
67
|
+
if (isRestylableComponent(elem)) {
|
68
|
+
const color = elem.getStyle().color;
|
69
|
+
if (color) {
|
70
|
+
colors.push(color);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
setColorInputValue(Color4.average(colors));
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
colorInput.disabled = true;
|
78
|
+
}
|
79
|
+
};
|
80
|
+
colorRow.replaceChildren(colorLabel, colorInputContainer);
|
81
|
+
container.replaceChildren(colorRow);
|
82
|
+
dropdown.replaceChildren(container);
|
83
|
+
return true;
|
84
|
+
}
|
85
|
+
}
|
13
86
|
export default class SelectionToolWidget extends BaseToolWidget {
|
14
87
|
constructor(editor, tool, localization) {
|
15
88
|
super(editor, tool, 'selection-tool-widget', localization);
|
@@ -26,13 +99,16 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
26
99
|
const selection = this.tool.getSelection();
|
27
100
|
this.editor.dispatch(yield selection.duplicateSelectedObjects());
|
28
101
|
}), localization);
|
102
|
+
const restyleButton = new RestyleSelectionWidget(editor, this.tool, localization);
|
29
103
|
this.addSubWidget(resizeButton);
|
30
104
|
this.addSubWidget(deleteButton);
|
31
105
|
this.addSubWidget(duplicateButton);
|
106
|
+
this.addSubWidget(restyleButton);
|
32
107
|
const updateDisabled = (disabled) => {
|
33
108
|
resizeButton.setDisabled(disabled);
|
34
109
|
deleteButton.setDisabled(disabled);
|
35
110
|
duplicateButton.setDisabled(disabled);
|
111
|
+
restyleButton.setDisabled(disabled);
|
36
112
|
};
|
37
113
|
updateDisabled(true);
|
38
114
|
// Enable/disable actions based on whether items are selected
|
@@ -42,7 +118,7 @@ export default class SelectionToolWidget extends BaseToolWidget {
|
|
42
118
|
}
|
43
119
|
if (toolEvt.tool === this.tool) {
|
44
120
|
const selection = this.tool.getSelection();
|
45
|
-
const hasSelection = selection && selection.
|
121
|
+
const hasSelection = selection && selection.getSelectedItemCount() > 0;
|
46
122
|
updateDisabled(!hasSelection);
|
47
123
|
}
|
48
124
|
});
|
package/dist/src/tools/Pen.js
CHANGED
@@ -152,7 +152,7 @@ export default class Pen extends BaseTool {
|
|
152
152
|
this.setThickness(newThickness);
|
153
153
|
return true;
|
154
154
|
}
|
155
|
-
if (key === 'control') {
|
155
|
+
if (key === 'control' || key === 'meta') {
|
156
156
|
this.ctrlKeyPressed = true;
|
157
157
|
return true;
|
158
158
|
}
|
@@ -164,7 +164,7 @@ export default class Pen extends BaseTool {
|
|
164
164
|
}
|
165
165
|
onKeyUp({ key }) {
|
166
166
|
key = key.toLowerCase();
|
167
|
-
if (key === 'control') {
|
167
|
+
if (key === 'control' || key === 'meta') {
|
168
168
|
this.ctrlKeyPressed = false;
|
169
169
|
return true;
|
170
170
|
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import { KeyPressEvent } from '../../types';
|
3
|
+
import BaseTool from '../BaseTool';
|
4
|
+
export default class SelectAllShortcutHandler extends BaseTool {
|
5
|
+
private editor;
|
6
|
+
constructor(editor: Editor);
|
7
|
+
onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean;
|
8
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import BaseTool from '../BaseTool';
|
2
|
+
import SelectionTool from './SelectionTool';
|
3
|
+
// Handles ctrl+a: Select all
|
4
|
+
export default class SelectAllShortcutHandler extends BaseTool {
|
5
|
+
constructor(editor) {
|
6
|
+
super(editor.notifier, editor.localization.selectAllTool);
|
7
|
+
this.editor = editor;
|
8
|
+
}
|
9
|
+
// @internal
|
10
|
+
onKeyPress({ key, ctrlKey }) {
|
11
|
+
if (ctrlKey && key === 'a') {
|
12
|
+
const selectionTools = this.editor.toolController.getMatchingTools(SelectionTool);
|
13
|
+
if (selectionTools.length > 0) {
|
14
|
+
const selectionTool = selectionTools[0];
|
15
|
+
selectionTool.setEnabled(true);
|
16
|
+
selectionTool.setSelection(this.editor.image.getAllElements());
|
17
|
+
return true;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
return false;
|
21
|
+
}
|
22
|
+
}
|
@@ -23,6 +23,12 @@ export default class Selection {
|
|
23
23
|
getTransform(): Mat33;
|
24
24
|
get preTransformRegion(): Rect2;
|
25
25
|
get region(): Rect2;
|
26
|
+
/**
|
27
|
+
* Computes and returns the bounding box of the selection without
|
28
|
+
* any additional padding. Computes directly from the elements that are selected.
|
29
|
+
* @internal
|
30
|
+
*/
|
31
|
+
computeTightBoundingBox(): Rect2;
|
26
32
|
get regionRotation(): number;
|
27
33
|
get preTransformedScreenRegion(): Rect2;
|
28
34
|
get preTransformedScreenRegionRotation(): number;
|