From 6e6a4cc175299f326104b785f3101fe6c56e33d1 Mon Sep 17 00:00:00 2001 From: Khwaja Date: Mon, 30 Jun 2025 15:47:50 -0500 Subject: [PATCH] Added center to interface --- .../components/CaseBuilder/CaseBuilder.tsx | 112 ++++++++++---- .../components/CaseBuilder/CircularData.ts | 14 +- .../components/CaseBuilder/NodesAndEdges.tsx | 63 ++++++-- .../CaseBuilder/PipelineBlock.module.css | 10 +- .../components/CaseBuilder/PipelineBlock.tsx | 137 ++++++++++++------ web/src/components/CaseBuilder/defaults.ts | 29 +++- 6 files changed, 269 insertions(+), 96 deletions(-) diff --git a/web/src/components/CaseBuilder/CaseBuilder.tsx b/web/src/components/CaseBuilder/CaseBuilder.tsx index 9ef425b..14d06df 100644 --- a/web/src/components/CaseBuilder/CaseBuilder.tsx +++ b/web/src/components/CaseBuilder/CaseBuilder.tsx @@ -11,10 +11,10 @@ import "../Common/Forms/Tables.css"; import Footer from "./Footer"; import React, { useState } from "react"; import {CircularData} from "./CircularData"; -import { defaultPlant, defaultProduct } from "./defaults"; +import { defaultPlant, defaultProduct, defaultCenter } from "./defaults"; import PipelineBlock from "./PipelineBlock"; import '@xyflow/react/dist/style.css'; -import { CircularPlant } from "./CircularData"; +import { CircularPlant, CircularCenter} from "./CircularData"; declare global { interface Window { nextX: number; @@ -25,7 +25,8 @@ declare global { const CaseBuilder = () => { const [circularData, setCircularData] = useState ( { plants: {}, - products: {} + products: {}, + centers: {} }); const onClear = () => {}; @@ -97,6 +98,39 @@ const CaseBuilder = () => { }; + const onAddCenter = () => { + setCircularData(prev => { + const name = prompt("Center name"); + if (!name || name in prev.centers) return prev; + + const [x,y] = randomPosition(); + const next = { + ...prev, + centers: { + ...prev.centers, + [name]: { ...defaultCenter, id:name, x, y, outputs: []} + + } + }; + return next; + + }); +}; + +const onSetCenterInput = (centerName: string, productName: string) => { + setCircularData((prev) => { + const center = prev.centers[centerName]; + if (!center) return prev; + return { + ...prev, + centers: { + ...prev.centers, + [centerName]: { ...center, input: productName}, + }, + }; + }); +}; + const onSetPlantInput = (plantName: string, productName: string) => { setCircularData((prevData: CircularData) => { @@ -135,43 +169,46 @@ const CaseBuilder = () => { }; -const onAddPlantOutput = (plantName: string, productName: string) => { - setCircularData((prevData) => { - const plant = prevData.plants[plantName]; +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, - - }, - - }; + // Build a new array of outputs, avoiding duplicates + const newOutputs = plant.outputs.includes(productName) + ? plant.outputs + : [...plant.outputs, productName]; + // Return updated state with outputs as an array return { - ...prevData, - plants: { - ...prevData.plants, - - [plantName]: updatedPlant, - + [plantName]: { + ...plant, + outputs: newOutputs, + }, }, - }; - }); - +}; + +const onAddCenterOutput = (centerName: string, productName: string) => { + setCircularData((prev) => { + const center = prev.centers[centerName]; + if (!center) return prev; + + const updatedOutputs = [...center.output, productName]; + return { + ...prev, + centers: { + ...prev.centers, + [centerName]: { ...center, output: updatedOutputs}, + }, + }; +}); }; @@ -199,6 +236,20 @@ const onAddPlantOutput = (plantName: string, productName: string) => { }; + const onMoveCenter = (centerName: string, x: number, y: number) => { + setCircularData((prev) => { + const center = prev.centers[centerName]; + if (!center) return prev; + return { + ...prev, + centers: { + ...prev.centers, + [centerName]: { ...center,x,y}, + }, + }; + }); + }; + return (
@@ -214,6 +265,11 @@ const onAddPlantOutput = (plantName: string, productName: string) => { products={circularData.products} onSetPlantInput={onSetPlantInput} onAddPlantOutput={onAddPlantOutput} + onAddCenter= {onAddCenter} + onAddCenterInput={onSetCenterInput} + onAddCenterOutput={onAddCenterOutput} + onMoveCenter={onMoveCenter} + centers={circularData.centers} />
diff --git a/web/src/components/CaseBuilder/CircularData.ts b/web/src/components/CaseBuilder/CircularData.ts index 391aa04..8100802 100644 --- a/web/src/components/CaseBuilder/CircularData.ts +++ b/web/src/components/CaseBuilder/CircularData.ts @@ -3,7 +3,7 @@ export interface CircularPlant { x: number; y: number; inputs: string[]; - outputs: Record; + outputs: string[]; } @@ -16,8 +16,18 @@ export interface CircularProduct { export interface CircularData { plants: Record; - products: Record; + centers: Record; + + +} +export interface CircularCenter { + id: string; + x: number; + y: number; + //single input, multiple outputs + input?: string; + output: string[]; } \ No newline at end of file diff --git a/web/src/components/CaseBuilder/NodesAndEdges.tsx b/web/src/components/CaseBuilder/NodesAndEdges.tsx index f8dd4c8..1404897 100644 --- a/web/src/components/CaseBuilder/NodesAndEdges.tsx +++ b/web/src/components/CaseBuilder/NodesAndEdges.tsx @@ -1,24 +1,57 @@ +// NodesAndEdges.tsx + import React from 'react'; -import { Position, NodeProps, Handle } from '@xyflow/react'; -import styles from './PipelineBlock.module.css'; +import { Handle, Position, NodeProps } from '@xyflow/react'; + +import styles from './PipelineBlock.module.css'; + export interface CustomNodeData { - label: string; - type: 'plant' | 'product'; + + label: string; + + type: 'plant' | 'product' | 'center'; + } + -export default function CustomNode({ data }: NodeProps) { - const nodeStyle = - data.type === 'plant' - ? styles.PlantNode: styles.ProductNode; +export default function CustomNode({ data, isConnectable }: NodeProps>) { + const typeClass = + data.type === 'plant' ? styles.PlantNode : + data.type === 'product' ? styles.ProductNode: + styles.CenterNode; return ( -
- {data.type === 'plant' && ( - - )} -
{data.label}
- +
+ + +
{data.label}
+ +
+ ); -}; \ No newline at end of file + +} + + \ No newline at end of file diff --git a/web/src/components/CaseBuilder/PipelineBlock.module.css b/web/src/components/CaseBuilder/PipelineBlock.module.css index 4a0f80d..e452381 100644 --- a/web/src/components/CaseBuilder/PipelineBlock.module.css +++ b/web/src/components/CaseBuilder/PipelineBlock.module.css @@ -6,14 +6,16 @@ } .PlantNode, -.ProductNode { +.ProductNode, +.CenterNode { border-color: rgba(0, 0, 0, 0.8) !important; color: black !important; font-size: 13px !important; border-width: 1px !important; border-radius: 6px !important; box-shadow: 0px 2px 4px -3px black !important; - width: 100px !important; + width: 100%; + height: 100%; } .PlantNode { @@ -23,3 +25,7 @@ .ProductNode { background-color: #e6e6e6 !important; } + +.CenterNode { + background-color: #d3a610; +} diff --git a/web/src/components/CaseBuilder/PipelineBlock.tsx b/web/src/components/CaseBuilder/PipelineBlock.tsx index bf89b4a..863de0f 100644 --- a/web/src/components/CaseBuilder/PipelineBlock.tsx +++ b/web/src/components/CaseBuilder/PipelineBlock.tsx @@ -1,4 +1,4 @@ -import { CircularPlant, CircularProduct } from "./CircularData"; +import { CircularPlant, CircularProduct, CircularCenter } from "./CircularData"; import { Node, Edge } from "@xyflow/react"; import styles from "./PipelineBlock.module.css"; import { ReactFlow, Background, Controls,MarkerType } from '@xyflow/react'; @@ -13,12 +13,18 @@ import CustomNode, { CustomNodeData }from "./NodesAndEdges"; interface PipelineBlockProps { onAddPlant: () => void; onAddProduct: () => void; + onAddCenter: () => void; onMovePlant: (name: string , x: number, y: number) => void; onMoveProduct: (name: string, x: number, y: number) => void; + onMoveCenter: (name: string, x: number, y: number) => void; onSetPlantInput: (plantName:string, productName: string) => void; onAddPlantOutput: (plantName: string, productName: string) => void; + onAddCenterInput: (plantName: string, productName: string) => void; + onAddCenterOutput: (plantName: string, productName: string) => void; + products: Record; plants: Record; + centers: Record; } const onNodeDoubleClick = () => {}; @@ -46,6 +52,13 @@ const PipelineBlock: React.FC = (props) => { props.onAddPlantOutput(source, target); } + else if (sourceType === "product" && targetType === "center") { + props.onAddCenterInput(target, source); + } + else if (sourceType === "center" && targetType === "product") { + props.onAddCenterOutput(source, target); + } + }; const onNodeDragStop =(_:any, node: Node) => { @@ -56,9 +69,11 @@ const onNodeDragStop =(_:any, node: Node) => { if (data.type === "product") { props.onMoveProduct(id, position.x, position.y); } + if (data.type === "center") { + props.onMoveCenter(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"; @@ -69,7 +84,7 @@ const onNodeDragStop =(_:any, node: Node) => { position: { x:product.x, y:product.y} }); } - + console.log("ALL PLANTS:", props.plants); for (const [plantName, plant] of Object.entries(props.plants) as [string, CircularPlant][]) { if(!plant.x || !plant.y) hasNullPositions = true; mapNameToType[plantName] = "plant"; @@ -93,21 +108,50 @@ const onNodeDragStop =(_:any, node: Node) => { }, }); - } - /**for (const outputProduct of plant.inputs){ - edges.push({ - id: `${plantName}-${outputProduct}`, - source: plantName, - target: outputProduct, - animated: true, - style: { stroke: "black" }, - - }); - } - */ + } + for (const outputProduct of plant.outputs ?? []) { + edges.push({ + id: `${plantName}-${outputProduct}`, + source: plantName, + target: outputProduct, + animated: true, + style: { stroke: 'black' }, + markerEnd: { type: MarkerType.ArrowClosed }, + }); + } + } } + for (const [centerName, center] of Object.entries(props.centers)) { + mapNameToType[centerName] = "center"; + nodes.push({ + id: centerName, + type: "default", + data: { label: centerName, type: "center"}, + position: {x: center.x, y: center.y}, + }); + if (center.input) { + edges.push({ + id: `${center.input}-${centerName}`, + source: center.input, + target:centerName, + style: { stroke: "black"}, + markerEnd: { type: MarkerType.ArrowClosed}, + }); + } + for (const out of center.output) { + edges.push({ + id: `${centerName}-${out}`, + source: centerName, + target:out, + style: { stroke: "black"}, + markerEnd: { type: MarkerType.ArrowClosed}, + }); + } + } + + useEffect(() => { if (hasNullPositions) onLayout(); @@ -116,11 +160,11 @@ const onNodeDragStop =(_:any, node: Node) => { return ( - <> -
- -
- +
+ +
+ - - - -
-
-
+
+ - - + - -
-
- + +
+
+ ); }; - export default PipelineBlock; \ No newline at end of file diff --git a/web/src/components/CaseBuilder/defaults.ts b/web/src/components/CaseBuilder/defaults.ts index 9a1f737..86bb3d2 100644 --- a/web/src/components/CaseBuilder/defaults.ts +++ b/web/src/components/CaseBuilder/defaults.ts @@ -1,4 +1,4 @@ -import { CircularPlant, CircularProduct } from "./CircularData"; +import { CircularData, CircularPlant, CircularProduct, CircularCenter } from "./CircularData"; export interface DefaultProduct extends CircularProduct{ x: number; @@ -10,16 +10,35 @@ export interface DefaultPlant extends CircularPlant{ y: number; } +export interface DefaultCenter extends CircularPlant{ + x: number; + y: number; +} + export const defaultProduct: DefaultProduct = { - "id": "", + id: "", x: 0, y: 0, }; export const defaultPlant: CircularPlant = { - "id": "", + id: "", x: 0, y: 0, inputs : [], - outputs: {}, -}; \ No newline at end of file + outputs: [], +}; + +export const defaultCenter: CircularCenter = { + id: "", + x: 0, + y: 0, + output: [], +}; + +export const DefaultData: CircularData = { + products: {}, + plants: {}, + centers: {} +}; +