diff --git a/web/package-lock.json b/web/package-lock.json index b5c64aa..efd2e55 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -20,8 +20,6 @@ "@types/node": "^16.18.126", "@types/pako": "^2.0.3", "@types/papaparse": "^5.3.16", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.3", "@xyflow/react": "^12.7.1", "ajv": "^8.17.1", "eslint": "^8.57.1", @@ -35,6 +33,8 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", "@types/tabulator-tables": "^6.2.6", "prettier": "3.5.3" } @@ -4048,6 +4048,13 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "license": "MIT" }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/q": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", @@ -4067,21 +4074,24 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", - "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "devOptional": true, "license": "MIT", "dependencies": { + "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", - "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^19.0.0" + "@types/react": "^18.0.0" } }, "node_modules/@types/resolve": { @@ -6686,6 +6696,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, "license": "MIT" }, "node_modules/d3-color": { diff --git a/web/package.json b/web/package.json index fee0f17..2ef9916 100644 --- a/web/package.json +++ b/web/package.json @@ -15,8 +15,6 @@ "@types/node": "^16.18.126", "@types/pako": "^2.0.3", "@types/papaparse": "^5.3.16", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.3", "@xyflow/react": "^12.7.1", "ajv": "^8.17.1", "eslint": "^8.57.1", @@ -60,6 +58,8 @@ ] }, "devDependencies": { + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", "@types/tabulator-tables": "^6.2.6", "prettier": "3.5.3" } diff --git a/web/src/components/CaseBuilder/CaseBuilder.tsx b/web/src/components/CaseBuilder/CaseBuilder.tsx index 807780a..9ef425b 100644 --- a/web/src/components/CaseBuilder/CaseBuilder.tsx +++ b/web/src/components/CaseBuilder/CaseBuilder.tsx @@ -14,6 +14,7 @@ import {CircularData} from "./CircularData"; import { defaultPlant, defaultProduct } from "./defaults"; import PipelineBlock from "./PipelineBlock"; import '@xyflow/react/dist/style.css'; +import { CircularPlant } from "./CircularData"; declare global { interface Window { nextX: number; @@ -96,13 +97,105 @@ const CaseBuilder = () => { }; + const onSetPlantInput = (plantName: string, productName: string) => { + + setCircularData((prevData: CircularData) => { + + const plant = prevData.plants[plantName]; + + if (!plant) return prevData; + + const updatedPlant: CircularPlant = { + + ...plant, + + inputs: plant.inputs.includes(productName) + + ? plant.inputs + + : [...plant.inputs, productName], + + }; + + return { + + ...prevData, + + plants: { + + ...prevData.plants, + + [plantName]: updatedPlant, + + }, + + }; + + }); + +}; + +const onAddPlantOutput = (plantName: string, productName: string) => { + setCircularData((prevData) => { + + const plant = prevData.plants[plantName]; + + if (!plant) return prevData; + + const updatedPlant: CircularPlant = { + + ...plant, + + outputs: { + + ...plant.outputs, + + [productName]: 0, + + }, + + }; + + return { + + ...prevData, + + plants: { + + ...prevData.plants, + + [plantName]: updatedPlant, + + }, + + }; + + }); + +}; + + + const onMovePlant = (plantName: string, x: number, y: number) => { - console.log("Move plant", plantName, x,y); + setCircularData((prevData: CircularData): CircularData => { + const newData: CircularData ={ ...prevData}; + if (!newData.plants[plantName]) return prevData; + newData.plants[plantName].x =x; + newData.plants[plantName].y =y; + return newData; + }); }; - const onMoveProduct = (productname: string, x: number, y: number) => { - console.log("Move product", productname, x,y); + const onMoveProduct = (productName: string, x: number, y: number) => { + setCircularData((prevData: CircularData): CircularData => { + const newData: CircularData ={ ...prevData}; + const product = newData.products[productName]; + if (!product) return prevData; + product.x = x; + product.y =y; + return newData; + }); }; @@ -119,6 +212,8 @@ const CaseBuilder = () => { onMoveProduct={onMoveProduct} plants={circularData.plants} products={circularData.products} + onSetPlantInput={onSetPlantInput} + onAddPlantOutput={onAddPlantOutput} /> diff --git a/web/src/components/CaseBuilder/CircularData.ts b/web/src/components/CaseBuilder/CircularData.ts index 785f933..391aa04 100644 --- a/web/src/components/CaseBuilder/CircularData.ts +++ b/web/src/components/CaseBuilder/CircularData.ts @@ -2,6 +2,8 @@ export interface CircularPlant { id: string; x: number; y: number; + inputs: string[]; + outputs: Record; } diff --git a/web/src/components/CaseBuilder/PipelineBlock.tsx b/web/src/components/CaseBuilder/PipelineBlock.tsx index b30910f..8934ca1 100644 --- a/web/src/components/CaseBuilder/PipelineBlock.tsx +++ b/web/src/components/CaseBuilder/PipelineBlock.tsx @@ -5,6 +5,7 @@ import { ReactFlow, Background, Controls } from '@xyflow/react'; import Section from '../Common/Section'; import Card from '../Common/Card'; import { useEffect } from "react"; +import { Connection } from '@xyflow/react'; @@ -13,12 +14,13 @@ interface PipelineBlockProps { onAddProduct: () => void; onMovePlant: (name: string , x: number, y: number) => void; onMoveProduct: (name: string, x: number, y: number) => void; + onSetPlantInput: (plantName:string, productName: string) => void; + onAddPlantOutput: (plantName: string, productName: string) => void; products: Record; plants: Record; } const onNodeDoubleClick = () => {}; -const onNodeDragStop = () => {}; -const onConnect = () => {}; + const handleNodesDelete = () => {}; const handleEdgesDelete = () => {}; const onLayout = () => {}; @@ -30,6 +32,32 @@ const PipelineBlock: React.FC = (props) => { let mapNameToType: Record = {}; let hasNullPositions: boolean = false; + const onConnect = (params: Connection) => { + const { source, target } = params; + if (!source || ! target) return; + + const sourceType = mapNameToType[source]; + const targetType = mapNameToType[target]; + + if (sourceType === "product" && targetType === "plant") { + props.onSetPlantInput(target,source); + } else if (sourceType === "plant" && targetType === "product") { + props.onAddPlantOutput(source, target); + } + +}; + +const onNodeDragStop =(_:any, node: Node) => { + const { id, position, data} = node; + if (data.type === "plant") { + props.onMovePlant(id, position.x, position.y); + } + if (data.type === "product") { + props.onMoveProduct(id, position.x, position.y); + } + +}; + for (const [productName, product] of Object.entries(props.products) as [string, CircularProduct][]) { if(!product.x || !product.y) hasNullPositions = true; mapNameToType[productName] = "product"; @@ -52,15 +80,26 @@ const PipelineBlock: React.FC = (props) => { }); if (plant) { + for (const inputProduct of plant.inputs){ edges.push({ - id: `${plantName}-${plantName}`, - source: plantName, + id: `${inputProduct}-${plantName}`, + source: inputProduct, target: plantName, animated: true, style: { stroke: "black" }, }); - + } + for (const outputProduct of plant.inputs){ + edges.push({ + id: `${plantName}-${outputProduct}`, + source: plantName, + target: outputProduct, + animated: true, + style: { stroke: "black" }, + + }); + } } } diff --git a/web/src/components/CaseBuilder/defaults.ts b/web/src/components/CaseBuilder/defaults.ts index 0769f1e..9a1f737 100644 --- a/web/src/components/CaseBuilder/defaults.ts +++ b/web/src/components/CaseBuilder/defaults.ts @@ -14,11 +14,12 @@ export const defaultProduct: DefaultProduct = { "id": "", x: 0, y: 0, - }; -export const defaultPlant: DefaultPlant = { +export const defaultPlant: CircularPlant = { "id": "", x: 0, y: 0, + inputs : [], + outputs: {}, }; \ No newline at end of file