js-draw 0.1.12 → 0.2.1
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/.firebaserc +5 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +34 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/ISSUE_TEMPLATE/translation.md +96 -0
- package/.github/workflows/firebase-hosting-merge.yml +25 -0
- package/.github/workflows/firebase-hosting-pull-request.yml +22 -0
- package/.github/workflows/github-pages.yml +52 -0
- package/CHANGELOG.md +9 -0
- package/README.md +11 -6
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +19 -0
- package/dist/src/Color4.js +24 -3
- package/dist/src/Editor.d.ts +129 -2
- package/dist/src/Editor.js +94 -17
- package/dist/src/EditorImage.d.ts +7 -2
- package/dist/src/EditorImage.js +41 -25
- package/dist/src/EventDispatcher.d.ts +18 -0
- package/dist/src/EventDispatcher.js +19 -4
- package/dist/src/Pointer.js +3 -2
- package/dist/src/UndoRedoHistory.js +15 -2
- package/dist/src/Viewport.js +4 -1
- package/dist/src/bundle/bundled.d.ts +1 -2
- package/dist/src/bundle/bundled.js +1 -2
- package/dist/src/commands/Duplicate.d.ts +1 -1
- package/dist/src/commands/Duplicate.js +3 -4
- package/dist/src/commands/Erase.d.ts +1 -1
- package/dist/src/commands/Erase.js +6 -5
- package/dist/src/commands/SerializableCommand.d.ts +4 -5
- package/dist/src/commands/SerializableCommand.js +12 -4
- package/dist/src/commands/invertCommand.d.ts +4 -0
- package/dist/src/commands/invertCommand.js +44 -0
- package/dist/src/commands/lib.d.ts +6 -0
- package/dist/src/commands/lib.js +6 -0
- package/dist/src/commands/localization.d.ts +1 -0
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/components/AbstractComponent.d.ts +13 -8
- package/dist/src/components/AbstractComponent.js +26 -15
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/src/components/SVGGlobalAttributesObject.js +7 -1
- package/dist/src/components/Stroke.d.ts +12 -2
- package/dist/src/components/Stroke.js +10 -7
- package/dist/src/components/Text.d.ts +2 -2
- package/dist/src/components/Text.js +6 -6
- package/dist/src/components/UnknownSVGObject.d.ts +1 -1
- package/dist/src/components/UnknownSVGObject.js +6 -1
- package/dist/src/components/lib.d.ts +4 -0
- package/dist/src/components/lib.js +4 -0
- package/dist/src/lib.d.ts +25 -0
- package/dist/src/lib.js +25 -0
- package/dist/src/localizations/de.d.ts +3 -0
- package/dist/src/localizations/de.js +4 -0
- package/dist/src/localizations/getLocalizationTable.js +2 -0
- package/dist/src/math/Mat33.d.ts +47 -1
- package/dist/src/math/Mat33.js +48 -20
- package/dist/src/math/Path.js +3 -3
- package/dist/src/math/Rect2.d.ts +2 -2
- package/dist/src/math/Vec3.d.ts +62 -0
- package/dist/src/math/Vec3.js +62 -14
- package/dist/src/math/lib.d.ts +7 -0
- package/dist/src/math/lib.js +7 -0
- package/dist/src/math/rounding.js +1 -0
- package/dist/src/rendering/Display.d.ts +44 -0
- package/dist/src/rendering/Display.js +45 -6
- package/dist/src/rendering/caching/CacheRecord.d.ts +1 -0
- package/dist/src/rendering/caching/CacheRecord.js +3 -0
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +4 -3
- package/dist/src/rendering/caching/CacheRecordManager.js +16 -4
- package/dist/src/rendering/caching/RenderingCache.d.ts +2 -3
- package/dist/src/rendering/caching/RenderingCache.js +9 -10
- package/dist/src/rendering/caching/types.d.ts +1 -3
- package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
- package/dist/src/toolbar/HTMLToolbar.js +1 -0
- package/dist/src/toolbar/makeColorInput.js +1 -1
- package/dist/src/toolbar/widgets/PenWidget.js +1 -0
- package/dist/src/tools/Pen.d.ts +1 -2
- package/dist/src/tools/Pen.js +8 -1
- package/dist/src/tools/PipetteTool.js +1 -0
- package/dist/src/tools/SelectionTool.js +45 -22
- package/dist/src/types.d.ts +17 -6
- package/dist/src/types.js +7 -5
- package/firebase.json +16 -0
- package/package.json +118 -101
- package/src/Color4.ts +23 -2
- package/src/Editor.ts +147 -25
- package/src/EditorImage.ts +45 -27
- package/src/EventDispatcher.ts +21 -6
- package/src/Pointer.ts +3 -2
- package/src/UndoRedoHistory.ts +18 -2
- package/src/Viewport.ts +5 -2
- package/src/bundle/bundled.ts +1 -2
- package/src/commands/Duplicate.ts +3 -4
- package/src/commands/Erase.ts +6 -5
- package/src/commands/SerializableCommand.ts +17 -9
- package/src/commands/invertCommand.ts +51 -0
- package/src/commands/lib.ts +14 -0
- package/src/commands/localization.ts +2 -0
- package/src/components/AbstractComponent.ts +31 -20
- package/src/components/SVGGlobalAttributesObject.ts +8 -1
- package/src/components/Stroke.test.ts +1 -1
- package/src/components/Stroke.ts +11 -7
- package/src/components/Text.ts +6 -7
- package/src/components/UnknownSVGObject.ts +7 -1
- package/src/components/lib.ts +9 -0
- package/src/lib.ts +28 -0
- package/src/localizations/de.ts +98 -0
- package/src/localizations/getLocalizationTable.ts +2 -0
- package/src/math/Mat33.ts +48 -20
- package/src/math/Path.ts +3 -3
- package/src/math/Rect2.ts +2 -2
- package/src/math/Vec3.ts +62 -14
- package/src/math/lib.ts +15 -0
- package/src/math/rounding.ts +2 -0
- package/src/rendering/Display.ts +46 -6
- package/src/rendering/caching/CacheRecord.test.ts +1 -1
- package/src/rendering/caching/CacheRecord.ts +4 -0
- package/src/rendering/caching/CacheRecordManager.ts +33 -7
- package/src/rendering/caching/RenderingCache.ts +10 -15
- package/src/rendering/caching/types.ts +1 -6
- package/src/rendering/renderers/CanvasRenderer.ts +1 -1
- package/src/styles.js +4 -0
- package/src/toolbar/HTMLToolbar.ts +1 -0
- package/src/toolbar/makeColorInput.ts +1 -1
- package/src/toolbar/toolbar.css +8 -2
- package/src/toolbar/widgets/PenWidget.ts +2 -0
- package/src/tools/PanZoom.ts +0 -1
- package/src/tools/Pen.ts +11 -2
- package/src/tools/PipetteTool.ts +2 -0
- package/src/tools/SelectionTool.ts +46 -18
- package/src/types.ts +19 -3
- package/tsconfig.json +4 -1
- package/typedoc.json +20 -0
@@ -1,6 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
/**
|
2
|
+
* Handles notifying listeners of events.
|
3
|
+
*
|
4
|
+
* `EventKeyType` is used to distinguish events (e.g. a `ClickEvent` vs a `TouchEvent`)
|
5
|
+
* while `EventMessageType` is the type of the data sent with an event (can be `void`).
|
6
|
+
*
|
7
|
+
* @example
|
8
|
+
* ```
|
9
|
+
* const dispatcher = new EventDispatcher<'event1'|'event2'|'event3', void>();
|
10
|
+
* dispatcher.on('event1', () => {
|
11
|
+
* console.log('Event 1 triggered.');
|
12
|
+
* });
|
13
|
+
* dispatcher.dispatch('event1');
|
14
|
+
* ```
|
15
|
+
*
|
16
|
+
* @packageDocumentation
|
17
|
+
*/
|
18
|
+
// { @inheritDoc EventDispatcher! }
|
4
19
|
export default class EventDispatcher {
|
5
20
|
constructor() {
|
6
21
|
this.listeners = {};
|
@@ -26,7 +41,7 @@ export default class EventDispatcher {
|
|
26
41
|
},
|
27
42
|
};
|
28
43
|
}
|
29
|
-
|
44
|
+
/** Removes an event listener. This is equivalent to calling `.remove()` on the object returned by `.on`. */
|
30
45
|
off(eventName, callback) {
|
31
46
|
const listeners = this.listeners[eventName];
|
32
47
|
if (!listeners)
|
package/dist/src/Pointer.js
CHANGED
@@ -9,7 +9,7 @@ export var PointerDevice;
|
|
9
9
|
PointerDevice[PointerDevice["Other"] = 5] = "Other";
|
10
10
|
})(PointerDevice || (PointerDevice = {}));
|
11
11
|
// Provides a snapshot containing information about a pointer. A Pointer
|
12
|
-
// object is immutable
|
12
|
+
// object is immutable — it will not be updated when the pointer's information changes.
|
13
13
|
export default class Pointer {
|
14
14
|
constructor(
|
15
15
|
// The (x, y) position of the pointer relative to the top-left corner
|
@@ -20,7 +20,7 @@ export default class Pointer {
|
|
20
20
|
canvasPos, pressure, isPrimary, down, device,
|
21
21
|
// Unique ID for the pointer
|
22
22
|
id,
|
23
|
-
// Numeric timestamp (milliseconds, as from (new Date).getTime())
|
23
|
+
// Numeric timestamp (milliseconds, as from `(new Date).getTime()`)
|
24
24
|
timeStamp) {
|
25
25
|
this.screenPos = screenPos;
|
26
26
|
this.canvasPos = canvasPos;
|
@@ -31,6 +31,7 @@ export default class Pointer {
|
|
31
31
|
this.id = id;
|
32
32
|
this.timeStamp = timeStamp;
|
33
33
|
}
|
34
|
+
// Creates a Pointer from a DOM event.
|
34
35
|
static ofEvent(evt, isDown, viewport) {
|
35
36
|
var _a, _b;
|
36
37
|
const screenPos = Vec2.of(evt.offsetX, evt.offsetY);
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { EditorEventType } from './types';
|
2
2
|
class UndoRedoHistory {
|
3
|
+
// @internal
|
3
4
|
constructor(editor, announceRedoCallback, announceUndoCallback) {
|
4
5
|
this.editor = editor;
|
5
6
|
this.announceRedoCallback = announceRedoCallback;
|
@@ -25,6 +26,10 @@ class UndoRedoHistory {
|
|
25
26
|
}
|
26
27
|
this.redoStack = [];
|
27
28
|
this.fireUpdateEvent();
|
29
|
+
this.editor.notifier.dispatch(EditorEventType.CommandDone, {
|
30
|
+
kind: EditorEventType.CommandDone,
|
31
|
+
command,
|
32
|
+
});
|
28
33
|
}
|
29
34
|
// Remove the last command from this' undo stack and apply it.
|
30
35
|
undo() {
|
@@ -33,8 +38,12 @@ class UndoRedoHistory {
|
|
33
38
|
this.redoStack.push(command);
|
34
39
|
command.unapply(this.editor);
|
35
40
|
this.announceUndoCallback(command);
|
41
|
+
this.fireUpdateEvent();
|
42
|
+
this.editor.notifier.dispatch(EditorEventType.CommandUndone, {
|
43
|
+
kind: EditorEventType.CommandUndone,
|
44
|
+
command,
|
45
|
+
});
|
36
46
|
}
|
37
|
-
this.fireUpdateEvent();
|
38
47
|
}
|
39
48
|
redo() {
|
40
49
|
const command = this.redoStack.pop();
|
@@ -42,8 +51,12 @@ class UndoRedoHistory {
|
|
42
51
|
this.undoStack.push(command);
|
43
52
|
command.apply(this.editor);
|
44
53
|
this.announceRedoCallback(command);
|
54
|
+
this.fireUpdateEvent();
|
55
|
+
this.editor.notifier.dispatch(EditorEventType.CommandDone, {
|
56
|
+
kind: EditorEventType.CommandDone,
|
57
|
+
command,
|
58
|
+
});
|
45
59
|
}
|
46
|
-
this.fireUpdateEvent();
|
47
60
|
}
|
48
61
|
get undoStackSize() {
|
49
62
|
return this.undoStack.length;
|
package/dist/src/Viewport.js
CHANGED
@@ -19,11 +19,13 @@ import { EditorEventType } from './types';
|
|
19
19
|
export class ViewportTransform extends Command {
|
20
20
|
}
|
21
21
|
export class Viewport {
|
22
|
+
// @internal
|
22
23
|
constructor(notifier) {
|
23
24
|
this.notifier = notifier;
|
24
25
|
this.resetTransform(Mat33.identity);
|
25
26
|
this.screenRect = Rect2.empty;
|
26
27
|
}
|
28
|
+
// @internal
|
27
29
|
updateScreenSize(screenSize) {
|
28
30
|
this.screenRect = this.screenRect.resizedTo(screenSize);
|
29
31
|
}
|
@@ -40,7 +42,7 @@ export class Viewport {
|
|
40
42
|
static transformBy(transform) {
|
41
43
|
return new Viewport.ViewportTransform(transform);
|
42
44
|
}
|
43
|
-
// Updates the transformation directly. Using transformBy is preferred.
|
45
|
+
// Updates the transformation directly. Using `transformBy` is preferred.
|
44
46
|
// [newTransform] should map from canvas coordinates to screen coordinates.
|
45
47
|
resetTransform(newTransform = Mat33.identity) {
|
46
48
|
const oldTransform = this.transform;
|
@@ -66,6 +68,7 @@ export class Viewport {
|
|
66
68
|
// Use transformVec3 to avoid translating the vector
|
67
69
|
return this.transform.transformVec3(Vec3.unitX).magnitude();
|
68
70
|
}
|
71
|
+
// Returns the size of one screen pixel in canvas units.
|
69
72
|
getSizeOfPixelOnCanvas() {
|
70
73
|
return 1 / this.getScaleFactor();
|
71
74
|
}
|
@@ -1,6 +1,5 @@
|
|
1
1
|
// Main entrypoint for Webpack when building a bundle for release.
|
2
2
|
import '../styles';
|
3
3
|
import Editor from '../Editor';
|
4
|
-
|
4
|
+
export * from '../lib';
|
5
5
|
export default Editor;
|
6
|
-
export { Editor, getLocalizationTable };
|
@@ -10,5 +10,5 @@ export default class Duplicate extends SerializableCommand {
|
|
10
10
|
apply(editor: Editor): void;
|
11
11
|
unapply(editor: Editor): void;
|
12
12
|
description(_editor: Editor, localizationTable: EditorLocalization): string;
|
13
|
-
protected
|
13
|
+
protected serializeToJSON(): string[];
|
14
14
|
}
|
@@ -21,13 +21,12 @@ export default class Duplicate extends SerializableCommand {
|
|
21
21
|
}
|
22
22
|
return localizationTable.duplicateAction((_a = describeComponentList(localizationTable, this.duplicates)) !== null && _a !== void 0 ? _a : localizationTable.elements, this.duplicates.length);
|
23
23
|
}
|
24
|
-
|
25
|
-
return
|
24
|
+
serializeToJSON() {
|
25
|
+
return this.toDuplicate.map(elem => elem.getId());
|
26
26
|
}
|
27
27
|
}
|
28
28
|
(() => {
|
29
|
-
SerializableCommand.register('duplicate', (
|
30
|
-
const json = JSON.parse(data);
|
29
|
+
SerializableCommand.register('duplicate', (json, editor) => {
|
31
30
|
const elems = json.map((id) => editor.image.lookupElement(id));
|
32
31
|
return new Duplicate(elems);
|
33
32
|
});
|
@@ -10,5 +10,5 @@ export default class Erase extends SerializableCommand {
|
|
10
10
|
unapply(editor: Editor): void;
|
11
11
|
onDrop(editor: Editor): void;
|
12
12
|
description(_editor: Editor, localizationTable: EditorLocalization): string;
|
13
|
-
protected
|
13
|
+
protected serializeToJSON(): string[];
|
14
14
|
}
|
@@ -42,15 +42,16 @@ export default class Erase extends SerializableCommand {
|
|
42
42
|
const description = (_a = describeComponentList(localizationTable, this.toRemove)) !== null && _a !== void 0 ? _a : localizationTable.elements;
|
43
43
|
return localizationTable.eraseAction(description, this.toRemove.length);
|
44
44
|
}
|
45
|
-
|
45
|
+
serializeToJSON() {
|
46
46
|
const elemIds = this.toRemove.map(elem => elem.getId());
|
47
|
-
return
|
47
|
+
return elemIds;
|
48
48
|
}
|
49
49
|
}
|
50
50
|
(() => {
|
51
|
-
SerializableCommand.register('erase', (
|
52
|
-
const
|
53
|
-
|
51
|
+
SerializableCommand.register('erase', (json, editor) => {
|
52
|
+
const elems = json
|
53
|
+
.map((elemId) => editor.image.lookupElement(elemId))
|
54
|
+
.filter((elem) => elem !== null);
|
54
55
|
return new Erase(elems);
|
55
56
|
});
|
56
57
|
})();
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import Command from './Command';
|
3
|
-
declare type DeserializationCallback = (data: string, editor: Editor) => SerializableCommand;
|
3
|
+
export declare type DeserializationCallback = (data: Record<string, any> | any[], editor: Editor) => SerializableCommand;
|
4
4
|
export default abstract class SerializableCommand extends Command {
|
5
5
|
private commandTypeId;
|
6
6
|
constructor(commandTypeId: string);
|
7
|
-
protected abstract
|
7
|
+
protected abstract serializeToJSON(): string | Record<string, any> | any[];
|
8
8
|
private static deserializationCallbacks;
|
9
|
-
serialize(): string
|
10
|
-
static deserialize(data: string, editor: Editor): SerializableCommand;
|
9
|
+
serialize(): Record<string | symbol, any>;
|
10
|
+
static deserialize(data: string | Record<string, any>, editor: Editor): SerializableCommand;
|
11
11
|
static register(commandTypeId: string, deserialize: DeserializationCallback): void;
|
12
12
|
}
|
13
|
-
export {};
|
@@ -7,20 +7,28 @@ export default class SerializableCommand extends Command {
|
|
7
7
|
throw new Error(`Command ${commandTypeId} must have a registered deserialization callback. To do this, call SerializableCommand.register.`);
|
8
8
|
}
|
9
9
|
}
|
10
|
+
// Convert this command to an object that can be passed to `JSON.stringify`.
|
11
|
+
//
|
12
|
+
// Do not rely on the stability of the optupt of this function — it can change
|
13
|
+
// form without a major version increase.
|
10
14
|
serialize() {
|
11
|
-
return
|
12
|
-
data: this.
|
15
|
+
return {
|
16
|
+
data: this.serializeToJSON(),
|
13
17
|
commandType: this.commandTypeId,
|
14
|
-
}
|
18
|
+
};
|
15
19
|
}
|
20
|
+
// Convert a `string` containing JSON data (or the output of `JSON.parse`) into a
|
21
|
+
// `Command`.
|
16
22
|
static deserialize(data, editor) {
|
17
|
-
const json = JSON.parse(data);
|
23
|
+
const json = typeof data === 'string' ? JSON.parse(data) : data;
|
18
24
|
const commandType = json.commandType;
|
19
25
|
if (!(commandType in SerializableCommand.deserializationCallbacks)) {
|
20
26
|
throw new Error(`Unrecognised command type ${commandType}!`);
|
21
27
|
}
|
22
28
|
return SerializableCommand.deserializationCallbacks[commandType](json.data, editor);
|
23
29
|
}
|
30
|
+
// Register a deserialization callback. This must be called at least once for every subclass of
|
31
|
+
// `SerializableCommand`.
|
24
32
|
static register(commandTypeId, deserialize) {
|
25
33
|
SerializableCommand.deserializationCallbacks[commandTypeId] = deserialize;
|
26
34
|
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import Command from './Command';
|
2
|
+
import SerializableCommand from './SerializableCommand';
|
3
|
+
// Returns a command taht does the opposite of the given command --- `result.apply()` calls
|
4
|
+
// `command.unapply()` and `result.unapply()` calls `command.apply()`.
|
5
|
+
const invertCommand = (command) => {
|
6
|
+
if (command instanceof SerializableCommand) {
|
7
|
+
// SerializableCommand that does the inverse of [command]
|
8
|
+
return new class extends SerializableCommand {
|
9
|
+
serializeToJSON() {
|
10
|
+
return command.serialize();
|
11
|
+
}
|
12
|
+
apply(editor) {
|
13
|
+
command.unapply(editor);
|
14
|
+
}
|
15
|
+
unapply(editor) {
|
16
|
+
command.unapply(editor);
|
17
|
+
}
|
18
|
+
description(editor, localizationTable) {
|
19
|
+
return localizationTable.inverseOf(command.description(editor, localizationTable));
|
20
|
+
}
|
21
|
+
}('inverse');
|
22
|
+
}
|
23
|
+
else {
|
24
|
+
// Command that does the inverse of [command].
|
25
|
+
const result = new class extends Command {
|
26
|
+
apply(editor) {
|
27
|
+
command.unapply(editor);
|
28
|
+
}
|
29
|
+
unapply(editor) {
|
30
|
+
command.apply(editor);
|
31
|
+
}
|
32
|
+
description(editor, localizationTable) {
|
33
|
+
return localizationTable.inverseOf(command.description(editor, localizationTable));
|
34
|
+
}
|
35
|
+
};
|
36
|
+
// We know that T does not extend SerializableCommand, and thus returning a Command
|
37
|
+
// is appropriate.
|
38
|
+
return result;
|
39
|
+
}
|
40
|
+
};
|
41
|
+
SerializableCommand.register('inverse', (data, editor) => {
|
42
|
+
return invertCommand(SerializableCommand.deserialize(data, editor));
|
43
|
+
});
|
44
|
+
export default invertCommand;
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import Command from './Command';
|
2
|
+
import Duplicate from './Duplicate';
|
3
|
+
import Erase from './Erase';
|
4
|
+
import invertCommand from './invertCommand';
|
5
|
+
import SerializableCommand from './SerializableCommand';
|
6
|
+
export { Command, Duplicate, Erase, SerializableCommand, invertCommand, };
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import Command from './Command';
|
2
|
+
import Duplicate from './Duplicate';
|
3
|
+
import Erase from './Erase';
|
4
|
+
import invertCommand from './invertCommand';
|
5
|
+
import SerializableCommand from './SerializableCommand';
|
6
|
+
export { Command, Duplicate, Erase, SerializableCommand, invertCommand, };
|
@@ -16,6 +16,7 @@ export interface CommandLocalization {
|
|
16
16
|
addElementAction: (elemDescription: string) => string;
|
17
17
|
eraseAction: (elemDescription: string, numElems: number) => string;
|
18
18
|
duplicateAction: (elemDescription: string, count: number) => string;
|
19
|
+
inverseOf: (actionDescription: string) => string;
|
19
20
|
selectedElements: (count: number) => string;
|
20
21
|
}
|
21
22
|
export declare const defaultCommandLocalization: CommandLocalization;
|
@@ -5,6 +5,7 @@ export const defaultCommandLocalization = {
|
|
5
5
|
addElementAction: (componentDescription) => `Added ${componentDescription}`,
|
6
6
|
eraseAction: (componentDescription, numElems) => `Erased ${numElems} ${componentDescription}`,
|
7
7
|
duplicateAction: (componentDescription, numElems) => `Duplicated ${numElems} ${componentDescription}`,
|
8
|
+
inverseOf: (actionDescription) => `Inverse of ${actionDescription}`,
|
8
9
|
elements: 'Elements',
|
9
10
|
erasedNoElements: 'Erased nothing',
|
10
11
|
duplicatedNoElements: 'Duplicated nothing',
|
@@ -1,12 +1,12 @@
|
|
1
|
-
import
|
1
|
+
import SerializableCommand from '../commands/SerializableCommand';
|
2
2
|
import LineSegment2 from '../math/LineSegment2';
|
3
3
|
import Mat33 from '../math/Mat33';
|
4
4
|
import Rect2 from '../math/Rect2';
|
5
5
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
|
-
declare type LoadSaveData = (string[] | Record<symbol, string | number>);
|
7
|
+
export declare type LoadSaveData = (string[] | Record<symbol, string | number>);
|
8
8
|
export declare type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
9
|
-
declare type DeserializeCallback = (data: string) => AbstractComponent;
|
9
|
+
export declare type DeserializeCallback = (data: string) => AbstractComponent;
|
10
10
|
export default abstract class AbstractComponent {
|
11
11
|
private readonly componentKind;
|
12
12
|
protected lastChangedTime: number;
|
@@ -25,15 +25,20 @@ export default abstract class AbstractComponent {
|
|
25
25
|
getBBox(): Rect2;
|
26
26
|
abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
27
27
|
abstract intersects(lineSegment: LineSegment2): boolean;
|
28
|
-
protected abstract
|
28
|
+
protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
|
29
29
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
30
|
-
transformBy(affineTransfm: Mat33):
|
30
|
+
transformBy(affineTransfm: Mat33): SerializableCommand;
|
31
31
|
private static TransformElementCommand;
|
32
32
|
abstract description(localizationTable: ImageComponentLocalization): string;
|
33
33
|
protected abstract createClone(): AbstractComponent;
|
34
34
|
clone(): AbstractComponent;
|
35
|
-
serialize():
|
35
|
+
serialize(): {
|
36
|
+
name: string;
|
37
|
+
zIndex: number;
|
38
|
+
id: string;
|
39
|
+
loadSaveData: LoadSaveDataTable;
|
40
|
+
data: string | number | any[] | Record<string, any>;
|
41
|
+
};
|
36
42
|
private static isNotDeserializable;
|
37
|
-
static deserialize(
|
43
|
+
static deserialize(json: string | any): AbstractComponent;
|
38
44
|
}
|
39
|
-
export {};
|
@@ -17,6 +17,8 @@ export default class AbstractComponent {
|
|
17
17
|
throw new Error(`Component ${componentKind} has not been registered using AbstractComponent.registerComponent`);
|
18
18
|
}
|
19
19
|
}
|
20
|
+
// Returns a unique ID for this element.
|
21
|
+
// @see { @link EditorImage!default.lookupElement }
|
20
22
|
getId() {
|
21
23
|
return this.id;
|
22
24
|
}
|
@@ -55,23 +57,30 @@ export default class AbstractComponent {
|
|
55
57
|
}
|
56
58
|
return clone;
|
57
59
|
}
|
60
|
+
// Convert the component to an object that can be passed to
|
61
|
+
// `JSON.stringify`.
|
62
|
+
//
|
63
|
+
// Do not rely on the output of this function to take a particular form —
|
64
|
+
// this function's output can change form without a major version increase.
|
58
65
|
serialize() {
|
59
|
-
const data = this.
|
66
|
+
const data = this.serializeToJSON();
|
60
67
|
if (data === null) {
|
61
68
|
throw new Error(`${this} cannot be serialized.`);
|
62
69
|
}
|
63
|
-
return
|
70
|
+
return {
|
64
71
|
name: this.componentKind,
|
65
72
|
zIndex: this.zIndex,
|
66
73
|
id: this.id,
|
67
74
|
loadSaveData: this.loadSaveData,
|
68
75
|
data,
|
69
|
-
}
|
76
|
+
};
|
70
77
|
}
|
71
|
-
// Returns true if
|
78
|
+
// Returns true if `data` is not deserializable. May return false even if [data]
|
72
79
|
// is not deserializable.
|
73
|
-
static isNotDeserializable(
|
74
|
-
|
80
|
+
static isNotDeserializable(json) {
|
81
|
+
if (typeof json === 'string') {
|
82
|
+
json = JSON.parse(json);
|
83
|
+
}
|
75
84
|
if (typeof json !== 'object') {
|
76
85
|
return true;
|
77
86
|
}
|
@@ -83,11 +92,14 @@ export default class AbstractComponent {
|
|
83
92
|
}
|
84
93
|
return false;
|
85
94
|
}
|
86
|
-
|
87
|
-
|
88
|
-
|
95
|
+
// Convert a string or an object produced by `JSON.parse` into an `AbstractComponent`.
|
96
|
+
static deserialize(json) {
|
97
|
+
if (typeof json === 'string') {
|
98
|
+
json = JSON.parse(json);
|
99
|
+
}
|
100
|
+
if (AbstractComponent.isNotDeserializable(json)) {
|
101
|
+
throw new Error(`Element with data ${json} cannot be deserialized.`);
|
89
102
|
}
|
90
|
-
const json = JSON.parse(data);
|
91
103
|
const instance = this.deserializationCallbacks[json.name](json.data);
|
92
104
|
instance.zIndex = json.zIndex;
|
93
105
|
instance.id = json.id;
|
@@ -135,16 +147,15 @@ AbstractComponent.TransformElementCommand = (_a = class extends SerializableComm
|
|
135
147
|
description(_editor, localizationTable) {
|
136
148
|
return localizationTable.transformedElements(1);
|
137
149
|
}
|
138
|
-
|
139
|
-
return
|
150
|
+
serializeToJSON() {
|
151
|
+
return {
|
140
152
|
id: this.component.getId(),
|
141
153
|
transfm: this.affineTransfm.toArray(),
|
142
|
-
}
|
154
|
+
};
|
143
155
|
}
|
144
156
|
},
|
145
157
|
(() => {
|
146
|
-
SerializableCommand.register('transform-element', (
|
147
|
-
const json = JSON.parse(data);
|
158
|
+
SerializableCommand.register('transform-element', (json, editor) => {
|
148
159
|
const elem = editor.image.lookupElement(json.id);
|
149
160
|
if (!elem) {
|
150
161
|
throw new Error(`Unable to retrieve non-existent element, ${elem}`);
|
@@ -14,7 +14,7 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
|
|
14
14
|
protected applyTransformation(_affineTransfm: Mat33): void;
|
15
15
|
protected createClone(): SVGGlobalAttributesObject;
|
16
16
|
description(localization: ImageComponentLocalization): string;
|
17
|
-
protected
|
17
|
+
protected serializeToJSON(): string | null;
|
18
18
|
static deserializeFromString(data: string): AbstractComponent;
|
19
19
|
}
|
20
20
|
export {};
|
@@ -1,3 +1,9 @@
|
|
1
|
+
//
|
2
|
+
// Used by `SVGLoader`s to store unrecognised global attributes
|
3
|
+
// (e.g. unrecognised XML namespace declarations).
|
4
|
+
// @internal
|
5
|
+
// @packageDocumentation
|
6
|
+
//
|
1
7
|
import Rect2 from '../math/Rect2';
|
2
8
|
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
3
9
|
import AbstractComponent from './AbstractComponent';
|
@@ -29,7 +35,7 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
|
|
29
35
|
description(localization) {
|
30
36
|
return localization.svgObject;
|
31
37
|
}
|
32
|
-
|
38
|
+
serializeToJSON() {
|
33
39
|
return JSON.stringify(this.attrs);
|
34
40
|
}
|
35
41
|
static deserializeFromString(data) {
|
@@ -16,6 +16,16 @@ export default class Stroke extends AbstractComponent {
|
|
16
16
|
getPath(): Path;
|
17
17
|
description(localization: ImageComponentLocalization): string;
|
18
18
|
protected createClone(): AbstractComponent;
|
19
|
-
protected
|
20
|
-
|
19
|
+
protected serializeToJSON(): {
|
20
|
+
style: {
|
21
|
+
fill: string;
|
22
|
+
stroke: {
|
23
|
+
color: string;
|
24
|
+
width: number;
|
25
|
+
} | undefined;
|
26
|
+
};
|
27
|
+
path: string;
|
28
|
+
}[];
|
29
|
+
/** @internal */
|
30
|
+
static deserializeFromJSON(json: any): Stroke;
|
21
31
|
}
|
@@ -87,18 +87,21 @@ export default class Stroke extends AbstractComponent {
|
|
87
87
|
createClone() {
|
88
88
|
return new Stroke(this.parts);
|
89
89
|
}
|
90
|
-
|
91
|
-
return
|
90
|
+
serializeToJSON() {
|
91
|
+
return this.parts.map(part => {
|
92
92
|
return {
|
93
93
|
style: styleToJSON(part.style),
|
94
94
|
path: part.path.serialize(),
|
95
95
|
};
|
96
|
-
})
|
96
|
+
});
|
97
97
|
}
|
98
|
-
|
99
|
-
|
98
|
+
/** @internal */
|
99
|
+
static deserializeFromJSON(json) {
|
100
|
+
if (typeof json === 'string') {
|
101
|
+
json = JSON.parse(json);
|
102
|
+
}
|
100
103
|
if (typeof json !== 'object' || typeof json.length !== 'number') {
|
101
|
-
throw new Error(`${
|
104
|
+
throw new Error(`${json} is missing required field, parts, or parts is of the wrong type.`);
|
102
105
|
}
|
103
106
|
const pathSpec = json.map((part) => {
|
104
107
|
const style = styleFromJSON(part.style);
|
@@ -107,4 +110,4 @@ export default class Stroke extends AbstractComponent {
|
|
107
110
|
return new Stroke(pathSpec);
|
108
111
|
}
|
109
112
|
}
|
110
|
-
AbstractComponent.registerComponent('stroke', Stroke.
|
113
|
+
AbstractComponent.registerComponent('stroke', Stroke.deserializeFromJSON);
|
@@ -31,7 +31,7 @@ export default class Text extends AbstractComponent {
|
|
31
31
|
protected createClone(): AbstractComponent;
|
32
32
|
private getText;
|
33
33
|
description(localizationTable: ImageComponentLocalization): string;
|
34
|
-
protected
|
35
|
-
static deserializeFromString(
|
34
|
+
protected serializeToJSON(): Record<string, any>;
|
35
|
+
static deserializeFromString(json: any, getTextDimens?: GetTextDimensCallback): Text;
|
36
36
|
}
|
37
37
|
export {};
|
@@ -7,6 +7,7 @@ const componentTypeId = 'text';
|
|
7
7
|
export default class Text extends AbstractComponent {
|
8
8
|
constructor(textObjects, transform, style,
|
9
9
|
// If not given, an HtmlCanvasElement is used to determine text boundaries.
|
10
|
+
// @internal
|
10
11
|
getTextDimens = Text.getTextDimens) {
|
11
12
|
super(componentTypeId);
|
12
13
|
this.textObjects = textObjects;
|
@@ -117,7 +118,7 @@ export default class Text extends AbstractComponent {
|
|
117
118
|
description(localizationTable) {
|
118
119
|
return localizationTable.text(this.getText());
|
119
120
|
}
|
120
|
-
|
121
|
+
serializeToJSON() {
|
121
122
|
const serializableStyle = Object.assign(Object.assign({}, this.style), { renderingStyle: styleToJSON(this.style.renderingStyle) });
|
122
123
|
const textObjects = this.textObjects.map(text => {
|
123
124
|
if (typeof text === 'string') {
|
@@ -127,18 +128,17 @@ export default class Text extends AbstractComponent {
|
|
127
128
|
}
|
128
129
|
else {
|
129
130
|
return {
|
130
|
-
json: text.
|
131
|
+
json: text.serializeToJSON(),
|
131
132
|
};
|
132
133
|
}
|
133
134
|
});
|
134
|
-
return
|
135
|
+
return {
|
135
136
|
textObjects,
|
136
137
|
transform: this.transform.toArray(),
|
137
138
|
style: serializableStyle,
|
138
|
-
}
|
139
|
+
};
|
139
140
|
}
|
140
|
-
static deserializeFromString(
|
141
|
-
const json = JSON.parse(data);
|
141
|
+
static deserializeFromString(json, getTextDimens = Text.getTextDimens) {
|
142
142
|
const style = {
|
143
143
|
renderingStyle: styleFromJSON(json.style.renderingStyle),
|
144
144
|
size: json.style.size,
|
@@ -13,5 +13,5 @@ export default class UnknownSVGObject extends AbstractComponent {
|
|
13
13
|
protected applyTransformation(_affineTransfm: Mat33): void;
|
14
14
|
protected createClone(): AbstractComponent;
|
15
15
|
description(localization: ImageComponentLocalization): string;
|
16
|
-
protected
|
16
|
+
protected serializeToJSON(): string | null;
|
17
17
|
}
|
@@ -1,3 +1,8 @@
|
|
1
|
+
//
|
2
|
+
// Stores objects loaded from an SVG that aren't recognised by the editor.
|
3
|
+
// @internal
|
4
|
+
// @packageDocumentation
|
5
|
+
//
|
1
6
|
import Rect2 from '../math/Rect2';
|
2
7
|
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
3
8
|
import AbstractComponent from './AbstractComponent';
|
@@ -26,7 +31,7 @@ export default class UnknownSVGObject extends AbstractComponent {
|
|
26
31
|
description(localization) {
|
27
32
|
return localization.svgObject;
|
28
33
|
}
|
29
|
-
|
34
|
+
serializeToJSON() {
|
30
35
|
return JSON.stringify({
|
31
36
|
html: this.svgObject.outerHTML,
|
32
37
|
});
|