diff --git a/frontend/src/app/features/schema/layout/dagre-layout.ts b/frontend/src/app/features/schema/layout/dagre-layout.ts index 91cbb5b..d834ba9 100644 --- a/frontend/src/app/features/schema/layout/dagre-layout.ts +++ b/frontend/src/app/features/schema/layout/dagre-layout.ts @@ -4,6 +4,9 @@ import { LAYER_MAPPING } from '../../../constants/layer-mapping'; import { SITE_PADDING, SITE_HEADER_HEIGHT, + DEVICE_HEADER_HEIGHT, + DEVICE_MIN_HEIGHT, + CARD_HEIGHT, } from '../../../constants/sizes'; function getLayerForCategory(category: string): number { @@ -137,6 +140,45 @@ function layoutSiteDevices( return { minX, minY, maxX, maxY }; } +/** + * Fix device children after dagre layout: + * 1. Ensure device height accommodates all visible cards + * 2. Reposition card-nodes at correct absolute positions inside devices + * + * setPosition() in X6 does NOT translate children, so cards stay + * at their old positions when a device moves — this function corrects that. + */ +function fixDeviceChildren(graph: Graph): void { + for (const device of graph.getNodes()) { + if (device.shape === 'site-node' || device.shape === 'card-node') continue; + + const cards = (device.getChildren() ?? []).filter( + (c): c is Node => c.isNode() && c.shape === 'card-node' && c.isVisible(), + ); + if (cards.length === 0) continue; + + const data = device.getData<{ collapsed?: boolean }>(); + if (data?.collapsed) continue; + + // Ensure device is tall enough for its visible cards + const size = device.getSize(); + const cardsHeight = cards.length * (CARD_HEIGHT + 4) + 4; + const minHeight = Math.max(DEVICE_MIN_HEIGHT, DEVICE_HEADER_HEIGHT + cardsHeight + 6); + if (size.height < minHeight) { + device.resize(size.width, minHeight); + } + + // Place each card at the correct absolute position + const pos = device.getPosition(); + for (let i = 0; i < cards.length; i++) { + cards[i].setPosition( + pos.x + 10, + pos.y + DEVICE_HEADER_HEIGHT + 4 + i * (CARD_HEIGHT + 4), + ); + } + } +} + function getDeviceChildren(siteNode: Node): Node[] { return (siteNode.getChildren() ?? []).filter( (c): c is Node => @@ -264,5 +306,7 @@ export function applyDagreLayout(graph: Graph): void { cursorY = rootSiteY + rootSiteH + siteGap; } + fixDeviceChildren(graph); + graph.stopBatch('dagre-layout'); }