@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
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);
|
|
@@ -245,9 +329,17 @@ async function renderSchemeAtRevision(
|
|
|
245
329
|
disposeActiveRender(div);
|
|
246
330
|
div.classList.remove('ss-diagram-error');
|
|
247
331
|
div.replaceChildren();
|
|
332
|
+
// The host page passes its wording the same way it passes the error texts: through the dataset,
|
|
333
|
+
// "enter|exit". Left out, the button keeps its English defaults.
|
|
334
|
+
const [enterLabel, exitLabel] = (div.dataset.diagramFullscreen ?? '').split('|');
|
|
335
|
+
|
|
248
336
|
let diagram: StockSharpDiagram;
|
|
249
337
|
try {
|
|
250
|
-
diagram = new StockSharpDiagram({
|
|
338
|
+
diagram = new StockSharpDiagram({
|
|
339
|
+
div,
|
|
340
|
+
catalog,
|
|
341
|
+
...(enterLabel && exitLabel ? { fullscreenLabels: { enter: enterLabel, exit: exitLabel } } : {}),
|
|
342
|
+
});
|
|
251
343
|
} catch (error) {
|
|
252
344
|
div.replaceChildren();
|
|
253
345
|
throw error;
|
|
@@ -404,6 +496,25 @@ function stopDisconnectedHostObserverWhenIdle(): void {
|
|
|
404
496
|
disconnectedHostObserver = null;
|
|
405
497
|
}
|
|
406
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
|
+
|
|
407
518
|
function note(div: HTMLElement, message: string, revision: number): void {
|
|
408
519
|
if (renderRevisions.get(div) !== revision) return;
|
|
409
520
|
disposeActiveRender(div);
|
|
@@ -421,30 +532,29 @@ export async function renderFromSource(
|
|
|
421
532
|
options: DiagramEmbedOptions = {},
|
|
422
533
|
): Promise<DiagramEmbedHandle | null> {
|
|
423
534
|
const revision = beginRender(div);
|
|
424
|
-
const errors = (div
|
|
425
|
-
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);
|
|
426
536
|
|
|
427
537
|
let raw: unknown;
|
|
428
538
|
try {
|
|
429
539
|
const resp = await fetch(srcUrl);
|
|
430
|
-
if (!resp.ok) { note(div,
|
|
540
|
+
if (!resp.ok) { note(div, errors.load, revision); return null; }
|
|
431
541
|
const text = (await resp.text()).replace(/^/, '');
|
|
432
542
|
raw = JSON.parse(text);
|
|
433
543
|
} catch {
|
|
434
|
-
note(div,
|
|
544
|
+
note(div, errors.load, revision);
|
|
435
545
|
return null;
|
|
436
546
|
}
|
|
437
547
|
|
|
438
548
|
const scheme = parseRawScheme(raw);
|
|
439
549
|
if (scheme.nodes.length === 0) {
|
|
440
|
-
note(div,
|
|
550
|
+
note(div, errors.empty, revision);
|
|
441
551
|
return null;
|
|
442
552
|
}
|
|
443
553
|
|
|
444
554
|
try {
|
|
445
555
|
return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
|
|
446
556
|
} catch {
|
|
447
|
-
note(div,
|
|
557
|
+
note(div, errors.draw, revision);
|
|
448
558
|
return null;
|
|
449
559
|
}
|
|
450
560
|
}
|
|
@@ -459,27 +569,26 @@ export async function renderFromInline(
|
|
|
459
569
|
options: DiagramEmbedOptions = {},
|
|
460
570
|
): Promise<DiagramEmbedHandle | null> {
|
|
461
571
|
const revision = beginRender(div);
|
|
462
|
-
const errors = (div
|
|
463
|
-
const [, errEmpty = 'Diagram is empty or malformed.', errDraw = 'Diagram could not be rendered.'] = errors;
|
|
572
|
+
const errors = errorTexts(div);
|
|
464
573
|
|
|
465
574
|
let raw: unknown;
|
|
466
575
|
try {
|
|
467
576
|
raw = JSON.parse(json);
|
|
468
577
|
} catch {
|
|
469
|
-
note(div,
|
|
578
|
+
note(div, errors.empty, revision);
|
|
470
579
|
return null;
|
|
471
580
|
}
|
|
472
581
|
|
|
473
582
|
const scheme = parseRawScheme(raw);
|
|
474
583
|
if (scheme.nodes.length === 0) {
|
|
475
|
-
note(div,
|
|
584
|
+
note(div, errors.empty, revision);
|
|
476
585
|
return null;
|
|
477
586
|
}
|
|
478
587
|
|
|
479
588
|
try {
|
|
480
589
|
return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
|
|
481
590
|
} catch {
|
|
482
|
-
note(div,
|
|
591
|
+
note(div, errors.draw, revision);
|
|
483
592
|
return null;
|
|
484
593
|
}
|
|
485
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
|
|