import { DSLComponents } from '@/interfaces/database/flow'; import { removeUselessFieldsFromValues } from '@/utils/form'; import dagre from 'dagre'; import { curry, isEmpty } from 'lodash'; import pipe from 'lodash/fp/pipe'; import { Edge, MarkerType, Node, Position } from 'reactflow'; import { v4 as uuidv4 } from 'uuid'; import { Operator, initialFormValuesMap } from './constant'; import { NodeData } from './interface'; const buildEdges = ( operatorIds: string[], currentId: string, allEdges: Edge[], isUpstream = false, ) => { operatorIds.forEach((cur) => { const source = isUpstream ? cur : currentId; const target = isUpstream ? currentId : cur; if (!allEdges.some((e) => e.source === source && e.target === target)) { allEdges.push({ id: uuidv4(), label: '', // type: 'step', source: source, target: target, markerEnd: { type: MarkerType.Arrow, }, }); } }); }; export const buildNodesAndEdgesFromDSLComponents = (data: DSLComponents) => { const nodes: Node[] = []; let edges: Edge[] = []; Object.entries(data).forEach(([key, value]) => { const downstream = [...value.downstream]; const upstream = [...value.upstream]; nodes.push({ id: key, type: 'textUpdater', position: { x: 0, y: 0 }, data: { label: value.obj.component_name, params: value.obj.params, downstream: downstream, upstream: upstream, }, sourcePosition: Position.Left, targetPosition: Position.Right, }); buildEdges(upstream, key, edges, true); buildEdges(downstream, key, edges, false); }); return { nodes, edges }; }; const dagreGraph = new dagre.graphlib.Graph(); dagreGraph.setDefaultEdgeLabel(() => ({})); const nodeWidth = 172; const nodeHeight = 36; export const getLayoutedElements = ( nodes: Node[], edges: Edge[], direction = 'TB', ) => { const isHorizontal = direction === 'LR'; dagreGraph.setGraph({ rankdir: direction }); nodes.forEach((node) => { dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }); }); edges.forEach((edge) => { dagreGraph.setEdge(edge.source, edge.target); }); dagre.layout(dagreGraph); nodes.forEach((node) => { const nodeWithPosition = dagreGraph.node(node.id); node.targetPosition = isHorizontal ? Position.Left : Position.Top; node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom; // We are shifting the dagre node position (anchor=center center) to the top left // so it matches the React Flow node anchor point (top left). node.position = { x: nodeWithPosition.x - nodeWidth / 2, y: nodeWithPosition.y - nodeHeight / 2, }; return node; }); return { nodes, edges }; }; const buildComponentDownstreamOrUpstream = ( edges: Edge[], nodeId: string, isBuildDownstream = true, ) => { return edges .filter((y) => y[isBuildDownstream ? 'source' : 'target'] === nodeId) .map((y) => y[isBuildDownstream ? 'target' : 'source']); }; const removeUselessDataInTheOperator = curry( (operatorName: string, params: Record) => { if (operatorName === Operator.Generate) { return removeUselessFieldsFromValues(params, ''); } return params; }, ); // initialize data for operators without parameters const initializeOperatorParams = curry((operatorName: string, values: any) => { if (isEmpty(values)) { return initialFormValuesMap[operatorName as Operator]; } return values; }); const buildOperatorParams = (operatorName: string) => pipe( removeUselessDataInTheOperator(operatorName), initializeOperatorParams(operatorName), // Final processing, for guarantee ); // construct a dsl based on the node information of the graph export const buildDslComponentsByGraph = ( nodes: Node[], edges: Edge[], ): DSLComponents => { const components: DSLComponents = {}; nodes.forEach((x) => { const id = x.id; const operatorName = x.data.label; components[id] = { obj: { component_name: operatorName, // params: // removeUselessDataInTheOperator( // operatorName, // x.data.form as Record, // ) ?? {}, params: buildOperatorParams(operatorName)( x.data.form as Record, ) ?? {}, }, downstream: buildComponentDownstreamOrUpstream(edges, id, true), upstream: buildComponentDownstreamOrUpstream(edges, id, false), }; }); return components; };