|
import { useCallback, useEffect } from 'react'; |
|
import Dagre from '@dagrejs/dagre'; |
|
import { usePrevious } from 'hooks/usePrevious'; |
|
import { isEqual } from 'lodash'; |
|
import { useNodesInitialized, useNodes, useReactFlow } from 'reactflow'; |
|
|
|
const getLayoutedElements = (nodes, edges) => { |
|
const graph = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); |
|
graph.setGraph({ |
|
rankdir: 'TB', |
|
marginy: 60, |
|
ranksep: 64, |
|
}); |
|
edges.forEach((edge) => graph.setEdge(edge.source, edge.target)); |
|
nodes.forEach((node) => graph.setNode(node.id, node)); |
|
|
|
Dagre.layout(graph); |
|
|
|
return { |
|
nodes: nodes.map((node) => { |
|
const { x, y, width, height } = graph.node(node.id); |
|
return { |
|
...node, |
|
position: { x: x - width / 2, y: y - height / 2 }, |
|
}; |
|
}), |
|
edges, |
|
}; |
|
}; |
|
|
|
export const useAutoLayout = () => { |
|
const nodes = useNodes(); |
|
const prevNodes = usePrevious(nodes); |
|
const nodesInitialized = useNodesInitialized(); |
|
const { getEdges, setNodes, setEdges } = useReactFlow(); |
|
|
|
const onLayout = useCallback( |
|
(nodes, edges) => { |
|
const layoutedElements = getLayoutedElements(nodes, edges); |
|
|
|
setNodes([ |
|
...layoutedElements.nodes.map((node) => ({ |
|
...node, |
|
data: { ...node.data, layouted: true }, |
|
})), |
|
]); |
|
setEdges([ |
|
...layoutedElements.edges.map((edge) => ({ |
|
...edge, |
|
data: { ...edge.data, layouted: true }, |
|
})), |
|
]); |
|
}, |
|
[setEdges, setNodes], |
|
); |
|
|
|
useEffect(() => { |
|
const shouldAutoLayout = |
|
nodesInitialized && |
|
!isEqual( |
|
nodes.map(({ width, height }) => ({ width, height })), |
|
prevNodes.map(({ width, height }) => ({ width, height })), |
|
); |
|
|
|
if (shouldAutoLayout) { |
|
onLayout(nodes, getEdges()); |
|
} |
|
}, [nodes]); |
|
}; |
|
|