From 46ca6a76208768a3605a9729935687347afcfd69 Mon Sep 17 00:00:00 2001 From: Khwaja Date: Fri, 11 Jul 2025 13:21:23 -0500 Subject: [PATCH] export pipeline button added and completed --- web/package-lock.json | 35 ++++++++ web/package.json | 3 + .../components/CaseBuilder/PipelineBlock.tsx | 81 +++++++++++++------ 3 files changed, 94 insertions(+), 25 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index efd2e55..a225849 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -22,7 +22,9 @@ "@types/papaparse": "^5.3.16", "@xyflow/react": "^12.7.1", "ajv": "^8.17.1", + "dagre": "^0.8.5", "eslint": "^8.57.1", + "html-to-image": "^1.11.13", "pako": "^2.1.0", "papaparse": "^5.5.2", "react": "^18.3.1", @@ -33,6 +35,7 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@types/dagre": "^0.7.53", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@types/tabulator-tables": "^6.2.6", @@ -3862,6 +3865,13 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/dagre": { + "version": "0.7.53", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.53.tgz", + "integrity": "sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "8.56.12", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", @@ -6804,6 +6814,16 @@ "node": ">=12" } }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -9156,6 +9176,15 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "license": "MIT" }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -9391,6 +9420,12 @@ "node": ">=12" } }, + "node_modules/html-to-image": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz", + "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", + "license": "MIT" + }, "node_modules/html-webpack-plugin": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", diff --git a/web/package.json b/web/package.json index 2ef9916..1a76852 100644 --- a/web/package.json +++ b/web/package.json @@ -17,7 +17,9 @@ "@types/papaparse": "^5.3.16", "@xyflow/react": "^12.7.1", "ajv": "^8.17.1", + "dagre": "^0.8.5", "eslint": "^8.57.1", + "html-to-image": "^1.11.13", "pako": "^2.1.0", "papaparse": "^5.5.2", "react": "^18.3.1", @@ -58,6 +60,7 @@ ] }, "devDependencies": { + "@types/dagre": "^0.7.53", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@types/tabulator-tables": "^6.2.6", diff --git a/web/src/components/CaseBuilder/PipelineBlock.tsx b/web/src/components/CaseBuilder/PipelineBlock.tsx index 34b869f..f81190d 100644 --- a/web/src/components/CaseBuilder/PipelineBlock.tsx +++ b/web/src/components/CaseBuilder/PipelineBlock.tsx @@ -1,13 +1,17 @@ import React, { useEffect, useCallback, useRef } from 'react'; import dagre from 'dagre'; import { - ReactFlow, useNodesState, useEdgesState, Background, - Controls, Node, Edge, Connection, MarkerType } from '@xyflow/react'; + ReactFlow,ReactFlowProvider, useNodesState, useEdgesState, Background, + Controls, Node, Edge, Connection, MarkerType, + getNodesBounds, + getViewportForBounds, + useReactFlow} from '@xyflow/react'; import { CircularPlant, CircularProduct, CircularCenter } from './CircularData'; import CustomNode, { CustomNodeData } from './NodesAndEdges'; import Section from '../Common/Section'; import Card from '../Common/Card'; import styles from './PipelineBlock.module.css'; +import { toPng } from 'html-to-image'; interface PipelineBlockProps { onAddPlant: () => void; @@ -52,6 +56,9 @@ function getLayouted( } const PipelineBlock: React.FC = props => { const mapRef = useRef>({}); + + const flowWrapper = useRef(null); + const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -149,13 +156,13 @@ const PipelineBlock: React.FC = props => { ]); useEffect(() => { rebuild(); }, [rebuild]); - const onConnect = (c: Connection) => { - const s = c.source!, t = c.target!; - const st = mapRef.current[s], tt = mapRef.current[t]; - if (st==='product' && tt==='plant') props.onSetPlantInput(t, s); - else if (st==='plant' && tt==='product') props.onAddPlantOutput(s, t); - else if (st==='product' && tt==='center') props.onAddCenterInput(t, s); - else if (st==='center' && tt==='product') props.onAddCenterOutput(s, t); + const onConnect = (c: Connection) => { + const s = c.source!, t = c.target!; + const st = mapRef.current[s], tt = mapRef.current[t]; + if (st==='product' && tt==='plant') props.onSetPlantInput(t, s); + else if (st==='plant' && tt==='product') props.onAddPlantOutput(s, t); + else if (st==='product' && tt==='center') props.onAddCenterInput(t, s); + else if (st==='center' && tt==='product') props.onAddCenterOutput(s, t); }; const onNodeDragStop = (_: any, n: Node) => { @@ -185,6 +192,23 @@ const PipelineBlock: React.FC = props => { if (n.data.type==='product') props.onRenameProduct(uniqueId, newName); if (n.data.type==='center') props.onRenameCenter(uniqueId, newName); }; + function DownloadButton() { + const onDownload = async () => { + if (!flowWrapper.current) return; + const node = flowWrapper.current; + const { width, height } = node.getBoundingClientRect(); + const dataUrl = await toPng(node, { + backgroundColor: '#fff', + width: Math.round(width), + height: Math.round(height) + }); + const downloadLink = document.createElement('a'); + downloadLink.href = dataUrl; + downloadLink.download = 'pipeline.png'; + downloadLink.click(); + }; + return ; + }; const onLayout = () => { const { nodes: ln, edges: le } = getLayouted(nodes, edges); @@ -200,38 +224,45 @@ const PipelineBlock: React.FC = props => { <>
-
+ +
+
- + + +
+
); + }; export default PipelineBlock;