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", "@types/papaparse": "^5.3.16",
"@xyflow/react": "^12.7.1", "@xyflow/react": "^12.7.1",
"ajv": "^8.17.1", "ajv": "^8.17.1",
"dagre": "^0.8.5",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"html-to-image": "^1.11.13",
"pako": "^2.1.0", "pako": "^2.1.0",
"papaparse": "^5.5.2", "papaparse": "^5.5.2",
"react": "^18.3.1", "react": "^18.3.1",
@ -33,6 +35,7 @@
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {
"@types/dagre": "^0.7.53",
"@types/react": "^18.3.23", "@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"@types/tabulator-tables": "^6.2.6", "@types/tabulator-tables": "^6.2.6",
@ -3862,6 +3865,13 @@
"@types/d3-selection": "*" "@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": { "node_modules/@types/eslint": {
"version": "8.56.12", "version": "8.56.12",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
@ -6804,6 +6814,16 @@
"node": ">=12" "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": { "node_modules/damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -9156,6 +9176,15 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"license": "MIT" "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": { "node_modules/gzip-size": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
@ -9391,6 +9420,12 @@
"node": ">=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": { "node_modules/html-webpack-plugin": {
"version": "5.6.3", "version": "5.6.3",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz",

@ -17,7 +17,9 @@
"@types/papaparse": "^5.3.16", "@types/papaparse": "^5.3.16",
"@xyflow/react": "^12.7.1", "@xyflow/react": "^12.7.1",
"ajv": "^8.17.1", "ajv": "^8.17.1",
"dagre": "^0.8.5",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"html-to-image": "^1.11.13",
"pako": "^2.1.0", "pako": "^2.1.0",
"papaparse": "^5.5.2", "papaparse": "^5.5.2",
"react": "^18.3.1", "react": "^18.3.1",
@ -58,6 +60,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@types/dagre": "^0.7.53",
"@types/react": "^18.3.23", "@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"@types/tabulator-tables": "^6.2.6", "@types/tabulator-tables": "^6.2.6",

@ -1,13 +1,17 @@
import React, { useEffect, useCallback, useRef } from 'react'; import React, { useEffect, useCallback, useRef } from 'react';
import dagre from 'dagre'; import dagre from 'dagre';
import { import {
ReactFlow, useNodesState, useEdgesState, Background, ReactFlow,ReactFlowProvider, useNodesState, useEdgesState, Background,
Controls, Node, Edge, Connection, MarkerType } from '@xyflow/react'; Controls, Node, Edge, Connection, MarkerType,
getNodesBounds,
getViewportForBounds,
useReactFlow} from '@xyflow/react';
import { CircularPlant, CircularProduct, CircularCenter } from './CircularData'; import { CircularPlant, CircularProduct, CircularCenter } from './CircularData';
import CustomNode, { CustomNodeData } from './NodesAndEdges'; import CustomNode, { CustomNodeData } from './NodesAndEdges';
import Section from '../Common/Section'; import Section from '../Common/Section';
import Card from '../Common/Card'; import Card from '../Common/Card';
import styles from './PipelineBlock.module.css'; import styles from './PipelineBlock.module.css';
import { toPng } from 'html-to-image';
interface PipelineBlockProps { interface PipelineBlockProps {
onAddPlant: () => void; onAddPlant: () => void;
@ -52,6 +56,9 @@ function getLayouted(
} }
const PipelineBlock: React.FC<PipelineBlockProps> = props => { const PipelineBlock: React.FC<PipelineBlockProps> = props => {
const mapRef = useRef<Record<string, 'plant'|'product'|'center'>>({}); const mapRef = useRef<Record<string, 'plant'|'product'|'center'>>({});
const flowWrapper = useRef<HTMLDivElement>(null);
const [nodes, setNodes, onNodesChange] = useNodesState([]); const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]); 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==='product') props.onRenameProduct(uniqueId, newName);
if (n.data.type==='center') props.onRenameCenter(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 onLayout = () => {
const { nodes: ln, edges: le } = getLayouted(nodes, edges); const { nodes: ln, edges: le } = getLayouted(nodes, edges);
@ -200,8 +224,10 @@ const PipelineBlock: React.FC<PipelineBlockProps> = props => {
<> <>
<Section title="Pipeline" /> <Section title="Pipeline" />
<Card> <Card>
<div className={styles.PipelineBlock}> <ReactFlowProvider>
<div ref={flowWrapper} className={styles.PipelineBlock} style={{ width: '100%', height: 600 }}>
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
@ -221,17 +247,22 @@ const PipelineBlock: React.FC<PipelineBlockProps> = props => {
<Controls showInteractive={false} /> <Controls showInteractive={false} />
</ReactFlow> </ReactFlow>
</div> </div>
<div style={{ textAlign: 'center', marginTop: '1rem' }}> <div style={{ textAlign: 'center', marginTop: '1rem' }}>
<button style={{ margin: '0 8px' }} onClick={props.onAddProduct}>Add product</button> <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.onAddPlant}>Add plant</button>
<button style={{ margin: '0 8px' }} onClick={props.onAddCenter}>Add center</button> <button style={{ margin: '0 8px' }} onClick={props.onAddCenter}>Add center</button>
<button style={{ margin: '0 8px' }} onClick={onLayout}>Auto Layout</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> </div>
</ReactFlowProvider>
</Card> </Card>
</> </>
); );
}; };
export default PipelineBlock; export default PipelineBlock;

Loading…
Cancel
Save