js-draw 0.1.4 → 0.1.7
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +15 -0
- package/README.md +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +6 -2
- package/dist/src/Editor.d.ts +1 -0
- package/dist/src/Editor.js +20 -9
- package/dist/src/EditorImage.d.ts +8 -13
- package/dist/src/EditorImage.js +51 -29
- package/dist/src/SVGLoader.js +6 -2
- package/dist/src/Viewport.d.ts +10 -2
- package/dist/src/Viewport.js +8 -6
- package/dist/src/commands/Command.d.ts +9 -8
- package/dist/src/commands/Command.js +15 -14
- package/dist/src/commands/Duplicate.d.ts +14 -0
- package/dist/src/commands/Duplicate.js +34 -0
- package/dist/src/commands/Erase.d.ts +5 -2
- package/dist/src/commands/Erase.js +28 -9
- package/dist/src/commands/SerializableCommand.d.ts +13 -0
- package/dist/src/commands/SerializableCommand.js +28 -0
- package/dist/src/commands/localization.d.ts +2 -0
- package/dist/src/commands/localization.js +2 -0
- package/dist/src/components/AbstractComponent.d.ts +15 -2
- package/dist/src/components/AbstractComponent.js +122 -26
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +6 -1
- package/dist/src/components/SVGGlobalAttributesObject.js +23 -1
- package/dist/src/components/Stroke.d.ts +5 -0
- package/dist/src/components/Stroke.js +32 -1
- package/dist/src/components/Text.d.ts +11 -4
- package/dist/src/components/Text.js +57 -3
- package/dist/src/components/UnknownSVGObject.d.ts +2 -0
- package/dist/src/components/UnknownSVGObject.js +12 -1
- package/dist/src/components/util/describeComponentList.d.ts +4 -0
- package/dist/src/components/util/describeComponentList.js +14 -0
- package/dist/src/geometry/Path.d.ts +4 -1
- package/dist/src/geometry/Path.js +4 -0
- package/dist/src/localization.d.ts +2 -1
- package/dist/src/localization.js +2 -1
- package/dist/src/rendering/Display.d.ts +5 -0
- package/dist/src/rendering/Display.js +32 -0
- package/dist/src/rendering/RenderingStyle.d.ts +24 -0
- package/dist/src/rendering/RenderingStyle.js +32 -0
- package/dist/src/rendering/localization.d.ts +5 -0
- package/dist/src/rendering/localization.js +4 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +1 -8
- package/dist/src/rendering/renderers/AbstractRenderer.js +1 -6
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +25 -0
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +40 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -1
- package/dist/src/toolbar/HTMLToolbar.js +52 -534
- package/dist/src/toolbar/icons.d.ts +5 -0
- package/dist/src/toolbar/icons.js +186 -13
- package/dist/src/toolbar/localization.d.ts +4 -0
- package/dist/src/toolbar/localization.js +4 -0
- package/dist/src/toolbar/makeColorInput.d.ts +5 -0
- package/dist/src/toolbar/makeColorInput.js +81 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +12 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.js +44 -0
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +32 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +148 -0
- package/dist/src/toolbar/widgets/EraserWidget.d.ts +6 -0
- package/dist/src/toolbar/widgets/EraserWidget.js +14 -0
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +133 -0
- package/dist/src/toolbar/widgets/PenWidget.d.ts +20 -0
- package/dist/src/toolbar/widgets/PenWidget.js +131 -0
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +11 -0
- package/dist/src/toolbar/widgets/SelectionWidget.js +56 -0
- package/dist/src/toolbar/widgets/TextToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/TextToolWidget.js +72 -0
- package/dist/src/tools/Pen.js +1 -1
- package/dist/src/tools/PipetteTool.d.ts +20 -0
- package/dist/src/tools/PipetteTool.js +40 -0
- package/dist/src/tools/SelectionTool.d.ts +2 -0
- package/dist/src/tools/SelectionTool.js +41 -23
- package/dist/src/tools/TextTool.d.ts +1 -0
- package/dist/src/tools/TextTool.js +5 -4
- package/dist/src/tools/ToolController.d.ts +3 -1
- package/dist/src/tools/ToolController.js +4 -0
- package/dist/src/tools/localization.d.ts +2 -1
- package/dist/src/tools/localization.js +3 -2
- package/dist/src/types.d.ts +7 -2
- package/dist/src/types.js +1 -0
- package/jest.config.js +2 -0
- package/package.json +6 -6
- package/src/Color4.ts +9 -3
- package/src/Editor.css +10 -0
- package/src/Editor.ts +24 -12
- package/src/EditorImage.test.ts +4 -4
- package/src/EditorImage.ts +61 -20
- package/src/SVGLoader.ts +9 -3
- package/src/Viewport.ts +7 -6
- package/src/commands/Command.ts +21 -19
- package/src/commands/Duplicate.ts +49 -0
- package/src/commands/Erase.ts +34 -13
- package/src/commands/SerializableCommand.ts +41 -0
- package/src/commands/localization.ts +5 -0
- package/src/components/AbstractComponent.ts +168 -26
- package/src/components/SVGGlobalAttributesObject.ts +34 -2
- package/src/components/Stroke.test.ts +53 -0
- package/src/components/Stroke.ts +37 -2
- package/src/components/Text.test.ts +38 -0
- package/src/components/Text.ts +80 -5
- package/src/components/UnknownSVGObject.test.ts +10 -0
- package/src/components/UnknownSVGObject.ts +15 -1
- package/src/components/builders/FreehandLineBuilder.ts +2 -1
- package/src/components/util/describeComponentList.ts +18 -0
- package/src/geometry/Path.ts +8 -1
- package/src/localization.ts +3 -1
- package/src/rendering/Display.ts +43 -1
- package/src/rendering/RenderingStyle.test.ts +68 -0
- package/src/rendering/RenderingStyle.ts +46 -0
- package/src/rendering/caching/RenderingCache.test.ts +1 -1
- package/src/rendering/localization.ts +10 -0
- package/src/rendering/renderers/AbstractRenderer.ts +1 -15
- package/src/rendering/renderers/CanvasRenderer.ts +2 -1
- package/src/rendering/renderers/DummyRenderer.ts +2 -1
- package/src/rendering/renderers/SVGRenderer.ts +2 -1
- package/src/rendering/renderers/TextOnlyRenderer.ts +52 -0
- package/src/toolbar/HTMLToolbar.ts +58 -660
- package/src/toolbar/icons.ts +205 -13
- package/src/toolbar/localization.ts +10 -2
- package/src/toolbar/makeColorInput.ts +105 -0
- package/src/toolbar/toolbar.css +116 -78
- package/src/toolbar/widgets/BaseToolWidget.ts +53 -0
- package/src/toolbar/widgets/BaseWidget.ts +175 -0
- package/src/toolbar/widgets/EraserWidget.ts +16 -0
- package/src/toolbar/widgets/HandToolWidget.ts +186 -0
- package/src/toolbar/widgets/PenWidget.ts +165 -0
- package/src/toolbar/widgets/SelectionWidget.ts +72 -0
- package/src/toolbar/widgets/TextToolWidget.ts +90 -0
- package/src/tools/Pen.ts +1 -1
- package/src/tools/PipetteTool.ts +56 -0
- package/src/tools/SelectionTool.test.ts +2 -4
- package/src/tools/SelectionTool.ts +47 -27
- package/src/tools/TextTool.ts +7 -3
- package/src/tools/ToolController.ts +10 -6
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/tools/localization.ts +6 -3
- package/src/types.ts +12 -1
package/dist/src/Color4.js
CHANGED
@@ -8,9 +8,13 @@ export default class Color4 {
|
|
8
8
|
}
|
9
9
|
// Each component should be in the range [0, 1]
|
10
10
|
static ofRGB(red, green, blue) {
|
11
|
-
return
|
11
|
+
return Color4.ofRGBA(red, green, blue, 1.0);
|
12
12
|
}
|
13
13
|
static ofRGBA(red, green, blue, alpha) {
|
14
|
+
red = Math.max(0, Math.min(red, 1));
|
15
|
+
green = Math.max(0, Math.min(green, 1));
|
16
|
+
blue = Math.max(0, Math.min(blue, 1));
|
17
|
+
alpha = Math.max(0, Math.min(alpha, 1));
|
14
18
|
return new Color4(red, green, blue, alpha);
|
15
19
|
}
|
16
20
|
static fromHex(hexString) {
|
@@ -40,7 +44,7 @@ export default class Color4 {
|
|
40
44
|
if (components.length !== 4) {
|
41
45
|
throw new Error(`Unable to parse ${hexString}: Wrong number of components.`);
|
42
46
|
}
|
43
|
-
return
|
47
|
+
return Color4.ofRGBA(components[0], components[1], components[2], components[3]);
|
44
48
|
}
|
45
49
|
// Like fromHex, but can handle additional colors if an HTML5Canvas is available.
|
46
50
|
static fromString(text) {
|
package/dist/src/Editor.d.ts
CHANGED
@@ -38,6 +38,7 @@ export declare class Editor {
|
|
38
38
|
addToolbar(defaultLayout?: boolean): HTMLToolbar;
|
39
39
|
private registerListeners;
|
40
40
|
dispatch(command: Command, addToHistory?: boolean): void;
|
41
|
+
dispatchNoAnnounce(command: Command, addToHistory?: boolean): void;
|
41
42
|
private asyncApplyOrUnapplyCommands;
|
42
43
|
asyncApplyCommands(commands: Command[], chunkSize: number): Promise<void>;
|
43
44
|
asyncUnapplyCommands(commands: Command[], chunkSize: number): Promise<void>;
|
package/dist/src/Editor.js
CHANGED
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
10
10
|
import EditorImage from './EditorImage';
|
11
11
|
import ToolController from './tools/ToolController';
|
12
12
|
import { InputEvtType, EditorEventType } from './types';
|
13
|
+
import Command from './commands/Command';
|
13
14
|
import UndoRedoHistory from './UndoRedoHistory';
|
14
15
|
import Viewport from './Viewport';
|
15
16
|
import EventDispatcher from './EventDispatcher';
|
@@ -69,7 +70,7 @@ export class Editor {
|
|
69
70
|
this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
|
70
71
|
this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
|
71
72
|
this.registerListeners();
|
72
|
-
this.
|
73
|
+
this.queueRerender();
|
73
74
|
this.hideLoadingWarning();
|
74
75
|
}
|
75
76
|
// Returns a reference to this' container.
|
@@ -211,7 +212,7 @@ export class Editor {
|
|
211
212
|
if (evt.ctrlKey) {
|
212
213
|
delta = Vec3.of(0, 0, evt.deltaY);
|
213
214
|
}
|
214
|
-
const pos = Vec2.of(evt.
|
215
|
+
const pos = Vec2.of(evt.offsetX, evt.offsetY);
|
215
216
|
if (this.toolController.dispatchInputEvent({
|
216
217
|
kind: InputEvtType.WheelEvt,
|
217
218
|
delta,
|
@@ -233,6 +234,7 @@ export class Editor {
|
|
233
234
|
this.queueRerender();
|
234
235
|
});
|
235
236
|
}
|
237
|
+
// Adds to history by default
|
236
238
|
dispatch(command, addToHistory = true) {
|
237
239
|
if (addToHistory) {
|
238
240
|
// .push applies [command] to this
|
@@ -243,6 +245,15 @@ export class Editor {
|
|
243
245
|
}
|
244
246
|
this.announceForAccessibility(command.description(this.localization));
|
245
247
|
}
|
248
|
+
// Dispatches a command without announcing it. By default, does not add to history.
|
249
|
+
dispatchNoAnnounce(command, addToHistory = false) {
|
250
|
+
if (addToHistory) {
|
251
|
+
this.history.push(command);
|
252
|
+
}
|
253
|
+
else {
|
254
|
+
command.apply(this);
|
255
|
+
}
|
256
|
+
}
|
246
257
|
// Apply a large transformation in chunks.
|
247
258
|
// If [apply] is false, the commands are unapplied.
|
248
259
|
// Triggers a re-render after each [updateChunkSize]-sized group of commands
|
@@ -365,7 +376,7 @@ export class Editor {
|
|
365
376
|
this.showLoadingWarning(0);
|
366
377
|
this.display.setDraftMode(true);
|
367
378
|
yield loader.start((component) => {
|
368
|
-
(
|
379
|
+
this.dispatchNoAnnounce(EditorImage.addElement(component));
|
369
380
|
}, (countProcessed, totalToProcess) => {
|
370
381
|
if (countProcessed % 500 === 0) {
|
371
382
|
this.showLoadingWarning(countProcessed / totalToProcess);
|
@@ -376,8 +387,8 @@ export class Editor {
|
|
376
387
|
}
|
377
388
|
return null;
|
378
389
|
}, (importExportRect) => {
|
379
|
-
this.setImportExportRect(importExportRect)
|
380
|
-
this.viewport.zoomTo(importExportRect)
|
390
|
+
this.dispatchNoAnnounce(this.setImportExportRect(importExportRect), false);
|
391
|
+
this.dispatchNoAnnounce(this.viewport.zoomTo(importExportRect), false);
|
381
392
|
});
|
382
393
|
this.hideLoadingWarning();
|
383
394
|
this.display.setDraftMode(false);
|
@@ -392,22 +403,22 @@ export class Editor {
|
|
392
403
|
setImportExportRect(imageRect) {
|
393
404
|
const origSize = this.importExportViewport.visibleRect.size;
|
394
405
|
const origTransform = this.importExportViewport.canvasToScreenTransform;
|
395
|
-
return {
|
406
|
+
return new class extends Command {
|
396
407
|
apply(editor) {
|
397
408
|
const viewport = editor.importExportViewport;
|
398
409
|
viewport.updateScreenSize(imageRect.size);
|
399
410
|
viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
|
400
411
|
editor.queueRerender();
|
401
|
-
}
|
412
|
+
}
|
402
413
|
unapply(editor) {
|
403
414
|
const viewport = editor.importExportViewport;
|
404
415
|
viewport.updateScreenSize(origSize);
|
405
416
|
viewport.resetTransform(origTransform);
|
406
417
|
editor.queueRerender();
|
407
|
-
}
|
418
|
+
}
|
408
419
|
description(localizationTable) {
|
409
420
|
return localizationTable.resizeOutputCommand(imageRect);
|
410
|
-
}
|
421
|
+
}
|
411
422
|
};
|
412
423
|
}
|
413
424
|
// Alias for loadFrom(SVGLoader.fromString).
|
@@ -1,31 +1,25 @@
|
|
1
|
-
import Editor from './Editor';
|
2
1
|
import AbstractRenderer from './rendering/renderers/AbstractRenderer';
|
2
|
+
import Command from './commands/Command';
|
3
3
|
import Viewport from './Viewport';
|
4
4
|
import AbstractComponent from './components/AbstractComponent';
|
5
5
|
import Rect2 from './geometry/Rect2';
|
6
|
-
import { EditorLocalization } from './localization';
|
7
6
|
import RenderingCache from './rendering/caching/RenderingCache';
|
8
7
|
export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
|
9
8
|
export default class EditorImage {
|
10
9
|
private root;
|
10
|
+
private componentsById;
|
11
11
|
constructor();
|
12
|
-
private addElement;
|
13
12
|
findParent(elem: AbstractComponent): ImageNode | null;
|
14
13
|
renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport): void;
|
15
14
|
render(renderer: AbstractRenderer, viewport: Viewport): void;
|
16
15
|
renderAll(renderer: AbstractRenderer): void;
|
17
16
|
getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
unapply(editor: Editor): void;
|
24
|
-
description(localization: EditorLocalization): string;
|
25
|
-
};
|
26
|
-
};
|
17
|
+
onDestroyElement(elem: AbstractComponent): void;
|
18
|
+
lookupElement(id: string): AbstractComponent | null;
|
19
|
+
private addElementDirectly;
|
20
|
+
static addElement(elem: AbstractComponent, applyByFlattening?: boolean): Command;
|
21
|
+
private static AddElementCommand;
|
27
22
|
}
|
28
|
-
export declare type AddElementCommand = typeof EditorImage.AddElementCommand.prototype;
|
29
23
|
declare type TooSmallToRenderCheck = (rect: Rect2) => boolean;
|
30
24
|
export declare class ImageNode {
|
31
25
|
private parent;
|
@@ -47,6 +41,7 @@ export declare class ImageNode {
|
|
47
41
|
addLeaf(leaf: AbstractComponent): ImageNode;
|
48
42
|
getBBox(): Rect2;
|
49
43
|
recomputeBBox(bubbleUp: boolean): void;
|
44
|
+
private updateParents;
|
50
45
|
private rebalance;
|
51
46
|
remove(): void;
|
52
47
|
render(renderer: AbstractRenderer, visibleRect: Rect2): void;
|
package/dist/src/EditorImage.js
CHANGED
@@ -1,16 +1,7 @@
|
|
1
|
-
var
|
2
|
-
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
5
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
6
|
-
};
|
7
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
11
|
-
};
|
12
|
-
var _element, _applyByFlattening, _a;
|
1
|
+
var _a;
|
2
|
+
import AbstractComponent from './components/AbstractComponent';
|
13
3
|
import Rect2 from './geometry/Rect2';
|
4
|
+
import SerializableCommand from './commands/SerializableCommand';
|
14
5
|
export const sortLeavesByZIndex = (leaves) => {
|
15
6
|
leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
|
16
7
|
};
|
@@ -18,9 +9,7 @@ export const sortLeavesByZIndex = (leaves) => {
|
|
18
9
|
export default class EditorImage {
|
19
10
|
constructor() {
|
20
11
|
this.root = new ImageNode();
|
21
|
-
|
22
|
-
addElement(elem) {
|
23
|
-
return this.root.addLeaf(elem);
|
12
|
+
this.componentsById = {};
|
24
13
|
}
|
25
14
|
// Returns the parent of the given element, if it exists.
|
26
15
|
findParent(elem) {
|
@@ -51,42 +40,65 @@ export default class EditorImage {
|
|
51
40
|
sortLeavesByZIndex(leaves);
|
52
41
|
return leaves.map(leaf => leaf.getContent());
|
53
42
|
}
|
43
|
+
onDestroyElement(elem) {
|
44
|
+
delete this.componentsById[elem.getId()];
|
45
|
+
}
|
46
|
+
lookupElement(id) {
|
47
|
+
var _a;
|
48
|
+
return (_a = this.componentsById[id]) !== null && _a !== void 0 ? _a : null;
|
49
|
+
}
|
50
|
+
addElementDirectly(elem) {
|
51
|
+
this.componentsById[elem.getId()] = elem;
|
52
|
+
return this.root.addLeaf(elem);
|
53
|
+
}
|
54
|
+
static addElement(elem, applyByFlattening = false) {
|
55
|
+
return new EditorImage.AddElementCommand(elem, applyByFlattening);
|
56
|
+
}
|
54
57
|
}
|
55
58
|
// A Command that can access private [EditorImage] functionality
|
56
|
-
EditorImage.AddElementCommand = (_a = class {
|
59
|
+
EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
|
57
60
|
// If [applyByFlattening], then the rendered content of this element
|
58
61
|
// is present on the display's wet ink canvas. As such, no re-render is necessary
|
59
62
|
// the first time this command is applied (the surfaces are joined instead).
|
60
63
|
constructor(element, applyByFlattening = false) {
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
if (isNaN(__classPrivateFieldGet(this, _element, "f").getBBox().area)) {
|
64
|
+
super('add-element');
|
65
|
+
this.element = element;
|
66
|
+
this.applyByFlattening = applyByFlattening;
|
67
|
+
if (isNaN(element.getBBox().area)) {
|
66
68
|
throw new Error('Elements in the image cannot have NaN bounding boxes');
|
67
69
|
}
|
68
70
|
}
|
69
71
|
apply(editor) {
|
70
|
-
editor.image.
|
71
|
-
if (!
|
72
|
+
editor.image.addElementDirectly(this.element);
|
73
|
+
if (!this.applyByFlattening) {
|
72
74
|
editor.queueRerender();
|
73
75
|
}
|
74
76
|
else {
|
75
|
-
|
77
|
+
this.applyByFlattening = false;
|
76
78
|
editor.display.flatten();
|
77
79
|
}
|
78
80
|
}
|
79
81
|
unapply(editor) {
|
80
|
-
const container = editor.image.findParent(
|
82
|
+
const container = editor.image.findParent(this.element);
|
81
83
|
container === null || container === void 0 ? void 0 : container.remove();
|
82
84
|
editor.queueRerender();
|
83
85
|
}
|
84
86
|
description(localization) {
|
85
|
-
return localization.addElementAction(
|
87
|
+
return localization.addElementAction(this.element.description(localization));
|
88
|
+
}
|
89
|
+
serializeToString() {
|
90
|
+
return JSON.stringify({
|
91
|
+
elemData: this.element.serialize(),
|
92
|
+
});
|
86
93
|
}
|
87
94
|
},
|
88
|
-
|
89
|
-
|
95
|
+
(() => {
|
96
|
+
SerializableCommand.register('add-element', (data, _editor) => {
|
97
|
+
const json = JSON.parse(data);
|
98
|
+
const elem = AbstractComponent.deserialize(json.elemData);
|
99
|
+
return new EditorImage.AddElementCommand(elem);
|
100
|
+
});
|
101
|
+
})(),
|
90
102
|
_a);
|
91
103
|
// TODO: Assign leaf nodes to CacheNodes. When leaf nodes are modified, the corresponding CacheNodes can be updated.
|
92
104
|
export class ImageNode {
|
@@ -174,6 +186,7 @@ export class ImageNode {
|
|
174
186
|
nodeForChildren.children = this.children;
|
175
187
|
this.children = [nodeForNewLeaf, nodeForChildren];
|
176
188
|
nodeForChildren.recomputeBBox(true);
|
189
|
+
nodeForChildren.updateParents();
|
177
190
|
return nodeForNewLeaf.addLeaf(leaf);
|
178
191
|
}
|
179
192
|
const containingNodes = this.children.filter(child => child.getBBox().containsRect(leafBBox));
|
@@ -221,6 +234,14 @@ export class ImageNode {
|
|
221
234
|
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.recomputeBBox(true);
|
222
235
|
}
|
223
236
|
}
|
237
|
+
updateParents(recursive = false) {
|
238
|
+
for (const child of this.children) {
|
239
|
+
child.parent = this;
|
240
|
+
if (recursive) {
|
241
|
+
child.updateParents(recursive);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
224
245
|
rebalance() {
|
225
246
|
// If the current node is its parent's only child,
|
226
247
|
if (this.parent && this.parent.children.length === 1) {
|
@@ -238,6 +259,7 @@ export class ImageNode {
|
|
238
259
|
else if (this.content === null) {
|
239
260
|
// Remove this and transfer this' children to the parent.
|
240
261
|
this.parent.children = this.children;
|
262
|
+
this.parent.updateParents();
|
241
263
|
this.parent = null;
|
242
264
|
}
|
243
265
|
}
|
@@ -253,7 +275,7 @@ export class ImageNode {
|
|
253
275
|
this.parent.children = this.parent.children.filter(node => {
|
254
276
|
return node !== this;
|
255
277
|
});
|
256
|
-
console.assert(this.parent.children.length === oldChildCount - 1);
|
278
|
+
console.assert(this.parent.children.length === oldChildCount - 1, `${oldChildCount - 1} ≠ ${this.parent.children.length} after removing all nodes equal to ${this}. Nodes should only be removed once.`);
|
257
279
|
this.parent.children.forEach(child => {
|
258
280
|
child.rebalance();
|
259
281
|
});
|
package/dist/src/SVGLoader.js
CHANGED
@@ -162,13 +162,17 @@ export default class SVGLoader {
|
|
162
162
|
}
|
163
163
|
const style = {
|
164
164
|
size: fontSize,
|
165
|
-
fontFamily: computedStyles.fontFamily || 'sans-serif',
|
165
|
+
fontFamily: computedStyles.fontFamily || elem.style.fontFamily || 'sans-serif',
|
166
166
|
renderingStyle: {
|
167
167
|
fill: Color4.fromString(computedStyles.fill)
|
168
168
|
},
|
169
169
|
};
|
170
|
+
let transformProperty = computedStyles.transform;
|
171
|
+
if (transformProperty === '' || transformProperty === 'none') {
|
172
|
+
transformProperty = elem.style.transform || 'none';
|
173
|
+
}
|
170
174
|
// Compute transform matrix
|
171
|
-
let transform = Mat33.fromCSSMatrix(
|
175
|
+
let transform = Mat33.fromCSSMatrix(transformProperty);
|
172
176
|
const supportedAttrs = [];
|
173
177
|
const elemX = elem.getAttribute('x');
|
174
178
|
const elemY = elem.getAttribute('y');
|
package/dist/src/Viewport.d.ts
CHANGED
@@ -11,11 +11,19 @@ export declare class Viewport {
|
|
11
11
|
private notifier;
|
12
12
|
static ViewportTransform: {
|
13
13
|
new (transform: Mat33): {
|
14
|
-
readonly "__#
|
14
|
+
readonly "__#679@#inverseTransform": Mat33;
|
15
15
|
readonly transform: Mat33;
|
16
16
|
apply(editor: Editor): void;
|
17
17
|
unapply(editor: Editor): void;
|
18
18
|
description(localizationTable: CommandLocalization): string;
|
19
|
+
onDrop(_editor: Editor): void;
|
20
|
+
};
|
21
|
+
union(a: Command, b: Command): Command;
|
22
|
+
readonly empty: {
|
23
|
+
description(_localizationTable: import("./localization").EditorLocalization): string;
|
24
|
+
apply(_editor: Editor): void;
|
25
|
+
unapply(_editor: Editor): void;
|
26
|
+
onDrop(_editor: Editor): void;
|
19
27
|
};
|
20
28
|
};
|
21
29
|
private transform;
|
@@ -26,7 +34,7 @@ export declare class Viewport {
|
|
26
34
|
get visibleRect(): Rect2;
|
27
35
|
screenToCanvas(screenPoint: Point2): Point2;
|
28
36
|
canvasToScreen(canvasPoint: Point2): Point2;
|
29
|
-
resetTransform(newTransform
|
37
|
+
resetTransform(newTransform?: Mat33): void;
|
30
38
|
get screenToCanvasTransform(): Mat33;
|
31
39
|
get canvasToScreenTransform(): Mat33;
|
32
40
|
getResolution(): Vec2;
|
package/dist/src/Viewport.js
CHANGED
@@ -10,6 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
11
11
|
};
|
12
12
|
var _inverseTransform, _a;
|
13
|
+
import Command from './commands/Command';
|
13
14
|
import Mat33 from './geometry/Mat33';
|
14
15
|
import Rect2 from './geometry/Rect2';
|
15
16
|
import { Vec2 } from './geometry/Vec2';
|
@@ -36,7 +37,7 @@ export class Viewport {
|
|
36
37
|
}
|
37
38
|
// Updates the transformation directly. Using ViewportTransform is preferred.
|
38
39
|
// [newTransform] should map from canvas coordinates to screen coordinates.
|
39
|
-
resetTransform(newTransform) {
|
40
|
+
resetTransform(newTransform = Mat33.identity) {
|
40
41
|
this.transform = newTransform;
|
41
42
|
this.inverseTransform = newTransform.inverse();
|
42
43
|
this.notifier.dispatch(EditorEventType.ViewportChanged, {
|
@@ -93,17 +94,17 @@ export class Viewport {
|
|
93
94
|
if (isNaN(toMakeVisible.size.magnitude())) {
|
94
95
|
throw new Error(`${toMakeVisible.toString()} rectangle has NaN size! Cannot zoom to!`);
|
95
96
|
}
|
96
|
-
// Try to move the selection within the center
|
97
|
+
// Try to move the selection within the center 4/5ths of the viewport.
|
97
98
|
const recomputeTargetRect = () => {
|
98
99
|
// transform transforms objects on the canvas. As such, we need to invert it
|
99
100
|
// to transform the viewport.
|
100
101
|
const visibleRect = this.visibleRect.transformedBoundingBox(transform.inverse());
|
101
|
-
return visibleRect.transformedBoundingBox(Mat33.scaling2D(
|
102
|
+
return visibleRect.transformedBoundingBox(Mat33.scaling2D(4 / 5, visibleRect.center));
|
102
103
|
};
|
103
104
|
let targetRect = recomputeTargetRect();
|
104
105
|
const largerThanTarget = targetRect.w < toMakeVisible.w || targetRect.h < toMakeVisible.h;
|
105
|
-
// Ensure that toMakeVisible is at least 1/
|
106
|
-
const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension <
|
106
|
+
// Ensure that toMakeVisible is at least 1/3rd of the visible region.
|
107
|
+
const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension < 1 / 3;
|
107
108
|
if ((largerThanTarget && allowZoomOut) || (muchSmallerThanTarget && allowZoomIn)) {
|
108
109
|
// If larger than the target, ensure that the longest axis is visible.
|
109
110
|
// If smaller, shrink the visible rectangle as much as possible
|
@@ -129,8 +130,9 @@ export class Viewport {
|
|
129
130
|
}
|
130
131
|
}
|
131
132
|
// Command that translates/scales the viewport.
|
132
|
-
Viewport.ViewportTransform = (_a = class {
|
133
|
+
Viewport.ViewportTransform = (_a = class extends Command {
|
133
134
|
constructor(transform) {
|
135
|
+
super();
|
134
136
|
this.transform = transform;
|
135
137
|
_inverseTransform.set(this, void 0);
|
136
138
|
__classPrivateFieldSet(this, _inverseTransform, transform.inverse(), "f");
|
@@ -1,15 +1,16 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import { EditorLocalization } from '../localization';
|
3
|
-
|
4
|
-
apply(editor: Editor): void;
|
5
|
-
unapply(editor: Editor): void;
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
export declare abstract class Command {
|
4
|
+
abstract apply(editor: Editor): void;
|
5
|
+
abstract unapply(editor: Editor): void;
|
6
|
+
onDrop(_editor: Editor): void;
|
7
|
+
abstract description(localizationTable: EditorLocalization): string;
|
8
|
+
static union(a: Command, b: Command): Command;
|
9
|
+
static readonly empty: {
|
10
|
+
description(_localizationTable: EditorLocalization): string;
|
10
11
|
apply(_editor: Editor): void;
|
11
12
|
unapply(_editor: Editor): void;
|
13
|
+
onDrop(_editor: Editor): void;
|
12
14
|
};
|
13
|
-
const union: (a: Command, b: Command) => Command;
|
14
15
|
}
|
15
16
|
export default Command;
|
@@ -1,20 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
(
|
4
|
-
|
5
|
-
|
6
|
-
unapply(_editor) { },
|
7
|
-
};
|
8
|
-
Command.union = (a, b) => {
|
9
|
-
return {
|
1
|
+
export class Command {
|
2
|
+
// Called when the command is being deleted
|
3
|
+
onDrop(_editor) { }
|
4
|
+
static union(a, b) {
|
5
|
+
return new class extends Command {
|
10
6
|
apply(editor) {
|
11
7
|
a.apply(editor);
|
12
8
|
b.apply(editor);
|
13
|
-
}
|
9
|
+
}
|
14
10
|
unapply(editor) {
|
15
11
|
b.unapply(editor);
|
16
12
|
a.unapply(editor);
|
17
|
-
}
|
13
|
+
}
|
18
14
|
description(localizationTable) {
|
19
15
|
const aDescription = a.description(localizationTable);
|
20
16
|
const bDescription = b.description(localizationTable);
|
@@ -22,8 +18,13 @@ var Command;
|
|
22
18
|
return aDescription;
|
23
19
|
}
|
24
20
|
return `${aDescription}, ${bDescription}`;
|
25
|
-
}
|
21
|
+
}
|
26
22
|
};
|
27
|
-
}
|
28
|
-
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
Command.empty = new class extends Command {
|
26
|
+
description(_localizationTable) { return ''; }
|
27
|
+
apply(_editor) { }
|
28
|
+
unapply(_editor) { }
|
29
|
+
};
|
29
30
|
export default Command;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import AbstractComponent from '../components/AbstractComponent';
|
2
|
+
import Editor from '../Editor';
|
3
|
+
import { EditorLocalization } from '../localization';
|
4
|
+
import SerializableCommand from './SerializableCommand';
|
5
|
+
export default class Duplicate extends SerializableCommand {
|
6
|
+
private toDuplicate;
|
7
|
+
private duplicates;
|
8
|
+
private reverse;
|
9
|
+
constructor(toDuplicate: AbstractComponent[]);
|
10
|
+
apply(editor: Editor): void;
|
11
|
+
unapply(editor: Editor): void;
|
12
|
+
description(localizationTable: EditorLocalization): string;
|
13
|
+
protected serializeToString(): string;
|
14
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import describeComponentList from '../components/util/describeComponentList';
|
2
|
+
import Erase from './Erase';
|
3
|
+
import SerializableCommand from './SerializableCommand';
|
4
|
+
export default class Duplicate extends SerializableCommand {
|
5
|
+
constructor(toDuplicate) {
|
6
|
+
super('duplicate');
|
7
|
+
this.toDuplicate = toDuplicate;
|
8
|
+
this.duplicates = toDuplicate.map(elem => elem.clone());
|
9
|
+
this.reverse = new Erase(this.duplicates);
|
10
|
+
}
|
11
|
+
apply(editor) {
|
12
|
+
this.reverse.unapply(editor);
|
13
|
+
}
|
14
|
+
unapply(editor) {
|
15
|
+
this.reverse.apply(editor);
|
16
|
+
}
|
17
|
+
description(localizationTable) {
|
18
|
+
var _a;
|
19
|
+
if (this.duplicates.length === 0) {
|
20
|
+
return localizationTable.duplicatedNoElements;
|
21
|
+
}
|
22
|
+
return localizationTable.duplicateAction((_a = describeComponentList(localizationTable, this.duplicates)) !== null && _a !== void 0 ? _a : localizationTable.elements, this.duplicates.length);
|
23
|
+
}
|
24
|
+
serializeToString() {
|
25
|
+
return JSON.stringify(this.toDuplicate.map(elem => elem.getId()));
|
26
|
+
}
|
27
|
+
}
|
28
|
+
(() => {
|
29
|
+
SerializableCommand.register('duplicate', (data, editor) => {
|
30
|
+
const json = JSON.parse(data);
|
31
|
+
const elems = json.map((id) => editor.image.lookupElement(id));
|
32
|
+
return new Duplicate(elems);
|
33
|
+
});
|
34
|
+
})();
|
@@ -1,11 +1,14 @@
|
|
1
1
|
import AbstractComponent from '../components/AbstractComponent';
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import { EditorLocalization } from '../localization';
|
4
|
-
import
|
5
|
-
export default class Erase
|
4
|
+
import SerializableCommand from './SerializableCommand';
|
5
|
+
export default class Erase extends SerializableCommand {
|
6
6
|
private toRemove;
|
7
|
+
private applied;
|
7
8
|
constructor(toRemove: AbstractComponent[]);
|
8
9
|
apply(editor: Editor): void;
|
9
10
|
unapply(editor: Editor): void;
|
11
|
+
onDrop(editor: Editor): void;
|
10
12
|
description(localizationTable: EditorLocalization): string;
|
13
|
+
protected serializeToString(): string;
|
11
14
|
}
|
@@ -1,8 +1,12 @@
|
|
1
|
+
import describeComponentList from '../components/util/describeComponentList';
|
1
2
|
import EditorImage from '../EditorImage';
|
2
|
-
|
3
|
+
import SerializableCommand from './SerializableCommand';
|
4
|
+
export default class Erase extends SerializableCommand {
|
3
5
|
constructor(toRemove) {
|
6
|
+
super('erase');
|
4
7
|
// Clone the list
|
5
8
|
this.toRemove = toRemove.map(elem => elem);
|
9
|
+
this.applied = false;
|
6
10
|
}
|
7
11
|
apply(editor) {
|
8
12
|
for (const part of this.toRemove) {
|
@@ -11,27 +15,42 @@ export default class Erase {
|
|
11
15
|
parent.remove();
|
12
16
|
}
|
13
17
|
}
|
18
|
+
this.applied = true;
|
14
19
|
editor.queueRerender();
|
15
20
|
}
|
16
21
|
unapply(editor) {
|
17
22
|
for (const part of this.toRemove) {
|
18
23
|
if (!editor.image.findParent(part)) {
|
19
|
-
|
24
|
+
EditorImage.addElement(part).apply(editor);
|
20
25
|
}
|
21
26
|
}
|
27
|
+
this.applied = false;
|
22
28
|
editor.queueRerender();
|
23
29
|
}
|
30
|
+
onDrop(editor) {
|
31
|
+
if (this.applied) {
|
32
|
+
for (const part of this.toRemove) {
|
33
|
+
editor.image.onDestroyElement(part);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
24
37
|
description(localizationTable) {
|
38
|
+
var _a;
|
25
39
|
if (this.toRemove.length === 0) {
|
26
40
|
return localizationTable.erasedNoElements;
|
27
41
|
}
|
28
|
-
|
29
|
-
for (const elem of this.toRemove) {
|
30
|
-
if (elem.description(localizationTable) !== description) {
|
31
|
-
description = localizationTable.elements;
|
32
|
-
break;
|
33
|
-
}
|
34
|
-
}
|
42
|
+
const description = (_a = describeComponentList(localizationTable, this.toRemove)) !== null && _a !== void 0 ? _a : localizationTable.elements;
|
35
43
|
return localizationTable.eraseAction(description, this.toRemove.length);
|
36
44
|
}
|
45
|
+
serializeToString() {
|
46
|
+
const elemIds = this.toRemove.map(elem => elem.getId());
|
47
|
+
return JSON.stringify(elemIds);
|
48
|
+
}
|
37
49
|
}
|
50
|
+
(() => {
|
51
|
+
SerializableCommand.register('erase', (data, editor) => {
|
52
|
+
const json = JSON.parse(data);
|
53
|
+
const elems = json.map((elemId) => editor.image.lookupElement(elemId));
|
54
|
+
return new Erase(elems);
|
55
|
+
});
|
56
|
+
})();
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import Editor from '../Editor';
|
2
|
+
import Command from './Command';
|
3
|
+
declare type DeserializationCallback = (data: string, editor: Editor) => SerializableCommand;
|
4
|
+
export default abstract class SerializableCommand extends Command {
|
5
|
+
private commandTypeId;
|
6
|
+
constructor(commandTypeId: string);
|
7
|
+
protected abstract serializeToString(): string;
|
8
|
+
private static deserializationCallbacks;
|
9
|
+
serialize(): string;
|
10
|
+
static deserialize(data: string, editor: Editor): SerializableCommand;
|
11
|
+
static register(commandTypeId: string, deserialize: DeserializationCallback): void;
|
12
|
+
}
|
13
|
+
export {};
|