@mica-js/core 0.0.1 → 0.1.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/LICENSE +21 -21
- package/README.md +431 -10
- package/dist/canvas/index.d.ts +38 -147
- package/dist/canvas/index.js +196 -153
- package/dist/canvas/index.js.map +1 -1
- package/dist/index.d.ts +445 -4
- package/dist/index.js +2432 -4
- package/dist/index.js.map +1 -1
- package/dist/schematic/index.d.ts +11 -10
- package/dist/schematic/index.js +3 -3
- package/dist/schematic/index.js.map +1 -1
- package/dist/{validate-COqYIMQg.js → validate-DuALfDe0.js} +5 -5
- package/dist/validate-DuALfDe0.js.map +1 -0
- package/package.json +9 -1
- package/dist/validate-COqYIMQg.js.map +0 -1
package/dist/canvas/index.d.ts
CHANGED
|
@@ -1,74 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Applies a canvas command to a copy of `canvas`.
|
|
3
|
-
*
|
|
4
|
-
* @throws {SchematicError} when the command is invalid; the input canvas is never mutated.
|
|
5
|
-
*/
|
|
6
|
-
export declare function applyCanvasCommand(canvas: CanvasDocument, command: CanvasCommand, options?: ApplyCanvasCommandOptions): CanvasDocument;
|
|
7
|
-
|
|
8
|
-
export declare interface ApplyCanvasCommandOptions {
|
|
9
|
-
/** Bound schematic; symbol references are validated against it when provided. */
|
|
10
|
-
readonly schematic?: SchematicDocument;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Where an image / binary property value lives (#7). */
|
|
14
|
-
declare type AssetMode = 'file' | 'inline' | 'store';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Reference to a project asset instead of inline bytes (#7): `file` is a project-root relative
|
|
18
|
-
* POSIX path, `inline` is base64 text and `store` is a host resource identifier.
|
|
19
|
-
*/
|
|
20
|
-
declare interface AssetReference {
|
|
21
|
-
readonly src: string;
|
|
22
|
-
readonly mode: AssetMode;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
1
|
/**
|
|
26
2
|
* Schema version of the `.cnv` canvas contract (see x-botlab/mica-js#67).
|
|
27
3
|
*
|
|
28
|
-
* A canvas is
|
|
29
|
-
*
|
|
30
|
-
*
|
|
4
|
+
* A canvas is an independent 2D plane: it records widget instances with their placement, property
|
|
5
|
+
* overrides and port-to-symbol bindings. Electrical topology stays in the `.sch` the canvas binds
|
|
6
|
+
* (see x-botlab/mica-js#6); the two coordinate spaces are unrelated.
|
|
31
7
|
*/
|
|
32
8
|
export declare const CANVAS_VERSION = 1;
|
|
33
9
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
readonly
|
|
37
|
-
readonly
|
|
38
|
-
readonly y: number;
|
|
39
|
-
readonly rotation?: number;
|
|
40
|
-
} | {
|
|
41
|
-
readonly type: 'remove-placement';
|
|
42
|
-
readonly symbolId: string;
|
|
43
|
-
} | {
|
|
44
|
-
readonly type: 'bind-schematic';
|
|
45
|
-
readonly schematic: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export declare interface CanvasDocument {
|
|
49
|
-
readonly version: CanvasVersion;
|
|
50
|
-
/** Canvas identity, unique within one project's `.cnv` files. */
|
|
51
|
-
readonly id: string;
|
|
52
|
-
/** Project-root relative POSIX path of the bound `.sch`. */
|
|
53
|
-
readonly schematic: string;
|
|
54
|
-
/** Symbol id → placement; ids are stable, so renaming a designator keeps the entry. */
|
|
55
|
-
readonly layout: Readonly<Record<string, CanvasPlacement>>;
|
|
10
|
+
/** Endpoint of a widget port: a port of one symbol instance in the bound schematic. */
|
|
11
|
+
export declare interface CanvasBindingTarget {
|
|
12
|
+
readonly symbol: string;
|
|
13
|
+
readonly port: string;
|
|
56
14
|
}
|
|
57
15
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
readonly
|
|
16
|
+
/** Root of a `.cnv` file; the project-root relative path is the canvas identity. */
|
|
17
|
+
export declare interface CanvasDocument {
|
|
18
|
+
readonly version: typeof CANVAS_VERSION;
|
|
19
|
+
/** Project-root relative POSIX path of the `.sch` the canvas draws and simulates against. */
|
|
20
|
+
readonly schematic?: string;
|
|
21
|
+
/** Widget instances in drawing order; later instances paint above earlier ones. */
|
|
22
|
+
readonly widgets: readonly CanvasWidgetInstance[];
|
|
61
23
|
}
|
|
62
24
|
|
|
63
|
-
/**
|
|
64
|
-
export declare interface
|
|
65
|
-
|
|
66
|
-
readonly
|
|
67
|
-
|
|
25
|
+
/** One widget placed on a canvas; the order in `widgets` is the drawing order (#67). */
|
|
26
|
+
export declare interface CanvasWidgetInstance extends WidgetPlacement {
|
|
27
|
+
/** Stable identity: UUID v4, unique within the document. */
|
|
28
|
+
readonly id: string;
|
|
29
|
+
/** Widget reference `"<package>:<widget>"`; built-ins use the reserved `builtin:` namespace. */
|
|
30
|
+
readonly widget: string;
|
|
31
|
+
/** Property overrides keyed by the widget definition; values follow that definition (#7). */
|
|
32
|
+
readonly properties?: Readonly<Record<string, unknown>>;
|
|
33
|
+
/** Port key → endpoint; only bound ports take part in messaging (#10). */
|
|
34
|
+
readonly bindings?: Readonly<Record<string, CanvasBindingTarget>>;
|
|
68
35
|
}
|
|
69
36
|
|
|
70
|
-
export declare type CanvasVersion = typeof CANVAS_VERSION;
|
|
71
|
-
|
|
72
37
|
export declare interface Diagnostic {
|
|
73
38
|
readonly severity: DiagnosticSeverity;
|
|
74
39
|
readonly code: DiagnosticCode;
|
|
@@ -80,7 +45,7 @@ export declare interface Diagnostic {
|
|
|
80
45
|
readonly related?: readonly DiagnosticRelated[];
|
|
81
46
|
}
|
|
82
47
|
|
|
83
|
-
export declare type DiagnosticCode = 'json/invalid' | 'json/not-object' | 'schema/unsupported-version' | 'schema/missing-field' | 'schema/invalid-type' | 'schema/invalid-value' | 'schema/unknown-field' | 'id/duplicate' | 'device/invalid-reference' | 'device/unresolved' | 'device/invalid-package' | 'device/duplicate-package' | 'device/reserved-namespace' | 'device/duplicate-device' | 'device/invalid-description' | 'device/unknown-pin' | 'device/no-symbol' | 'wire/invalid-points' | 'wire/not-orthogonal' | 'junction/unknown-wire' | 'junction/duplicate-wire' | 'junction/off-wire' | 'symbol/missing-net' | 'symbol/unsupported-field' | 'symbol/designator-duplicate' | 'net/naming-conflict' | 'canvas/unknown-symbol' | 'edit/unknown-entity' | 'edit/invalid-command';
|
|
48
|
+
export declare type DiagnosticCode = 'json/invalid' | 'json/not-object' | 'schema/unsupported-version' | 'schema/missing-field' | 'schema/invalid-type' | 'schema/invalid-value' | 'schema/unknown-field' | 'id/duplicate' | 'device/invalid-reference' | 'device/unresolved' | 'device/invalid-package' | 'device/duplicate-package' | 'device/reserved-namespace' | 'device/duplicate-device' | 'device/invalid-description' | 'device/unknown-pin' | 'device/no-symbol' | 'wire/invalid-points' | 'wire/not-orthogonal' | 'junction/unknown-wire' | 'junction/duplicate-wire' | 'junction/off-wire' | 'symbol/missing-net' | 'symbol/unsupported-field' | 'symbol/designator-duplicate' | 'net/naming-conflict' | 'canvas/unknown-symbol' | 'canvas/unknown-widget' | 'canvas/unknown-property' | 'canvas/unknown-port' | 'canvas/unknown-schematic' | 'canvas/unknown-target-port' | 'topic/type-mismatch' | 'type/unknown' | 'edit/unknown-entity' | 'edit/invalid-command';
|
|
84
49
|
|
|
85
50
|
export declare interface DiagnosticEntity {
|
|
86
51
|
readonly kind: 'symbol' | 'wire' | 'junction';
|
|
@@ -93,54 +58,9 @@ export declare interface DiagnosticRelated {
|
|
|
93
58
|
|
|
94
59
|
export declare type DiagnosticSeverity = 'error' | 'warning';
|
|
95
60
|
|
|
96
|
-
/** Parses a canvas and validates its layout against the bound schematic. @throws {SchematicError} */
|
|
97
|
-
export declare function loadCanvas(source: string | CanvasDocument, options?: LoadCanvasOptions): LoadedCanvas;
|
|
98
|
-
|
|
99
|
-
export declare interface LoadCanvasOptions {
|
|
100
|
-
/** Bound schematic; layout keys are validated against its symbols when provided. */
|
|
101
|
-
readonly schematic?: SchematicDocument;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export declare interface LoadedCanvas {
|
|
105
|
-
readonly document: CanvasDocument;
|
|
106
|
-
/** Layout entries sorted by symbol id, so loading is deterministic. */
|
|
107
|
-
readonly placements: readonly CanvasLayoutEntry[];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
61
|
/** @throws {SchematicError} when the input does not match the `.cnv` contract. */
|
|
111
62
|
export declare function parseCanvas(input: string | unknown): CanvasDocument;
|
|
112
63
|
|
|
113
|
-
declare interface Placement {
|
|
114
|
-
readonly x: number;
|
|
115
|
-
readonly y: number;
|
|
116
|
-
readonly rotation: number;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
declare interface Point {
|
|
120
|
-
readonly x: number;
|
|
121
|
-
readonly y: number;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Instance property value stored in `symbol.properties` (see #6 / #7): a scalar, a resource
|
|
126
|
-
* reference for `image` / `binary` properties, or `null` for an explicitly cleared nullable one.
|
|
127
|
-
*/
|
|
128
|
-
declare type PropertyValue = string | number | boolean | AssetReference | null;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Schema version of the `.sch` contract (see x-botlab/mica-js#6).
|
|
132
|
-
*
|
|
133
|
-
* 2D placement lives in the separate `.cnv` canvas contract (see x-botlab/mica-js#67 and
|
|
134
|
-
* `@mica-js/core/canvas`).
|
|
135
|
-
*/
|
|
136
|
-
declare const SCHEMATIC_VERSION = 1;
|
|
137
|
-
|
|
138
|
-
/** Root of a `.sch` file; the file name is the schematic's identity. */
|
|
139
|
-
declare interface SchematicDocument {
|
|
140
|
-
readonly version: SchematicVersion;
|
|
141
|
-
readonly sheet: SchematicSheet;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
64
|
/**
|
|
145
65
|
* Thrown when input violates the schematic format contract.
|
|
146
66
|
* `diagnostics` carries every problem found, so callers can report them all at once.
|
|
@@ -152,50 +72,21 @@ export declare class SchematicError extends Error {
|
|
|
152
72
|
get code(): DiagnosticCode | undefined;
|
|
153
73
|
}
|
|
154
74
|
|
|
155
|
-
|
|
156
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Serializes with a fixed field order and 2-space indentation, matching the example in #67.
|
|
77
|
+
* `properties` and `bindings` are written whenever the document carries them, even when empty, so
|
|
78
|
+
* a parse / serialize round trip keeps the original shape.
|
|
79
|
+
*/
|
|
80
|
+
export declare function serializeCanvas(document: CanvasDocument): string;
|
|
81
|
+
|
|
82
|
+
/** Placement and size of one widget instance, in logical units. */
|
|
83
|
+
export declare interface WidgetPlacement {
|
|
157
84
|
readonly x: number;
|
|
158
85
|
readonly y: number;
|
|
159
|
-
readonly
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
readonly symbols: readonly SchematicSymbol[];
|
|
164
|
-
readonly wires: readonly SchematicWire[];
|
|
165
|
-
readonly junctions: readonly SchematicJunction[];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
declare interface SchematicSymbol extends Placement {
|
|
169
|
-
/** Stable identity used by selections, undo and `.view` layout keys. */
|
|
170
|
-
readonly id: string;
|
|
171
|
-
readonly device: string;
|
|
172
|
-
/** Instance property overrides; absent means "use device defaults". */
|
|
173
|
-
readonly properties?: Readonly<Record<string, PropertyValue>>;
|
|
174
|
-
/**
|
|
175
|
-
* Reference designator of a component (`U1`, `R2`); defaults to {@link id}.
|
|
176
|
-
* Only components carry one — the format rejects it elsewhere.
|
|
177
|
-
*/
|
|
178
|
-
readonly designator?: string;
|
|
179
|
-
/**
|
|
180
|
-
* Net name of a net label, power or ground symbol (`VCC`, `N1`). It is the text drawn next to
|
|
181
|
-
* the anchor and the key same-named ports merge on. Only net-naming symbols carry one.
|
|
182
|
-
*/
|
|
183
|
-
readonly net?: string;
|
|
184
|
-
/**
|
|
185
|
-
* Reference designator anchor in symbol-local coordinates (unrotated, unplaced).
|
|
186
|
-
* Absent means the default position at the body's top-left corner.
|
|
187
|
-
*/
|
|
188
|
-
readonly reference?: Point;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
declare type SchematicVersion = typeof SCHEMATIC_VERSION;
|
|
192
|
-
|
|
193
|
-
declare interface SchematicWire {
|
|
194
|
-
readonly id: string;
|
|
195
|
-
readonly points: readonly Point[];
|
|
86
|
+
readonly width: number;
|
|
87
|
+
readonly height: number;
|
|
88
|
+
/** Clockwise rotation in degrees around the instance center. */
|
|
89
|
+
readonly rotation: number;
|
|
196
90
|
}
|
|
197
91
|
|
|
198
|
-
/** Serializes with a fixed field order and 2-space indentation, matching the example in #67. */
|
|
199
|
-
export declare function serializeCanvas(canvas: CanvasDocument): string;
|
|
200
|
-
|
|
201
92
|
export { }
|
package/dist/canvas/index.js
CHANGED
|
@@ -1,15 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as readArrayField, d as readStringField, f as report, h as SchematicError, i as isRecord, m as parseInput, n as forbidUnknownFields, o as readIntegerField, r as hasErrors, s as readNumberField } from "../validate-DuALfDe0.js";
|
|
2
2
|
//#region src/canvas/format/types.ts
|
|
3
3
|
/**
|
|
4
4
|
* Schema version of the `.cnv` canvas contract (see x-botlab/mica-js#67).
|
|
5
5
|
*
|
|
6
|
-
* A canvas is
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* A canvas is an independent 2D plane: it records widget instances with their placement, property
|
|
7
|
+
* overrides and port-to-symbol bindings. Electrical topology stays in the `.sch` the canvas binds
|
|
8
|
+
* (see x-botlab/mica-js#6); the two coordinate spaces are unrelated.
|
|
9
9
|
*/
|
|
10
10
|
var CANVAS_VERSION = 1;
|
|
11
11
|
//#endregion
|
|
12
|
+
//#region src/format/uuid.ts
|
|
13
|
+
var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
14
|
+
/** Strict UUID v4 check: 8-4-4-4-12 hex with the version and variant bits fixed. */
|
|
15
|
+
function isUuidV4(value) {
|
|
16
|
+
return UUID_V4.test(value);
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
12
19
|
//#region src/canvas/format/parse.ts
|
|
20
|
+
/**
|
|
21
|
+
* `"<package>:<widget>"`; `builtin` matches the package pattern, so the same expression also
|
|
22
|
+
* accepts the reserved `builtin:<widget>` form (#67).
|
|
23
|
+
*/
|
|
24
|
+
var WIDGET_REFERENCE = /^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*$/;
|
|
25
|
+
var WIDGET_FIELDS = [
|
|
26
|
+
"id",
|
|
27
|
+
"widget",
|
|
28
|
+
"x",
|
|
29
|
+
"y",
|
|
30
|
+
"width",
|
|
31
|
+
"height",
|
|
32
|
+
"rotation",
|
|
33
|
+
"properties",
|
|
34
|
+
"bindings"
|
|
35
|
+
];
|
|
13
36
|
/** @throws {SchematicError} when the input does not match the `.cnv` contract. */
|
|
14
37
|
function parseCanvas(input) {
|
|
15
38
|
const diagnostics = [];
|
|
@@ -26,20 +49,20 @@ function readDocument(value, diagnostics) {
|
|
|
26
49
|
}
|
|
27
50
|
forbidUnknownFields(value, [
|
|
28
51
|
"version",
|
|
29
|
-
"id",
|
|
30
52
|
"schematic",
|
|
31
|
-
"
|
|
53
|
+
"widgets"
|
|
32
54
|
], [], diagnostics);
|
|
33
55
|
const version = readVersion(value, diagnostics);
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
const schematic = readSchematic(value, diagnostics);
|
|
57
|
+
const widgets = readWidgets(readArrayField(value, "widgets", [], diagnostics), diagnostics);
|
|
58
|
+
if (version === void 0 || widgets === void 0) return;
|
|
59
|
+
return schematic === void 0 ? {
|
|
60
|
+
version,
|
|
61
|
+
widgets
|
|
62
|
+
} : {
|
|
39
63
|
version,
|
|
40
|
-
id,
|
|
41
64
|
schematic,
|
|
42
|
-
|
|
65
|
+
widgets
|
|
43
66
|
};
|
|
44
67
|
}
|
|
45
68
|
function readVersion(value, diagnostics) {
|
|
@@ -51,173 +74,193 @@ function readVersion(value, diagnostics) {
|
|
|
51
74
|
}
|
|
52
75
|
return 1;
|
|
53
76
|
}
|
|
54
|
-
function
|
|
55
|
-
if (value
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
function readSchematic(value, diagnostics) {
|
|
78
|
+
if (!hasField(value, "schematic")) return;
|
|
79
|
+
return readStringField(value, "schematic", [], diagnostics, { nonEmpty: true });
|
|
80
|
+
}
|
|
81
|
+
function readWidgets(raw, diagnostics) {
|
|
82
|
+
if (raw === void 0) return;
|
|
83
|
+
const widgets = [];
|
|
84
|
+
const seen = /* @__PURE__ */ new Set();
|
|
85
|
+
raw.forEach((item, index) => {
|
|
86
|
+
const path = ["widgets", String(index)];
|
|
87
|
+
const widget = readWidget(item, path, diagnostics);
|
|
88
|
+
if (widget === void 0) return;
|
|
89
|
+
if (seen.has(widget.id)) {
|
|
90
|
+
report(diagnostics, "id/duplicate", `Duplicate widget id "${widget.id}".`, [...path, "id"]);
|
|
91
|
+
return;
|
|
62
92
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
return
|
|
93
|
+
seen.add(widget.id);
|
|
94
|
+
widgets.push(widget);
|
|
95
|
+
});
|
|
96
|
+
return widgets;
|
|
67
97
|
}
|
|
68
|
-
function
|
|
98
|
+
function readWidget(value, path, diagnostics) {
|
|
69
99
|
if (!isRecord(value)) {
|
|
70
|
-
report(diagnostics, "schema/invalid-type", "
|
|
100
|
+
report(diagnostics, "schema/invalid-type", "Widget instance must be an object.", path);
|
|
71
101
|
return;
|
|
72
102
|
}
|
|
73
|
-
forbidUnknownFields(value,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
forbidUnknownFields(value, WIDGET_FIELDS, path, diagnostics);
|
|
104
|
+
const id = readId(value, path, diagnostics);
|
|
105
|
+
const widget = readWidgetReference(value, path, diagnostics);
|
|
106
|
+
const placement = readPlacement(value, path, diagnostics);
|
|
107
|
+
const hasProperties = hasField(value, "properties");
|
|
108
|
+
const properties = hasProperties ? readProperties(value["properties"], [...path, "properties"], diagnostics) : void 0;
|
|
109
|
+
const hasBindings = hasField(value, "bindings");
|
|
110
|
+
const bindings = hasBindings ? readBindings(value["bindings"], [...path, "bindings"], diagnostics) : void 0;
|
|
111
|
+
if (id === void 0 || widget === void 0 || placement === void 0 || hasProperties && properties === void 0 || hasBindings && bindings === void 0) return;
|
|
112
|
+
return {
|
|
113
|
+
id,
|
|
114
|
+
widget,
|
|
115
|
+
...placement,
|
|
116
|
+
...properties === void 0 ? {} : { properties },
|
|
117
|
+
...bindings === void 0 ? {} : { bindings }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function readId(value, path, diagnostics) {
|
|
121
|
+
const id = readStringField(value, "id", path, diagnostics, { nonEmpty: true });
|
|
122
|
+
if (id === void 0) return;
|
|
123
|
+
if (!isUuidV4(id)) {
|
|
124
|
+
report(diagnostics, "schema/invalid-value", "Field \"id\" must be a UUID v4.", [...path, "id"]);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
return id;
|
|
128
|
+
}
|
|
129
|
+
function readWidgetReference(value, path, diagnostics) {
|
|
130
|
+
const widget = readStringField(value, "widget", path, diagnostics, { nonEmpty: true });
|
|
131
|
+
if (widget === void 0) return;
|
|
132
|
+
if (!WIDGET_REFERENCE.test(widget)) {
|
|
133
|
+
report(diagnostics, "schema/invalid-value", "Field \"widget\" must be \"<package>:<widget>\" or \"builtin:<widget>\".", [...path, "widget"]);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
return widget;
|
|
137
|
+
}
|
|
138
|
+
function readPlacement(value, path, diagnostics) {
|
|
78
139
|
const x = readNumberField(value, "x", path, diagnostics);
|
|
79
140
|
const y = readNumberField(value, "y", path, diagnostics);
|
|
141
|
+
const width = readPositiveNumber(value, "width", path, diagnostics);
|
|
142
|
+
const height = readPositiveNumber(value, "height", path, diagnostics);
|
|
80
143
|
const rotation = readNumberField(value, "rotation", path, diagnostics);
|
|
81
|
-
if (x === void 0 || y === void 0 || rotation === void 0) return;
|
|
144
|
+
if (x === void 0 || y === void 0 || width === void 0 || height === void 0 || rotation === void 0) return;
|
|
82
145
|
return {
|
|
83
146
|
x,
|
|
84
147
|
y,
|
|
148
|
+
width,
|
|
149
|
+
height,
|
|
85
150
|
rotation
|
|
86
151
|
};
|
|
87
152
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const placement = canvas.layout[symbolId];
|
|
95
|
-
if (placement === void 0) continue;
|
|
96
|
-
layout[symbolId] = {
|
|
97
|
-
x: placement.x,
|
|
98
|
-
y: placement.y,
|
|
99
|
-
rotation: placement.rotation
|
|
100
|
-
};
|
|
153
|
+
function readPositiveNumber(value, key, path, diagnostics) {
|
|
154
|
+
const field = readNumberField(value, key, path, diagnostics);
|
|
155
|
+
if (field === void 0) return;
|
|
156
|
+
if (field <= 0) {
|
|
157
|
+
report(diagnostics, "schema/invalid-value", `Field "${key}" must be a positive number.`, [...path, key]);
|
|
158
|
+
return;
|
|
101
159
|
}
|
|
102
|
-
|
|
103
|
-
version: canvas.version,
|
|
104
|
-
id: canvas.id,
|
|
105
|
-
schematic: canvas.schematic,
|
|
106
|
-
layout
|
|
107
|
-
};
|
|
108
|
-
return `${JSON.stringify(serializable, null, 2)}\n`;
|
|
160
|
+
return field;
|
|
109
161
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Property values are opaque to the format: their types come from the widget definition (#7), so
|
|
164
|
+
* only the map shape and key spelling are checked here.
|
|
165
|
+
*/
|
|
166
|
+
function readProperties(value, path, diagnostics) {
|
|
167
|
+
if (!isRecord(value)) {
|
|
168
|
+
report(diagnostics, "schema/invalid-type", "Field \"properties\" must be an object.", path);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const properties = Object.create(null);
|
|
172
|
+
let failed = false;
|
|
173
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
174
|
+
if (key.length === 0) {
|
|
175
|
+
report(diagnostics, "schema/invalid-value", "Property keys must not be empty.", path);
|
|
176
|
+
failed = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
properties[key] = clonePropertyValue(entry);
|
|
180
|
+
}
|
|
181
|
+
return failed ? void 0 : properties;
|
|
182
|
+
}
|
|
183
|
+
/** Values are copied so the parsed document never aliases the caller's input object. */
|
|
184
|
+
function clonePropertyValue(value) {
|
|
185
|
+
if (Array.isArray(value)) return value.map(clonePropertyValue);
|
|
186
|
+
const prototype = isRecord(value) ? Object.getPrototypeOf(value) : void 0;
|
|
187
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
188
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clonePropertyValue(entry)]));
|
|
189
|
+
}
|
|
190
|
+
function readBindings(value, path, diagnostics) {
|
|
191
|
+
if (!isRecord(value)) {
|
|
192
|
+
report(diagnostics, "schema/invalid-type", "Field \"bindings\" must be an object.", path);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const bindings = Object.create(null);
|
|
196
|
+
let failed = false;
|
|
197
|
+
for (const [port, entry] of Object.entries(value)) {
|
|
198
|
+
if (port.length === 0) {
|
|
199
|
+
report(diagnostics, "schema/invalid-value", "Binding port keys must not be empty.", path);
|
|
200
|
+
failed = true;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const target = readBindingTarget(entry, [...path, port], diagnostics);
|
|
204
|
+
if (target === void 0) {
|
|
205
|
+
failed = true;
|
|
128
206
|
continue;
|
|
129
207
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
208
|
+
bindings[port] = target;
|
|
209
|
+
}
|
|
210
|
+
return failed ? void 0 : bindings;
|
|
211
|
+
}
|
|
212
|
+
function readBindingTarget(value, path, diagnostics) {
|
|
213
|
+
if (!isRecord(value)) {
|
|
214
|
+
report(diagnostics, "schema/invalid-type", "Binding target must be an object.", path);
|
|
215
|
+
return;
|
|
134
216
|
}
|
|
135
|
-
|
|
217
|
+
forbidUnknownFields(value, ["symbol", "port"], path, diagnostics);
|
|
218
|
+
const symbol = readStringField(value, "symbol", path, diagnostics, { nonEmpty: true });
|
|
219
|
+
const port = readStringField(value, "port", path, diagnostics, { nonEmpty: true });
|
|
220
|
+
if (symbol === void 0 || port === void 0) return;
|
|
136
221
|
return {
|
|
137
|
-
|
|
138
|
-
|
|
222
|
+
symbol,
|
|
223
|
+
port
|
|
139
224
|
};
|
|
140
225
|
}
|
|
226
|
+
function hasField(value, key) {
|
|
227
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
228
|
+
}
|
|
141
229
|
//#endregion
|
|
142
|
-
//#region src/canvas/
|
|
230
|
+
//#region src/canvas/format/serialize.ts
|
|
143
231
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
232
|
+
* Serializes with a fixed field order and 2-space indentation, matching the example in #67.
|
|
233
|
+
* `properties` and `bindings` are written whenever the document carries them, even when empty, so
|
|
234
|
+
* a parse / serialize round trip keeps the original shape.
|
|
147
235
|
*/
|
|
148
|
-
function
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (command.symbolId.length === 0) {
|
|
154
|
-
diagnostics.push({
|
|
155
|
-
severity: "error",
|
|
156
|
-
code: "schema/invalid-value",
|
|
157
|
-
message: "Placement symbol id must not be empty.",
|
|
158
|
-
pointer: toPointer(["layout"])
|
|
159
|
-
});
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
requireKnownSymbol(command.symbolId, options, diagnostics);
|
|
163
|
-
const existing = layout[command.symbolId];
|
|
164
|
-
layout[command.symbolId] = {
|
|
165
|
-
x: command.x,
|
|
166
|
-
y: command.y,
|
|
167
|
-
rotation: command.rotation ?? existing?.rotation ?? 0
|
|
168
|
-
};
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
case "remove-placement":
|
|
172
|
-
if (!Object.prototype.hasOwnProperty.call(layout, command.symbolId)) {
|
|
173
|
-
diagnostics.push({
|
|
174
|
-
severity: "error",
|
|
175
|
-
code: "edit/unknown-entity",
|
|
176
|
-
message: `Canvas has no placement for symbol "${command.symbolId}".`,
|
|
177
|
-
pointer: toPointer(["layout", command.symbolId])
|
|
178
|
-
});
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
delete layout[command.symbolId];
|
|
182
|
-
break;
|
|
183
|
-
case "bind-schematic":
|
|
184
|
-
if (command.schematic.length === 0) {
|
|
185
|
-
diagnostics.push({
|
|
186
|
-
severity: "error",
|
|
187
|
-
code: "schema/invalid-value",
|
|
188
|
-
message: "Bound schematic reference must not be empty.",
|
|
189
|
-
pointer: toPointer(["schematic"])
|
|
190
|
-
});
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
if (diagnostics.length === 0) return {
|
|
194
|
-
...canvas,
|
|
195
|
-
schematic: command.schematic
|
|
196
|
-
};
|
|
197
|
-
break;
|
|
198
|
-
default: diagnostics.push({
|
|
199
|
-
severity: "error",
|
|
200
|
-
code: "edit/invalid-command",
|
|
201
|
-
message: `Unsupported canvas command ${JSON.stringify(command.type)}.`
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
if (diagnostics.length > 0) throw new SchematicError(diagnostics);
|
|
205
|
-
return {
|
|
206
|
-
...canvas,
|
|
207
|
-
layout
|
|
208
|
-
};
|
|
236
|
+
function serializeCanvas(document) {
|
|
237
|
+
const serializable = { version: document.version };
|
|
238
|
+
if (document.schematic !== void 0) serializable["schematic"] = document.schematic;
|
|
239
|
+
serializable["widgets"] = document.widgets.map(serializeWidget);
|
|
240
|
+
return `${JSON.stringify(serializable, null, 2)}\n`;
|
|
209
241
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
242
|
+
function serializeWidget(widget) {
|
|
243
|
+
const serialized = {
|
|
244
|
+
id: widget.id,
|
|
245
|
+
widget: widget.widget,
|
|
246
|
+
x: widget.x,
|
|
247
|
+
y: widget.y,
|
|
248
|
+
width: widget.width,
|
|
249
|
+
height: widget.height,
|
|
250
|
+
rotation: widget.rotation
|
|
251
|
+
};
|
|
252
|
+
if (widget.properties !== void 0) serialized["properties"] = { ...widget.properties };
|
|
253
|
+
if (widget.bindings !== void 0) {
|
|
254
|
+
const bindings = Object.create(null);
|
|
255
|
+
for (const [port, target] of Object.entries(widget.bindings)) bindings[port] = {
|
|
256
|
+
symbol: target.symbol,
|
|
257
|
+
port: target.port
|
|
258
|
+
};
|
|
259
|
+
serialized["bindings"] = bindings;
|
|
260
|
+
}
|
|
261
|
+
return serialized;
|
|
219
262
|
}
|
|
220
263
|
//#endregion
|
|
221
|
-
export { CANVAS_VERSION, SchematicError,
|
|
264
|
+
export { CANVAS_VERSION, SchematicError, parseCanvas, serializeCanvas };
|
|
222
265
|
|
|
223
266
|
//# sourceMappingURL=index.js.map
|