@dforge-core/diagram 0.0.3 → 0.0.4
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/SimpleDiagram/ModelDiagram.svelte +45 -23
- package/dist/SimpleDiagram/ModelDiagram.svelte.d.ts.map +1 -1
- package/dist/SimpleDiagram/SimpleDiagram.svelte +89 -31
- package/dist/SimpleDiagram/SimpleDiagram.svelte.d.ts +7 -0
- package/dist/SimpleDiagram/SimpleDiagram.svelte.d.ts.map +1 -1
- package/dist/WorkspaceMap/WorkspaceMap.svelte +45 -23
- package/dist/WorkspaceMap/WorkspaceMap.svelte.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model-graph.d.ts +58 -3
- package/dist/model-graph.d.ts.map +1 -1
- package/dist/model-graph.js +124 -10
- package/dist/model-types.d.ts +6 -0
- package/dist/model-types.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -264,14 +264,18 @@
|
|
|
264
264
|
});
|
|
265
265
|
</script>
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
268
|
+
<div
|
|
269
|
+
class="model-diagram"
|
|
270
|
+
bind:this={containerEl}
|
|
271
|
+
onpointermove={handlePointerMove}
|
|
272
|
+
onpointerup={handlePointerUp}
|
|
273
|
+
>
|
|
268
274
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
269
275
|
<svg
|
|
270
276
|
bind:this={svgEl}
|
|
271
277
|
role="application"
|
|
272
278
|
onpointerdown={handleBackgroundPointerDown}
|
|
273
|
-
onpointermove={handlePointerMove}
|
|
274
|
-
onpointerup={handlePointerUp}
|
|
275
279
|
>
|
|
276
280
|
<defs>
|
|
277
281
|
<pattern id={patternId} width="20" height="20" patternUnits="userSpaceOnUse"
|
|
@@ -395,26 +399,33 @@
|
|
|
395
399
|
{/if}
|
|
396
400
|
{/if}
|
|
397
401
|
|
|
398
|
-
<!-- Nodes -->
|
|
399
|
-
{#each nodes as node (node.id)}
|
|
400
|
-
{@const pos = nodePositions[node.id] ?? { x: node.x, y: node.y }}
|
|
401
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
402
|
-
<foreignObject
|
|
403
|
-
x={pos.x}
|
|
404
|
-
y={pos.y}
|
|
405
|
-
width={node.width}
|
|
406
|
-
height={node.height}
|
|
407
|
-
onpointerdown={(e: PointerEvent) => handleNodePointerDown(e, node.id)}
|
|
408
|
-
class="diagram-node-fo"
|
|
409
|
-
>
|
|
410
|
-
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
411
|
-
<EntityNode data={node.data} />
|
|
412
|
-
</div>
|
|
413
|
-
</foreignObject>
|
|
414
|
-
{/each}
|
|
415
402
|
</g>
|
|
416
403
|
</svg>
|
|
417
404
|
|
|
405
|
+
<!--
|
|
406
|
+
Nodes render in an HTML overlay layer (not SVG <foreignObject>) so they
|
|
407
|
+
scale correctly in Safari/WebKit, which does not apply a scaled SVG
|
|
408
|
+
ancestor transform to foreignObject HTML content. The layer shares the
|
|
409
|
+
same pan/zoom transform as the SVG <g> above via a CSS transform.
|
|
410
|
+
-->
|
|
411
|
+
<div
|
|
412
|
+
class="node-layer"
|
|
413
|
+
style:transform="translate({pz.transform.x}px, {pz.transform.y}px) scale({pz.transform.k})"
|
|
414
|
+
style:transform-origin="0 0"
|
|
415
|
+
>
|
|
416
|
+
{#each nodes as node (node.id)}
|
|
417
|
+
{@const pos = nodePositions[node.id] ?? { x: node.x, y: node.y }}
|
|
418
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
419
|
+
<div
|
|
420
|
+
class="diagram-node"
|
|
421
|
+
style="left: {pos.x}px; top: {pos.y}px; width: {node.width}px; height: {node.height}px;"
|
|
422
|
+
onpointerdown={(e: PointerEvent) => handleNodePointerDown(e, node.id)}
|
|
423
|
+
>
|
|
424
|
+
<EntityNode data={node.data} />
|
|
425
|
+
</div>
|
|
426
|
+
{/each}
|
|
427
|
+
</div>
|
|
428
|
+
|
|
418
429
|
<!-- Controls -->
|
|
419
430
|
<div class="diagram-controls">
|
|
420
431
|
<IconButton icon="bi-plus-lg" title={t.zoom_in} size="sm" onclick={() => pz.zoomBy(1.3)} />
|
|
@@ -442,12 +453,23 @@
|
|
|
442
453
|
cursor: grabbing;
|
|
443
454
|
}
|
|
444
455
|
|
|
445
|
-
.
|
|
456
|
+
.node-layer {
|
|
457
|
+
position: absolute;
|
|
458
|
+
inset: 0;
|
|
459
|
+
/* Empty areas let pointer events fall through to the SVG (pan / edge hit
|
|
460
|
+
areas); individual nodes re-enable events below. transform /
|
|
461
|
+
transform-origin are applied via style: directives. */
|
|
462
|
+
pointer-events: none;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.diagram-node {
|
|
466
|
+
position: absolute;
|
|
467
|
+
box-sizing: border-box;
|
|
468
|
+
pointer-events: auto;
|
|
446
469
|
cursor: grab;
|
|
447
|
-
overflow: visible;
|
|
448
470
|
}
|
|
449
471
|
|
|
450
|
-
.diagram-node
|
|
472
|
+
.diagram-node:active {
|
|
451
473
|
cursor: grabbing;
|
|
452
474
|
}
|
|
453
475
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelDiagram.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/SimpleDiagram/ModelDiagram.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,EAAe,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAiI,KAAK,QAAQ,EAAsB,MAAM,iBAAiB,CAAC;AAMlM,UAAU,KAAK;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9D,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;CAC7E;
|
|
1
|
+
{"version":3,"file":"ModelDiagram.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/SimpleDiagram/ModelDiagram.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,EAAe,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAiI,KAAK,QAAQ,EAAsB,MAAM,iBAAiB,CAAC;AAMlM,UAAU,KAAK;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9D,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,EAAE,KAAK,IAAI,CAAC;CAC7E;AAmWF,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -16,12 +16,19 @@
|
|
|
16
16
|
nodes: DiagramNode[];
|
|
17
17
|
edges: ResolvedEdge[];
|
|
18
18
|
storageKey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Refit the diagram to its container whenever this value changes. For a
|
|
21
|
+
* host that resizes the viewport out from under the diagram (entering a
|
|
22
|
+
* full-screen or print layout), where the saved transform would otherwise
|
|
23
|
+
* leave the content parked off to one side of a much larger canvas.
|
|
24
|
+
*/
|
|
25
|
+
fitKey?: string | number;
|
|
19
26
|
minZoom?: number;
|
|
20
27
|
maxZoom?: number;
|
|
21
28
|
onNodeDblClick?: (nodeId: string) => void;
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
let { nodes, edges, storageKey, minZoom = 0.1, maxZoom = 2, onNodeDblClick }: Props = $props();
|
|
31
|
+
let { nodes, edges, storageKey, fitKey, minZoom = 0.1, maxZoom = 2, onNodeDblClick }: Props = $props();
|
|
25
32
|
|
|
26
33
|
let svgEl: SVGSVGElement;
|
|
27
34
|
let containerEl: HTMLDivElement;
|
|
@@ -173,10 +180,14 @@
|
|
|
173
180
|
|
|
174
181
|
// --- Delegated event helpers ---
|
|
175
182
|
|
|
176
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Walk up from event target to find a data attribute. Delegation runs on the
|
|
185
|
+
* container, so nodes (HTML overlay) and edges/handles (SVG) both resolve here;
|
|
186
|
+
* we stop at the container element.
|
|
187
|
+
*/
|
|
177
188
|
function closest(target: Element, attr: string): Element | null {
|
|
178
189
|
let el: Element | null = target;
|
|
179
|
-
while (el && el !==
|
|
190
|
+
while (el && el !== containerEl) {
|
|
180
191
|
if (el.hasAttribute(attr)) return el;
|
|
181
192
|
// parentElement is null at HTML↔SVG boundary in some browsers; fall back to parentNode
|
|
182
193
|
el = el.parentElement ?? (el.parentNode as Element | null);
|
|
@@ -193,6 +204,11 @@
|
|
|
193
204
|
function handlePointerDown(e: PointerEvent) {
|
|
194
205
|
const target = e.target as Element;
|
|
195
206
|
|
|
207
|
+
// Ignore interactions that originate on the overlaid controls (buttons
|
|
208
|
+
// handle their own clicks). Handlers now live on the container, so control
|
|
209
|
+
// clicks would otherwise reach this delegated handler.
|
|
210
|
+
if (target.closest('.diagram-controls')) return;
|
|
211
|
+
|
|
196
212
|
// Segment drag handle
|
|
197
213
|
const handle = closest(target, 'data-handle');
|
|
198
214
|
if (handle) {
|
|
@@ -413,6 +429,25 @@
|
|
|
413
429
|
requestAnimationFrame(() => fitView());
|
|
414
430
|
}
|
|
415
431
|
|
|
432
|
+
// Refit on fitKey change. Skips the very first run so it doesn't fight the
|
|
433
|
+
// restored transform on mount.
|
|
434
|
+
//
|
|
435
|
+
// The first fit is synchronous, not deferred to a frame: a host entering a
|
|
436
|
+
// print layout flips fitKey inside `beforeprint` and flushes, and the print
|
|
437
|
+
// snapshot is taken as soon as that handler returns — anything left to rAF
|
|
438
|
+
// misses it entirely. Measuring here forces the pending layout, so the new
|
|
439
|
+
// container size is already available. The rAF pass after it is a cheap
|
|
440
|
+
// idempotent safety net for hosts whose box settles a frame late (CSS
|
|
441
|
+
// transitions on the shell); re-fitting to the same bounds is a no-op.
|
|
442
|
+
let fitKeySeen = false;
|
|
443
|
+
$effect(() => {
|
|
444
|
+
fitKey; // track
|
|
445
|
+
if (fitKey === undefined) return;
|
|
446
|
+
if (!fitKeySeen) { fitKeySeen = true; return; }
|
|
447
|
+
fitView();
|
|
448
|
+
requestAnimationFrame(() => fitView());
|
|
449
|
+
});
|
|
450
|
+
|
|
416
451
|
onMount(() => {
|
|
417
452
|
svgEl.addEventListener('wheel', pz.handleWheel, { passive: false });
|
|
418
453
|
if (!restoredTransform) {
|
|
@@ -425,15 +460,18 @@
|
|
|
425
460
|
});
|
|
426
461
|
</script>
|
|
427
462
|
|
|
428
|
-
|
|
429
|
-
|
|
463
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
464
|
+
<div
|
|
465
|
+
class="model-diagram"
|
|
466
|
+
bind:this={containerEl}
|
|
467
|
+
onpointerdown={handlePointerDown}
|
|
468
|
+
onpointermove={handlePointerMove}
|
|
469
|
+
onpointerup={handlePointerUp}
|
|
470
|
+
ondblclick={handleDblClick}
|
|
471
|
+
>
|
|
430
472
|
<svg
|
|
431
473
|
bind:this={svgEl}
|
|
432
474
|
role="application"
|
|
433
|
-
onpointerdown={handlePointerDown}
|
|
434
|
-
onpointermove={handlePointerMove}
|
|
435
|
-
onpointerup={handlePointerUp}
|
|
436
|
-
ondblclick={handleDblClick}
|
|
437
475
|
>
|
|
438
476
|
<defs>
|
|
439
477
|
<pattern id={patternId} width="20" height="20" patternUnits="userSpaceOnUse"
|
|
@@ -503,24 +541,6 @@
|
|
|
503
541
|
{/if}
|
|
504
542
|
{/each}
|
|
505
543
|
|
|
506
|
-
<!-- Nodes -->
|
|
507
|
-
{#each nodes as node (node.id)}
|
|
508
|
-
{@const pos = nodePositions[node.id] ?? node}
|
|
509
|
-
{@const isSelected = selectedNodeIds.has(node.id)}
|
|
510
|
-
<foreignObject
|
|
511
|
-
x={pos.x}
|
|
512
|
-
y={pos.y}
|
|
513
|
-
width={node.width}
|
|
514
|
-
height={node.height}
|
|
515
|
-
class="diagram-node-fo"
|
|
516
|
-
class:selected={isSelected}
|
|
517
|
-
>
|
|
518
|
-
<div xmlns="http://www.w3.org/1999/xhtml" data-node-id={node.id}>
|
|
519
|
-
<EntityNode data={node.data} />
|
|
520
|
-
</div>
|
|
521
|
-
</foreignObject>
|
|
522
|
-
{/each}
|
|
523
|
-
|
|
524
544
|
<!-- Selected edge on top of nodes -->
|
|
525
545
|
{#if selectedEdgeId}
|
|
526
546
|
{#each resolvedEdges as edge (edge.id)}
|
|
@@ -587,6 +607,32 @@
|
|
|
587
607
|
</g>
|
|
588
608
|
</svg>
|
|
589
609
|
|
|
610
|
+
<!--
|
|
611
|
+
Nodes render in an HTML overlay layer (not SVG <foreignObject>) so they
|
|
612
|
+
scale correctly in Safari/WebKit, which does not apply a scaled SVG
|
|
613
|
+
ancestor transform to foreignObject HTML content. The layer shares the
|
|
614
|
+
same pan/zoom transform as the SVG <g> above. Pointer events are handled
|
|
615
|
+
by delegation on the container (see handlePointerDown), so nodes only need
|
|
616
|
+
their data-node-id and to opt back into pointer events.
|
|
617
|
+
-->
|
|
618
|
+
<div
|
|
619
|
+
class="node-layer"
|
|
620
|
+
style:transform="translate({pz.transform.x}px, {pz.transform.y}px) scale({pz.transform.k})"
|
|
621
|
+
style:transform-origin="0 0"
|
|
622
|
+
>
|
|
623
|
+
{#each nodes as node (node.id)}
|
|
624
|
+
{@const pos = nodePositions[node.id] ?? node}
|
|
625
|
+
<div
|
|
626
|
+
class="diagram-node"
|
|
627
|
+
class:selected={selectedNodeIds.has(node.id)}
|
|
628
|
+
style="left: {pos.x}px; top: {pos.y}px; width: {node.width}px; height: {node.height}px;"
|
|
629
|
+
data-node-id={node.id}
|
|
630
|
+
>
|
|
631
|
+
<EntityNode data={node.data} />
|
|
632
|
+
</div>
|
|
633
|
+
{/each}
|
|
634
|
+
</div>
|
|
635
|
+
|
|
590
636
|
<!-- Controls -->
|
|
591
637
|
<div class="diagram-controls">
|
|
592
638
|
<IconButton icon="bi-plus-lg" title={t.zoom_in} size="sm" onclick={() => pz.zoomBy(1.3)} />
|
|
@@ -615,16 +661,28 @@
|
|
|
615
661
|
cursor: grabbing;
|
|
616
662
|
}
|
|
617
663
|
|
|
618
|
-
.
|
|
664
|
+
.node-layer {
|
|
665
|
+
position: absolute;
|
|
666
|
+
inset: 0;
|
|
667
|
+
/* Empty areas let pointer events fall through to the SVG (pan / area
|
|
668
|
+
select / edge hit areas); individual nodes re-enable events below.
|
|
669
|
+
transform / transform-origin are applied via style: directives so the
|
|
670
|
+
layer tracks the SVG <g> transform exactly. */
|
|
671
|
+
pointer-events: none;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.diagram-node {
|
|
675
|
+
position: absolute;
|
|
676
|
+
box-sizing: border-box;
|
|
677
|
+
pointer-events: auto;
|
|
619
678
|
cursor: grab;
|
|
620
|
-
overflow: visible;
|
|
621
679
|
}
|
|
622
680
|
|
|
623
|
-
.diagram-node
|
|
681
|
+
.diagram-node.selected {
|
|
624
682
|
filter: drop-shadow(0 0 4px var(--bs-primary, #0d6efd));
|
|
625
683
|
}
|
|
626
684
|
|
|
627
|
-
.diagram-node
|
|
685
|
+
.diagram-node:active {
|
|
628
686
|
cursor: grabbing;
|
|
629
687
|
}
|
|
630
688
|
|
|
@@ -3,6 +3,13 @@ interface Props {
|
|
|
3
3
|
nodes: DiagramNode[];
|
|
4
4
|
edges: ResolvedEdge[];
|
|
5
5
|
storageKey?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Refit the diagram to its container whenever this value changes. For a
|
|
8
|
+
* host that resizes the viewport out from under the diagram (entering a
|
|
9
|
+
* full-screen or print layout), where the saved transform would otherwise
|
|
10
|
+
* leave the content parked off to one side of a much larger canvas.
|
|
11
|
+
*/
|
|
12
|
+
fitKey?: string | number;
|
|
6
13
|
minZoom?: number;
|
|
7
14
|
maxZoom?: number;
|
|
8
15
|
onNodeDblClick?: (nodeId: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SimpleDiagram.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/SimpleDiagram/SimpleDiagram.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,kBAAkB,CAAC;AAUxE,UAAU,KAAK;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;
|
|
1
|
+
{"version":3,"file":"SimpleDiagram.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/SimpleDiagram/SimpleDiagram.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,kBAAkB,CAAC;AAUxE,UAAU,KAAK;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AA+iBF,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -207,14 +207,18 @@
|
|
|
207
207
|
});
|
|
208
208
|
</script>
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
211
|
+
<div
|
|
212
|
+
class="ws-map"
|
|
213
|
+
bind:this={containerEl}
|
|
214
|
+
onpointermove={handlePointerMove}
|
|
215
|
+
onpointerup={handlePointerUp}
|
|
216
|
+
>
|
|
211
217
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
212
218
|
<svg
|
|
213
219
|
bind:this={svgEl}
|
|
214
220
|
role="application"
|
|
215
221
|
onpointerdown={handleBackgroundPointerDown}
|
|
216
|
-
onpointermove={handlePointerMove}
|
|
217
|
-
onpointerup={handlePointerUp}
|
|
218
222
|
>
|
|
219
223
|
<defs>
|
|
220
224
|
<pattern id={patternId} width="20" height="20" patternUnits="userSpaceOnUse"
|
|
@@ -242,26 +246,33 @@
|
|
|
242
246
|
/>
|
|
243
247
|
{/each}
|
|
244
248
|
|
|
245
|
-
<!-- Module cards -->
|
|
246
|
-
{#each modules as mod (mod.id)}
|
|
247
|
-
{@const pos = modulePositions[mod.id]}
|
|
248
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
249
|
-
<foreignObject
|
|
250
|
-
x={pos.x}
|
|
251
|
-
y={pos.y}
|
|
252
|
-
width={mod.width}
|
|
253
|
-
height={mod.height}
|
|
254
|
-
onpointerdown={(e: PointerEvent) => handleModulePointerDown(e, mod.id)}
|
|
255
|
-
class="ws-module-fo"
|
|
256
|
-
>
|
|
257
|
-
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
258
|
-
<ModuleCard module={mod} />
|
|
259
|
-
</div>
|
|
260
|
-
</foreignObject>
|
|
261
|
-
{/each}
|
|
262
249
|
</g>
|
|
263
250
|
</svg>
|
|
264
251
|
|
|
252
|
+
<!--
|
|
253
|
+
Module cards render in an HTML overlay layer (not SVG <foreignObject>) so
|
|
254
|
+
they scale correctly in Safari/WebKit, which does not apply a scaled SVG
|
|
255
|
+
ancestor transform to foreignObject HTML content. The layer shares the same
|
|
256
|
+
pan/zoom transform as the SVG <g> above.
|
|
257
|
+
-->
|
|
258
|
+
<div
|
|
259
|
+
class="ws-node-layer"
|
|
260
|
+
style:transform="translate({pz.transform.x}px, {pz.transform.y}px) scale({pz.transform.k})"
|
|
261
|
+
style:transform-origin="0 0"
|
|
262
|
+
>
|
|
263
|
+
{#each modules as mod (mod.id)}
|
|
264
|
+
{@const pos = modulePositions[mod.id]}
|
|
265
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
266
|
+
<div
|
|
267
|
+
class="ws-module"
|
|
268
|
+
style="left: {pos.x}px; top: {pos.y}px; width: {mod.width}px; height: {mod.height}px;"
|
|
269
|
+
onpointerdown={(e: PointerEvent) => handleModulePointerDown(e, mod.id)}
|
|
270
|
+
>
|
|
271
|
+
<ModuleCard module={mod} />
|
|
272
|
+
</div>
|
|
273
|
+
{/each}
|
|
274
|
+
</div>
|
|
275
|
+
|
|
265
276
|
<!-- Controls -->
|
|
266
277
|
<div class="ws-controls">
|
|
267
278
|
<IconButton icon="bi-plus-lg" title={t.zoom_in} size="sm" onclick={() => pz.zoomBy(1.3)} />
|
|
@@ -290,12 +301,23 @@
|
|
|
290
301
|
cursor: grabbing;
|
|
291
302
|
}
|
|
292
303
|
|
|
293
|
-
.ws-
|
|
304
|
+
.ws-node-layer {
|
|
305
|
+
position: absolute;
|
|
306
|
+
inset: 0;
|
|
307
|
+
/* Empty areas let pointer events fall through to the SVG for panning;
|
|
308
|
+
individual cards re-enable events below. transform / transform-origin
|
|
309
|
+
are applied via style: directives. */
|
|
310
|
+
pointer-events: none;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.ws-module {
|
|
314
|
+
position: absolute;
|
|
315
|
+
box-sizing: border-box;
|
|
316
|
+
pointer-events: auto;
|
|
294
317
|
cursor: grab;
|
|
295
|
-
overflow: visible;
|
|
296
318
|
}
|
|
297
319
|
|
|
298
|
-
.ws-module
|
|
320
|
+
.ws-module:active {
|
|
299
321
|
cursor: grabbing;
|
|
300
322
|
}
|
|
301
323
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceMap.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/WorkspaceMap/WorkspaceMap.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAQvE,UAAU,KAAK;IACd,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"WorkspaceMap.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/WorkspaceMap/WorkspaceMap.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAQvE,UAAU,KAAK;IACd,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AA2PF,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { default as EntityNode } from './EntityNode.svelte';
|
|
|
8
8
|
export { default as DiagramDebug } from './SimpleDiagram/DiagramDebug.svelte';
|
|
9
9
|
export { simpleLayout, moduleLayout, routeEdge, routeAllEdges } from './SimpleDiagram/simple-layout';
|
|
10
10
|
export type { NodeLookup } from './SimpleDiagram/simple-layout';
|
|
11
|
-
export { buildGraph, buildEntityMap, getModuleColor, MODULE_COLORS } from './model-graph';
|
|
11
|
+
export { buildGraph, buildEntityMap, columnLabel, entityLabel, expandBridgeSelection, findRelatedModules, getModuleColor, MODULE_COLORS } from './model-graph';
|
|
12
12
|
export { buildSvgPath, edgeLabelPos, prepareDragPoints, consolidatePoints, insertJog, getSegmentHandles, applySegmentDrag } from './SimpleDiagram/diagram-geometry';
|
|
13
13
|
export type { SegHandle } from './SimpleDiagram/diagram-geometry';
|
|
14
14
|
export { createPanZoom } from './pan-zoom.svelte';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC;AACjG,YAAY,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAGlD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGjI,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG9G,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAG9E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACrG,YAAY,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC;AACjG,YAAY,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAGlD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGjI,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG9G,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAG9E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACrG,YAAY,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG/J,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpK,YAAY,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACtG,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAGzE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC/J,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ export { default as DiagramDebug } from './SimpleDiagram/DiagramDebug.svelte';
|
|
|
8
8
|
// Layout
|
|
9
9
|
export { simpleLayout, moduleLayout, routeEdge, routeAllEdges } from './SimpleDiagram/simple-layout';
|
|
10
10
|
// Graph building
|
|
11
|
-
export { buildGraph, buildEntityMap, getModuleColor, MODULE_COLORS } from './model-graph';
|
|
11
|
+
export { buildGraph, buildEntityMap, columnLabel, entityLabel, expandBridgeSelection, findRelatedModules, getModuleColor, MODULE_COLORS } from './model-graph';
|
|
12
12
|
// Geometry
|
|
13
13
|
export { buildSvgPath, edgeLabelPos, prepareDragPoints, consolidatePoints, insertJog, getSegmentHandles, applySegmentDrag } from './SimpleDiagram/diagram-geometry';
|
|
14
14
|
// Pan/Zoom & persistence utilities
|
package/dist/model-graph.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import type { DiagramNode, DiagramEdge } from './diagram-types';
|
|
2
|
-
import type { ModelGetResponse, ModelModule } from './model-types';
|
|
2
|
+
import type { ModelColumn, ModelEntity, ModelGetResponse, ModelModule } from './model-types';
|
|
3
|
+
/**
|
|
4
|
+
* Display label for an entity / column.
|
|
5
|
+
*
|
|
6
|
+
* `resLabel` is the install-time label the module author wrote — the same string
|
|
7
|
+
* for every viewer, and the model browser's default because it matches what is in
|
|
8
|
+
* the module package. `resLabelLocalized` is the translation for the viewer's
|
|
9
|
+
* culture, present only where the tenant has one; `localized` opts into it and
|
|
10
|
+
* still falls back per-item, so a partly translated module reads as a mix rather
|
|
11
|
+
* than losing the untranslated half.
|
|
12
|
+
*/
|
|
13
|
+
export declare const entityLabel: (ent: ModelEntity, localized?: boolean) => string;
|
|
14
|
+
export declare const columnLabel: (col: ModelColumn, localized?: boolean) => string;
|
|
3
15
|
export declare const MODULE_COLORS: string[];
|
|
4
|
-
export declare
|
|
16
|
+
export declare const getModuleColor: (index: number) => string;
|
|
5
17
|
/**
|
|
6
18
|
* Build a diagram graph from the given modules.
|
|
7
19
|
* Nodes start at position {0,0} — call simpleLayout() to compute positions.
|
|
@@ -9,6 +21,14 @@ export declare function getModuleColor(index: number): string;
|
|
|
9
21
|
export declare function buildGraph(modules: ModelModule[], entityMap: Map<number, string>, options?: {
|
|
10
22
|
intraModuleEdgesOnly?: boolean;
|
|
11
23
|
colorOffset?: number;
|
|
24
|
+
/** Show translated entity/column labels where the tenant has them. */
|
|
25
|
+
localized?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Palette index per module, overriding the default "position in `modules`".
|
|
28
|
+
* Needed when `modules` is an arbitrary subset (a module plus the ones it
|
|
29
|
+
* connects to) that must keep the colors those modules have elsewhere.
|
|
30
|
+
*/
|
|
31
|
+
moduleColorIndex?: (moduleCd: string) => number | undefined;
|
|
12
32
|
}): {
|
|
13
33
|
nodes: DiagramNode[];
|
|
14
34
|
edges: DiagramEdge[];
|
|
@@ -16,5 +36,40 @@ export declare function buildGraph(modules: ModelModule[], entityMap: Map<number
|
|
|
16
36
|
/**
|
|
17
37
|
* Build the full entityId → label map from a ModelGetResponse.
|
|
18
38
|
*/
|
|
19
|
-
export declare function buildEntityMap(response: ModelGetResponse): Map<number, string>;
|
|
39
|
+
export declare function buildEntityMap(response: ModelGetResponse, localized?: boolean): Map<number, string>;
|
|
40
|
+
/**
|
|
41
|
+
* Module codes related to `moduleCd` — the candidate set for a "connected
|
|
42
|
+
* modules" view of that module's diagram.
|
|
43
|
+
*
|
|
44
|
+
* A bridge module (`crm-fin`, `wms-fin`) usually owns no entities at all: it
|
|
45
|
+
* only adds extension columns to the entities of the modules it bridges. Its
|
|
46
|
+
* own diagram is therefore empty, and nothing in the entity graph points back
|
|
47
|
+
* at it either — the columns it contributes are stored on the *target* entity.
|
|
48
|
+
* So the relation is collected from three independent places:
|
|
49
|
+
*
|
|
50
|
+
* 1. declared dependencies — what this module was built on top of. This is
|
|
51
|
+
* the only signal that works for a bridge with no entities.
|
|
52
|
+
* 2. reverse dependencies — modules built on top of this one. A core module
|
|
53
|
+
* has no way of knowing which bridges were installed against it.
|
|
54
|
+
* 3. cross-module references — FK edges leaving or entering this module's
|
|
55
|
+
* entities, which still resolves links that skip a manifest declaration.
|
|
56
|
+
*
|
|
57
|
+
* The module itself and system modules are never included. Results come back in
|
|
58
|
+
* `response.modules` order so chip order and palette indices stay stable.
|
|
59
|
+
*/
|
|
60
|
+
export declare function findRelatedModules(response: ModelGetResponse, moduleCd: string): string[];
|
|
61
|
+
/**
|
|
62
|
+
* Expand a chosen set of modules to one that actually draws something.
|
|
63
|
+
*
|
|
64
|
+
* Picking a bridge means "show me what this connects", but the bridge itself
|
|
65
|
+
* has no entities — its whole contribution is extension columns living on the
|
|
66
|
+
* entities of the modules it bridges. Selecting one alone would therefore change
|
|
67
|
+
* the URL and redraw nothing, so each bridge in the set brings its own drawable
|
|
68
|
+
* dependencies along.
|
|
69
|
+
*
|
|
70
|
+
* Expansion is one level deep: a bridge that depends on another bridge is not a
|
|
71
|
+
* shape any module in the wild has, and recursing would quietly pull in half the
|
|
72
|
+
* workspace. Returns the input order with the additions appended, deduped.
|
|
73
|
+
*/
|
|
74
|
+
export declare function expandBridgeSelection(response: ModelGetResponse, moduleCds: string[]): string[];
|
|
20
75
|
//# sourceMappingURL=model-graph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-graph.d.ts","sourceRoot":"","sources":["../src/lib/model-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"model-graph.d.ts","sourceRoot":"","sources":["../src/lib/model-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE7F;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,WAAW,EAAE,mBAAiB,KAAG,MACc,CAAC;AAEjF,eAAO,MAAM,WAAW,GAAI,KAAK,WAAW,EAAE,mBAAiB,KAAG,MACc,CAAC;AAEjF,eAAO,MAAM,aAAa,UASzB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,MACH,CAAC;AAM7C;;;GAGG;AACH,wBAAgB,UAAU,CACzB,OAAO,EAAE,WAAW,EAAE,EACtB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE;IACT,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC5D,GACC;IAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE,CAoFhD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,EAAE,SAAS,UAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQjG;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA4CzF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAc/F"}
|
package/dist/model-graph.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display label for an entity / column.
|
|
3
|
+
*
|
|
4
|
+
* `resLabel` is the install-time label the module author wrote — the same string
|
|
5
|
+
* for every viewer, and the model browser's default because it matches what is in
|
|
6
|
+
* the module package. `resLabelLocalized` is the translation for the viewer's
|
|
7
|
+
* culture, present only where the tenant has one; `localized` opts into it and
|
|
8
|
+
* still falls back per-item, so a partly translated module reads as a mix rather
|
|
9
|
+
* than losing the untranslated half.
|
|
10
|
+
*/
|
|
11
|
+
export const entityLabel = (ent, localized = false) => (localized ? ent.resLabelLocalized : undefined) || ent.resLabel || ent.entityCd;
|
|
12
|
+
export const columnLabel = (col, localized = false) => (localized ? col.resLabelLocalized : undefined) || col.resLabel || col.columnCd;
|
|
1
13
|
export const MODULE_COLORS = [
|
|
2
14
|
'#0d6efd', // blue
|
|
3
15
|
'#198754', // green
|
|
@@ -8,9 +20,7 @@ export const MODULE_COLORS = [
|
|
|
8
20
|
'#d63384', // pink
|
|
9
21
|
'#0dcaf0', // cyan
|
|
10
22
|
];
|
|
11
|
-
export
|
|
12
|
-
return MODULE_COLORS[index % MODULE_COLORS.length];
|
|
13
|
-
}
|
|
23
|
+
export const getModuleColor = (index) => MODULE_COLORS[index % MODULE_COLORS.length];
|
|
14
24
|
const NODE_WIDTH = 260;
|
|
15
25
|
const NODE_HEIGHT_BASE = 60;
|
|
16
26
|
const ROW_HEIGHT = 22;
|
|
@@ -30,9 +40,11 @@ export function buildGraph(modules, entityMap, options) {
|
|
|
30
40
|
}
|
|
31
41
|
}
|
|
32
42
|
const offset = options?.colorOffset ?? 0;
|
|
43
|
+
const localized = options?.localized ?? false;
|
|
44
|
+
const colorOf = (moduleCd, mi) => getModuleColor(options?.moduleColorIndex?.(moduleCd) ?? mi + offset);
|
|
33
45
|
for (let mi = 0; mi < modules.length; mi++) {
|
|
34
46
|
const mod = modules[mi];
|
|
35
|
-
const color =
|
|
47
|
+
const color = colorOf(mod.moduleCd, mi);
|
|
36
48
|
for (const ent of mod.entities) {
|
|
37
49
|
const nodeId = `entity-${ent.entityId}`;
|
|
38
50
|
entityNodeMap.set(ent.entityId, nodeId);
|
|
@@ -45,11 +57,15 @@ export function buildGraph(modules, entityMap, options) {
|
|
|
45
57
|
height: nodeHeight,
|
|
46
58
|
data: {
|
|
47
59
|
entityCd: ent.entityCd,
|
|
48
|
-
resLabel: ent
|
|
60
|
+
resLabel: entityLabel(ent, localized),
|
|
49
61
|
resIcon: ent.resIcon,
|
|
50
62
|
moduleCd: mod.moduleCd,
|
|
51
63
|
moduleColor: color,
|
|
52
|
-
|
|
64
|
+
// Only rewrite the label the node renders; everything else on the
|
|
65
|
+
// column is passed through untouched.
|
|
66
|
+
columns: localized
|
|
67
|
+
? ent.columns.map(col => ({ ...col, resLabel: columnLabel(col, true) }))
|
|
68
|
+
: ent.columns,
|
|
53
69
|
entityMap,
|
|
54
70
|
},
|
|
55
71
|
});
|
|
@@ -58,7 +74,7 @@ export function buildGraph(modules, entityMap, options) {
|
|
|
58
74
|
// Build edges for FK columns (type 'R')
|
|
59
75
|
for (let mi = 0; mi < modules.length; mi++) {
|
|
60
76
|
const mod = modules[mi];
|
|
61
|
-
const color =
|
|
77
|
+
const color = colorOf(mod.moduleCd, mi);
|
|
62
78
|
for (const ent of mod.entities) {
|
|
63
79
|
const sourceNodeId = entityNodeMap.get(ent.entityId);
|
|
64
80
|
if (!sourceNodeId)
|
|
@@ -77,7 +93,7 @@ export function buildGraph(modules, entityMap, options) {
|
|
|
77
93
|
sourceHandle: col.columnCd,
|
|
78
94
|
target: targetNodeId,
|
|
79
95
|
color,
|
|
80
|
-
label: col
|
|
96
|
+
label: columnLabel(col, localized),
|
|
81
97
|
isNullable: col.isNullable ?? false,
|
|
82
98
|
});
|
|
83
99
|
}
|
|
@@ -89,12 +105,110 @@ export function buildGraph(modules, entityMap, options) {
|
|
|
89
105
|
/**
|
|
90
106
|
* Build the full entityId → label map from a ModelGetResponse.
|
|
91
107
|
*/
|
|
92
|
-
export function buildEntityMap(response) {
|
|
108
|
+
export function buildEntityMap(response, localized = false) {
|
|
93
109
|
const entityMap = new Map();
|
|
94
110
|
for (const mod of response.modules) {
|
|
95
111
|
for (const ent of mod.entities) {
|
|
96
|
-
entityMap.set(ent.entityId, ent
|
|
112
|
+
entityMap.set(ent.entityId, entityLabel(ent, localized));
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
115
|
return entityMap;
|
|
100
116
|
}
|
|
117
|
+
/** A module with no entities of its own contributes no nodes to a diagram. */
|
|
118
|
+
const isBridge = (mod) => mod.entities.length === 0;
|
|
119
|
+
/**
|
|
120
|
+
* Module codes related to `moduleCd` — the candidate set for a "connected
|
|
121
|
+
* modules" view of that module's diagram.
|
|
122
|
+
*
|
|
123
|
+
* A bridge module (`crm-fin`, `wms-fin`) usually owns no entities at all: it
|
|
124
|
+
* only adds extension columns to the entities of the modules it bridges. Its
|
|
125
|
+
* own diagram is therefore empty, and nothing in the entity graph points back
|
|
126
|
+
* at it either — the columns it contributes are stored on the *target* entity.
|
|
127
|
+
* So the relation is collected from three independent places:
|
|
128
|
+
*
|
|
129
|
+
* 1. declared dependencies — what this module was built on top of. This is
|
|
130
|
+
* the only signal that works for a bridge with no entities.
|
|
131
|
+
* 2. reverse dependencies — modules built on top of this one. A core module
|
|
132
|
+
* has no way of knowing which bridges were installed against it.
|
|
133
|
+
* 3. cross-module references — FK edges leaving or entering this module's
|
|
134
|
+
* entities, which still resolves links that skip a manifest declaration.
|
|
135
|
+
*
|
|
136
|
+
* The module itself and system modules are never included. Results come back in
|
|
137
|
+
* `response.modules` order so chip order and palette indices stay stable.
|
|
138
|
+
*/
|
|
139
|
+
export function findRelatedModules(response, moduleCd) {
|
|
140
|
+
const self = response.modules.find(mod => mod.moduleCd === moduleCd);
|
|
141
|
+
if (!self)
|
|
142
|
+
return [];
|
|
143
|
+
const related = new Set(self.dependencies ?? []);
|
|
144
|
+
for (const mod of response.modules) {
|
|
145
|
+
if (mod.moduleCd !== moduleCd && (mod.dependencies ?? []).includes(moduleCd)) {
|
|
146
|
+
related.add(mod.moduleCd);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const moduleOfEntity = new Map();
|
|
150
|
+
for (const mod of response.modules) {
|
|
151
|
+
for (const ent of mod.entities)
|
|
152
|
+
moduleOfEntity.set(ent.entityId, mod.moduleCd);
|
|
153
|
+
}
|
|
154
|
+
for (const mod of response.modules) {
|
|
155
|
+
for (const ent of mod.entities) {
|
|
156
|
+
for (const col of ent.columns) {
|
|
157
|
+
if (col.columnType !== 'R' || !col.refEntityId)
|
|
158
|
+
continue;
|
|
159
|
+
const targetModuleCd = moduleOfEntity.get(col.refEntityId);
|
|
160
|
+
if (!targetModuleCd || targetModuleCd === mod.moduleCd)
|
|
161
|
+
continue;
|
|
162
|
+
// Outgoing from this module, or incoming into it.
|
|
163
|
+
if (mod.moduleCd === moduleCd)
|
|
164
|
+
related.add(targetModuleCd);
|
|
165
|
+
else if (targetModuleCd === moduleCd)
|
|
166
|
+
related.add(mod.moduleCd);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// A bridge owns no entities, so on its own it draws nothing — a chip for one
|
|
171
|
+
// is only meaningful next to what it bridges. Pull each related bridge's own
|
|
172
|
+
// dependencies into the candidate set, which is how `fin` becomes reachable
|
|
173
|
+
// from crm's page at all: crm neither depends on fin nor references it, and
|
|
174
|
+
// the only thing tying them together is crm-fin.
|
|
175
|
+
for (const mod of response.modules) {
|
|
176
|
+
if (mod.moduleCd === moduleCd || !related.has(mod.moduleCd))
|
|
177
|
+
continue;
|
|
178
|
+
if (!isBridge(mod))
|
|
179
|
+
continue;
|
|
180
|
+
for (const dep of mod.dependencies ?? [])
|
|
181
|
+
related.add(dep);
|
|
182
|
+
}
|
|
183
|
+
return response.modules
|
|
184
|
+
.filter(mod => mod.moduleCd !== moduleCd && !mod.isSystem && related.has(mod.moduleCd))
|
|
185
|
+
.map(mod => mod.moduleCd);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Expand a chosen set of modules to one that actually draws something.
|
|
189
|
+
*
|
|
190
|
+
* Picking a bridge means "show me what this connects", but the bridge itself
|
|
191
|
+
* has no entities — its whole contribution is extension columns living on the
|
|
192
|
+
* entities of the modules it bridges. Selecting one alone would therefore change
|
|
193
|
+
* the URL and redraw nothing, so each bridge in the set brings its own drawable
|
|
194
|
+
* dependencies along.
|
|
195
|
+
*
|
|
196
|
+
* Expansion is one level deep: a bridge that depends on another bridge is not a
|
|
197
|
+
* shape any module in the wild has, and recursing would quietly pull in half the
|
|
198
|
+
* workspace. Returns the input order with the additions appended, deduped.
|
|
199
|
+
*/
|
|
200
|
+
export function expandBridgeSelection(response, moduleCds) {
|
|
201
|
+
const byCode = new Map(response.modules.map(mod => [mod.moduleCd, mod]));
|
|
202
|
+
const expanded = new Set(moduleCds);
|
|
203
|
+
for (const cd of moduleCds) {
|
|
204
|
+
const mod = byCode.get(cd);
|
|
205
|
+
if (!mod || !isBridge(mod))
|
|
206
|
+
continue;
|
|
207
|
+
for (const dep of mod.dependencies ?? []) {
|
|
208
|
+
const target = byCode.get(dep);
|
|
209
|
+
if (target && !target.isSystem && !isBridge(target))
|
|
210
|
+
expanded.add(dep);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return [...expanded];
|
|
214
|
+
}
|
package/dist/model-types.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface ModelModule {
|
|
|
15
15
|
license?: string;
|
|
16
16
|
authorName?: string;
|
|
17
17
|
tags?: string;
|
|
18
|
+
/** Module codes this module declares a dependency on (manifest `dependencies` keys). */
|
|
19
|
+
dependencies?: string[];
|
|
18
20
|
entities: ModelEntity[];
|
|
19
21
|
}
|
|
20
22
|
export interface ModelEntity {
|
|
@@ -25,6 +27,8 @@ export interface ModelEntity {
|
|
|
25
27
|
dbObject?: string;
|
|
26
28
|
toString?: string;
|
|
27
29
|
resLabel?: string;
|
|
30
|
+
/** Label for the viewer's culture, when a translation exists. Absent = fall back to `resLabel`. */
|
|
31
|
+
resLabelLocalized?: string;
|
|
28
32
|
resIcon?: string;
|
|
29
33
|
columns: ModelColumn[];
|
|
30
34
|
constraints: ModelConstraint[];
|
|
@@ -52,5 +56,7 @@ export interface ModelColumn {
|
|
|
52
56
|
thisKey?: string;
|
|
53
57
|
otherKey?: string;
|
|
54
58
|
resLabel?: string;
|
|
59
|
+
/** Label for the viewer's culture, when a translation exists. Absent = fall back to `resLabel`. */
|
|
60
|
+
resLabelLocalized?: string;
|
|
55
61
|
}
|
|
56
62
|
//# sourceMappingURL=model-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-types.d.ts","sourceRoot":"","sources":["../src/lib/model-types.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEvF,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"model-types.d.ts","sourceRoot":"","sources":["../src/lib/model-types.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEvF,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforge-core/diagram",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Entity-relationship and workspace diagram components for dForge models. Standalone Svelte 5 library with no dForge runtime dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dForge",
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
"dist"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@dforge-core/metadata": "0.0.
|
|
42
|
+
"@dforge-core/metadata": "0.0.22"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"svelte": "^5.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@sveltejs/package": "^2.5.
|
|
49
|
-
"@sveltejs/vite-plugin-svelte": "^7.
|
|
50
|
-
"svelte": "^5.
|
|
51
|
-
"typescript": "^6.0.
|
|
48
|
+
"@sveltejs/package": "^2.5.8",
|
|
49
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
50
|
+
"svelte": "^5.56.8",
|
|
51
|
+
"typescript": "^6.0.3"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "svelte-package",
|