@stocksharp/diagram 0.2.2 → 0.3.0
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/README.md +12 -0
- package/dist/esm/canvas-renderer.js +28 -22
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/core/action-registry.js +24 -0
- package/dist/esm/core/action-registry.js.map +1 -1
- package/dist/esm/core/document.js +85 -28
- package/dist/esm/core/document.js.map +1 -1
- package/dist/esm/core/json.js +17 -0
- package/dist/esm/core/json.js.map +1 -0
- package/dist/esm/core/model.js +4 -0
- package/dist/esm/core/model.js.map +1 -1
- package/dist/esm/core/state.js +16 -2
- package/dist/esm/core/state.js.map +1 -1
- package/dist/esm/diagram/catalog.js +17 -5
- package/dist/esm/diagram/catalog.js.map +1 -1
- package/dist/esm/diagram/event-emitter.js +10 -0
- package/dist/esm/diagram/event-emitter.js.map +1 -1
- package/dist/esm/diagram/stocksharp-diagram.js +69 -57
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/diagram/types.js +2 -1
- package/dist/esm/diagram/types.js.map +1 -1
- package/dist/esm/embed.js +129 -39
- package/dist/esm/embed.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/ssdiagram.js +314 -147
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/canvas-renderer.d.ts +3 -5
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/core/action-registry.d.ts +7 -0
- package/dist/types/core/action-registry.d.ts.map +1 -1
- package/dist/types/core/document.d.ts.map +1 -1
- package/dist/types/core/json.d.ts +11 -0
- package/dist/types/core/json.d.ts.map +1 -0
- package/dist/types/core/model.d.ts +10 -1
- package/dist/types/core/model.d.ts.map +1 -1
- package/dist/types/core/state.d.ts +24 -1
- package/dist/types/core/state.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +13 -6
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/catalog.d.ts +2 -1
- package/dist/types/diagram/catalog.d.ts.map +1 -1
- package/dist/types/diagram/event-emitter.d.ts +6 -1
- package/dist/types/diagram/event-emitter.d.ts.map +1 -1
- package/dist/types/diagram/palette.d.ts +1 -1
- package/dist/types/diagram/palette.d.ts.map +1 -1
- package/dist/types/diagram/stocksharp-diagram.d.ts +32 -21
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/diagram/types.d.ts +4 -4
- package/dist/types/diagram/types.d.ts.map +1 -1
- package/dist/types/embed.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/canvas-renderer.ts +26 -24
- package/src/core/action-registry.ts +25 -0
- package/src/core/document.ts +91 -31
- package/src/core/json.ts +16 -0
- package/src/core/model.ts +14 -1
- package/src/core/state.ts +41 -3
- package/src/diagram/api.ts +14 -5
- package/src/diagram/catalog.ts +18 -6
- package/src/diagram/event-emitter.ts +12 -1
- package/src/diagram/palette.ts +1 -1
- package/src/diagram/stocksharp-diagram.ts +91 -75
- package/src/diagram/types.ts +6 -5
- package/src/embed.ts +149 -40
- package/src/index.ts +4 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../src/embed.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGpE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../src/embed.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGpE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AA4BnE,MAAM,WAAW,sBAAsB;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;CAAE;AAC3G,MAAM,WAAW,sBAAsB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CAAE;AACvG,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,KAAK,EAAE,sBAAsB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IACnC,qBAAqB,CAAC,EAAE,CACvB,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,kBAAkB,KACtB,IAAI,CAAC;IACV,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACnD;AAgPD,wBAAsB,YAAY,CACjC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAEpC;AA6JD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAGhE;AA4DD,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAKD,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,mBAAwB,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAwBpC;AAKD,wBAAgB,SAAS,CAAC,IAAI,GAAE,UAAqB,EAAE,OAAO,GAAE,mBAAwB,GAAG,IAAI,CAkB9F"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,11 +4,11 @@ export type { DiagramAction, DiagramActionState, } from './core/action-registry.
|
|
|
4
4
|
export { DiagramCommandHistory } from './core/history.js';
|
|
5
5
|
export type { DiagramCommand, DiagramHistoryListener, DiagramHistoryState, } from './core/history.js';
|
|
6
6
|
export { DIAGRAM_DOCUMENT_VERSION } from './core/model.js';
|
|
7
|
-
export type { DiagramDocument, DiagramDocumentEndpoint, DiagramDocumentInput, DiagramDocumentLink, DiagramDocumentLinkInput, DiagramDocumentNode, DiagramDocumentNodeInput, DiagramDocumentPort, DiagramDocumentPortInput, DiagramDocumentVersion, DiagramParameterSchema, JsonObject, JsonPrimitive, JsonValue, } from './core/model.js';
|
|
8
|
-
export { createEditableDiagramPermissions, createDiagramNodeRuntimeState, createDiagramPortRuntimeState, createDiagramRuntimeState, cloneDiagramRuntimeState, createDiagramSelection, createDiagramViewState, createReadOnlyDiagramPermissions, } from './core/state.js';
|
|
7
|
+
export type { DiagramDocument, DiagramDocumentEndpoint, DiagramDocumentInput, DiagramDocumentLink, DiagramDocumentLinkInput, DiagramDocumentNode, DiagramDocumentNodeInput, DiagramDocumentPort, DiagramDocumentPortInput, DiagramDocumentVersion, DiagramParameterSchema, JsonObject, JsonPrimitive, JsonValue, PortDynamicMode, } from './core/model.js';
|
|
8
|
+
export { createEditableDiagramPermissions, createDiagramNodeRuntimeState, createDiagramPortRuntimeState, createDiagramRuntimeState, cloneDiagramNodeErrors, cloneDiagramRuntimeState, createDiagramSelection, createDiagramViewState, createReadOnlyDiagramPermissions, } from './core/state.js';
|
|
9
9
|
export { DIAGRAM_VIEW_STATE_VERSION, DiagramViewStateError, createDiagramViewStateDocument, parseDiagramViewState, serializeDiagramViewState, } from './core/view-state.js';
|
|
10
10
|
export type { DiagramViewStateDocument } from './core/view-state.js';
|
|
11
|
-
export type { DiagramErrorState, DiagramGlobalErrorKind, DiagramInteractionPermissions, DiagramNodeErrorKind, DiagramNodePortRuntimeState, DiagramNodeRuntimeState, DiagramPortDirection, DiagramPortRuntimeState, DiagramRuntimeState, DiagramSelectedPort, DiagramSelection, DiagramViewState, } from './core/state.js';
|
|
11
|
+
export type { DiagramErrorState, DiagramGlobalErrorKind, DiagramInteractionPermissions, DiagramNodeErrorKind, DiagramNodeErrors, DiagramNodePortRuntimeState, DiagramNodeRuntimeState, DiagramPortDirection, DiagramPortRuntimeState, DiagramRuntimeState, DiagramSelectedPort, DiagramSelection, DiagramSnapshot, DiagramViewState, } from './core/state.js';
|
|
12
12
|
export { StockSharpDiagram, } from './diagram/stocksharp-diagram.js';
|
|
13
13
|
export type { ContextCommand, ContextCommandPayload, ContextCommandState, ContextMenuRequestedPayload, DiagramEvents, DiagramClipboard, DiagramGridSettings, DiagramLoadOptions, DiagramNodeBounds, DiagramOptions, DiagramPoint, DiagramScreenshotOptions, DiagramScreenshotScope, DiagramThemeOptions, DocumentLoadFailedPayload, LinkChangePayload, LinkHoverPayload, LinkRelinkedPayload, LinkSelectedPayload, LinkValidationPayload, LinkValidationReason, LinkValidationResult, LinkValidator, LinkValidatorArgs, LoadFinishedPayload, NodeChangePayload, NodeErrorKind, NodeErrorOptions, NodeHoverPayload, NodeMovedPayload, NodeSelectedPayload, PortHoverPayload, PortClickAction, PortClickedPayload, PortSelectedPayload, } from './diagram/api.js';
|
|
14
14
|
export { StockSharpCatalog } from './diagram/catalog.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,YAAY,EACR,aAAa,EACb,kBAAkB,GACrB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EACR,cAAc,EACd,sBAAsB,EACtB,mBAAmB,GACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,YAAY,EACR,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,UAAU,EACV,aAAa,EACb,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stocksharp/diagram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Browser strategy-diagram editor for StockSharp with typed ports, palette, canvas rendering, persistence, and a TypeScript API.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": {
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"scripts": {
|
|
108
108
|
"build": "node build.mjs",
|
|
109
109
|
"prepack": "npm run build",
|
|
110
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
110
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.demo.json && tsc --noEmit -p tsconfig.test.json",
|
|
111
111
|
"api:check": "node tools/check-public-api.mjs",
|
|
112
112
|
"api:update": "node tools/check-public-api.mjs --update",
|
|
113
113
|
"serve": "node serve.mjs",
|
package/src/canvas-renderer.ts
CHANGED
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import { createDiagramDocument, parseDiagramDocument } from './core/document.js';
|
|
8
8
|
import { DiagramCommandHistory } from './core/history.js';
|
|
9
|
+
import { setJsonKey } from './core/json.js';
|
|
10
|
+
import { toPortDynamicMode } from './core/model.js';
|
|
9
11
|
import type {
|
|
10
12
|
DiagramDocument,
|
|
11
13
|
DiagramParameterSchema,
|
|
12
14
|
JsonObject,
|
|
13
15
|
JsonValue,
|
|
16
|
+
PortDynamicMode,
|
|
14
17
|
} from './core/model.js';
|
|
15
18
|
import type {
|
|
16
19
|
DiagramGlobalErrorKind,
|
|
@@ -40,7 +43,7 @@ export interface PortInit {
|
|
|
40
43
|
maxLinks?: number;
|
|
41
44
|
availableTypes?: string[];
|
|
42
45
|
isDynamic?: boolean;
|
|
43
|
-
dynamicMode?:
|
|
46
|
+
dynamicMode?: PortDynamicMode;
|
|
44
47
|
isSibling?: boolean;
|
|
45
48
|
metadata?: JsonObject;
|
|
46
49
|
}
|
|
@@ -258,7 +261,7 @@ function copyJsonValue(value: JsonValue): JsonValue {
|
|
|
258
261
|
function copyJsonObject(value: JsonObject | undefined): JsonObject {
|
|
259
262
|
if (value === undefined) return {};
|
|
260
263
|
const result: JsonObject = {};
|
|
261
|
-
for (const [key, item] of Object.entries(value)) result
|
|
264
|
+
for (const [key, item] of Object.entries(value)) setJsonKey(result, key, copyJsonValue(item));
|
|
262
265
|
return result;
|
|
263
266
|
}
|
|
264
267
|
|
|
@@ -298,7 +301,7 @@ export class PortModel {
|
|
|
298
301
|
maxLinks: number;
|
|
299
302
|
availableTypes: string[];
|
|
300
303
|
isDynamic: boolean;
|
|
301
|
-
dynamicMode:
|
|
304
|
+
dynamicMode: PortDynamicMode;
|
|
302
305
|
isSibling: boolean;
|
|
303
306
|
metadata: JsonObject;
|
|
304
307
|
// layout cache (world coords), filled at draw time
|
|
@@ -313,7 +316,7 @@ export class PortModel {
|
|
|
313
316
|
this.maxLinks = typeof init.maxLinks === 'number' ? init.maxLinks : 0;
|
|
314
317
|
this.availableTypes = [...(init.availableTypes ?? [])];
|
|
315
318
|
this.isDynamic = init.isDynamic ?? false;
|
|
316
|
-
this.dynamicMode = init.dynamicMode
|
|
319
|
+
this.dynamicMode = toPortDynamicMode(init.dynamicMode);
|
|
317
320
|
this.isSibling = init.isSibling ?? false;
|
|
318
321
|
this.metadata = copyJsonObject(init.metadata);
|
|
319
322
|
}
|
|
@@ -439,10 +442,8 @@ export class LinkModel {
|
|
|
439
442
|
}
|
|
440
443
|
}
|
|
441
444
|
|
|
442
|
-
const HEADER_H = 22;
|
|
443
445
|
const PORT_R = 6;
|
|
444
446
|
const PORT_ROW_H = 20;
|
|
445
|
-
const NODE_PAD = 10;
|
|
446
447
|
const RELINK_HANDLE_PX = 8; // WPF relink adornment is an 8x8 diamond
|
|
447
448
|
const RELINK_DRAG_THRESHOLD_PX = 4;
|
|
448
449
|
const PORT_SQ = 9; // socket square size — sits outside the node
|
|
@@ -530,8 +531,6 @@ export class Diagram {
|
|
|
530
531
|
private dragNode: NodeModel | null = null;
|
|
531
532
|
private dragStart: Array<{ n: NodeModel; x: number; y: number }> = []; // group-drag origin
|
|
532
533
|
private dragAnchor = { wx: 0, wy: 0 };
|
|
533
|
-
private dragDX = 0;
|
|
534
|
-
private dragDY = 0;
|
|
535
534
|
private panning = false;
|
|
536
535
|
private panX = 0;
|
|
537
536
|
private panY = 0;
|
|
@@ -1121,7 +1120,7 @@ export class Diagram {
|
|
|
1121
1120
|
setNodeParamValue(nodeId: string, name: string, value: string | undefined): boolean {
|
|
1122
1121
|
return this.updateNodeState(nodeId, 'set node parameter', (node) => {
|
|
1123
1122
|
if (value === undefined) delete node.paramValues[name];
|
|
1124
|
-
else node.paramValues
|
|
1123
|
+
else setJsonKey(node.paramValues, name, value);
|
|
1125
1124
|
});
|
|
1126
1125
|
}
|
|
1127
1126
|
|
|
@@ -1207,7 +1206,7 @@ export class Diagram {
|
|
|
1207
1206
|
for (const node of this.nodes) {
|
|
1208
1207
|
if (node.loadError.length === 0) continue;
|
|
1209
1208
|
const state = createDiagramNodeRuntimeState();
|
|
1210
|
-
state.
|
|
1209
|
+
state.errors.load = { kind: 'load', message: node.loadError, pulse: ++this.runtimePulse };
|
|
1211
1210
|
this.runtimeState.nodes[node.id] = state;
|
|
1212
1211
|
}
|
|
1213
1212
|
if (Object.keys(this.runtimeState.nodes).length > 0) this.emitRuntimeStateChanged();
|
|
@@ -1418,15 +1417,17 @@ export class Diagram {
|
|
|
1418
1417
|
this.runtimePulse = Math.max(
|
|
1419
1418
|
this.runtimePulse,
|
|
1420
1419
|
this.runtimeState.globalError?.pulse ?? 0,
|
|
1421
|
-
...Object.values(this.runtimeState.nodes)
|
|
1420
|
+
...Object.values(this.runtimeState.nodes)
|
|
1421
|
+
.flatMap((node) => [node.errors.runtime?.pulse ?? 0, node.errors.load?.pulse ?? 0]),
|
|
1422
1422
|
);
|
|
1423
1423
|
for (const node of this.nodes) {
|
|
1424
|
-
const
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
node.
|
|
1428
|
-
|
|
1429
|
-
|
|
1424
|
+
const errors = this.runtimeState.nodes[node.id]?.errors;
|
|
1425
|
+
const previousRuntime = previous.nodes[node.id]?.errors.runtime;
|
|
1426
|
+
const runtimeError = errors?.runtime;
|
|
1427
|
+
node.runtimeError = runtimeError?.message ?? '';
|
|
1428
|
+
node.loadError = errors?.load?.message ?? '';
|
|
1429
|
+
if (runtimeError !== undefined) {
|
|
1430
|
+
if (previousRuntime === undefined || previousRuntime.pulse !== runtimeError.pulse) {
|
|
1430
1431
|
node.errorFlashStart = performance.now();
|
|
1431
1432
|
}
|
|
1432
1433
|
} else {
|
|
@@ -1486,7 +1487,11 @@ export class Diagram {
|
|
|
1486
1487
|
const state = this.getRuntimeState();
|
|
1487
1488
|
const nodeState = state.nodes[id] ?? createDiagramNodeRuntimeState();
|
|
1488
1489
|
state.nodes[id] = nodeState;
|
|
1489
|
-
|
|
1490
|
+
// Only this kind's slot is written, so an error of the other kind that is
|
|
1491
|
+
// already on the node survives.
|
|
1492
|
+
if (message.length === 0) delete nodeState.errors[kind];
|
|
1493
|
+
else if (kind === 'load') nodeState.errors.load = { kind, message, pulse: ++this.runtimePulse };
|
|
1494
|
+
else nodeState.errors.runtime = { kind, message, pulse: ++this.runtimePulse };
|
|
1490
1495
|
this.runtimeState = state;
|
|
1491
1496
|
this.emitRuntimeStateChanged();
|
|
1492
1497
|
return true;
|
|
@@ -1501,12 +1506,9 @@ export class Diagram {
|
|
|
1501
1506
|
if (kind === undefined || kind === 'load') node.loadError = '';
|
|
1502
1507
|
const state = this.getRuntimeState();
|
|
1503
1508
|
const nodeState = state.nodes[id];
|
|
1504
|
-
if (nodeState !== undefined
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
: node.loadError.length > 0
|
|
1508
|
-
? { kind: 'load', message: node.loadError, pulse: ++this.runtimePulse }
|
|
1509
|
-
: null;
|
|
1509
|
+
if (nodeState !== undefined) {
|
|
1510
|
+
if (kind === undefined || kind === 'runtime') delete nodeState.errors.runtime;
|
|
1511
|
+
if (kind === undefined || kind === 'load') delete nodeState.errors.load;
|
|
1510
1512
|
}
|
|
1511
1513
|
this.runtimeState = state;
|
|
1512
1514
|
this.emitRuntimeStateChanged();
|
|
@@ -22,6 +22,31 @@ export class DiagramActionRegistry<TId extends string, TContext> {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Registers one action per id from a table keyed by TId, in key order.
|
|
27
|
+
* Because the table is a total Record, adding a member to TId without adding
|
|
28
|
+
* its action stops compiling instead of producing a command that silently
|
|
29
|
+
* does nothing.
|
|
30
|
+
*/
|
|
31
|
+
registerAll(actions: Record<TId, Omit<DiagramAction<TId, TContext>, 'id'>>): () => void {
|
|
32
|
+
// Object.keys is typed string[] because a value can carry keys beyond the
|
|
33
|
+
// ones its type names. This parameter is a total Record over TId, so its
|
|
34
|
+
// keys are exactly TId.
|
|
35
|
+
const ids = Object.keys(actions) as TId[];
|
|
36
|
+
// Delegating rather than spreading: a spread would copy own fields and
|
|
37
|
+
// drop the prototype, so a class-based action would register cleanly and
|
|
38
|
+
// then throw on its first call.
|
|
39
|
+
const disposers = ids.map((id) => {
|
|
40
|
+
const action = actions[id];
|
|
41
|
+
return this.register({
|
|
42
|
+
id,
|
|
43
|
+
canExecute: (context) => action.canExecute(context),
|
|
44
|
+
execute: (context) => action.execute(context),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return () => disposers.forEach((dispose) => dispose());
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
get(id: TId): DiagramAction<TId, TContext> | null {
|
|
26
51
|
return this.actions.get(id) ?? null;
|
|
27
52
|
}
|
package/src/core/document.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { setJsonKey } from './json.js';
|
|
2
|
+
import { toPortDynamicMode } from './model.js';
|
|
1
3
|
import {
|
|
2
4
|
DIAGRAM_DOCUMENT_VERSION,
|
|
3
5
|
type DiagramDocument,
|
|
4
6
|
type DiagramDocumentEndpoint,
|
|
5
7
|
type DiagramDocumentInput,
|
|
6
8
|
type DiagramDocumentLink,
|
|
7
|
-
type DiagramDocumentLinkInput,
|
|
8
9
|
type DiagramDocumentNode,
|
|
9
|
-
type DiagramDocumentNodeInput,
|
|
10
10
|
type DiagramDocumentPort,
|
|
11
|
-
type DiagramDocumentPortInput,
|
|
12
11
|
type DiagramParameterSchema,
|
|
12
|
+
type PortDynamicMode,
|
|
13
13
|
type JsonObject,
|
|
14
14
|
type JsonValue,
|
|
15
15
|
} from './model.js';
|
|
@@ -40,13 +40,18 @@ export class DiagramDocumentError extends Error {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function createDiagramDocument(input: DiagramDocumentInput = {}): DiagramDocument {
|
|
43
|
-
|
|
43
|
+
// DiagramDocumentInput is a structural interface, so its shape is a claim by
|
|
44
|
+
// the caller, not a guarantee. Elements are checked the same way parsed ones
|
|
45
|
+
// are, otherwise a malformed one surfaces as a TypeError naming no path.
|
|
46
|
+
const rawNodes = mapElements(input.nodes, '$.nodes');
|
|
47
|
+
const rawLinks = mapElements(input.links, '$.links');
|
|
48
|
+
const nodes = rawNodes.map((node, index) => normalizeNode(node, `$.nodes[${index}]`));
|
|
44
49
|
const usedLinkIds = new Set<string>();
|
|
45
50
|
|
|
46
|
-
for (let index = 0; index <
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
requireIdentifier(
|
|
51
|
+
for (let index = 0; index < rawLinks.length; index++) {
|
|
52
|
+
const raw = rawLinks[index].id;
|
|
53
|
+
if (raw === undefined) continue;
|
|
54
|
+
const id = requireIdentifier(raw, `$.links[${index}].id`);
|
|
50
55
|
if (usedLinkIds.has(id)) {
|
|
51
56
|
throw new DiagramDocumentError(`duplicate link id "${id}"`, `$.links[${index}].id`);
|
|
52
57
|
}
|
|
@@ -54,12 +59,14 @@ export function createDiagramDocument(input: DiagramDocumentInput = {}): Diagram
|
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
let sequence = 1;
|
|
57
|
-
const links =
|
|
62
|
+
const links = rawLinks.map((link, index) => {
|
|
58
63
|
let id = link.id;
|
|
59
64
|
if (id === undefined) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
usedLinkIds.
|
|
65
|
+
let generated: string;
|
|
66
|
+
do generated = `link_${sequence++}`;
|
|
67
|
+
while (usedLinkIds.has(generated));
|
|
68
|
+
usedLinkIds.add(generated);
|
|
69
|
+
id = generated;
|
|
63
70
|
}
|
|
64
71
|
return normalizeLink({ ...link, id }, `$.links[${index}]`);
|
|
65
72
|
});
|
|
@@ -92,7 +99,7 @@ export function parseDiagramDocument(source: string | unknown): DiagramDocument
|
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
|
|
95
|
-
const root =
|
|
102
|
+
const root = requireStructure(value, '$');
|
|
96
103
|
const version = requireNumber(root.version, '$.version');
|
|
97
104
|
if (version !== DIAGRAM_DOCUMENT_VERSION) {
|
|
98
105
|
throw new DiagramDocumentError(`unsupported document version ${version}`, '$.version');
|
|
@@ -107,7 +114,8 @@ export function parseDiagramDocument(source: string | unknown): DiagramDocument
|
|
|
107
114
|
return document;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
function normalizeNode(
|
|
117
|
+
function normalizeNode(value: unknown, path: string): DiagramDocumentNode {
|
|
118
|
+
const input = requireStructure(value, path);
|
|
111
119
|
const id = requireIdentifier(input.id, `${path}.id`);
|
|
112
120
|
return {
|
|
113
121
|
id,
|
|
@@ -122,30 +130,36 @@ function normalizeNode(input: DiagramDocumentNodeInput, path: string): DiagramDo
|
|
|
122
130
|
icon: requireString(input.icon ?? '', `${path}.icon`),
|
|
123
131
|
message: requireString(input.message ?? '', `${path}.message`),
|
|
124
132
|
openAction: requireString(input.openAction ?? '', `${path}.openAction`),
|
|
125
|
-
inPorts: (input.inPorts
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
inPorts: mapElements(input.inPorts, `${path}.inPorts`)
|
|
134
|
+
.map((port, index) => normalizePort(port, `${path}.inPorts[${index}]`)),
|
|
135
|
+
outPorts: mapElements(input.outPorts, `${path}.outPorts`)
|
|
136
|
+
.map((port, index) => normalizePort(port, `${path}.outPorts[${index}]`)),
|
|
137
|
+
parameters: mapElements(input.parameters, `${path}.parameters`)
|
|
138
|
+
.map((parameter, index) => normalizeParameter(parameter, `${path}.parameters[${index}]`)),
|
|
128
139
|
paramValues: cloneStringRecord(input.paramValues ?? {}, `${path}.paramValues`),
|
|
129
140
|
metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`),
|
|
130
141
|
};
|
|
131
142
|
}
|
|
132
143
|
|
|
133
|
-
function normalizePort(
|
|
144
|
+
function normalizePort(value: unknown, path: string): DiagramDocumentPort {
|
|
145
|
+
const input = requireStructure(value, path);
|
|
134
146
|
return {
|
|
135
147
|
id: requireIdentifier(input.id, `${path}.id`),
|
|
136
148
|
name: requireString(input.name, `${path}.name`),
|
|
137
149
|
description: requireString(input.description ?? '', `${path}.description`),
|
|
138
150
|
type: requireString(input.type ?? '', `${path}.type`),
|
|
139
151
|
maxLinks: requireNonNegativeInteger(input.maxLinks ?? 0, `${path}.maxLinks`),
|
|
140
|
-
availableTypes: (input.availableTypes ?? []
|
|
152
|
+
availableTypes: requireArray(input.availableTypes ?? [], `${path}.availableTypes`)
|
|
153
|
+
.map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
|
|
141
154
|
isDynamic: requireBoolean(input.isDynamic ?? false, `${path}.isDynamic`),
|
|
142
|
-
dynamicMode:
|
|
155
|
+
dynamicMode: requirePortDynamicMode(input.dynamicMode ?? '', `${path}.dynamicMode`),
|
|
143
156
|
isSibling: requireBoolean(input.isSibling ?? false, `${path}.isSibling`),
|
|
144
157
|
metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`),
|
|
145
158
|
};
|
|
146
159
|
}
|
|
147
160
|
|
|
148
|
-
function normalizeParameter(
|
|
161
|
+
function normalizeParameter(value: unknown, path: string): DiagramParameterSchema {
|
|
162
|
+
const input = requireStructure(value, path);
|
|
149
163
|
return {
|
|
150
164
|
name: requireIdentifier(input.name, `${path}.name`),
|
|
151
165
|
displayName: requireString(input.displayName, `${path}.displayName`),
|
|
@@ -162,7 +176,8 @@ function normalizeParameter(input: DiagramParameterSchema, path: string): Diagra
|
|
|
162
176
|
};
|
|
163
177
|
}
|
|
164
178
|
|
|
165
|
-
function normalizeLink(
|
|
179
|
+
function normalizeLink(value: unknown, path: string): DiagramDocumentLink {
|
|
180
|
+
const input = requireStructure(value, path);
|
|
166
181
|
return {
|
|
167
182
|
id: requireIdentifier(input.id, `${path}.id`),
|
|
168
183
|
from: normalizeEndpoint(input.from, `${path}.from`),
|
|
@@ -171,7 +186,8 @@ function normalizeLink(input: DiagramDocumentLinkInput & { id: string }, path: s
|
|
|
171
186
|
};
|
|
172
187
|
}
|
|
173
188
|
|
|
174
|
-
function normalizeEndpoint(
|
|
189
|
+
function normalizeEndpoint(value: unknown, path: string): DiagramDocumentEndpoint {
|
|
190
|
+
const input = requireStructure(value, path);
|
|
175
191
|
return {
|
|
176
192
|
nodeId: requireIdentifier(input.nodeId, `${path}.nodeId`),
|
|
177
193
|
portId: requireIdentifier(input.portId, `${path}.portId`),
|
|
@@ -179,7 +195,7 @@ function normalizeEndpoint(input: DiagramDocumentEndpoint, path: string): Diagra
|
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
function parseNode(value: unknown, path: string): DiagramDocumentNode {
|
|
182
|
-
const node =
|
|
198
|
+
const node = requireStructure(value, path);
|
|
183
199
|
return normalizeNode({
|
|
184
200
|
id: requireString(node.id, `${path}.id`),
|
|
185
201
|
typeId: requireString(node.typeId, `${path}.typeId`),
|
|
@@ -202,7 +218,7 @@ function parseNode(value: unknown, path: string): DiagramDocumentNode {
|
|
|
202
218
|
}
|
|
203
219
|
|
|
204
220
|
function parsePort(value: unknown, path: string): DiagramDocumentPort {
|
|
205
|
-
const port =
|
|
221
|
+
const port = requireStructure(value, path);
|
|
206
222
|
return normalizePort({
|
|
207
223
|
id: requireString(port.id, `${path}.id`),
|
|
208
224
|
name: requireString(port.name, `${path}.name`),
|
|
@@ -211,14 +227,14 @@ function parsePort(value: unknown, path: string): DiagramDocumentPort {
|
|
|
211
227
|
maxLinks: requireNumber(port.maxLinks, `${path}.maxLinks`),
|
|
212
228
|
availableTypes: requireArray(port.availableTypes, `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
|
|
213
229
|
isDynamic: requireBoolean(port.isDynamic, `${path}.isDynamic`),
|
|
214
|
-
dynamicMode:
|
|
230
|
+
dynamicMode: requirePortDynamicMode(port.dynamicMode, `${path}.dynamicMode`),
|
|
215
231
|
isSibling: requireBoolean(port.isSibling, `${path}.isSibling`),
|
|
216
232
|
metadata: cloneJsonObject(port.metadata, `${path}.metadata`),
|
|
217
233
|
}, path);
|
|
218
234
|
}
|
|
219
235
|
|
|
220
236
|
function parseParameter(value: unknown, path: string): DiagramParameterSchema {
|
|
221
|
-
const parameter =
|
|
237
|
+
const parameter = requireStructure(value, path);
|
|
222
238
|
return normalizeParameter({
|
|
223
239
|
name: requireString(parameter.name, `${path}.name`),
|
|
224
240
|
displayName: requireString(parameter.displayName, `${path}.displayName`),
|
|
@@ -236,7 +252,7 @@ function parseParameter(value: unknown, path: string): DiagramParameterSchema {
|
|
|
236
252
|
}
|
|
237
253
|
|
|
238
254
|
function parseLink(value: unknown, path: string): DiagramDocumentLink {
|
|
239
|
-
const link =
|
|
255
|
+
const link = requireStructure(value, path);
|
|
240
256
|
return normalizeLink({
|
|
241
257
|
id: requireString(link.id, `${path}.id`),
|
|
242
258
|
from: parseEndpoint(link.from, `${path}.from`),
|
|
@@ -246,7 +262,7 @@ function parseLink(value: unknown, path: string): DiagramDocumentLink {
|
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
function parseEndpoint(value: unknown, path: string): DiagramDocumentEndpoint {
|
|
249
|
-
const endpoint =
|
|
265
|
+
const endpoint = requireStructure(value, path);
|
|
250
266
|
return {
|
|
251
267
|
nodeId: requireIdentifier(endpoint.nodeId, `${path}.nodeId`),
|
|
252
268
|
portId: requireIdentifier(endpoint.portId, `${path}.portId`),
|
|
@@ -298,7 +314,7 @@ function validateUniquePorts(ports: readonly DiagramDocumentPort[], path: string
|
|
|
298
314
|
function cloneStringRecord(value: unknown, path: string): Record<string, string> {
|
|
299
315
|
const object = requireObject(value, path);
|
|
300
316
|
const result: Record<string, string> = {};
|
|
301
|
-
for (const [key, item] of Object.entries(object)) result
|
|
317
|
+
for (const [key, item] of Object.entries(object)) setJsonKey(result, key, requireString(item, `${path}.${key}`));
|
|
302
318
|
return result;
|
|
303
319
|
}
|
|
304
320
|
|
|
@@ -309,7 +325,7 @@ function cloneJsonObject(value: unknown, path: string, ancestors = new WeakSet<o
|
|
|
309
325
|
const result: JsonObject = {};
|
|
310
326
|
try {
|
|
311
327
|
for (const [key, item] of Object.entries(object)) {
|
|
312
|
-
result
|
|
328
|
+
setJsonKey(result, key, cloneJsonValue(item, `${path}.${key}`, ancestors));
|
|
313
329
|
}
|
|
314
330
|
} finally {
|
|
315
331
|
ancestors.delete(object);
|
|
@@ -333,6 +349,23 @@ function cloneJsonValue(value: unknown, path: string, ancestors: WeakSet<object>
|
|
|
333
349
|
throw new DiagramDocumentError('expected a JSON value', path);
|
|
334
350
|
}
|
|
335
351
|
|
|
352
|
+
/**
|
|
353
|
+
* A container whose fields are read individually. Any object will do, including
|
|
354
|
+
* a class instance: hosts build schemes out of DiagramNode and Port, not object
|
|
355
|
+
* literals, and every field is validated on its own below.
|
|
356
|
+
*/
|
|
357
|
+
function requireStructure(value: unknown, path: string): Record<string, unknown> {
|
|
358
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
359
|
+
throw new DiagramDocumentError('expected an object', path);
|
|
360
|
+
}
|
|
361
|
+
return value as Record<string, unknown>;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* A value that has to survive JSON round-tripping unchanged. Stricter than
|
|
366
|
+
* requireStructure on purpose: a Date or any other class instance would
|
|
367
|
+
* serialize to something the parser could not read back.
|
|
368
|
+
*/
|
|
336
369
|
function requireObject(value: unknown, path: string): Record<string, unknown> {
|
|
337
370
|
if (!isObject(value)) throw new DiagramDocumentError('expected an object', path);
|
|
338
371
|
return value;
|
|
@@ -344,6 +377,21 @@ function isObject(value: unknown): value is Record<string, unknown> {
|
|
|
344
377
|
return prototype === Object.prototype || prototype === null;
|
|
345
378
|
}
|
|
346
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Reads an optional array of objects, rejecting a non-array, a hole or a
|
|
382
|
+
* non-object element. null counts as absent, like the `?? []` every other
|
|
383
|
+
* optional field is read through.
|
|
384
|
+
*/
|
|
385
|
+
function mapElements(value: unknown, path: string): Record<string, unknown>[] {
|
|
386
|
+
if (value === undefined || value === null) return [];
|
|
387
|
+
// Array.from rather than map: map skips holes, carrying an undefined element
|
|
388
|
+
// into the result instead of reporting it.
|
|
389
|
+
return Array.from(
|
|
390
|
+
requireArray(value, path),
|
|
391
|
+
(element, index) => requireStructure(element, `${path}[${index}]`),
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
347
395
|
function requireArray(value: unknown, path: string): unknown[] {
|
|
348
396
|
if (!Array.isArray(value)) throw new DiagramDocumentError('expected an array', path);
|
|
349
397
|
return value;
|
|
@@ -354,6 +402,18 @@ function requireString(value: unknown, path: string): string {
|
|
|
354
402
|
return value;
|
|
355
403
|
}
|
|
356
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Coerces rather than rejects. The set is closed in the types, which is where a
|
|
407
|
+
* typo gets caught, but a persisted scheme can carry anything a JavaScript host
|
|
408
|
+
* wrote before that -- and refusing to load it would brick the whole document
|
|
409
|
+
* over a field whose every value except 'onConnect' behaves identically. The
|
|
410
|
+
* type must still be a string, so a number or an object is a real error.
|
|
411
|
+
*/
|
|
412
|
+
function requirePortDynamicMode(value: unknown, path: string): PortDynamicMode {
|
|
413
|
+
const mode = requireString(value, path);
|
|
414
|
+
return toPortDynamicMode(mode);
|
|
415
|
+
}
|
|
416
|
+
|
|
357
417
|
function requireIdentifier(value: unknown, path: string): string {
|
|
358
418
|
const id = requireString(value, path);
|
|
359
419
|
if (id.trim().length === 0) throw new DiagramDocumentError('identifier cannot be empty', path);
|
package/src/core/json.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stores a key that JSON can legitimately carry but plain assignment cannot hold.
|
|
3
|
+
*
|
|
4
|
+
* `JSON.parse` produces `__proto__` as an ordinary own key, yet writing it back
|
|
5
|
+
* with `target[key] = value` hits the inherited `__proto__` setter: the value is
|
|
6
|
+
* dropped and the object's prototype is replaced instead. A document parsed that
|
|
7
|
+
* way loses data and stops satisfying the parser's own "plain object" guard, so
|
|
8
|
+
* anything the parser accepted could no longer be serialized or cloned.
|
|
9
|
+
*/
|
|
10
|
+
export function setJsonKey<T>(target: Record<string, T>, key: string, value: T): void {
|
|
11
|
+
if (key === '__proto__') {
|
|
12
|
+
Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
target[key] = value;
|
|
16
|
+
}
|
package/src/core/model.ts
CHANGED
|
@@ -20,6 +20,19 @@ export interface DiagramParameterSchema {
|
|
|
20
20
|
editorType: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* How a dynamic port grows. Only 'onConnect' makes the editor add a typed
|
|
25
|
+
* sibling when a link lands on the port; '' and 'manual' leave it to the host.
|
|
26
|
+
* The renderer compares this with ===, so a value outside the set silently
|
|
27
|
+
* selects the do-nothing branch -- hence a union rather than a string.
|
|
28
|
+
*/
|
|
29
|
+
export type PortDynamicMode = '' | 'manual' | 'onConnect';
|
|
30
|
+
|
|
31
|
+
/** Narrows an untyped value to the closed set, defaulting to the inert mode. */
|
|
32
|
+
export function toPortDynamicMode(value: unknown): PortDynamicMode {
|
|
33
|
+
return value === 'manual' || value === 'onConnect' ? value : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
export interface DiagramDocumentPort {
|
|
24
37
|
id: string;
|
|
25
38
|
name: string;
|
|
@@ -28,7 +41,7 @@ export interface DiagramDocumentPort {
|
|
|
28
41
|
maxLinks: number;
|
|
29
42
|
availableTypes: string[];
|
|
30
43
|
isDynamic: boolean;
|
|
31
|
-
dynamicMode:
|
|
44
|
+
dynamicMode: PortDynamicMode;
|
|
32
45
|
isSibling: boolean;
|
|
33
46
|
metadata: JsonObject;
|
|
34
47
|
}
|
package/src/core/state.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively read-only view of a state object. The snapshot accessors hand back
|
|
3
|
+
* detached copies, so writing to one is silently discarded -- this makes that a
|
|
4
|
+
* compile error instead. The setters accept it, so a snapshot still round-trips.
|
|
5
|
+
*/
|
|
6
|
+
export type DiagramSnapshot<T> = {
|
|
7
|
+
readonly [K in keyof T]: DiagramSnapshotValue<T[K]>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Distributes over unions, so an optional or nullable object property keeps
|
|
11
|
+
// being descended into instead of falling through as its writable self.
|
|
12
|
+
type DiagramSnapshotValue<T> = T extends readonly (infer TItem)[]
|
|
13
|
+
? readonly DiagramSnapshotValue<TItem>[]
|
|
14
|
+
: T extends object
|
|
15
|
+
? DiagramSnapshot<T>
|
|
16
|
+
: T;
|
|
17
|
+
|
|
1
18
|
export type DiagramPortDirection = 'in' | 'out';
|
|
2
19
|
export type DiagramNodeErrorKind = 'runtime' | 'load';
|
|
3
20
|
export type DiagramGlobalErrorKind = 'invalid' | 'load' | 'locked' | 'encrypted';
|
|
@@ -23,9 +40,18 @@ export interface DiagramNodePortRuntimeState {
|
|
|
23
40
|
out: Record<string, DiagramPortRuntimeState>;
|
|
24
41
|
}
|
|
25
42
|
|
|
43
|
+
/**
|
|
44
|
+
* At most one error per kind. A node can carry a load error and a runtime error
|
|
45
|
+
* at the same time -- the tooltip shows both -- so a single slot could not hold
|
|
46
|
+
* what the editor already models, and every state write dropped one of them.
|
|
47
|
+
*/
|
|
48
|
+
export type DiagramNodeErrors = {
|
|
49
|
+
[TKind in DiagramNodeErrorKind]?: DiagramErrorState<TKind>;
|
|
50
|
+
};
|
|
51
|
+
|
|
26
52
|
export interface DiagramNodeRuntimeState {
|
|
27
53
|
active: boolean;
|
|
28
|
-
|
|
54
|
+
errors: DiagramNodeErrors;
|
|
29
55
|
ports: DiagramNodePortRuntimeState;
|
|
30
56
|
}
|
|
31
57
|
|
|
@@ -77,7 +103,7 @@ export function cloneDiagramRuntimeState(state: DiagramRuntimeState): DiagramRun
|
|
|
77
103
|
globalError: state.globalError === null ? null : { ...state.globalError },
|
|
78
104
|
nodes: Object.fromEntries(Object.entries(state.nodes).map(([nodeId, node]) => [nodeId, {
|
|
79
105
|
active: node.active,
|
|
80
|
-
|
|
106
|
+
errors: cloneDiagramNodeErrors(node.errors),
|
|
81
107
|
ports: {
|
|
82
108
|
in: Object.fromEntries(Object.entries(node.ports.in)
|
|
83
109
|
.map(([portId, port]) => [portId, { ...port }])),
|
|
@@ -142,7 +168,19 @@ export function createReadOnlyDiagramPermissions(): DiagramInteractionPermission
|
|
|
142
168
|
export function createDiagramNodeRuntimeState(): DiagramNodeRuntimeState {
|
|
143
169
|
return {
|
|
144
170
|
active: false,
|
|
145
|
-
|
|
171
|
+
errors: {},
|
|
146
172
|
ports: { in: {}, out: {} },
|
|
147
173
|
};
|
|
148
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Tolerates a missing map: hosts persist runtime snapshots and build them by
|
|
178
|
+
* hand, so a state written before this field existed still has to be readable.
|
|
179
|
+
*/
|
|
180
|
+
export function cloneDiagramNodeErrors(errors: DiagramNodeErrors | undefined | null): DiagramNodeErrors {
|
|
181
|
+
const result: DiagramNodeErrors = {};
|
|
182
|
+
if (errors === undefined || errors === null) return result;
|
|
183
|
+
if (errors.runtime !== undefined) result.runtime = { ...errors.runtime };
|
|
184
|
+
if (errors.load !== undefined) result.load = { ...errors.load };
|
|
185
|
+
return result;
|
|
186
|
+
}
|