@dforge-core/diagram 0.0.3 → 0.0.5
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/CHANGELOG.md +112 -0
- package/dist/SimpleDiagram/ModelDiagram.svelte +45 -23
- package/dist/SimpleDiagram/ModelDiagram.svelte.d.ts.map +1 -1
- package/dist/SimpleDiagram/SimpleDiagram.svelte +189 -44
- package/dist/SimpleDiagram/SimpleDiagram.svelte.d.ts +7 -0
- package/dist/SimpleDiagram/SimpleDiagram.svelte.d.ts.map +1 -1
- package/dist/SimpleDiagram/simple-layout.d.ts.map +1 -1
- package/dist/SimpleDiagram/simple-layout.js +13 -5
- package/dist/WorkspaceMap/WorkspaceMap.svelte +45 -23
- package/dist/WorkspaceMap/WorkspaceMap.svelte.d.ts.map +1 -1
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +2 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +2 -0
- 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 +59 -57
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@dforge-core/diagram` are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.0.4] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Nodes no longer scale wrongly in Safari/WebKit.** Entity nodes rendered inside an
|
|
13
|
+
SVG `<foreignObject>`, and WebKit does not apply a scaled SVG ancestor transform to
|
|
14
|
+
the HTML content of a `foreignObject` — so zooming moved the boxes without resizing
|
|
15
|
+
their contents. Nodes now render in an HTML overlay layer that carries the same
|
|
16
|
+
pan/zoom transform as the SVG `<g>` beneath it, which every browser applies the same
|
|
17
|
+
way.
|
|
18
|
+
|
|
19
|
+
The layer is `pointer-events: none` with each node opting back in, so empty space
|
|
20
|
+
still falls through to the SVG for panning, area-select and edge hit areas. Pointer
|
|
21
|
+
and double-click handlers moved from the `<svg>` up to the container, since events
|
|
22
|
+
now originate on both sides of that boundary; the delegated walk-up stops at the
|
|
23
|
+
container rather than the SVG element. Overlaid zoom controls are excluded from the
|
|
24
|
+
delegated handler so their clicks aren't read as a pan.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **`buildGraph` takes `localized` and `moduleColorIndex` options.**
|
|
29
|
+
|
|
30
|
+
`localized` draws `resLabelLocalized` in place of `resLabel` wherever a translation
|
|
31
|
+
exists, so a tenant that has translated its metadata can read the diagram in its own
|
|
32
|
+
language. The base label stays the default: the model browser is a developer view,
|
|
33
|
+
and `metadata.getModel` deliberately loads install-time labels.
|
|
34
|
+
|
|
35
|
+
`moduleColorIndex` overrides the default palette assignment of "position in
|
|
36
|
+
`modules`". Needed when `modules` is an arbitrary subset — a module plus the ones it
|
|
37
|
+
connects to — which must keep the colors those modules carry on their own diagrams.
|
|
38
|
+
`intraModuleEdgesOnly` and `colorOffset` are unchanged.
|
|
39
|
+
|
|
40
|
+
- **`findRelatedModules` and `expandBridgeSelection`**, for drawing a module alongside
|
|
41
|
+
its neighbours. A bridge module (`crm-fin`, `wms-fin`) owns no tables of its own — it
|
|
42
|
+
only adds extension columns to the entities of the modules it bridges — so its
|
|
43
|
+
diagram was empty, hiding the one thing the module does.
|
|
44
|
+
|
|
45
|
+
`findRelatedModules(response, moduleCd)` returns the modules on either end of a
|
|
46
|
+
dependency plus those reachable through a cross-module reference.
|
|
47
|
+
`expandBridgeSelection(response, moduleCds)` pulls in a selected bridge's non-system,
|
|
48
|
+
non-bridge dependencies, so picking the bridge alone still draws something.
|
|
49
|
+
|
|
50
|
+
- **`entityLabel(ent, localized?)` and `columnLabel(col, localized?)`** — the label
|
|
51
|
+
fallback used internally by `buildGraph`, exported so a consumer rendering its own
|
|
52
|
+
chrome resolves labels the same way.
|
|
53
|
+
|
|
54
|
+
- **`SimpleDiagram` accepts `fitKey`** (`string | number`, optional). Changing it
|
|
55
|
+
re-fits the content to the viewport. Added for the print view, where the diagram is
|
|
56
|
+
laid out into a fixed sheet box rather than the screen viewport, and the fit has to
|
|
57
|
+
be recomputed when that box appears.
|
|
58
|
+
|
|
59
|
+
- **Three optional fields on the model contract**, mirroring what `metadata.getModel`
|
|
60
|
+
now returns: `ModelModule.dependencies` (manifest `dependencies` keys),
|
|
61
|
+
`ModelEntity.resLabelLocalized` and `ModelColumn.resLabelLocalized` (label for the
|
|
62
|
+
viewer's culture, absent when no translation exists).
|
|
63
|
+
|
|
64
|
+
All of the above are additive. Component props are unchanged apart from the optional
|
|
65
|
+
`fitKey`, so 0.0.3 code compiles and behaves the same against this release.
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- `@dforge-core/metadata` moves from `0.0.2` to `0.0.22`. It is a type-only import
|
|
70
|
+
(`model-types.ts` pulls `BaseDatatypeCd`, `ColumnTypeCd` and `FieldTypeCd` from it),
|
|
71
|
+
so the jump carries no runtime code.
|
|
72
|
+
|
|
73
|
+
## [0.0.3] — 2026-06-13
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **The model contract's code unions come from `@dforge-core/metadata`.**
|
|
78
|
+
`model-types.ts` had restated `BaseDatatypeCd`, `ColumnTypeCd` and `FieldTypeCd`
|
|
79
|
+
inline; it now type-imports them from the package that owns those registries, which
|
|
80
|
+
is the same source the SDK reads. Adds `@dforge-core/metadata` as this package's only
|
|
81
|
+
dependency.
|
|
82
|
+
|
|
83
|
+
## [0.0.2] — 2026-06-12
|
|
84
|
+
|
|
85
|
+
### Removed
|
|
86
|
+
|
|
87
|
+
- **The `bootstrap-icons` dependency.** `IconButton` rendered icons as Bootstrap Icons
|
|
88
|
+
font classes, which silently drew nothing unless the host page happened to load that
|
|
89
|
+
stylesheet. The handful of icons the diagram controls need are now inline SVG paths
|
|
90
|
+
in `icons.ts`, so the package renders correctly on any page.
|
|
91
|
+
|
|
92
|
+
## [0.0.1] — 2026-06-09
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
|
|
96
|
+
- Initial release, published so the diagram can be used outside this repo (the VS Code
|
|
97
|
+
extension is the first such consumer). `SimpleDiagram`, `ModelDiagram`, `EntityNode`,
|
|
98
|
+
`WorkspaceMap` and `ModuleCard`, with the layout, edge-routing, graph-building,
|
|
99
|
+
geometry, pan/zoom and i18n helpers behind them.
|
|
100
|
+
- `model-types.ts` — a local mirror of the dForge API `metadata.getModel` shape, so the
|
|
101
|
+
package types the payload it draws without depending on the SDK.
|
|
102
|
+
- MIT `LICENSE` and `README.md`.
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
|
|
106
|
+
- Renamed from `@dforge/diagram` to `@dforge-core/diagram`.
|
|
107
|
+
|
|
108
|
+
### Removed
|
|
109
|
+
|
|
110
|
+
- **The `@dforge/sdk` and `@dforge/ui` dependencies**, both workspace-only packages
|
|
111
|
+
that made this one unpublishable. The single component used from `@dforge/ui`,
|
|
112
|
+
`IconButton`, is now local. `svelte` is the only peer dependency.
|
|
@@ -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;
|
|
@@ -34,6 +41,66 @@
|
|
|
34
41
|
let draggingSeg = $state<{ edgeId: string; segIndex: number; orientation: 'H' | 'V' } | null>(null);
|
|
35
42
|
let patternId = `grid-${Math.random().toString(36).slice(2, 8)}`;
|
|
36
43
|
|
|
44
|
+
// --- Undo / redo of layout edits (node moves, edge reshapes, resets) ---
|
|
45
|
+
|
|
46
|
+
interface LayoutSnapshot {
|
|
47
|
+
dragOffsets: Record<string, { dx: number; dy: number }>;
|
|
48
|
+
edgeOverrides: Record<string, Point[]>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const HISTORY_LIMIT = 100;
|
|
52
|
+
let undoStack = $state.raw<LayoutSnapshot[]>([]);
|
|
53
|
+
let redoStack = $state.raw<LayoutSnapshot[]>([]);
|
|
54
|
+
let gestureStart: LayoutSnapshot | null = null;
|
|
55
|
+
let gestureMoved = false;
|
|
56
|
+
|
|
57
|
+
const takeSnapshot = (): LayoutSnapshot =>
|
|
58
|
+
$state.snapshot({ dragOffsets, edgeOverrides }) as LayoutSnapshot;
|
|
59
|
+
|
|
60
|
+
function pushHistory(snap: LayoutSnapshot) {
|
|
61
|
+
undoStack = [...undoStack.slice(-(HISTORY_LIMIT - 1)), snap];
|
|
62
|
+
redoStack = [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Record the gesture's starting state, if the gesture changed anything. */
|
|
66
|
+
function commitGesture() {
|
|
67
|
+
gestureMoved = false;
|
|
68
|
+
if (!gestureStart) return;
|
|
69
|
+
if (JSON.stringify(gestureStart) !== JSON.stringify(takeSnapshot())) pushHistory(gestureStart);
|
|
70
|
+
gestureStart = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function applySnapshot(snap: LayoutSnapshot) {
|
|
74
|
+
dragOffsets = snap.dragOffsets;
|
|
75
|
+
edgeOverrides = snap.edgeOverrides;
|
|
76
|
+
autoEdges = routeWithOffsets(snap.dragOffsets);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function undo() {
|
|
80
|
+
const snap = undoStack.at(-1);
|
|
81
|
+
if (!snap) return;
|
|
82
|
+
redoStack = [...redoStack, takeSnapshot()];
|
|
83
|
+
undoStack = undoStack.slice(0, -1);
|
|
84
|
+
applySnapshot(snap);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function redo() {
|
|
88
|
+
const snap = redoStack.at(-1);
|
|
89
|
+
if (!snap) return;
|
|
90
|
+
undoStack = [...undoStack, takeSnapshot()];
|
|
91
|
+
redoStack = redoStack.slice(0, -1);
|
|
92
|
+
applySnapshot(snap);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
96
|
+
if (!(e.metaKey || e.ctrlKey) || e.altKey || dragging || draggingSeg) return;
|
|
97
|
+
const key = e.key.toLowerCase();
|
|
98
|
+
if (key === 'z' && !e.shiftKey) undo();
|
|
99
|
+
else if ((key === 'z' && e.shiftKey) || (key === 'y' && e.ctrlKey)) redo();
|
|
100
|
+
else return;
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
}
|
|
103
|
+
|
|
37
104
|
const pz = createPanZoom({
|
|
38
105
|
get minZoom() { return minZoom; },
|
|
39
106
|
get maxZoom() { return maxZoom; },
|
|
@@ -80,18 +147,8 @@
|
|
|
80
147
|
edgeOverrides = saved.edgeOverrides;
|
|
81
148
|
pz.setTransform(saved.transform);
|
|
82
149
|
restoredTransform = true;
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
const posMap: Record<string, DiagramNode> = {};
|
|
86
|
-
for (const n of nodes) {
|
|
87
|
-
const off = offsets[n.id];
|
|
88
|
-
posMap[n.id] = off ? { ...n, x: n.x + off.dx, y: n.y + off.dy } : n;
|
|
89
|
-
}
|
|
90
|
-
const posLookup = (id: string) => posMap[id];
|
|
91
|
-
autoEdges = edges.map((e) => {
|
|
92
|
-
if (!movedIds.has(e.source) && !movedIds.has(e.target)) return e;
|
|
93
|
-
return routeEdge(e, posLookup);
|
|
94
|
-
});
|
|
150
|
+
// Use local `offsets` to avoid reading $state
|
|
151
|
+
autoEdges = routeWithOffsets(offsets);
|
|
95
152
|
} else if (saved) {
|
|
96
153
|
dragOffsets = {};
|
|
97
154
|
edgeOverrides = saved.edgeOverrides;
|
|
@@ -106,8 +163,25 @@
|
|
|
106
163
|
}
|
|
107
164
|
selectedNodeIds = new Set();
|
|
108
165
|
selectedEdgeId = null;
|
|
166
|
+
undoStack = [];
|
|
167
|
+
redoStack = [];
|
|
109
168
|
});
|
|
110
169
|
|
|
170
|
+
/** Auto-route edges for the given node offsets; edges between unmoved nodes keep their layout route. */
|
|
171
|
+
function routeWithOffsets(offsets: Record<string, { dx: number; dy: number }>): ResolvedEdge[] {
|
|
172
|
+
const movedIds = new Set(Object.keys(offsets));
|
|
173
|
+
const posMap: Record<string, DiagramNode> = {};
|
|
174
|
+
for (const n of nodes) {
|
|
175
|
+
const off = offsets[n.id];
|
|
176
|
+
posMap[n.id] = off ? { ...n, x: n.x + off.dx, y: n.y + off.dy } : n;
|
|
177
|
+
}
|
|
178
|
+
const posLookup = (id: string) => posMap[id];
|
|
179
|
+
return edges.map((e) => {
|
|
180
|
+
if (!movedIds.has(e.source) && !movedIds.has(e.target)) return e;
|
|
181
|
+
return routeEdge(e, posLookup);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
111
185
|
// Derive node positions with drag offsets applied
|
|
112
186
|
const nodePositions = $derived.by(() => {
|
|
113
187
|
const pos: Record<string, DiagramNode> = {};
|
|
@@ -173,10 +247,14 @@
|
|
|
173
247
|
|
|
174
248
|
// --- Delegated event helpers ---
|
|
175
249
|
|
|
176
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* Walk up from event target to find a data attribute. Delegation runs on the
|
|
252
|
+
* container, so nodes (HTML overlay) and edges/handles (SVG) both resolve here;
|
|
253
|
+
* we stop at the container element.
|
|
254
|
+
*/
|
|
177
255
|
function closest(target: Element, attr: string): Element | null {
|
|
178
256
|
let el: Element | null = target;
|
|
179
|
-
while (el && el !==
|
|
257
|
+
while (el && el !== containerEl) {
|
|
180
258
|
if (el.hasAttribute(attr)) return el;
|
|
181
259
|
// parentElement is null at HTML↔SVG boundary in some browsers; fall back to parentNode
|
|
182
260
|
el = el.parentElement ?? (el.parentNode as Element | null);
|
|
@@ -193,6 +271,14 @@
|
|
|
193
271
|
function handlePointerDown(e: PointerEvent) {
|
|
194
272
|
const target = e.target as Element;
|
|
195
273
|
|
|
274
|
+
// Ignore interactions that originate on the overlaid controls (buttons
|
|
275
|
+
// handle their own clicks). Handlers now live on the container, so control
|
|
276
|
+
// clicks would otherwise reach this delegated handler.
|
|
277
|
+
if (target.closest('.diagram-controls')) return;
|
|
278
|
+
|
|
279
|
+
// Take focus so Ctrl/Cmd+Z reaches handleKeyDown
|
|
280
|
+
containerEl.focus({ preventScroll: true });
|
|
281
|
+
|
|
196
282
|
// Segment drag handle
|
|
197
283
|
const handle = closest(target, 'data-handle');
|
|
198
284
|
if (handle) {
|
|
@@ -270,6 +356,7 @@
|
|
|
270
356
|
}
|
|
271
357
|
if (pz.onPointerMove(e)) return;
|
|
272
358
|
if (draggingSeg) {
|
|
359
|
+
gestureMoved = true;
|
|
273
360
|
const pos = pz.clientToSvg(e.clientX, e.clientY);
|
|
274
361
|
const pts = getEdgePoints(draggingSeg.edgeId);
|
|
275
362
|
if (pts.length === 0) return;
|
|
@@ -278,6 +365,7 @@
|
|
|
278
365
|
return;
|
|
279
366
|
}
|
|
280
367
|
if (dragging) {
|
|
368
|
+
gestureMoved = true;
|
|
281
369
|
const pos = pz.clientToSvg(e.clientX, e.clientY);
|
|
282
370
|
const newOffsets = { ...dragOffsets };
|
|
283
371
|
for (const [nid, off] of Object.entries(dragging.offsets)) {
|
|
@@ -318,12 +406,16 @@
|
|
|
318
406
|
if (draggingSeg) {
|
|
319
407
|
const edgeId = draggingSeg.edgeId;
|
|
320
408
|
const pts = edgeOverrides[edgeId];
|
|
321
|
-
|
|
409
|
+
// A handle click without a move must not turn the auto route into an override
|
|
410
|
+
if (!gestureMoved && gestureStart) {
|
|
411
|
+
edgeOverrides = gestureStart.edgeOverrides;
|
|
412
|
+
} else if (pts) {
|
|
322
413
|
edgeOverrides = { ...edgeOverrides, [edgeId]: consolidatePoints(pts) };
|
|
323
414
|
}
|
|
324
415
|
}
|
|
325
416
|
dragging = null;
|
|
326
417
|
draggingSeg = null;
|
|
418
|
+
commitGesture();
|
|
327
419
|
}
|
|
328
420
|
|
|
329
421
|
// --- Node interaction ---
|
|
@@ -351,12 +443,15 @@
|
|
|
351
443
|
offsets[nid] = { offsetX: pos.x - np.x, offsetY: pos.y - np.y };
|
|
352
444
|
}
|
|
353
445
|
}
|
|
446
|
+
gestureStart = takeSnapshot();
|
|
354
447
|
dragging = { nodeId, offsets };
|
|
355
448
|
}
|
|
356
449
|
|
|
357
450
|
// --- Edge interaction ---
|
|
358
451
|
|
|
359
452
|
function resetEdgeRoute(edgeId: string) {
|
|
453
|
+
if (!edgeOverrides[edgeId]) return;
|
|
454
|
+
pushHistory(takeSnapshot());
|
|
360
455
|
const { [edgeId]: _, ...rest } = edgeOverrides;
|
|
361
456
|
edgeOverrides = rest;
|
|
362
457
|
}
|
|
@@ -364,6 +459,7 @@
|
|
|
364
459
|
function startSegDrag(_e: PointerEvent, edgeId: string, segIndex: number, orientation: 'H' | 'V') {
|
|
365
460
|
const pts = getEdgePoints(edgeId);
|
|
366
461
|
if (pts.length === 0) return;
|
|
462
|
+
gestureStart = takeSnapshot();
|
|
367
463
|
|
|
368
464
|
if (!edgeOverrides[edgeId]) {
|
|
369
465
|
edgeOverrides = { ...edgeOverrides, [edgeId]: pts.map((p) => ({ ...p })) };
|
|
@@ -377,6 +473,7 @@
|
|
|
377
473
|
function startSplitDrag(_e: PointerEvent, edgeId: string, segIndex: number, orientation: 'H' | 'V') {
|
|
378
474
|
const pts = getEdgePoints(edgeId);
|
|
379
475
|
if (pts.length === 0) return;
|
|
476
|
+
gestureStart = takeSnapshot();
|
|
380
477
|
|
|
381
478
|
const jog = insertJog(pts, segIndex, orientation);
|
|
382
479
|
|
|
@@ -405,6 +502,7 @@
|
|
|
405
502
|
}
|
|
406
503
|
|
|
407
504
|
function resetLayout() {
|
|
505
|
+
if (Object.keys(dragOffsets).length > 0 || Object.keys(edgeOverrides).length > 0) pushHistory(takeSnapshot());
|
|
408
506
|
dragOffsets = {};
|
|
409
507
|
edgeOverrides = {};
|
|
410
508
|
selectedNodeIds = new Set();
|
|
@@ -413,6 +511,25 @@
|
|
|
413
511
|
requestAnimationFrame(() => fitView());
|
|
414
512
|
}
|
|
415
513
|
|
|
514
|
+
// Refit on fitKey change. Skips the very first run so it doesn't fight the
|
|
515
|
+
// restored transform on mount.
|
|
516
|
+
//
|
|
517
|
+
// The first fit is synchronous, not deferred to a frame: a host entering a
|
|
518
|
+
// print layout flips fitKey inside `beforeprint` and flushes, and the print
|
|
519
|
+
// snapshot is taken as soon as that handler returns — anything left to rAF
|
|
520
|
+
// misses it entirely. Measuring here forces the pending layout, so the new
|
|
521
|
+
// container size is already available. The rAF pass after it is a cheap
|
|
522
|
+
// idempotent safety net for hosts whose box settles a frame late (CSS
|
|
523
|
+
// transitions on the shell); re-fitting to the same bounds is a no-op.
|
|
524
|
+
let fitKeySeen = false;
|
|
525
|
+
$effect(() => {
|
|
526
|
+
fitKey; // track
|
|
527
|
+
if (fitKey === undefined) return;
|
|
528
|
+
if (!fitKeySeen) { fitKeySeen = true; return; }
|
|
529
|
+
fitView();
|
|
530
|
+
requestAnimationFrame(() => fitView());
|
|
531
|
+
});
|
|
532
|
+
|
|
416
533
|
onMount(() => {
|
|
417
534
|
svgEl.addEventListener('wheel', pz.handleWheel, { passive: false });
|
|
418
535
|
if (!restoredTransform) {
|
|
@@ -425,15 +542,20 @@
|
|
|
425
542
|
});
|
|
426
543
|
</script>
|
|
427
544
|
|
|
428
|
-
|
|
429
|
-
|
|
545
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
546
|
+
<div
|
|
547
|
+
class="model-diagram"
|
|
548
|
+
bind:this={containerEl}
|
|
549
|
+
tabindex="-1"
|
|
550
|
+
onkeydown={handleKeyDown}
|
|
551
|
+
onpointerdown={handlePointerDown}
|
|
552
|
+
onpointermove={handlePointerMove}
|
|
553
|
+
onpointerup={handlePointerUp}
|
|
554
|
+
ondblclick={handleDblClick}
|
|
555
|
+
>
|
|
430
556
|
<svg
|
|
431
557
|
bind:this={svgEl}
|
|
432
558
|
role="application"
|
|
433
|
-
onpointerdown={handlePointerDown}
|
|
434
|
-
onpointermove={handlePointerMove}
|
|
435
|
-
onpointerup={handlePointerUp}
|
|
436
|
-
ondblclick={handleDblClick}
|
|
437
559
|
>
|
|
438
560
|
<defs>
|
|
439
561
|
<pattern id={patternId} width="20" height="20" patternUnits="userSpaceOnUse"
|
|
@@ -503,24 +625,6 @@
|
|
|
503
625
|
{/if}
|
|
504
626
|
{/each}
|
|
505
627
|
|
|
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
628
|
<!-- Selected edge on top of nodes -->
|
|
525
629
|
{#if selectedEdgeId}
|
|
526
630
|
{#each resolvedEdges as edge (edge.id)}
|
|
@@ -587,11 +691,39 @@
|
|
|
587
691
|
</g>
|
|
588
692
|
</svg>
|
|
589
693
|
|
|
694
|
+
<!--
|
|
695
|
+
Nodes render in an HTML overlay layer (not SVG <foreignObject>) so they
|
|
696
|
+
scale correctly in Safari/WebKit, which does not apply a scaled SVG
|
|
697
|
+
ancestor transform to foreignObject HTML content. The layer shares the
|
|
698
|
+
same pan/zoom transform as the SVG <g> above. Pointer events are handled
|
|
699
|
+
by delegation on the container (see handlePointerDown), so nodes only need
|
|
700
|
+
their data-node-id and to opt back into pointer events.
|
|
701
|
+
-->
|
|
702
|
+
<div
|
|
703
|
+
class="node-layer"
|
|
704
|
+
style:transform="translate({pz.transform.x}px, {pz.transform.y}px) scale({pz.transform.k})"
|
|
705
|
+
style:transform-origin="0 0"
|
|
706
|
+
>
|
|
707
|
+
{#each nodes as node (node.id)}
|
|
708
|
+
{@const pos = nodePositions[node.id] ?? node}
|
|
709
|
+
<div
|
|
710
|
+
class="diagram-node"
|
|
711
|
+
class:selected={selectedNodeIds.has(node.id)}
|
|
712
|
+
style="left: {pos.x}px; top: {pos.y}px; width: {node.width}px; height: {node.height}px;"
|
|
713
|
+
data-node-id={node.id}
|
|
714
|
+
>
|
|
715
|
+
<EntityNode data={node.data} />
|
|
716
|
+
</div>
|
|
717
|
+
{/each}
|
|
718
|
+
</div>
|
|
719
|
+
|
|
590
720
|
<!-- Controls -->
|
|
591
721
|
<div class="diagram-controls">
|
|
592
722
|
<IconButton icon="bi-plus-lg" title={t.zoom_in} size="sm" onclick={() => pz.zoomBy(1.3)} />
|
|
593
723
|
<IconButton icon="bi-dash-lg" title={t.zoom_out} size="sm" onclick={() => pz.zoomBy(1 / 1.3)} />
|
|
594
724
|
<IconButton icon="bi-fullscreen" title={t.fit_view} size="sm" onclick={fitView} />
|
|
725
|
+
<IconButton icon="bi-arrow-90deg-left" title={t.undo} size="sm" disabled={undoStack.length === 0} onclick={undo} />
|
|
726
|
+
<IconButton icon="bi-arrow-90deg-right" title={t.redo} size="sm" disabled={redoStack.length === 0} onclick={redo} />
|
|
595
727
|
<IconButton icon="bi-arrow-counterclockwise" title={t.reset_layout} size="sm" onclick={resetLayout} />
|
|
596
728
|
</div>
|
|
597
729
|
</div>
|
|
@@ -602,6 +734,7 @@
|
|
|
602
734
|
height: 100%;
|
|
603
735
|
position: relative;
|
|
604
736
|
overflow: hidden;
|
|
737
|
+
outline: none;
|
|
605
738
|
}
|
|
606
739
|
|
|
607
740
|
.model-diagram svg {
|
|
@@ -615,16 +748,28 @@
|
|
|
615
748
|
cursor: grabbing;
|
|
616
749
|
}
|
|
617
750
|
|
|
618
|
-
.
|
|
751
|
+
.node-layer {
|
|
752
|
+
position: absolute;
|
|
753
|
+
inset: 0;
|
|
754
|
+
/* Empty areas let pointer events fall through to the SVG (pan / area
|
|
755
|
+
select / edge hit areas); individual nodes re-enable events below.
|
|
756
|
+
transform / transform-origin are applied via style: directives so the
|
|
757
|
+
layer tracks the SVG <g> transform exactly. */
|
|
758
|
+
pointer-events: none;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.diagram-node {
|
|
762
|
+
position: absolute;
|
|
763
|
+
box-sizing: border-box;
|
|
764
|
+
pointer-events: auto;
|
|
619
765
|
cursor: grab;
|
|
620
|
-
overflow: visible;
|
|
621
766
|
}
|
|
622
767
|
|
|
623
|
-
.diagram-node
|
|
768
|
+
.diagram-node.selected {
|
|
624
769
|
filter: drop-shadow(0 0 4px var(--bs-primary, #0d6efd));
|
|
625
770
|
}
|
|
626
771
|
|
|
627
|
-
.diagram-node
|
|
772
|
+
.diagram-node:active {
|
|
628
773
|
cursor: grabbing;
|
|
629
774
|
}
|
|
630
775
|
|
|
@@ -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;AAooBF,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simple-layout.d.ts","sourceRoot":"","sources":["../../src/lib/SimpleDiagram/simple-layout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"simple-layout.d.ts","sourceRoot":"","sources":["../../src/lib/SimpleDiagram/simple-layout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,kBAAkB,CAAC;AAetF,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;AA0HjE;;GAEG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,WAAW,EAAE,EACpB,KAAK,EAAE,WAAW,EAAE,GAClB;IAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,CAejD;AAqFD;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,WAAW,EAAE,EACpB,KAAK,EAAE,WAAW,EAAE,GAClB;IAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,CAmCjD;AA+GD;;GAEG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,UAAU,GAChB,YAAY,CAcd;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC5B,KAAK,EAAE,WAAW,EAAE,EACpB,KAAK,EAAE,WAAW,EAAE,GAClB,YAAY,EAAE,CAIhB"}
|
|
@@ -7,6 +7,7 @@ const HEADER_HEIGHT = 30;
|
|
|
7
7
|
const ROW_HEIGHT = 22;
|
|
8
8
|
const MODULE_BLOCK_GAP = 80;
|
|
9
9
|
const OVERLAP_MARGIN = 30;
|
|
10
|
+
const MIN_ROUTE_GAP = 12;
|
|
10
11
|
/** Group items by a key function into a Map of arrays. */
|
|
11
12
|
function groupBy(items, key) {
|
|
12
13
|
const map = new Map();
|
|
@@ -271,24 +272,31 @@ function routeSelfEdge(edge, node, srcY, tgtY) {
|
|
|
271
272
|
}
|
|
272
273
|
/** Route stubs that avoid crossing through the connected nodes. */
|
|
273
274
|
function stubRoute(src, tgt, dir, srcNode, tgtNode) {
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
let stubSrc = src.x + dir * STUB_LENGTH;
|
|
276
|
+
let stubTgt = tgt.x - dir * STUB_LENGTH;
|
|
276
277
|
// Find midY that doesn't cross through either node
|
|
277
278
|
const srcBottom = srcNode.y + srcNode.height;
|
|
278
279
|
const tgtTop = tgtNode.y;
|
|
279
280
|
const srcTop = srcNode.y;
|
|
280
281
|
const tgtBottom = tgtNode.y + tgtNode.height;
|
|
281
282
|
let midY;
|
|
282
|
-
if (srcBottom +
|
|
283
|
+
if (srcBottom + MIN_ROUTE_GAP < tgtTop) {
|
|
283
284
|
// Gap between nodes — route through it
|
|
284
285
|
midY = (srcBottom + tgtTop) / 2;
|
|
285
286
|
}
|
|
286
|
-
else if (tgtBottom +
|
|
287
|
+
else if (tgtBottom + MIN_ROUTE_GAP < srcTop) {
|
|
287
288
|
midY = (tgtBottom + srcTop) / 2;
|
|
288
289
|
}
|
|
289
290
|
else {
|
|
290
|
-
// Nodes overlap vertically — route above both
|
|
291
|
+
// Nodes overlap vertically — route above both; a stub that would land inside the other node moves outside both
|
|
291
292
|
midY = Math.min(srcTop, tgtTop) - OVERLAP_MARGIN;
|
|
293
|
+
const left = Math.min(srcNode.x, tgtNode.x) - STUB_LENGTH;
|
|
294
|
+
const right = Math.max(srcNode.x + srcNode.width, tgtNode.x + tgtNode.width) + STUB_LENGTH;
|
|
295
|
+
const inside = (x, n) => x > n.x && x < n.x + n.width;
|
|
296
|
+
if (inside(stubSrc, tgtNode))
|
|
297
|
+
stubSrc = dir > 0 ? right : left;
|
|
298
|
+
if (inside(stubTgt, srcNode))
|
|
299
|
+
stubTgt = dir > 0 ? left : right;
|
|
292
300
|
}
|
|
293
301
|
return [
|
|
294
302
|
src,
|
|
@@ -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/i18n.d.ts
CHANGED
package/dist/i18n.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/lib/i18n.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/lib/i18n.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,uBAAuB,EAAE,mBAUrC,CAAC;AAIF,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAE9E;AAED,wBAAgB,sBAAsB,IAAI,mBAAmB,CAK5D"}
|
package/dist/i18n.js
CHANGED
package/dist/icons.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../src/lib/icons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../src/lib/icons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAaxC,CAAC"}
|
package/dist/icons.js
CHANGED
|
@@ -13,5 +13,7 @@ export const ICONS = {
|
|
|
13
13
|
'bi-plus-lg': '<path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2"/>',
|
|
14
14
|
'bi-dash-lg': '<path fill-rule="evenodd" d="M2 8a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11A.5.5 0 0 1 2 8"/>',
|
|
15
15
|
'bi-fullscreen': '<path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5M.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5m15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5"/>',
|
|
16
|
+
'bi-arrow-90deg-left': '<path fill-rule="evenodd" d="M1.146 4.854a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H12.5A2.5 2.5 0 0 1 15 6.5v8a.5.5 0 0 1-1 0v-8A1.5 1.5 0 0 0 12.5 5H2.707l3.147 3.146a.5.5 0 1 1-.708.708z"/>',
|
|
17
|
+
'bi-arrow-90deg-right': '<path fill-rule="evenodd" d="M14.854 4.854a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 4H3.5A2.5 2.5 0 0 0 1 6.5v8a.5.5 0 0 0 1 0v-8A1.5 1.5 0 0 1 3.5 5h9.793l-3.147 3.146a.5.5 0 0 0 .708.708z"/>',
|
|
16
18
|
'bi-arrow-counterclockwise': '<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2z"/><path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466"/>',
|
|
17
19
|
};
|
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,58 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
2
|
+
"name": "@dforge-core/diagram",
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "Entity-relationship and workspace diagram components for dForge models. Standalone Svelte 5 library with no dForge runtime dependencies.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "dForge",
|
|
7
|
+
"homepage": "https://github.com/iash44/dForge-core/tree/main/clients/packages/diagram#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/iash44/dForge-core.git",
|
|
11
|
+
"directory": "clients/packages/diagram"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/iash44/dForge-core/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"svelte",
|
|
18
|
+
"diagram",
|
|
19
|
+
"erd",
|
|
20
|
+
"entity-relationship",
|
|
21
|
+
"dforge",
|
|
22
|
+
"visualization"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"svelte": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"source": "./src/lib/index.ts",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"svelte": "./dist/index.js",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "svelte-package",
|
|
44
|
+
"dev": "svelte-package --watch",
|
|
45
|
+
"prepack": "pnpm build",
|
|
46
|
+
"package": "pnpm pack"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@dforge-core/metadata": "workspace:*"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"svelte": "^5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@sveltejs/package": "^2.5.8",
|
|
56
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
57
|
+
"svelte": "^5.56.8",
|
|
58
|
+
"typescript": "^6.0.3"
|
|
59
|
+
}
|
|
60
|
+
}
|