js-draw 0.1.6 → 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 +7 -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 +19 -8
- package/dist/src/EditorImage.d.ts +8 -13
- package/dist/src/EditorImage.js +51 -29
- package/dist/src/Viewport.d.ts +9 -1
- package/dist/src/Viewport.js +3 -1
- 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/rendering/Display.d.ts +3 -0
- package/dist/src/rendering/Display.js +13 -0
- package/dist/src/rendering/RenderingStyle.d.ts +24 -0
- package/dist/src/rendering/RenderingStyle.js +32 -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 +2 -1
- 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.js +1 -1
- 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.ts +23 -11
- package/src/EditorImage.test.ts +4 -4
- package/src/EditorImage.ts +61 -20
- package/src/SVGLoader.ts +2 -1
- package/src/Viewport.ts +2 -1
- 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/rendering/Display.ts +17 -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/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 +2 -1
- 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 +1 -1
- 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.
|
@@ -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/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;
|
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';
|
@@ -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 {};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import Command from './Command';
|
2
|
+
export default class SerializableCommand extends Command {
|
3
|
+
constructor(commandTypeId) {
|
4
|
+
super();
|
5
|
+
this.commandTypeId = commandTypeId;
|
6
|
+
if (!(commandTypeId in SerializableCommand.deserializationCallbacks)) {
|
7
|
+
throw new Error(`Command ${commandTypeId} must have a registered deserialization callback. To do this, call SerializableCommand.register.`);
|
8
|
+
}
|
9
|
+
}
|
10
|
+
serialize() {
|
11
|
+
return JSON.stringify({
|
12
|
+
data: this.serializeToString(),
|
13
|
+
commandType: this.commandTypeId,
|
14
|
+
});
|
15
|
+
}
|
16
|
+
static deserialize(data, editor) {
|
17
|
+
const json = JSON.parse(data);
|
18
|
+
const commandType = json.commandType;
|
19
|
+
if (!(commandType in SerializableCommand.deserializationCallbacks)) {
|
20
|
+
throw new Error(`Unrecognised command type ${commandType}!`);
|
21
|
+
}
|
22
|
+
return SerializableCommand.deserializationCallbacks[commandType](json.data, editor);
|
23
|
+
}
|
24
|
+
static register(commandTypeId, deserialize) {
|
25
|
+
SerializableCommand.deserializationCallbacks[commandTypeId] = deserialize;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
SerializableCommand.deserializationCallbacks = {};
|
@@ -8,12 +8,14 @@ export interface CommandLocalization {
|
|
8
8
|
zoomedOut: string;
|
9
9
|
zoomedIn: string;
|
10
10
|
erasedNoElements: string;
|
11
|
+
duplicatedNoElements: string;
|
11
12
|
elements: string;
|
12
13
|
updatedViewport: string;
|
13
14
|
transformedElements: (elemCount: number) => string;
|
14
15
|
resizeOutputCommand: (newSize: Rect2) => string;
|
15
16
|
addElementAction: (elemDescription: string) => string;
|
16
17
|
eraseAction: (elemDescription: string, numElems: number) => string;
|
18
|
+
duplicateAction: (elemDescription: string, count: number) => string;
|
17
19
|
selectedElements: (count: number) => string;
|
18
20
|
}
|
19
21
|
export declare const defaultCommandLocalization: CommandLocalization;
|
@@ -4,8 +4,10 @@ export const defaultCommandLocalization = {
|
|
4
4
|
resizeOutputCommand: (newSize) => `Resized image to ${newSize.w}x${newSize.h}`,
|
5
5
|
addElementAction: (componentDescription) => `Added ${componentDescription}`,
|
6
6
|
eraseAction: (componentDescription, numElems) => `Erased ${numElems} ${componentDescription}`,
|
7
|
+
duplicateAction: (componentDescription, numElems) => `Duplicated ${numElems} ${componentDescription}`,
|
7
8
|
elements: 'Elements',
|
8
9
|
erasedNoElements: 'Erased nothing',
|
10
|
+
duplicatedNoElements: 'Duplicated nothing',
|
9
11
|
rotatedBy: (degrees) => `Rotated by ${Math.abs(degrees)} degrees ${degrees < 0 ? 'clockwise' : 'counter-clockwise'}`,
|
10
12
|
movedLeft: 'Moved left',
|
11
13
|
movedUp: 'Moved up',
|