@k-4u/resource-mapper-core 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/coverage/junit.xml +17 -17
- package/coverage/test-results.json +1 -1
- package/data/services/api/api-gateway.yaml +18 -18
- package/data/services/api/group-info.yaml +7 -7
- package/data/services/api/lambda-orders.yaml +21 -21
- package/data/services/api/lambda-products.yaml +15 -15
- package/data/services/api/lambda-users.yaml +15 -15
- package/data/services/compute/alb.yaml +15 -15
- package/data/services/compute/ecs-inventory.yaml +16 -16
- package/data/services/compute/ecs-notification.yaml +15 -15
- package/data/services/compute/group-info.yaml +7 -7
- package/data/services/data/dynamodb-notifications.yaml +12 -12
- package/data/services/data/dynamodb-orders.yaml +9 -9
- package/data/services/data/dynamodb-products.yaml +9 -9
- package/data/services/data/dynamodb-users.yaml +9 -9
- package/data/services/data/group-info.yaml +7 -7
- package/data/services/data/rds-postgres.yaml +9 -9
- package/data/services/data/redis.yaml +10 -10
- package/data/services/frontend/cloudfront.yaml +12 -12
- package/data/services/frontend/group-info.yaml +7 -7
- package/data/services/frontend/route53.yaml +15 -15
- package/data/services/frontend/s3-website.yaml +9 -9
- package/data/teams/cloud-shepherds.yaml +15 -15
- package/data/teams/data-wizards.yaml +15 -15
- package/data/teams/interface-architects.yaml +18 -18
- package/e2e/demo.test.ts +54 -54
- package/e2e/header-toolbar.spec.ts +53 -53
- package/e2e/layout.spec.ts +30 -30
- package/package.json +74 -69
- package/playwright.config.ts +10 -10
- package/plugins/mapper-data-plugin.ts +32 -32
- package/project.json +22 -22
- package/src/app.css +125 -125
- package/src/app.d.ts +31 -31
- package/src/app.html +11 -11
- package/src/lib/assets/favicon.svg +18 -18
- package/src/lib/components/EmptyState.svelte +37 -37
- package/src/lib/components/ErrorDisplay.svelte +82 -82
- package/src/lib/components/FlowCanvas.svelte +223 -223
- package/src/lib/components/GenericSidebarCard.svelte +43 -43
- package/src/lib/components/GroupDetailSidebar.svelte +31 -31
- package/src/lib/components/Header.svelte +57 -57
- package/src/lib/components/Legend.svelte +25 -25
- package/src/lib/components/LoadingOverlay.svelte +42 -42
- package/src/lib/components/LoadingSpinner.svelte +10 -10
- package/src/lib/components/ServiceDetailSidebar.svelte +89 -89
- package/src/lib/components/TeamContactCard.svelte +166 -166
- package/src/lib/components/flow/ExternalNode.svelte +45 -45
- package/src/lib/components/flow/MainGroupNode.svelte +24 -24
- package/src/lib/components/flow/ServiceGroupNode.svelte +17 -17
- package/src/lib/components/flow/ServiceNode.svelte +40 -40
- package/src/lib/components/flow/SnakeEdge.svelte +206 -206
- package/src/lib/components/flow/index.ts +5 -5
- package/src/lib/components/index.ts +12 -12
- package/src/lib/data/connections.ts +26 -26
- package/src/lib/data/groups.ts +11 -11
- package/src/lib/data/services.ts +12 -12
- package/src/lib/data/teams.ts +11 -11
- package/src/lib/index.ts +1 -1
- package/src/lib/state/theme.svelte.ts +21 -21
- package/src/lib/stores/diagram.ts +6 -6
- package/src/lib/stores/routingStore.test.ts +133 -133
- package/src/lib/stores/routingStore.ts +232 -232
- package/src/lib/utils/awsIcons.ts +117 -117
- package/src/lib/utils/flow/groupOverviewGraph.ts +73 -73
- package/src/lib/utils/flow/helpers.ts +14 -14
- package/src/lib/utils/flow/layout.test.ts +271 -271
- package/src/lib/utils/flow/layout.ts +240 -240
- package/src/lib/utils/flow/serviceIds.ts +4 -4
- package/src/lib/utils/flow/servicesGraph.test.ts +119 -119
- package/src/lib/utils/flow/servicesGraph.ts +258 -258
- package/src/routes/+error.svelte +36 -36
- package/src/routes/+layout.svelte +27 -27
- package/src/routes/+page.svelte +81 -81
- package/src/routes/+page.ts +31 -31
- package/src/routes/group/[groupId]/+page.svelte +102 -102
- package/src/routes/group/[groupId]/+page.ts +48 -48
- package/static/static/robots.txt +3 -3
- package/svelte.config.js +27 -27
- package/tailwind.config.js +12 -12
- package/tsconfig.json +22 -22
- package/vite.config.ts +80 -80
- package/.aws-icons-last-updated +0 -1
- package/.svelte-kit/ambient.d.ts +0 -263
- package/.svelte-kit/generated/client/app.js +0 -31
- package/.svelte-kit/generated/client/matchers.js +0 -1
- package/.svelte-kit/generated/client/nodes/0.js +0 -1
- package/.svelte-kit/generated/client/nodes/1.js +0 -1
- package/.svelte-kit/generated/client/nodes/2.js +0 -3
- package/.svelte-kit/generated/client/nodes/3.js +0 -3
- package/.svelte-kit/generated/client-optimized/app.js +0 -31
- package/.svelte-kit/generated/client-optimized/matchers.js +0 -1
- package/.svelte-kit/generated/client-optimized/nodes/0.js +0 -1
- package/.svelte-kit/generated/client-optimized/nodes/1.js +0 -1
- package/.svelte-kit/generated/client-optimized/nodes/2.js +0 -3
- package/.svelte-kit/generated/client-optimized/nodes/3.js +0 -3
- package/.svelte-kit/generated/root.js +0 -3
- package/.svelte-kit/generated/root.svelte +0 -68
- package/.svelte-kit/generated/server/internal.js +0 -53
- package/.svelte-kit/non-ambient.d.ts +0 -43
- package/.svelte-kit/output/client/.vite/manifest.json +0 -175
- package/.svelte-kit/output/client/_app/immutable/assets/0.Czt_67iE.css +0 -1
- package/.svelte-kit/output/client/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +0 -1
- package/.svelte-kit/output/client/_app/immutable/assets/helpers.ysDrpaDf.css +0 -1
- package/.svelte-kit/output/client/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
- package/.svelte-kit/output/client/_app/immutable/chunks/BlLuv0eP.js +0 -46
- package/.svelte-kit/output/client/_app/immutable/chunks/CSBHmwYv.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/chunks/CTCi5ueQ.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/chunks/CfOzjaik.js +0 -2
- package/.svelte-kit/output/client/_app/immutable/chunks/D4PdvFNs.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/chunks/DXgP-QUS.js +0 -2
- package/.svelte-kit/output/client/_app/immutable/chunks/DlbDC5An.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/chunks/wRWe7aK9.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/entry/app.ConrMuHl.js +0 -2
- package/.svelte-kit/output/client/_app/immutable/entry/start.Bm6FyGme.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/nodes/0.d3cL-ETU.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/nodes/1.D6z9rPGv.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/nodes/2.CLD-8chl.js +0 -1
- package/.svelte-kit/output/client/_app/immutable/nodes/3.DXYeBoel.js +0 -1
- package/.svelte-kit/output/client/_app/version.json +0 -1
- package/.svelte-kit/output/client/libavoid.wasm +0 -0
- package/.svelte-kit/output/client/static/robots.txt +0 -3
- package/.svelte-kit/output/prerendered/dependencies/_app/env.js +0 -1
- package/.svelte-kit/output/server/.vite/manifest.json +0 -224
- package/.svelte-kit/output/server/_app/immutable/assets/LoadingOverlay.DBbe6V8W.css +0 -1
- package/.svelte-kit/output/server/_app/immutable/assets/_layout.Czt_67iE.css +0 -1
- package/.svelte-kit/output/server/_app/immutable/assets/_page.D9P41uDZ.css +0 -1
- package/.svelte-kit/output/server/chunks/ErrorDisplay.js +0 -59
- package/.svelte-kit/output/server/chunks/LoadingOverlay.js +0 -12
- package/.svelte-kit/output/server/chunks/LoadingOverlay.svelte_svelte_type_style_lang.js +0 -1671
- package/.svelte-kit/output/server/chunks/connections.js +0 -33
- package/.svelte-kit/output/server/chunks/diagram.js +0 -7
- package/.svelte-kit/output/server/chunks/environment.js +0 -34
- package/.svelte-kit/output/server/chunks/equality.js +0 -57
- package/.svelte-kit/output/server/chunks/exports.js +0 -174
- package/.svelte-kit/output/server/chunks/false.js +0 -4
- package/.svelte-kit/output/server/chunks/index.js +0 -59
- package/.svelte-kit/output/server/chunks/index2.js +0 -2939
- package/.svelte-kit/output/server/chunks/index3.js +0 -20
- package/.svelte-kit/output/server/chunks/internal.js +0 -1017
- package/.svelte-kit/output/server/chunks/shared.js +0 -770
- package/.svelte-kit/output/server/chunks/utils.js +0 -43
- package/.svelte-kit/output/server/entries/pages/_error.svelte.js +0 -64
- package/.svelte-kit/output/server/entries/pages/_layout.svelte.js +0 -65
- package/.svelte-kit/output/server/entries/pages/_page.svelte.js +0 -3991
- package/.svelte-kit/output/server/entries/pages/_page.ts.js +0 -30
- package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.svelte.js +0 -67
- package/.svelte-kit/output/server/entries/pages/group/_groupId_/_page.ts.js +0 -47
- package/.svelte-kit/output/server/index.js +0 -3747
- package/.svelte-kit/output/server/internal.js +0 -13
- package/.svelte-kit/output/server/manifest-full.js +0 -47
- package/.svelte-kit/output/server/manifest.js +0 -47
- package/.svelte-kit/output/server/nodes/0.js +0 -8
- package/.svelte-kit/output/server/nodes/1.js +0 -8
- package/.svelte-kit/output/server/nodes/2.js +0 -10
- package/.svelte-kit/output/server/nodes/3.js +0 -10
- package/.svelte-kit/output/server/remote-entry.js +0 -557
- package/.svelte-kit/tsconfig.json +0 -61
- package/.svelte-kit/types/route_meta_data.json +0 -8
- package/.svelte-kit/types/src/routes/$types.d.ts +0 -26
- package/.svelte-kit/types/src/routes/group/[groupId]/$types.d.ts +0 -21
- package/.svelte-kit/types/src/routes/group/[groupId]/proxy+page.ts +0 -49
- package/.svelte-kit/types/src/routes/proxy+page.ts +0 -33
- package/build/_app/env.js +0 -1
- package/build/_app/env.js.br +0 -1
- package/build/_app/env.js.gz +0 -0
- package/build/_app/immutable/assets/0.Czt_67iE.css +0 -1
- package/build/_app/immutable/assets/0.Czt_67iE.css.br +0 -0
- package/build/_app/immutable/assets/0.Czt_67iE.css.gz +0 -0
- package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css +0 -1
- package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.br +0 -0
- package/build/_app/immutable/assets/TeamContactCard.Dxj5nUCr.css.gz +0 -0
- package/build/_app/immutable/assets/helpers.ysDrpaDf.css +0 -1
- package/build/_app/immutable/assets/helpers.ysDrpaDf.css.br +0 -0
- package/build/_app/immutable/assets/helpers.ysDrpaDf.css.gz +0 -0
- package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm +0 -0
- package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.br +0 -0
- package/build/_app/immutable/assets/libavoid.DQJapW5w.wasm.gz +0 -0
- package/build/_app/immutable/chunks/BlLuv0eP.js +0 -46
- package/build/_app/immutable/chunks/BlLuv0eP.js.br +0 -0
- package/build/_app/immutable/chunks/BlLuv0eP.js.gz +0 -0
- package/build/_app/immutable/chunks/CSBHmwYv.js +0 -1
- package/build/_app/immutable/chunks/CSBHmwYv.js.br +0 -0
- package/build/_app/immutable/chunks/CSBHmwYv.js.gz +0 -0
- package/build/_app/immutable/chunks/CTCi5ueQ.js +0 -1
- package/build/_app/immutable/chunks/CTCi5ueQ.js.br +0 -0
- package/build/_app/immutable/chunks/CTCi5ueQ.js.gz +0 -0
- package/build/_app/immutable/chunks/CfOzjaik.js +0 -2
- package/build/_app/immutable/chunks/CfOzjaik.js.br +0 -0
- package/build/_app/immutable/chunks/CfOzjaik.js.gz +0 -0
- package/build/_app/immutable/chunks/D4PdvFNs.js +0 -1
- package/build/_app/immutable/chunks/D4PdvFNs.js.br +0 -0
- package/build/_app/immutable/chunks/D4PdvFNs.js.gz +0 -0
- package/build/_app/immutable/chunks/DXgP-QUS.js +0 -2
- package/build/_app/immutable/chunks/DXgP-QUS.js.br +0 -0
- package/build/_app/immutable/chunks/DXgP-QUS.js.gz +0 -0
- package/build/_app/immutable/chunks/DlbDC5An.js +0 -1
- package/build/_app/immutable/chunks/DlbDC5An.js.br +0 -0
- package/build/_app/immutable/chunks/DlbDC5An.js.gz +0 -0
- package/build/_app/immutable/chunks/wRWe7aK9.js +0 -1
- package/build/_app/immutable/chunks/wRWe7aK9.js.br +0 -0
- package/build/_app/immutable/chunks/wRWe7aK9.js.gz +0 -0
- package/build/_app/immutable/entry/app.ConrMuHl.js +0 -2
- package/build/_app/immutable/entry/app.ConrMuHl.js.br +0 -0
- package/build/_app/immutable/entry/app.ConrMuHl.js.gz +0 -0
- package/build/_app/immutable/entry/start.Bm6FyGme.js +0 -1
- package/build/_app/immutable/entry/start.Bm6FyGme.js.br +0 -2
- package/build/_app/immutable/entry/start.Bm6FyGme.js.gz +0 -0
- package/build/_app/immutable/nodes/0.d3cL-ETU.js +0 -1
- package/build/_app/immutable/nodes/0.d3cL-ETU.js.br +0 -0
- package/build/_app/immutable/nodes/0.d3cL-ETU.js.gz +0 -0
- package/build/_app/immutable/nodes/1.D6z9rPGv.js +0 -1
- package/build/_app/immutable/nodes/1.D6z9rPGv.js.br +0 -0
- package/build/_app/immutable/nodes/1.D6z9rPGv.js.gz +0 -0
- package/build/_app/immutable/nodes/2.CLD-8chl.js +0 -1
- package/build/_app/immutable/nodes/2.CLD-8chl.js.br +0 -0
- package/build/_app/immutable/nodes/2.CLD-8chl.js.gz +0 -0
- package/build/_app/immutable/nodes/3.DXYeBoel.js +0 -1
- package/build/_app/immutable/nodes/3.DXYeBoel.js.br +0 -0
- package/build/_app/immutable/nodes/3.DXYeBoel.js.gz +0 -0
- package/build/_app/version.json +0 -1
- package/build/_app/version.json.br +0 -0
- package/build/_app/version.json.gz +0 -0
- package/build/index.html +0 -34
- package/build/index.html.br +0 -0
- package/build/index.html.gz +0 -0
- package/build/libavoid.wasm +0 -0
- package/build/libavoid.wasm.br +0 -0
- package/build/libavoid.wasm.gz +0 -0
- package/build/static/robots.txt +0 -3
- package/coverage/coverage-final.json +0 -6
- package/coverage/coverage-summary.json +0 -7
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/stores/index.html +0 -116
- package/coverage/lcov-report/stores/routingStore.ts.html +0 -781
- package/coverage/lcov-report/utils/flow/helpers.ts.html +0 -127
- package/coverage/lcov-report/utils/flow/index.html +0 -161
- package/coverage/lcov-report/utils/flow/layout.ts.html +0 -805
- package/coverage/lcov-report/utils/flow/serviceIds.ts.html +0 -97
- package/coverage/lcov-report/utils/flow/servicesGraph.ts.html +0 -859
- package/coverage/lcov.info +0 -646
|
@@ -1,240 +1,240 @@
|
|
|
1
|
-
import ELK from 'elkjs/lib/elk.bundled.js';
|
|
2
|
-
import {type Edge, type Node} from '@xyflow/svelte';
|
|
3
|
-
import type {FlowEdgeData, FlowGraphInput, FlowGraphOutput, FlowNodeData} from '$shared/flow-types';
|
|
4
|
-
import type {ElkNode} from "elkjs/lib/elk-api";
|
|
5
|
-
|
|
6
|
-
const elk = new ELK();
|
|
7
|
-
const PADDING = 10;
|
|
8
|
-
export const OFFSET_STEP = 20;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Helper to calculate absolute position of a node by traversing its parent chain.
|
|
12
|
-
*/
|
|
13
|
-
export function getAbsolutePosition(nodeId: string, nodes: Node<FlowNodeData>[]): { x: number, y: number } {
|
|
14
|
-
const node = nodes.find(n => n.id === nodeId);
|
|
15
|
-
if (!node) return {x: 0, y: 0};
|
|
16
|
-
|
|
17
|
-
let x = node.position?.x ?? 0;
|
|
18
|
-
let y = node.position?.y ?? 0;
|
|
19
|
-
|
|
20
|
-
if (node.parentId) {
|
|
21
|
-
const parentPos = getAbsolutePosition(node.parentId, nodes);
|
|
22
|
-
x += parentPos.x;
|
|
23
|
-
y += parentPos.y;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return {x, y};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Calculates a deterministic offset for an edge to prevent overlapping.
|
|
31
|
-
* TODO: Replace this method with elk's built-in edge routing and spacing options once we can guarantee it works well in all cases.
|
|
32
|
-
* This probably means that we'll have to replace it once libavoid is integrated into elk: https://github.com/kieler/elkjs/issues/210
|
|
33
|
-
* This function handles two types of offsets:
|
|
34
|
-
* 1. Handle Offset: For multiple edges sharing the same handle on a node.
|
|
35
|
-
* 2. Trunk Offset: For edges that don't share a handle but might have overlapping
|
|
36
|
-
* paths (trunks) in the same "lane".
|
|
37
|
-
*/
|
|
38
|
-
export function calculateEdgeOffset(
|
|
39
|
-
edgeId: string,
|
|
40
|
-
nodes: Node<FlowNodeData>[],
|
|
41
|
-
edges: Edge<FlowEdgeData>[],
|
|
42
|
-
isSource: boolean
|
|
43
|
-
): number {
|
|
44
|
-
const edge = edges.find(e => e.id === edgeId);
|
|
45
|
-
if (!edge) return 0;
|
|
46
|
-
|
|
47
|
-
const nodeId = isSource ? edge.source : edge.target;
|
|
48
|
-
const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
|
|
49
|
-
|
|
50
|
-
const handle = isSource ? edge.sourceHandle : edge.targetHandle;
|
|
51
|
-
const isVertical = handle?.toLowerCase().includes('top') || handle?.toLowerCase().includes('bottom');
|
|
52
|
-
|
|
53
|
-
let finalSiblings: Edge<FlowEdgeData>[] = [];
|
|
54
|
-
let usedAreaFallback = false;
|
|
55
|
-
|
|
56
|
-
// HANDLE OFFSET: Siblings on the same node and same handle
|
|
57
|
-
const siblings = edges.filter(e =>
|
|
58
|
-
(isSource ? e.source : e.target) === nodeId &&
|
|
59
|
-
(isSource ? e.sourceHandle : e.targetHandle) === handleId
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
if (siblings.length <= 1) {
|
|
63
|
-
// Fallback for bidirectional or single edges between same nodes
|
|
64
|
-
usedAreaFallback = true;
|
|
65
|
-
const otherId = isSource ? edge.target : edge.source;
|
|
66
|
-
finalSiblings = edges.filter(e => {
|
|
67
|
-
const isSamePair = (e.source === nodeId && e.target === otherId) ||
|
|
68
|
-
(e.source === otherId && e.target === nodeId);
|
|
69
|
-
return isSamePair && (e.source === nodeId || e.target === nodeId);
|
|
70
|
-
});
|
|
71
|
-
} else {
|
|
72
|
-
finalSiblings = siblings;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (finalSiblings.length <= 1) return 0;
|
|
76
|
-
|
|
77
|
-
// Sort:
|
|
78
|
-
finalSiblings.sort((a, b) => {
|
|
79
|
-
if (usedAreaFallback) {
|
|
80
|
-
// For horizontal paths (Left/Right), we split them vertically by their overall Y midpoint
|
|
81
|
-
const aSPos = getAbsolutePosition(a.source, nodes);
|
|
82
|
-
const aTPos = getAbsolutePosition(a.target, nodes);
|
|
83
|
-
const bSPos = getAbsolutePosition(b.source, nodes);
|
|
84
|
-
const bTPos = getAbsolutePosition(b.target, nodes);
|
|
85
|
-
|
|
86
|
-
if (isVertical) {
|
|
87
|
-
const midA = (aSPos.x + aTPos.x) / 2;
|
|
88
|
-
const midB = (bSPos.x + bTPos.x) / 2;
|
|
89
|
-
if (midA !== midB) return midA - midB;
|
|
90
|
-
} else {
|
|
91
|
-
const midA = (aSPos.y + aTPos.y) / 2;
|
|
92
|
-
const midB = (bSPos.y + bTPos.y) / 2;
|
|
93
|
-
if (midA !== midB) return midA - midB;
|
|
94
|
-
}
|
|
95
|
-
return a.id.localeCompare(b.id);
|
|
96
|
-
}
|
|
97
|
-
const otherIdA = isSource ? a.target : a.source;
|
|
98
|
-
const otherIdB = isSource ? b.target : b.source;
|
|
99
|
-
const posA = getAbsolutePosition(otherIdA, nodes);
|
|
100
|
-
const posB = getAbsolutePosition(otherIdB, nodes);
|
|
101
|
-
const primary = isVertical ? (posA.x - posB.x) : (posA.y - posB.y);
|
|
102
|
-
if (primary !== 0) return primary;
|
|
103
|
-
return a.id.localeCompare(b.id);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const index = finalSiblings.findIndex(e => e.id === edgeId);
|
|
107
|
-
return (index - (finalSiblings.length - 1) / 2) * OFFSET_STEP;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const ELK_OPTIONS: Record<string, string> = {
|
|
111
|
-
'elk.algorithm': 'layered',
|
|
112
|
-
'elk.direction': 'RIGHT',
|
|
113
|
-
//Horizontal
|
|
114
|
-
'elk.spacing.nodeNodeBetweenLayers': '80',
|
|
115
|
-
//Vertical
|
|
116
|
-
'elk.spacing.nodeNode': '80',
|
|
117
|
-
|
|
118
|
-
'elk.spacing.edgeNode': '60', // Gap between lines and boxes
|
|
119
|
-
'elk.spacing.edgeEdge': '40', // Gap between parallel lines
|
|
120
|
-
|
|
121
|
-
'elk.layered.spacing.edgeNodeBetweenLayers': '60',
|
|
122
|
-
'elk.edgeRouting': 'ORTHOGONAL',
|
|
123
|
-
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
|
|
124
|
-
'elk.portConstraints': 'FREE',
|
|
125
|
-
'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
|
|
126
|
-
'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
function convertEdgesToElkEdges(input: FlowGraphInput) {
|
|
130
|
-
return input.edges.map((edge) => ({
|
|
131
|
-
id: edge.id,
|
|
132
|
-
sources: [edge.source],
|
|
133
|
-
targets: [edge.target],
|
|
134
|
-
originalEdge: edge,
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Helper to get the dimensions of a node, prioritizing measured values from Svelte Flow.
|
|
140
|
-
*/
|
|
141
|
-
export function getNodeDimensions(node: Node<FlowNodeData>): { w: number, h: number } {
|
|
142
|
-
const w = Math.round(node.measured?.width ?? node.width ?? 150);
|
|
143
|
-
const h = Math.round(node.measured?.height ?? node.height ?? 40);
|
|
144
|
-
return {w, h};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
export async function layoutFlowGraph(input: FlowGraphInput): Promise<FlowGraphOutput> {
|
|
149
|
-
const elkEdges = convertEdgesToElkEdges(input);
|
|
150
|
-
// console.debug('[layoutFlowGraph] Starting layout with input:', {input});
|
|
151
|
-
// Helper: Build node properties and handle dimensions
|
|
152
|
-
const prepareElkNode = (node: Node<FlowNodeData>) => {
|
|
153
|
-
const {w, h} = getNodeDimensions(node);
|
|
154
|
-
return {
|
|
155
|
-
...node,
|
|
156
|
-
width: w,
|
|
157
|
-
height: h
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// 1. Build the nested ELK structure
|
|
162
|
-
const elkChildren: ElkNode[] = input.groupNodes.map(parent => ({
|
|
163
|
-
...prepareElkNode(parent),
|
|
164
|
-
layoutOptions: {
|
|
165
|
-
...ELK_OPTIONS,
|
|
166
|
-
'elk.padding': `[top=60,left=${PADDING},bottom=${PADDING},right=${PADDING}]`,
|
|
167
|
-
},
|
|
168
|
-
children: input.serviceNodes.filter(child => child.parentId === parent.id).map(prepareElkNode)
|
|
169
|
-
}));
|
|
170
|
-
|
|
171
|
-
// Add nodes that aren't in any group
|
|
172
|
-
input.serviceNodes
|
|
173
|
-
.filter(child => child.parentId === undefined)
|
|
174
|
-
.forEach(node => elkChildren.push(prepareElkNode(node)));
|
|
175
|
-
|
|
176
|
-
const elkGraph = {
|
|
177
|
-
id: 'root',
|
|
178
|
-
layoutOptions: ELK_OPTIONS,
|
|
179
|
-
children: elkChildren,
|
|
180
|
-
edges: elkEdges
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
return elk.layout(elkGraph).then(async (layoutedGraph) => {
|
|
184
|
-
console.debug('[layoutFlowGraph] Layout completed:', {layoutedGraph});
|
|
185
|
-
const flattenedNodes: Node<FlowNodeData>[] = [];
|
|
186
|
-
const flattenedEdges: Edge<FlowEdgeData>[] = [];
|
|
187
|
-
|
|
188
|
-
// Lookup maps to handle the coordinate system bridge
|
|
189
|
-
const parentPosLookup = new Map<string, { x: number, y: number }>();
|
|
190
|
-
const nodeToParent = new Map<string, string>();
|
|
191
|
-
const nodeLookup = new Map<String, Node<FlowNodeData>>();
|
|
192
|
-
|
|
193
|
-
// 2. Process the results
|
|
194
|
-
layoutedGraph.children?.forEach(parentOrOrphan => {
|
|
195
|
-
const px = parentOrOrphan.x || 0;
|
|
196
|
-
const py = parentOrOrphan.y || 0;
|
|
197
|
-
|
|
198
|
-
// If it has children, it's a group node in our context
|
|
199
|
-
if (parentOrOrphan.children) {
|
|
200
|
-
parentPosLookup.set(parentOrOrphan.id, {x: px, y: py});
|
|
201
|
-
|
|
202
|
-
flattenedNodes.push({
|
|
203
|
-
...parentOrOrphan,
|
|
204
|
-
position: {x: px, y: py},
|
|
205
|
-
draggable: true,
|
|
206
|
-
} as Node<FlowNodeData>);
|
|
207
|
-
|
|
208
|
-
// Map children and record their parent for edge offsetting
|
|
209
|
-
parentOrOrphan.children.forEach(child => {
|
|
210
|
-
nodeToParent.set(child.id, parentOrOrphan.id);
|
|
211
|
-
const node = {
|
|
212
|
-
...child,
|
|
213
|
-
position: {x: child.x || 0, y: child.y || 0},
|
|
214
|
-
draggable: true,
|
|
215
|
-
extent: [[PADDING, 50], [(parentOrOrphan.width || 0) - PADDING, (parentOrOrphan.height || 0) - PADDING]]
|
|
216
|
-
} as Node<FlowNodeData>;
|
|
217
|
-
flattenedNodes.push(node);
|
|
218
|
-
nodeLookup.set(child.id, node);
|
|
219
|
-
});
|
|
220
|
-
} else {
|
|
221
|
-
// It's a top-level orphan node
|
|
222
|
-
const node = {
|
|
223
|
-
...parentOrOrphan,
|
|
224
|
-
position: {x: px, y: py},
|
|
225
|
-
draggable: true,
|
|
226
|
-
} as Node<FlowNodeData>;
|
|
227
|
-
flattenedNodes.push(node);
|
|
228
|
-
nodeLookup.set(node.id, node);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
const edges = input.edges.filter(e => e.data.connectionType !== 'service-group');
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
nodes: flattenedNodes,
|
|
236
|
-
edges,
|
|
237
|
-
signature: input.signature
|
|
238
|
-
};
|
|
239
|
-
});
|
|
240
|
-
}
|
|
1
|
+
import ELK from 'elkjs/lib/elk.bundled.js';
|
|
2
|
+
import {type Edge, type Node} from '@xyflow/svelte';
|
|
3
|
+
import type {FlowEdgeData, FlowGraphInput, FlowGraphOutput, FlowNodeData} from '$shared/flow-types';
|
|
4
|
+
import type {ElkNode} from "elkjs/lib/elk-api";
|
|
5
|
+
|
|
6
|
+
const elk = new ELK();
|
|
7
|
+
const PADDING = 10;
|
|
8
|
+
export const OFFSET_STEP = 20;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Helper to calculate absolute position of a node by traversing its parent chain.
|
|
12
|
+
*/
|
|
13
|
+
export function getAbsolutePosition(nodeId: string, nodes: Node<FlowNodeData>[]): { x: number, y: number } {
|
|
14
|
+
const node = nodes.find(n => n.id === nodeId);
|
|
15
|
+
if (!node) return {x: 0, y: 0};
|
|
16
|
+
|
|
17
|
+
let x = node.position?.x ?? 0;
|
|
18
|
+
let y = node.position?.y ?? 0;
|
|
19
|
+
|
|
20
|
+
if (node.parentId) {
|
|
21
|
+
const parentPos = getAbsolutePosition(node.parentId, nodes);
|
|
22
|
+
x += parentPos.x;
|
|
23
|
+
y += parentPos.y;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {x, y};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Calculates a deterministic offset for an edge to prevent overlapping.
|
|
31
|
+
* TODO: Replace this method with elk's built-in edge routing and spacing options once we can guarantee it works well in all cases.
|
|
32
|
+
* This probably means that we'll have to replace it once libavoid is integrated into elk: https://github.com/kieler/elkjs/issues/210
|
|
33
|
+
* This function handles two types of offsets:
|
|
34
|
+
* 1. Handle Offset: For multiple edges sharing the same handle on a node.
|
|
35
|
+
* 2. Trunk Offset: For edges that don't share a handle but might have overlapping
|
|
36
|
+
* paths (trunks) in the same "lane".
|
|
37
|
+
*/
|
|
38
|
+
export function calculateEdgeOffset(
|
|
39
|
+
edgeId: string,
|
|
40
|
+
nodes: Node<FlowNodeData>[],
|
|
41
|
+
edges: Edge<FlowEdgeData>[],
|
|
42
|
+
isSource: boolean
|
|
43
|
+
): number {
|
|
44
|
+
const edge = edges.find(e => e.id === edgeId);
|
|
45
|
+
if (!edge) return 0;
|
|
46
|
+
|
|
47
|
+
const nodeId = isSource ? edge.source : edge.target;
|
|
48
|
+
const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
|
|
49
|
+
|
|
50
|
+
const handle = isSource ? edge.sourceHandle : edge.targetHandle;
|
|
51
|
+
const isVertical = handle?.toLowerCase().includes('top') || handle?.toLowerCase().includes('bottom');
|
|
52
|
+
|
|
53
|
+
let finalSiblings: Edge<FlowEdgeData>[] = [];
|
|
54
|
+
let usedAreaFallback = false;
|
|
55
|
+
|
|
56
|
+
// HANDLE OFFSET: Siblings on the same node and same handle
|
|
57
|
+
const siblings = edges.filter(e =>
|
|
58
|
+
(isSource ? e.source : e.target) === nodeId &&
|
|
59
|
+
(isSource ? e.sourceHandle : e.targetHandle) === handleId
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (siblings.length <= 1) {
|
|
63
|
+
// Fallback for bidirectional or single edges between same nodes
|
|
64
|
+
usedAreaFallback = true;
|
|
65
|
+
const otherId = isSource ? edge.target : edge.source;
|
|
66
|
+
finalSiblings = edges.filter(e => {
|
|
67
|
+
const isSamePair = (e.source === nodeId && e.target === otherId) ||
|
|
68
|
+
(e.source === otherId && e.target === nodeId);
|
|
69
|
+
return isSamePair && (e.source === nodeId || e.target === nodeId);
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
finalSiblings = siblings;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (finalSiblings.length <= 1) return 0;
|
|
76
|
+
|
|
77
|
+
// Sort:
|
|
78
|
+
finalSiblings.sort((a, b) => {
|
|
79
|
+
if (usedAreaFallback) {
|
|
80
|
+
// For horizontal paths (Left/Right), we split them vertically by their overall Y midpoint
|
|
81
|
+
const aSPos = getAbsolutePosition(a.source, nodes);
|
|
82
|
+
const aTPos = getAbsolutePosition(a.target, nodes);
|
|
83
|
+
const bSPos = getAbsolutePosition(b.source, nodes);
|
|
84
|
+
const bTPos = getAbsolutePosition(b.target, nodes);
|
|
85
|
+
|
|
86
|
+
if (isVertical) {
|
|
87
|
+
const midA = (aSPos.x + aTPos.x) / 2;
|
|
88
|
+
const midB = (bSPos.x + bTPos.x) / 2;
|
|
89
|
+
if (midA !== midB) return midA - midB;
|
|
90
|
+
} else {
|
|
91
|
+
const midA = (aSPos.y + aTPos.y) / 2;
|
|
92
|
+
const midB = (bSPos.y + bTPos.y) / 2;
|
|
93
|
+
if (midA !== midB) return midA - midB;
|
|
94
|
+
}
|
|
95
|
+
return a.id.localeCompare(b.id);
|
|
96
|
+
}
|
|
97
|
+
const otherIdA = isSource ? a.target : a.source;
|
|
98
|
+
const otherIdB = isSource ? b.target : b.source;
|
|
99
|
+
const posA = getAbsolutePosition(otherIdA, nodes);
|
|
100
|
+
const posB = getAbsolutePosition(otherIdB, nodes);
|
|
101
|
+
const primary = isVertical ? (posA.x - posB.x) : (posA.y - posB.y);
|
|
102
|
+
if (primary !== 0) return primary;
|
|
103
|
+
return a.id.localeCompare(b.id);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const index = finalSiblings.findIndex(e => e.id === edgeId);
|
|
107
|
+
return (index - (finalSiblings.length - 1) / 2) * OFFSET_STEP;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const ELK_OPTIONS: Record<string, string> = {
|
|
111
|
+
'elk.algorithm': 'layered',
|
|
112
|
+
'elk.direction': 'RIGHT',
|
|
113
|
+
//Horizontal
|
|
114
|
+
'elk.spacing.nodeNodeBetweenLayers': '80',
|
|
115
|
+
//Vertical
|
|
116
|
+
'elk.spacing.nodeNode': '80',
|
|
117
|
+
|
|
118
|
+
'elk.spacing.edgeNode': '60', // Gap between lines and boxes
|
|
119
|
+
'elk.spacing.edgeEdge': '40', // Gap between parallel lines
|
|
120
|
+
|
|
121
|
+
'elk.layered.spacing.edgeNodeBetweenLayers': '60',
|
|
122
|
+
'elk.edgeRouting': 'ORTHOGONAL',
|
|
123
|
+
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
|
|
124
|
+
'elk.portConstraints': 'FREE',
|
|
125
|
+
'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
|
|
126
|
+
'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
function convertEdgesToElkEdges(input: FlowGraphInput) {
|
|
130
|
+
return input.edges.map((edge) => ({
|
|
131
|
+
id: edge.id,
|
|
132
|
+
sources: [edge.source],
|
|
133
|
+
targets: [edge.target],
|
|
134
|
+
originalEdge: edge,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Helper to get the dimensions of a node, prioritizing measured values from Svelte Flow.
|
|
140
|
+
*/
|
|
141
|
+
export function getNodeDimensions(node: Node<FlowNodeData>): { w: number, h: number } {
|
|
142
|
+
const w = Math.round(node.measured?.width ?? node.width ?? 150);
|
|
143
|
+
const h = Math.round(node.measured?.height ?? node.height ?? 40);
|
|
144
|
+
return {w, h};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
export async function layoutFlowGraph(input: FlowGraphInput): Promise<FlowGraphOutput> {
|
|
149
|
+
const elkEdges = convertEdgesToElkEdges(input);
|
|
150
|
+
// console.debug('[layoutFlowGraph] Starting layout with input:', {input});
|
|
151
|
+
// Helper: Build node properties and handle dimensions
|
|
152
|
+
const prepareElkNode = (node: Node<FlowNodeData>) => {
|
|
153
|
+
const {w, h} = getNodeDimensions(node);
|
|
154
|
+
return {
|
|
155
|
+
...node,
|
|
156
|
+
width: w,
|
|
157
|
+
height: h
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// 1. Build the nested ELK structure
|
|
162
|
+
const elkChildren: ElkNode[] = input.groupNodes.map(parent => ({
|
|
163
|
+
...prepareElkNode(parent),
|
|
164
|
+
layoutOptions: {
|
|
165
|
+
...ELK_OPTIONS,
|
|
166
|
+
'elk.padding': `[top=60,left=${PADDING},bottom=${PADDING},right=${PADDING}]`,
|
|
167
|
+
},
|
|
168
|
+
children: input.serviceNodes.filter(child => child.parentId === parent.id).map(prepareElkNode)
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
// Add nodes that aren't in any group
|
|
172
|
+
input.serviceNodes
|
|
173
|
+
.filter(child => child.parentId === undefined)
|
|
174
|
+
.forEach(node => elkChildren.push(prepareElkNode(node)));
|
|
175
|
+
|
|
176
|
+
const elkGraph = {
|
|
177
|
+
id: 'root',
|
|
178
|
+
layoutOptions: ELK_OPTIONS,
|
|
179
|
+
children: elkChildren,
|
|
180
|
+
edges: elkEdges
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return elk.layout(elkGraph).then(async (layoutedGraph) => {
|
|
184
|
+
console.debug('[layoutFlowGraph] Layout completed:', {layoutedGraph});
|
|
185
|
+
const flattenedNodes: Node<FlowNodeData>[] = [];
|
|
186
|
+
const flattenedEdges: Edge<FlowEdgeData>[] = [];
|
|
187
|
+
|
|
188
|
+
// Lookup maps to handle the coordinate system bridge
|
|
189
|
+
const parentPosLookup = new Map<string, { x: number, y: number }>();
|
|
190
|
+
const nodeToParent = new Map<string, string>();
|
|
191
|
+
const nodeLookup = new Map<String, Node<FlowNodeData>>();
|
|
192
|
+
|
|
193
|
+
// 2. Process the results
|
|
194
|
+
layoutedGraph.children?.forEach(parentOrOrphan => {
|
|
195
|
+
const px = parentOrOrphan.x || 0;
|
|
196
|
+
const py = parentOrOrphan.y || 0;
|
|
197
|
+
|
|
198
|
+
// If it has children, it's a group node in our context
|
|
199
|
+
if (parentOrOrphan.children) {
|
|
200
|
+
parentPosLookup.set(parentOrOrphan.id, {x: px, y: py});
|
|
201
|
+
|
|
202
|
+
flattenedNodes.push({
|
|
203
|
+
...parentOrOrphan,
|
|
204
|
+
position: {x: px, y: py},
|
|
205
|
+
draggable: true,
|
|
206
|
+
} as Node<FlowNodeData>);
|
|
207
|
+
|
|
208
|
+
// Map children and record their parent for edge offsetting
|
|
209
|
+
parentOrOrphan.children.forEach(child => {
|
|
210
|
+
nodeToParent.set(child.id, parentOrOrphan.id);
|
|
211
|
+
const node = {
|
|
212
|
+
...child,
|
|
213
|
+
position: {x: child.x || 0, y: child.y || 0},
|
|
214
|
+
draggable: true,
|
|
215
|
+
extent: [[PADDING, 50], [(parentOrOrphan.width || 0) - PADDING, (parentOrOrphan.height || 0) - PADDING]]
|
|
216
|
+
} as Node<FlowNodeData>;
|
|
217
|
+
flattenedNodes.push(node);
|
|
218
|
+
nodeLookup.set(child.id, node);
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
// It's a top-level orphan node
|
|
222
|
+
const node = {
|
|
223
|
+
...parentOrOrphan,
|
|
224
|
+
position: {x: px, y: py},
|
|
225
|
+
draggable: true,
|
|
226
|
+
} as Node<FlowNodeData>;
|
|
227
|
+
flattenedNodes.push(node);
|
|
228
|
+
nodeLookup.set(node.id, node);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const edges = input.edges.filter(e => e.data.connectionType !== 'service-group');
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
nodes: flattenedNodes,
|
|
236
|
+
edges,
|
|
237
|
+
signature: input.signature
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ServiceDefinition } from '$shared/types'
|
|
2
|
-
|
|
3
|
-
//TODO: Do you need to exist, or can i put you somewhere else?
|
|
4
|
-
export const getServiceNodeIdFromDefinition = (service: ServiceDefinition) =>
|
|
1
|
+
import type { ServiceDefinition } from '$shared/types'
|
|
2
|
+
|
|
3
|
+
//TODO: Do you need to exist, or can i put you somewhere else?
|
|
4
|
+
export const getServiceNodeIdFromDefinition = (service: ServiceDefinition) =>
|
|
5
5
|
`svc::${service.groupId}::${service.identifier}`
|