export pipeline button added and completed

pull/33/head
Khwaja 3 months ago
parent 2062be1ed8
commit 46ca6a7620

@ -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",

@ -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",

@ -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<PipelineBlockProps> = props => {
const mapRef = useRef<Record<string, 'plant'|'product'|'center'>>({});
const flowWrapper = useRef<HTMLDivElement>(null);
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
@ -185,6 +192,23 @@ const PipelineBlock: React.FC<PipelineBlockProps> = 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 <button style={{ margin: '0 8px' }} onClick={onDownload}>Export Pipeline</button>;
};
const onLayout = () => {
const { nodes: ln, edges: le } = getLayouted(nodes, edges);
@ -200,38 +224,45 @@ const PipelineBlock: React.FC<PipelineBlockProps> = props => {
<>
<Section title="Pipeline" />
<Card>
<div className={styles.PipelineBlock}>
<ReactFlowProvider>
<div ref={flowWrapper} className={styles.PipelineBlock} style={{ width: '100%', height: 600 }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeDoubleClick={onNodeDoubleClick}
onNodeDragStop={onNodeDragStop}
onNodesDelete={handleNodesDelete}
deleteKeyCode="Delete"
maxZoom={1.25}
minZoom={0.5}
snapToGrid
preventScrolling
nodeTypes={{ default: CustomNode }}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeDoubleClick={onNodeDoubleClick}
onNodeDragStop={onNodeDragStop}
onNodesDelete={handleNodesDelete}
deleteKeyCode="Delete"
maxZoom={1.25}
minZoom={0.5}
snapToGrid
preventScrolling
nodeTypes={{ default: CustomNode }}
>
<Background />
<Controls showInteractive={false} />
</ReactFlow>
</div>
<div style={{ textAlign: 'center', marginTop: '1rem' }}>
<button style={{ margin: '0 8px' }} onClick={props.onAddProduct}>Add product</button>
<button style={{ margin: '0 8px' }} onClick={props.onAddPlant}>Add plant</button>
<button style={{ margin: '0 8px' }} onClick={props.onAddCenter}>Add center</button>
<button style={{ margin: '0 8px' }} onClick={onLayout}>Auto Layout</button>
<button title="Drag & connect. Double-click to rename. Delete to remove." style={{ margin: '0 8px' }}>?</button>
<DownloadButton />
<button style={{ margin: '0 8px' }} title="Drag & connect. Double-click to rename. Delete to remove.">?</button>
</div>
</ReactFlowProvider>
</Card>
</>
);
};
export default PipelineBlock;

Loading…
Cancel
Save