@stocksharp/diagram 0.2.3 → 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/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 +58 -56
- 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 +121 -38
- 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 +297 -145
- 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 +6 -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 +29 -22
- 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 +6 -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 +79 -76
- package/src/diagram/types.ts +6 -5
- package/src/embed.ts +140 -39
- package/src/index.ts +4 -0
package/src/embed.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { StockSharpDiagram } from './diagram/stocksharp-diagram.js';
|
|
|
11
11
|
import { StockSharpCatalog } from './diagram/catalog.js';
|
|
12
12
|
import { DiagramNode, Link, Node, Port, PortType } from './diagram/types.js';
|
|
13
13
|
import type { FullscreenRequestedPayload } from './diagram/api.js';
|
|
14
|
+
import { toPortDynamicMode } from './core/model.js';
|
|
15
|
+
import type { PortDynamicMode } from './core/model.js';
|
|
14
16
|
|
|
15
17
|
interface PalettePort {
|
|
16
18
|
key: string;
|
|
@@ -19,7 +21,7 @@ interface PalettePort {
|
|
|
19
21
|
maxLinks?: number;
|
|
20
22
|
availableTypes?: string[];
|
|
21
23
|
isDynamic?: boolean;
|
|
22
|
-
dynamicMode?:
|
|
24
|
+
dynamicMode?: PortDynamicMode;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
interface PaletteElement {
|
|
@@ -166,59 +168,136 @@ function toDiagramNodes(
|
|
|
166
168
|
// Walk the persisted Content.Value.Scheme.Model.{Nodes,Links} structure into
|
|
167
169
|
// the thin scheme. Defensive at every field so a partially
|
|
168
170
|
// broken document yields as many nodes as are readable; a wholly unusable one yields an empty scheme.
|
|
171
|
+
// Every reader below narrows rather than asserts: the input is arbitrary JSON
|
|
172
|
+
// from a URL or an inline <script>, so a type assertion here would only be a
|
|
173
|
+
// promise the data never made. Anything unreadable is skipped, never thrown on.
|
|
174
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
175
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
179
|
+
return isRecord(value) ? value : undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function asArray(value: unknown): unknown[] {
|
|
183
|
+
return Array.isArray(value) ? value : [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function asString(value: unknown): string {
|
|
187
|
+
return typeof value === 'string' ? value : '';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function asFiniteNumber(value: unknown): number {
|
|
191
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
169
194
|
function nodeName(node: Record<string, unknown>): string {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
return nameVal;
|
|
176
|
-
if (typeof node?.Figure === 'string' && (node.Figure as string).length > 0)
|
|
177
|
-
return node.Figure as string;
|
|
178
|
-
return typeof node?.Key === 'string' ? node.Key as string : '';
|
|
195
|
+
const named = asRecord(asRecord(asRecord(node.Settings)?.Parameters)?.Name)?.Value;
|
|
196
|
+
if (typeof named === 'string' && named.length > 0)
|
|
197
|
+
return named;
|
|
198
|
+
const figure = asString(node.Figure);
|
|
199
|
+
return figure.length > 0 ? figure : asString(node.Key);
|
|
179
200
|
}
|
|
180
201
|
|
|
181
202
|
function parseRawScheme(raw: unknown): DiagramEmbedScheme {
|
|
182
203
|
const nodes: DiagramEmbedSchemeNode[] = [];
|
|
183
204
|
const links: DiagramEmbedSchemeLink[] = [];
|
|
184
205
|
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
const scheme = value?.Scheme as Record<string, unknown> | undefined;
|
|
189
|
-
const model = scheme?.Model as Record<string, unknown> | undefined;
|
|
190
|
-
if (!model)
|
|
206
|
+
const model = asRecord(asRecord(asRecord(asRecord(raw)?.Content)?.Value)?.Scheme)?.Model;
|
|
207
|
+
const fields = asRecord(model);
|
|
208
|
+
if (fields === undefined)
|
|
191
209
|
return { nodes, links };
|
|
192
210
|
|
|
193
|
-
for (const
|
|
194
|
-
const
|
|
211
|
+
for (const entry of asArray(fields.Nodes)) {
|
|
212
|
+
const node = asRecord(entry);
|
|
213
|
+
if (node === undefined)
|
|
214
|
+
continue;
|
|
215
|
+
const key = node.Key;
|
|
195
216
|
if (typeof key !== 'string' || key.length === 0)
|
|
196
217
|
continue;
|
|
197
218
|
nodes.push({
|
|
198
219
|
id: key,
|
|
199
|
-
typeId:
|
|
200
|
-
name: nodeName(
|
|
201
|
-
x:
|
|
202
|
-
y:
|
|
220
|
+
typeId: asString(node.TypeId),
|
|
221
|
+
name: nodeName(node),
|
|
222
|
+
x: asFiniteNumber(node.X),
|
|
223
|
+
y: asFiniteNumber(node.Y),
|
|
203
224
|
});
|
|
204
225
|
}
|
|
205
226
|
|
|
206
|
-
for (const
|
|
207
|
-
const
|
|
208
|
-
|
|
227
|
+
for (const entry of asArray(fields.Links)) {
|
|
228
|
+
const link = asRecord(entry);
|
|
229
|
+
if (link === undefined)
|
|
230
|
+
continue;
|
|
231
|
+
const from = link.From;
|
|
232
|
+
const to = link.To;
|
|
209
233
|
if (typeof from !== 'string' || typeof to !== 'string')
|
|
210
234
|
continue;
|
|
211
235
|
links.push({
|
|
212
236
|
from,
|
|
213
|
-
fromPort:
|
|
237
|
+
fromPort: asString(link.FromPort),
|
|
214
238
|
to,
|
|
215
|
-
toPort:
|
|
239
|
+
toPort: asString(link.ToPort),
|
|
216
240
|
});
|
|
217
241
|
}
|
|
218
242
|
|
|
219
243
|
return { nodes, links };
|
|
220
244
|
}
|
|
221
245
|
|
|
246
|
+
function parsePalettePorts(value: unknown): PalettePort[] {
|
|
247
|
+
const ports: PalettePort[] = [];
|
|
248
|
+
for (const entry of asArray(value)) {
|
|
249
|
+
const port = asRecord(entry);
|
|
250
|
+
if (port === undefined)
|
|
251
|
+
continue;
|
|
252
|
+
const key = asString(port.key);
|
|
253
|
+
if (key.length === 0)
|
|
254
|
+
continue;
|
|
255
|
+
ports.push({
|
|
256
|
+
key,
|
|
257
|
+
name: asString(port.name),
|
|
258
|
+
type: asString(port.type),
|
|
259
|
+
maxLinks: asFiniteNumber(port.maxLinks),
|
|
260
|
+
availableTypes: asArray(port.availableTypes).filter((item): item is string => typeof item === 'string'),
|
|
261
|
+
isDynamic: port.isDynamic === true,
|
|
262
|
+
dynamicMode: toPortDynamicMode(port.dynamicMode),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return ports;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function parsePalette(value: unknown): Palette {
|
|
269
|
+
const socketTypes: Palette['socketTypes'] = [];
|
|
270
|
+
const elements: PaletteElement[] = [];
|
|
271
|
+
const root = asRecord(value);
|
|
272
|
+
if (root === undefined)
|
|
273
|
+
return { socketTypes, elements };
|
|
274
|
+
|
|
275
|
+
for (const entry of asArray(root.socketTypes)) {
|
|
276
|
+
const socket = asRecord(entry);
|
|
277
|
+
const name = asString(socket?.name);
|
|
278
|
+
if (name.length === 0)
|
|
279
|
+
continue;
|
|
280
|
+
socketTypes.push({ name, color: asString(socket?.color) });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const entry of asArray(root.elements)) {
|
|
284
|
+
const element = asRecord(entry);
|
|
285
|
+
const typeId = asString(element?.typeId);
|
|
286
|
+
if (element === undefined || typeId.length === 0)
|
|
287
|
+
continue;
|
|
288
|
+
elements.push({
|
|
289
|
+
typeId,
|
|
290
|
+
name: asString(element.name),
|
|
291
|
+
groupName: asString(element.groupName),
|
|
292
|
+
icon: asString(element.icon),
|
|
293
|
+
inPorts: parsePalettePorts(element.inPorts),
|
|
294
|
+
outPorts: parsePalettePorts(element.outPorts),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return { socketTypes, elements };
|
|
299
|
+
}
|
|
300
|
+
|
|
222
301
|
export async function renderScheme(
|
|
223
302
|
div: HTMLElement,
|
|
224
303
|
paletteUrl: string,
|
|
@@ -237,7 +316,12 @@ async function renderSchemeAtRevision(
|
|
|
237
316
|
): Promise<DiagramEmbedHandle | null> {
|
|
238
317
|
if (renderRevisions.get(div) !== revision)
|
|
239
318
|
return null;
|
|
240
|
-
|
|
319
|
+
// Same treatment the schema fetch gets: a non-2xx answer is a failure, not a
|
|
320
|
+
// body to parse. Callers that degrade already catch it.
|
|
321
|
+
const response = await fetch(paletteUrl);
|
|
322
|
+
if (!response.ok)
|
|
323
|
+
throw new Error(`Palette request to ${paletteUrl} failed with status ${response.status}.`);
|
|
324
|
+
const palette = parsePalette(await response.json());
|
|
241
325
|
if (renderRevisions.get(div) !== revision)
|
|
242
326
|
return null;
|
|
243
327
|
const catalog = buildCatalog(palette);
|
|
@@ -412,6 +496,25 @@ function stopDisconnectedHostObserverWhenIdle(): void {
|
|
|
412
496
|
disconnectedHostObserver = null;
|
|
413
497
|
}
|
|
414
498
|
|
|
499
|
+
interface EmbedErrorTexts {
|
|
500
|
+
load: string;
|
|
501
|
+
empty: string;
|
|
502
|
+
draw: string;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// The host overrides the inline notes with data-diagram-errors="load|empty|draw".
|
|
506
|
+
// Each slot falls back when it is empty rather than when it is absent: split on a
|
|
507
|
+
// missing attribute yields [''], one present empty string, so a destructuring
|
|
508
|
+
// default would never fire and the note would render blank.
|
|
509
|
+
function errorTexts(div: HTMLElement): EmbedErrorTexts {
|
|
510
|
+
const parts = (div.dataset.diagramErrors ?? '').split('|');
|
|
511
|
+
return {
|
|
512
|
+
load: parts[0] || 'Diagram source could not be loaded.',
|
|
513
|
+
empty: parts[1] || 'Diagram is empty or malformed.',
|
|
514
|
+
draw: parts[2] || 'Diagram could not be rendered.',
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
415
518
|
function note(div: HTMLElement, message: string, revision: number): void {
|
|
416
519
|
if (renderRevisions.get(div) !== revision) return;
|
|
417
520
|
disposeActiveRender(div);
|
|
@@ -429,30 +532,29 @@ export async function renderFromSource(
|
|
|
429
532
|
options: DiagramEmbedOptions = {},
|
|
430
533
|
): Promise<DiagramEmbedHandle | null> {
|
|
431
534
|
const revision = beginRender(div);
|
|
432
|
-
const errors = (div
|
|
433
|
-
const [errLoad = 'Diagram source could not be loaded.', errEmpty = 'Diagram is empty or malformed.', errDraw = 'Diagram could not be rendered.'] = errors;
|
|
535
|
+
const errors = errorTexts(div);
|
|
434
536
|
|
|
435
537
|
let raw: unknown;
|
|
436
538
|
try {
|
|
437
539
|
const resp = await fetch(srcUrl);
|
|
438
|
-
if (!resp.ok) { note(div,
|
|
540
|
+
if (!resp.ok) { note(div, errors.load, revision); return null; }
|
|
439
541
|
const text = (await resp.text()).replace(/^/, '');
|
|
440
542
|
raw = JSON.parse(text);
|
|
441
543
|
} catch {
|
|
442
|
-
note(div,
|
|
544
|
+
note(div, errors.load, revision);
|
|
443
545
|
return null;
|
|
444
546
|
}
|
|
445
547
|
|
|
446
548
|
const scheme = parseRawScheme(raw);
|
|
447
549
|
if (scheme.nodes.length === 0) {
|
|
448
|
-
note(div,
|
|
550
|
+
note(div, errors.empty, revision);
|
|
449
551
|
return null;
|
|
450
552
|
}
|
|
451
553
|
|
|
452
554
|
try {
|
|
453
555
|
return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
|
|
454
556
|
} catch {
|
|
455
|
-
note(div,
|
|
557
|
+
note(div, errors.draw, revision);
|
|
456
558
|
return null;
|
|
457
559
|
}
|
|
458
560
|
}
|
|
@@ -467,27 +569,26 @@ export async function renderFromInline(
|
|
|
467
569
|
options: DiagramEmbedOptions = {},
|
|
468
570
|
): Promise<DiagramEmbedHandle | null> {
|
|
469
571
|
const revision = beginRender(div);
|
|
470
|
-
const errors = (div
|
|
471
|
-
const [, errEmpty = 'Diagram is empty or malformed.', errDraw = 'Diagram could not be rendered.'] = errors;
|
|
572
|
+
const errors = errorTexts(div);
|
|
472
573
|
|
|
473
574
|
let raw: unknown;
|
|
474
575
|
try {
|
|
475
576
|
raw = JSON.parse(json);
|
|
476
577
|
} catch {
|
|
477
|
-
note(div,
|
|
578
|
+
note(div, errors.empty, revision);
|
|
478
579
|
return null;
|
|
479
580
|
}
|
|
480
581
|
|
|
481
582
|
const scheme = parseRawScheme(raw);
|
|
482
583
|
if (scheme.nodes.length === 0) {
|
|
483
|
-
note(div,
|
|
584
|
+
note(div, errors.empty, revision);
|
|
484
585
|
return null;
|
|
485
586
|
}
|
|
486
587
|
|
|
487
588
|
try {
|
|
488
589
|
return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
|
|
489
590
|
} catch {
|
|
490
|
-
note(div,
|
|
591
|
+
note(div, errors.draw, revision);
|
|
491
592
|
return null;
|
|
492
593
|
}
|
|
493
594
|
}
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ export type {
|
|
|
36
36
|
JsonObject,
|
|
37
37
|
JsonPrimitive,
|
|
38
38
|
JsonValue,
|
|
39
|
+
PortDynamicMode,
|
|
39
40
|
} from './core/model.js';
|
|
40
41
|
|
|
41
42
|
export {
|
|
@@ -43,6 +44,7 @@ export {
|
|
|
43
44
|
createDiagramNodeRuntimeState,
|
|
44
45
|
createDiagramPortRuntimeState,
|
|
45
46
|
createDiagramRuntimeState,
|
|
47
|
+
cloneDiagramNodeErrors,
|
|
46
48
|
cloneDiagramRuntimeState,
|
|
47
49
|
createDiagramSelection,
|
|
48
50
|
createDiagramViewState,
|
|
@@ -64,6 +66,7 @@ export type {
|
|
|
64
66
|
DiagramGlobalErrorKind,
|
|
65
67
|
DiagramInteractionPermissions,
|
|
66
68
|
DiagramNodeErrorKind,
|
|
69
|
+
DiagramNodeErrors,
|
|
67
70
|
DiagramNodePortRuntimeState,
|
|
68
71
|
DiagramNodeRuntimeState,
|
|
69
72
|
DiagramPortDirection,
|
|
@@ -71,6 +74,7 @@ export type {
|
|
|
71
74
|
DiagramRuntimeState,
|
|
72
75
|
DiagramSelectedPort,
|
|
73
76
|
DiagramSelection,
|
|
77
|
+
DiagramSnapshot,
|
|
74
78
|
DiagramViewState,
|
|
75
79
|
} from './core/state.js';
|
|
76
80
|
|