visual/react/app/page.tsx (110 lines of code) (raw):
'use client';
import { useLayoutEffect } from 'react';
import ReactFlow, {
Panel,
Controls,
Background,
Node,
Edge,
ReactFlowProvider,
useNodesState,
useEdgesState,
useReactFlow,
NodeTypes,
MarkerType,
Position,
} from 'reactflow';
import 'reactflow/dist/style.css';
import { layoutElk } from '../lib/layout'
import { toElk } from '../lib/util_elk';
import { initialized_nodes_edges } from '../lib/initialized';
import Step from '../components/Step';
import SubFlow from '../components/SubFlow';
const nodeTypes: NodeTypes = {
SubFlow,
Step,
};
function applyConfig({ nodes, edges }: { nodes: Node[], edges: Edge[] }): { layoutNodes: Node[], layoutEdges: Edge[] } {
let isParent = new Set<string>();
nodes.forEach((node: Node) => {
if (node.parentId) {
isParent.add(node.parentId);
}
})
return {
layoutNodes: nodes.map((node: Node) => {
return {
className: 'px-2 py-0.5 bg-white rounded-lg shadow-lg border-solid border-neutral-200 border-1' + ' ' + (
isParent.has(node.id) ?
// Step
'' :
// SubFlow
'max-w-xs'
),
deletable: false,
connectable: false,
extent: node.parentId ? 'parent' : undefined,
type: isParent.has(node.id) ? 'SubFlow' : 'Step',
expandParent: true, // TODO: remove
targetPosition: Position.Left,
sourcePosition: Position.Right,
...node,
}
}),
layoutEdges: edges.map((edge: Edge) => {
return {
animated: true,
deletable: false,
focusable: false,
markerEnd: { type: MarkerType.ArrowClosed },
...edge,
}
}),
}
}
function Flow() {
const { fitView } = useReactFlow();
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onLayout = () => {
layoutElk(toElk({ nodes, edges })).then(({ nodes, edges }) => {
const { layoutNodes, layoutEdges } = applyConfig({ nodes, edges });
setNodes(layoutNodes)
setEdges(layoutEdges)
}).catch((e) => console.error(e));
};
const onRefresh = () => {
const doRefresh = async () => {
const elkNode = await initialized_nodes_edges('/flow')
const { layoutNodes, layoutEdges } = applyConfig(await layoutElk(elkNode))
setNodes(layoutNodes)
setEdges(layoutEdges)
}
doRefresh();
window.requestAnimationFrame(() => fitView());
};
useLayoutEffect(onRefresh, []);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
nodeTypes={nodeTypes}
maxZoom={10}
minZoom={0.1}
>
<Background />
<Panel position='top-right'>
<button className='text-stone-800' onClick={onRefresh}>Refresh</button>
<p />
<button className='text-stone-800' onClick={onLayout}>Layout</button>
</Panel>
<Controls />
</ReactFlow>
);
};
export default function FlowWithProvider() {
return (
<ReactFlowProvider>
<Flow />
</ReactFlowProvider>
);
}