1- import states from './states.json' ;
21import { createRoot } from 'react-dom/client' ;
32import ELK from 'elkjs/lib/elk.bundled.js' ;
4- import React , { useCallback , useState , useEffect , useMemo } from 'react' ;
3+ import type { ElkNode , LayoutOptions } from 'elkjs' ;
4+ import { useCallback , useState , useEffect , useMemo } from 'react' ;
55import ReactFlow , {
66 ReactFlowProvider ,
77 Panel ,
@@ -11,26 +11,69 @@ import ReactFlow, {
1111 Handle ,
1212 Position ,
1313} from 'reactflow' ;
14-
14+ import type {
15+ Edge as ReactFlowEdge ,
16+ FitView ,
17+ Node as ReactFlowNode ,
18+ NodeProps ,
19+ } from 'reactflow' ;
1520import 'reactflow/dist/style.css' ;
1621
22+ import statesData from './states.json' ;
23+
24+ type InputNodeData = {
25+ id : string ;
26+ label : string ;
27+ comboId : string ;
28+ } ;
29+ type InputEdgeData = {
30+ source : string ;
31+ target : string ;
32+ } ;
33+ type InputComboData = {
34+ id : string ;
35+ label : string ;
36+ } ;
37+ type StateData = {
38+ nodes : Array < InputNodeData > ;
39+ removedNodes : Array < InputNodeData > ;
40+ edges : Array < InputEdgeData > ;
41+ removedEdges : Array < InputEdgeData > ;
42+ combos : Array < InputComboData > ;
43+ removedCombos : Array < InputComboData > ;
44+ appliedRules : Array < string > ;
45+ } ;
46+ type InputData = Array < StateData > ;
47+
48+ type NodeData = {
49+ label : string ;
50+ } ;
51+ type Node = ReactFlowNode < NodeData > ;
52+ type Edge = ReactFlowEdge < null > ;
53+
54+ // TODO proper parsing here
55+ const states = statesData as InputData ;
56+
1757// First is initial state
1858const totalIterations = states . length - 1 ;
1959const data = {
2060 nodes : states [ 0 ] . nodes ,
2161 edges : states [ 0 ] . edges ,
2262 combos : states [ 0 ] . combos ,
2363} ;
24- const sizeByNode = ( n ) => [ 60 + n . label . length * 5 , 30 ] ;
25- const toGroupNode = ( n ) => ( {
64+ const sizeByNode = ( n : InputNodeData ) : [ number , number ] => [
65+ 60 + n . label . length * 5 ,
66+ 30 ,
67+ ] ;
68+ const toGroupNode = ( n : InputComboData ) : Node => ( {
2669 ...n ,
2770 type : 'group' ,
2871 data : { label : n . label } ,
2972 position : { x : 0 , y : 0 } ,
3073 width : 200 ,
3174 height : 200 ,
3275} ) ;
33- const toRegularNode = ( n ) => ( {
76+ const toRegularNode = ( n : InputNodeData ) : Node => ( {
3477 ...n ,
3578 type : 'default' ,
3679 extent : 'parent' ,
@@ -41,7 +84,7 @@ const toRegularNode = (n) => ({
4184 draggable : false ,
4285 connectable : false ,
4386} ) ;
44- const toEdge = ( n ) => ( {
87+ const toEdge = ( n : InputEdgeData ) : Edge => ( {
4588 ...n ,
4689 id : `${ n . source } ->${ n . target } ` ,
4790 style :
@@ -55,15 +98,15 @@ const initialNodes = data.combos
5598const initialEdges = data . edges . map ( toEdge ) ;
5699
57100async function layout (
58- options ,
59- nodes ,
60- edges ,
61- setNodes ,
62- setEdges ,
63- fitView ,
64- navHistory ,
65- showOnlySelected ,
66- abortSignal ,
101+ options : LayoutOptions ,
102+ nodes : Array < Node > ,
103+ edges : Array < Edge > ,
104+ setNodes : ( nodes : Array < Node > ) => void ,
105+ setEdges : ( nodes : Array < Edge > ) => void ,
106+ fitView : FitView ,
107+ navHistory : NavHistoryState ,
108+ showOnlySelected : boolean ,
109+ abortSignal : AbortSignal ,
67110) {
68111 const defaultOptions = {
69112 'elk.algorithm' : 'layered' ,
@@ -76,13 +119,21 @@ async function layout(
76119
77120 nodes . forEach ( ( n ) => {
78121 if ( n . style && n . style . width && n . style . height ) {
122+ if ( typeof n . style . width === 'string' ) {
123+ throw new Error ( 'Unexpeted CSS width' ) ;
124+ }
125+ if ( typeof n . style . height === 'string' ) {
126+ throw new Error ( 'Unexpeted CSS height' ) ;
127+ }
79128 n . width = n . style . width ;
80129 n . height = n . style . height ;
81130 }
82131 } ) ;
83132 nodes = nodes . filter ( ( n ) => ! isHiddenNode ( showOnlySelected , navHistory , n ) ) ;
84133 edges = edges . filter ( ( e ) => ! isHiddenEdge ( showOnlySelected , navHistory , e ) ) ;
85134
135+ type ElkNodeWithChildren = ElkNode & { children : Array < ElkNode > } ;
136+
86137 const nodesMap = new Map (
87138 nodes . map ( ( node ) => [
88139 node . id ,
@@ -93,7 +144,7 @@ async function layout(
93144 width : node . width ?? undefined ,
94145 height : node . height ?? undefined ,
95146 children : [ ] ,
96- } ,
147+ } as ElkNodeWithChildren ,
97148 } ,
98149 ] ) ,
99150 ) ;
@@ -105,7 +156,8 @@ async function layout(
105156 if ( node . parentNode === undefined ) {
106157 return ;
107158 }
108- nodesMap . get ( node . parentNode ) . elkNode . children . push ( elkNode ) ;
159+ // Safety: we've just inserted every node from nodes to map
160+ nodesMap . get ( node . parentNode ) ! . elkNode . children . push ( elkNode ) ;
109161 }
110162
111163 // Primitive edges are deprecated in ELK, so we should use ElkExtendedEdge, that use arrays, essentially hyperedges
@@ -115,7 +167,7 @@ async function layout(
115167 targets : [ edge . target ] ,
116168 } ) ) ;
117169
118- const graph = {
170+ const graph : ElkNode = {
119171 id : 'root' ,
120172 layoutOptions,
121173 children : [ ...nodesMap . values ( ) ]
@@ -124,17 +176,24 @@ async function layout(
124176 edges : elkEdges ,
125177 } ;
126178
127- function elk2flow ( elkNode , flatChildren ) {
128- const node = nodesMap . get ( elkNode . id ) . node ;
179+ function elk2flow ( elkNode : ElkNode , flatChildren : Array < Node > ) : void {
180+ const nodePair = nodesMap . get ( elkNode . id ) ;
181+ if ( nodePair === undefined ) {
182+ throw new Error ( 'Unexpected node id from ELK' ) ;
183+ }
184+ const node = nodePair . node ;
129185
186+ if ( elkNode . x === undefined || elkNode . y === undefined ) {
187+ throw new Error ( 'Unexpected position from ELK' ) ;
188+ }
130189 node . position = { x : elkNode . x , y : elkNode . y } ;
131190 node . style = {
132191 ...node . style ,
133192 width : elkNode . width ,
134193 height : elkNode . height ,
135194 } ;
136- node . width = elkNode . width ;
137- node . height = elkNode . height ;
195+ node . width = elkNode . width ?? null ;
196+ node . height = elkNode . height ?? null ;
138197 flatChildren . push ( node ) ;
139198 ( elkNode . children ?? [ ] ) . forEach ( ( child ) => {
140199 elk2flow ( child , flatChildren ) ;
@@ -150,9 +209,9 @@ async function layout(
150209
151210 // By mutating the children in-place we saves ourselves from creating a
152211 // needless copy of the nodes array.
153- const flatChildren = [ ] ;
212+ const flatChildren : Array < Node > = [ ] ;
154213
155- children . forEach ( ( elkNode ) => {
214+ ( children ?? [ ] ) . forEach ( ( elkNode ) => {
156215 elk2flow ( elkNode , flatChildren ) ;
157216 } ) ;
158217
@@ -176,14 +235,18 @@ async function layout(
176235const highlightColor = 'rgba(170,255,170,0.71)' ;
177236const selectColor = 'rgba(170,187,255,0.71)' ;
178237
179- const zoomTo = ( fitView , classId ) => {
238+ const zoomTo = ( fitView : FitView , classId : Array < string > ) : void => {
180239 if ( ! classId ) {
181240 return ;
182241 }
183242 fitView ( { duration : 600 , nodes : classId . map ( ( id ) => ( { id : `c${ id } ` } ) ) } ) ;
184243} ;
185244
186- function isHiddenNode ( showOnlySelected , navHistory , n ) {
245+ function isHiddenNode (
246+ showOnlySelected : boolean ,
247+ navHistory : NavHistoryState ,
248+ n : Node ,
249+ ) : boolean {
187250 return (
188251 showOnlySelected &&
189252 navHistory . indexOf (
@@ -192,7 +255,11 @@ function isHiddenNode(showOnlySelected, navHistory, n) {
192255 ) ;
193256}
194257
195- const nodeStyles = ( nodes , navHistory , showOnlySelected ) => {
258+ const nodeStyles = (
259+ nodes : Array < Node > ,
260+ navHistory : NavHistoryState ,
261+ showOnlySelected : boolean ,
262+ ) : Array < Node > => {
196263 return nodes . map ( ( n ) => {
197264 return {
198265 ...n ,
@@ -208,15 +275,23 @@ const nodeStyles = (nodes, navHistory, showOnlySelected) => {
208275 } ) ;
209276} ;
210277
211- function isHiddenEdge ( showOnlySelected , navHistory , e ) {
278+ function isHiddenEdge (
279+ showOnlySelected : boolean ,
280+ navHistory : NavHistoryState ,
281+ e : Edge ,
282+ ) : boolean {
212283 return (
213284 showOnlySelected &&
214285 ( navHistory . indexOf ( e . source . replace ( / ^ ( \d + ) ( - ? ) .* $ / , '$1' ) ) === - 1 ||
215286 navHistory . indexOf ( e . target . replace ( / ^ ( \d + ) ( - ? ) .* $ / , '$1' ) ) === - 1 )
216287 ) ;
217288}
218289
219- const edgeStyles = ( edges , navHistory , showOnlySelected ) => {
290+ const edgeStyles = (
291+ edges : Array < Edge > ,
292+ navHistory : NavHistoryState ,
293+ showOnlySelected : boolean ,
294+ ) : Array < Edge > => {
220295 return edges . map ( ( e ) => {
221296 return {
222297 ...e ,
@@ -225,7 +300,7 @@ const edgeStyles = (edges, navHistory, showOnlySelected) => {
225300 } ) ;
226301} ;
227302
228- const splitLabel = ( label ) => {
303+ const splitLabel = ( label : string ) : Array < string > => {
229304 const result = [ '' ] ;
230305 let isDigit = false ;
231306 for ( let i = 0 ; i < label . length ; i ++ ) {
@@ -245,8 +320,9 @@ const splitLabel = (label) => {
245320} ;
246321
247322const ChildrenNode =
248- ( { navigate /*, nodes*/ } ) =>
249- ( { data : { label } } ) => {
323+ ( { navigate /*, nodes*/ } : { navigate : ( id : string ) => void } ) =>
324+ ( props : NodeProps < NodeData > ) => {
325+ const { label } = props . data ;
250326 return (
251327 < div >
252328 < Handle type = "target" position = { Position . Top } />
@@ -276,12 +352,19 @@ const ChildrenNode =
276352 ) ;
277353 } ;
278354
279- function jsonClone ( t ) {
355+ type PreNodesState = {
356+ preNodes : Array < Node > ;
357+ preEdges : Array < Edge > ;
358+ } ;
359+
360+ function jsonClone < T > ( t : T ) : T {
280361 return JSON . parse ( JSON . stringify ( t ) ) ;
281362}
282363
364+ type NavHistoryState = Array < string > ;
365+
283366const LayoutFlow = ( ) => {
284- const [ { preNodes, preEdges } , setPreNodesEdges ] = useState ( {
367+ const [ { preNodes, preEdges } , setPreNodesEdges ] = useState < PreNodesState > ( {
285368 preNodes : initialNodes ,
286369 preEdges : initialEdges ,
287370 } ) ;
@@ -295,8 +378,8 @@ const LayoutFlow = () => {
295378 const { fitView } = useReactFlow ( ) ;
296379
297380 const [ navigateTo , setNavigateTo ] = useState ( '' ) ;
298- const [ navHistory , setNavHistory ] = useState ( [ ] ) ;
299- const [ showOnlySelected , setShowOnlySelected ] = useState ( false ) ;
381+ const [ navHistory , setNavHistory ] = useState < NavHistoryState > ( [ ] ) ;
382+ const [ showOnlySelected , setShowOnlySelected ] = useState < boolean > ( false ) ;
300383
301384 const prevState = ( ) => {
302385 if ( stateIdx === 0 ) {
@@ -305,7 +388,7 @@ const LayoutFlow = () => {
305388 let newNodes = preNodes ;
306389 let newEdges = preEdges ;
307390 const toRemove = states [ stateIdx ] ;
308- let toRemoveNodeIds = toRemove . nodes
391+ let toRemoveNodeIds = ( toRemove . nodes as Array < { id : string } > )
309392 . concat ( toRemove . combos )
310393 . map ( ( n ) => n . id ) ;
311394 let toRemoveEdgeIds = toRemove . edges . map ( ( n ) => toEdge ( n ) . id ) ;
@@ -317,14 +400,14 @@ const LayoutFlow = () => {
317400 newNodes = newNodes . concat (
318401 ( toRemove . removedNodes || [ ] ) . map ( toRegularNode ) ,
319402 ) ;
320- const edgeMap = ( toRemove . removedEdges || [ ] )
403+ const edgeMap : Record < string , Edge > = ( toRemove . removedEdges || [ ] )
321404 . map ( toEdge )
322405 . reduce ( ( acc , val ) => ( { ...acc , [ val . id ] : val } ) , { } ) ;
323406 newEdges = newEdges . concat (
324407 Object . keys ( edgeMap ) . map ( ( key ) => edgeMap [ key ] ) ,
325408 ) ;
326409 const toHighlight = states [ stateIdx - 1 ] ;
327- const toHighlightNodeIds = toHighlight . nodes
410+ const toHighlightNodeIds = ( toHighlight . nodes as Array < { id : string } > )
328411 . concat ( toHighlight . combos )
329412 . map ( ( n ) => n . id ) ;
330413 newNodes = newNodes . map ( ( n ) => ( {
@@ -348,7 +431,7 @@ const LayoutFlow = () => {
348431 let newEdges = preEdges ;
349432 setStateIdx ( stateIdx + 1 ) ;
350433 const toAdd = states [ stateIdx + 1 ] ;
351- let toRemoveNodeIds = toAdd . removedNodes
434+ let toRemoveNodeIds = ( toAdd . removedNodes as Array < { id : string } > )
352435 . concat ( toAdd . removedCombos )
353436 . map ( ( n ) => n . id ) ;
354437 let toRemoveEdgeIds = toAdd . removedEdges . map ( ( n ) => toEdge ( n ) . id ) ;
@@ -367,7 +450,7 @@ const LayoutFlow = () => {
367450 style : { ...n . style , backgroundColor : highlightColor } ,
368451 } ) ) ,
369452 ) ;
370- const edgeMap = ( toAdd . edges || [ ] )
453+ const edgeMap : Record < string , Edge > = ( toAdd . edges || [ ] )
371454 . map ( toEdge )
372455 . reduce ( ( acc , val ) => ( { ...acc , [ val . id ] : val } ) , { } ) ;
373456 newEdges = newEdges . concat (
@@ -378,7 +461,7 @@ const LayoutFlow = () => {
378461 } ;
379462
380463 const navigate = useCallback (
381- ( id ) => {
464+ ( id : string ) : void => {
382465 zoomTo ( fitView , [ id ] ) ;
383466 if ( ! navHistory . includes ( id ) ) {
384467 setNavHistory ( navHistory . concat ( id ) ) ;
0 commit comments