### What problem does this PR solve? feat: remove dagre and elkjs #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.9.0
| "antd": "^5.12.7", | "antd": "^5.12.7", | ||||
| "axios": "^1.6.3", | "axios": "^1.6.3", | ||||
| "classnames": "^2.5.1", | "classnames": "^2.5.1", | ||||
| "dagre": "^0.8.5", | |||||
| "dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
| "dompurify": "^3.1.6", | "dompurify": "^3.1.6", | ||||
| "elkjs": "^0.9.3", | |||||
| "eventsource-parser": "^1.1.2", | "eventsource-parser": "^1.1.2", | ||||
| "human-id": "^4.1.1", | "human-id": "^4.1.1", | ||||
| "i18next": "^23.7.16", | "i18next": "^23.7.16", | ||||
| "@redux-devtools/extension": "^3.3.0", | "@redux-devtools/extension": "^3.3.0", | ||||
| "@testing-library/jest-dom": "^6.4.5", | "@testing-library/jest-dom": "^6.4.5", | ||||
| "@testing-library/react": "^15.0.7", | "@testing-library/react": "^15.0.7", | ||||
| "@types/dagre": "^0.7.52", | |||||
| "@types/dompurify": "^3.0.5", | "@types/dompurify": "^3.0.5", | ||||
| "@types/jest": "^29.5.12", | "@types/jest": "^29.5.12", | ||||
| "@types/lodash": "^4.14.202", | "@types/lodash": "^4.14.202", | ||||
| "@types/d3-selection": "*" | "@types/d3-selection": "*" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@types/dagre": { | |||||
| "version": "0.7.52", | |||||
| "resolved": "https://registry.npmmirror.com/@types/dagre/-/dagre-0.7.52.tgz", | |||||
| "integrity": "sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw==", | |||||
| "dev": true | |||||
| }, | |||||
| "node_modules/@types/debug": { | "node_modules/@types/debug": { | ||||
| "version": "4.1.12", | "version": "4.1.12", | ||||
| "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", | "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", | ||||
| "node": ">=12" | "node": ">=12" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/dagre": { | |||||
| "version": "0.8.5", | |||||
| "resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz", | |||||
| "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", | |||||
| "dependencies": { | |||||
| "graphlib": "^2.1.8", | |||||
| "lodash": "^4.17.15" | |||||
| } | |||||
| }, | |||||
| "node_modules/data-uri-to-buffer": { | "node_modules/data-uri-to-buffer": { | ||||
| "version": "4.0.1", | "version": "4.0.1", | ||||
| "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", | "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", | ||||
| "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.767.tgz", | "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.767.tgz", | ||||
| "integrity": "sha512-nzzHfmQqBss7CE3apQHkHjXW77+8w3ubGCIoEijKCJebPufREaFETgGXWTkh32t259F3Kcq+R8MZdFdOJROgYw==" | "integrity": "sha512-nzzHfmQqBss7CE3apQHkHjXW77+8w3ubGCIoEijKCJebPufREaFETgGXWTkh32t259F3Kcq+R8MZdFdOJROgYw==" | ||||
| }, | }, | ||||
| "node_modules/elkjs": { | |||||
| "version": "0.9.3", | |||||
| "resolved": "https://registry.npmmirror.com/elkjs/-/elkjs-0.9.3.tgz", | |||||
| "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==" | |||||
| }, | |||||
| "node_modules/elliptic": { | "node_modules/elliptic": { | ||||
| "version": "6.5.5", | "version": "6.5.5", | ||||
| "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.5.tgz", | "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.5.tgz", | ||||
| "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", | "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", | ||||
| "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" | "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" | ||||
| }, | }, | ||||
| "node_modules/graphlib": { | |||||
| "version": "2.1.8", | |||||
| "resolved": "https://registry.npmmirror.com/graphlib/-/graphlib-2.1.8.tgz", | |||||
| "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", | |||||
| "dependencies": { | |||||
| "lodash": "^4.17.15" | |||||
| } | |||||
| }, | |||||
| "node_modules/gzip-size": { | "node_modules/gzip-size": { | ||||
| "version": "6.0.0", | "version": "6.0.0", | ||||
| "resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz", | "resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz", |
| "antd": "^5.12.7", | "antd": "^5.12.7", | ||||
| "axios": "^1.6.3", | "axios": "^1.6.3", | ||||
| "classnames": "^2.5.1", | "classnames": "^2.5.1", | ||||
| "dagre": "^0.8.5", | |||||
| "dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
| "dompurify": "^3.1.6", | "dompurify": "^3.1.6", | ||||
| "elkjs": "^0.9.3", | |||||
| "eventsource-parser": "^1.1.2", | "eventsource-parser": "^1.1.2", | ||||
| "human-id": "^4.1.1", | "human-id": "^4.1.1", | ||||
| "i18next": "^23.7.16", | "i18next": "^23.7.16", | ||||
| "@redux-devtools/extension": "^3.3.0", | "@redux-devtools/extension": "^3.3.0", | ||||
| "@testing-library/jest-dom": "^6.4.5", | "@testing-library/jest-dom": "^6.4.5", | ||||
| "@testing-library/react": "^15.0.7", | "@testing-library/react": "^15.0.7", | ||||
| "@types/dagre": "^0.7.52", | |||||
| "@types/dompurify": "^3.0.5", | "@types/dompurify": "^3.0.5", | ||||
| "@types/jest": "^29.5.12", | "@types/jest": "^29.5.12", | ||||
| "@types/lodash": "^4.14.202", | "@types/lodash": "^4.14.202", |
| import { useCallback, useLayoutEffect } from 'react'; | |||||
| import { getLayoutedElements } from './elk-utils'; | |||||
| export const elkOptions = { | |||||
| 'elk.algorithm': 'layered', | |||||
| 'elk.layered.spacing.nodeNodeBetweenLayers': '100', | |||||
| 'elk.spacing.nodeNode': '80', | |||||
| }; | |||||
| export const useLayoutGraph = ( | |||||
| initialNodes, | |||||
| initialEdges, | |||||
| setNodes, | |||||
| setEdges, | |||||
| ) => { | |||||
| const onLayout = useCallback(({ direction, useInitialNodes = false }) => { | |||||
| const opts = { 'elk.direction': direction, ...elkOptions }; | |||||
| const ns = initialNodes; | |||||
| const es = initialEdges; | |||||
| getLayoutedElements(ns, es, opts).then( | |||||
| ({ nodes: layoutedNodes, edges: layoutedEdges }) => { | |||||
| setNodes(layoutedNodes); | |||||
| setEdges(layoutedEdges); | |||||
| // window.requestAnimationFrame(() => fitView()); | |||||
| }, | |||||
| ); | |||||
| }, []); | |||||
| // Calculate the initial layout on mount. | |||||
| useLayoutEffect(() => { | |||||
| onLayout({ direction: 'RIGHT', useInitialNodes: true }); | |||||
| }, [onLayout]); | |||||
| }; |
| import ELK from 'elkjs/lib/elk.bundled.js'; | |||||
| import { Edge, Node } from 'reactflow'; | |||||
| const elk = new ELK(); | |||||
| export const getLayoutedElements = ( | |||||
| nodes: Node[], | |||||
| edges: Edge[], | |||||
| options = {}, | |||||
| ) => { | |||||
| const isHorizontal = options?.['elk.direction'] === 'RIGHT'; | |||||
| const graph = { | |||||
| id: 'root', | |||||
| layoutOptions: options, | |||||
| children: nodes.map((node) => ({ | |||||
| ...node, | |||||
| // Adjust the target and source handle positions based on the layout | |||||
| // direction. | |||||
| targetPosition: isHorizontal ? 'left' : 'top', | |||||
| sourcePosition: isHorizontal ? 'right' : 'bottom', | |||||
| // Hardcode a width and height for elk to use when layouting. | |||||
| width: 150, | |||||
| height: 50, | |||||
| })), | |||||
| edges: edges, | |||||
| }; | |||||
| return elk | |||||
| .layout(graph) | |||||
| .then((layoutedGraph) => ({ | |||||
| nodes: layoutedGraph.children.map((node) => ({ | |||||
| ...node, | |||||
| // React Flow expects a position property on the node instead of `x` | |||||
| // and `y` fields. | |||||
| position: { x: node.x, y: node.y }, | |||||
| })), | |||||
| edges: layoutedGraph.edges, | |||||
| })) | |||||
| .catch(console.error); | |||||
| }; |
| import { DSLComponents } from '@/interfaces/database/flow'; | import { DSLComponents } from '@/interfaces/database/flow'; | ||||
| import { removeUselessFieldsFromValues } from '@/utils/form'; | import { removeUselessFieldsFromValues } from '@/utils/form'; | ||||
| import dagre from 'dagre'; | |||||
| import { humanId } from 'human-id'; | import { humanId } from 'human-id'; | ||||
| import { curry, sample } from 'lodash'; | import { curry, sample } from 'lodash'; | ||||
| import pipe from 'lodash/fp/pipe'; | import pipe from 'lodash/fp/pipe'; | ||||
| return { nodes, edges }; | 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 = ( | const buildComponentDownstreamOrUpstream = ( | ||||
| edges: Edge[], | edges: Edge[], | ||||
| nodeId: string, | nodeId: string, |