js-draw 0.1.6 → 0.1.9
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 +16 -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 +23 -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/builders/RectangleBuilder.d.ts +3 -1
- package/dist/src/components/builders/RectangleBuilder.js +17 -8
- 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/caching/RenderingCacheNode.js +5 -1
- 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 +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +57 -535
- 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 +95 -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 +28 -11
- package/src/EditorImage.test.ts +5 -5
- 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/builders/RectangleBuilder.ts +23 -8
- 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/caching/RenderingCacheNode.ts +6 -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 +64 -661
- package/src/toolbar/icons.ts +205 -13
- package/src/toolbar/localization.ts +10 -2
- package/src/toolbar/makeColorInput.ts +120 -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
|
@@ -290,6 +301,10 @@ export class Editor {
|
|
290
301
|
}
|
291
302
|
rerender(showImageBounds = true) {
|
292
303
|
this.display.startRerender();
|
304
|
+
// Don't render if the display has zero size.
|
305
|
+
if (this.display.width === 0 || this.display.height === 0) {
|
306
|
+
return;
|
307
|
+
}
|
293
308
|
// Draw a rectangle around the region that will be visible on save
|
294
309
|
const renderer = this.display.getDryInkRenderer();
|
295
310
|
this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
|
@@ -365,7 +380,7 @@ export class Editor {
|
|
365
380
|
this.showLoadingWarning(0);
|
366
381
|
this.display.setDraftMode(true);
|
367
382
|
yield loader.start((component) => {
|
368
|
-
(
|
383
|
+
this.dispatchNoAnnounce(EditorImage.addElement(component));
|
369
384
|
}, (countProcessed, totalToProcess) => {
|
370
385
|
if (countProcessed % 500 === 0) {
|
371
386
|
this.showLoadingWarning(countProcessed / totalToProcess);
|
@@ -376,8 +391,8 @@ export class Editor {
|
|
376
391
|
}
|
377
392
|
return null;
|
378
393
|
}, (importExportRect) => {
|
379
|
-
this.setImportExportRect(importExportRect)
|
380
|
-
this.viewport.zoomTo(importExportRect)
|
394
|
+
this.dispatchNoAnnounce(this.setImportExportRect(importExportRect), false);
|
395
|
+
this.dispatchNoAnnounce(this.viewport.zoomTo(importExportRect), false);
|
381
396
|
});
|
382
397
|
this.hideLoadingWarning();
|
383
398
|
this.display.setDraftMode(false);
|
@@ -392,22 +407,22 @@ export class Editor {
|
|
392
407
|
setImportExportRect(imageRect) {
|
393
408
|
const origSize = this.importExportViewport.visibleRect.size;
|
394
409
|
const origTransform = this.importExportViewport.canvasToScreenTransform;
|
395
|
-
return {
|
410
|
+
return new class extends Command {
|
396
411
|
apply(editor) {
|
397
412
|
const viewport = editor.importExportViewport;
|
398
413
|
viewport.updateScreenSize(imageRect.size);
|
399
414
|
viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
|
400
415
|
editor.queueRerender();
|
401
|
-
}
|
416
|
+
}
|
402
417
|
unapply(editor) {
|
403
418
|
const viewport = editor.importExportViewport;
|
404
419
|
viewport.updateScreenSize(origSize);
|
405
420
|
viewport.resetTransform(origTransform);
|
406
421
|
editor.queueRerender();
|
407
|
-
}
|
422
|
+
}
|
408
423
|
description(localizationTable) {
|
409
424
|
return localizationTable.resizeOutputCommand(imageRect);
|
410
|
-
}
|
425
|
+
}
|
411
426
|
};
|
412
427
|
}
|
413
428
|
// 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',
|