@@ -11,18 +11,21 @@ import ReactFlow, {
1111import ViewShell from '../components/ViewShell'
1212import { runElkLayout , runGridLayout , type LayoutEngine } from '../layout/layoutCore'
1313
14+ interface ElkLayoutParams {
15+ nodes : Array < { id : string } >
16+ edges : Array < { source : string ; target : string } >
17+ engine : LayoutEngine
18+ columns : number
19+ cancelled : { current : boolean }
20+ startedAt : number
21+ onPositions : ( positions : Record < string , { x : number ; y : number } > ) => void
22+ onCompleted : ( engine : LayoutEngine , durationMs : number , nodeCount : number ) => void
23+ onFinish : ( ) => void
24+ }
25+
1426/** Run ELK layout (in-thread or via worker), calling back with positions on completion. */
15- async function applyElkLayout (
16- nodes : Array < { id : string } > ,
17- edges : Array < { source : string ; target : string } > ,
18- engine : LayoutEngine ,
19- columns : number ,
20- cancelled : { current : boolean } ,
21- startedAt : number ,
22- onPositions : ( positions : Record < string , { x : number ; y : number } > ) => void ,
23- onCompleted : ( engine : LayoutEngine , durationMs : number , nodeCount : number ) => void ,
24- onFinish : ( ) => void ,
25- ) : Promise < void > {
27+ async function applyElkLayout ( params : Readonly < ElkLayoutParams > ) : Promise < void > {
28+ const { nodes, edges, engine, columns, cancelled, startedAt, onPositions, onCompleted, onFinish } = params
2629 try {
2730 if ( nodes . length > 1000 ) {
2831 const worker = new Worker ( new URL ( '../layout/layoutWorker.ts' , import . meta. url ) , {
@@ -88,6 +91,35 @@ function overlayColorForNode(overlay: OverlayState, naturalKey: string, fallback
8891 return '#1d4ed8'
8992}
9093
94+ function buildRFNode (
95+ node : TwinGraphResponse [ 'nodes' ] [ number ] ,
96+ index : number ,
97+ overlay : OverlayState ,
98+ selectedNodeId : string ,
99+ layoutPositions : Record < string , { x : number ; y : number } > ,
100+ columns : number ,
101+ showLabels : boolean ,
102+ ) : RFNode {
103+ const runtime = overlay . runtimeByNodeKey [ node . natural_key ] || overlay . runtimeByNodeKey [ node . name ]
104+ const isSelected = selectedNodeId === node . id
105+ const position = layoutPositions [ node . id ] || { x : ( index % columns ) * 260 , y : Math . floor ( index / columns ) * 130 }
106+ const nodeOpacity = overlay . mode === 'runtime' && runtime ?. error_rate === undefined ? 0.84 : 1
107+ return {
108+ id : node . id ,
109+ data : { label : showLabels ? node . name : '' } ,
110+ position,
111+ style : {
112+ width : 210 ,
113+ fontSize : 11 ,
114+ borderRadius : 10 ,
115+ border : isSelected ? '2px solid #f59e0b' : `1px solid ${ overlayColorForNode ( overlay , node . natural_key , node . name ) } ` ,
116+ background : '#ffffff' ,
117+ boxShadow : isSelected ? '0 0 0 3px rgba(245, 158, 11, 0.22)' : 'none' ,
118+ opacity : nodeOpacity ,
119+ } ,
120+ }
121+ }
122+
91123export default function TopologyView ( {
92124 graph,
93125 state,
@@ -134,44 +166,24 @@ export default function TopologyView({
134166 }
135167
136168 const cancelRef = { current : false }
137- applyElkLayout (
138- graph . nodes . map ( ( node ) => ( { id : node . id } ) ) ,
139- graph . edges . map ( ( edge ) => ( { source : edge . source_node_id , target : edge . target_node_id } ) ) ,
140- preferredEngine ,
169+ applyElkLayout ( {
170+ nodes : graph . nodes . map ( ( node ) => ( { id : node . id } ) ) ,
171+ edges : graph . edges . map ( ( edge ) => ( { source : edge . source_node_id , target : edge . target_node_id } ) ) ,
172+ engine : preferredEngine ,
141173 columns,
142- cancelRef ,
143- performance . now ( ) ,
144- setLayoutPositions ,
145- onLayoutCompleted ,
146- ( ) => setLayoutStatus ( 'refined' ) ,
147- )
174+ cancelled : cancelRef ,
175+ startedAt : performance . now ( ) ,
176+ onPositions : setLayoutPositions ,
177+ onCompleted : onLayoutCompleted ,
178+ onFinish : ( ) => setLayoutStatus ( 'refined' ) ,
179+ } )
148180 return ( ) => {
149181 cancelRef . current = true
150182 }
151183 } , [ graph . edges , graph . nodes , columns , preferredEngine , onLayoutCompleted ] )
152184
153185 const nodes = useMemo < RFNode [ ] > ( ( ) => {
154- return graph . nodes . map ( ( node , index ) => {
155- const runtime = overlay . runtimeByNodeKey [ node . natural_key ] || overlay . runtimeByNodeKey [ node . name ]
156- const isSelected = selectedNodeId === node . id
157- const position = layoutPositions [ node . id ] || { x : ( index % columns ) * 260 , y : Math . floor ( index / columns ) * 130 }
158- return {
159- id : node . id ,
160- data : {
161- label : showLabels ? node . name : '' ,
162- } ,
163- position,
164- style : {
165- width : 210 ,
166- fontSize : 11 ,
167- borderRadius : 10 ,
168- border : isSelected ? '2px solid #f59e0b' : `1px solid ${ overlayColorForNode ( overlay , node . natural_key , node . name ) } ` ,
169- background : '#ffffff' ,
170- boxShadow : isSelected ? '0 0 0 3px rgba(245, 158, 11, 0.22)' : 'none' ,
171- opacity : overlay . mode === 'runtime' && runtime ?. error_rate === undefined ? 0.84 : 1 ,
172- } ,
173- }
174- } )
186+ return graph . nodes . map ( ( node , index ) => buildRFNode ( node , index , overlay , selectedNodeId , layoutPositions , columns , showLabels ) )
175187 } , [ graph . nodes , showLabels , layoutPositions , columns , overlay , selectedNodeId ] )
176188
177189 const edges = useMemo < RFEdge [ ] > ( ( ) => {
0 commit comments