autoformat button works

pull/33/head
Khwaja 3 months ago
parent b095e4bbbe
commit 6262047671

@ -8,6 +8,8 @@ import styles from './PipelineBlock.module.css';
export interface CustomNodeData { export interface CustomNodeData {
[key:string]: unknown;
label: string; label: string;
type: 'plant' | 'product' | 'center'; type: 'plant' | 'product' | 'center';

@ -1,242 +1,233 @@
import { CircularPlant, CircularProduct, CircularCenter } from "./CircularData"; import React, { useEffect, useCallback } from 'react';
import { Node, Edge } from "@xyflow/react"; import dagre from 'dagre';
import styles from "./PipelineBlock.module.css"; import {
import { ReactFlow, Background, Controls, MarkerType } from '@xyflow/react'; ReactFlow,
Background,
Controls,
Node,
Edge,
Connection,
MarkerType
} from '@xyflow/react';
import { CircularPlant, CircularProduct, CircularCenter } from './CircularData';
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 { useEffect, useCallback } from "react"; import styles from './PipelineBlock.module.css';
import { Connection } from '@xyflow/react';
import CustomNode, { CustomNodeData }from "./NodesAndEdges";
interface PipelineBlockProps { interface PipelineBlockProps {
onAddPlant: () => void; onAddPlant: () => void;
onAddProduct: () => void; onAddProduct: () => void;
onAddCenter: () => void; onAddCenter: () => void;
onMovePlant: (name: string , x: number, y: number) => void; onMovePlant: (name: string, x: number, y: number) => void;
onMoveProduct: (name: string, x: number, y: number) => void; onMoveProduct: (name: string, x: number, y: number) => void;
onMoveCenter: (name: string, x: number, y: number) => void; onMoveCenter: (name: string, x: number, y: number) => void;
onSetPlantInput: (plantName:string, productName: string) => void; onSetPlantInput: (plantName: string, productName: string) => void;
onAddPlantOutput: (plantName: string, productName: string) => void; onAddPlantOutput: (plantName: string, productName: string) => void;
onAddCenterInput: (plantName: string, productName: string) => void; onAddCenterInput: (centerName: string, productName: string) => void;
onAddCenterOutput: (plantName: string, productName: string) => void; onAddCenterOutput: (centerName: string, productName: string) => void;
onRemovePlant: (id:string) => void; onRemovePlant: (id: string) => void;
onRemoveProduct: (id: string) => void; onRemoveProduct: (id: string) => void;
onRemoveCenter: (id: string) => void; onRemoveCenter: (id: string) => void;
products: Record<string, CircularProduct>;
products: Record<string, CircularProduct>; plants: Record<string, CircularPlant>;
plants: Record<string, CircularPlant>; centers: Record<string, CircularCenter>;
centers: Record<string, CircularCenter>;
} }
const onNodeDoubleClick = () => {};
const handleNodesDelete = () => {};
const handleEdgesDelete = () => {};
const onLayout = () => {};
const PipelineBlock: React.FC<PipelineBlockProps> = (props) => {
const nodes: Node[] = [];
const edges: Edge[] = [];
let mapNameToType: Record<string,string> = {};
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);
}
else if (sourceType === "product" && targetType === "center") { function getLayoutedNodesAndEdges(
props.onAddCenterInput(target, source); nodes: Node<CustomNodeData>[],
} edges: Edge[]
else if (sourceType === "center" && targetType === "product") { ): { nodes: Node<CustomNodeData>[]; edges: Edge[] } {
props.onAddCenterOutput(source, target); const NODE_WIDTH = 125;
} const NODE_HEIGHT = 45;
const g = new dagre.graphlib.Graph();
}; g.setDefaultEdgeLabel(() => ({}));
g.setGraph({ rankdir: 'LR' });
const onNodeDragStop =(_:any, node: Node) => { nodes.forEach(n => g.setNode(n.id, { width: NODE_WIDTH, height: NODE_HEIGHT }));
const { id, position, data} = node; edges.forEach(e => g.setEdge(e.source, e.target));
if (data.type === "plant") { dagre.layout(g);
props.onMovePlant(id, position.x, position.y); const layouted = nodes.map(n => {
} const d = g.node(n.id)!;
if (data.type === "product") { return {
props.onMoveProduct(id, position.x, position.y); ...n,
} position: {
if (data.type === "center") { x: d.x - NODE_WIDTH / 2,
props.onMoveCenter(id, position.x, position.y); y: d.y - NODE_HEIGHT / 2
}
};
const handleNodesDelete = useCallback((deleted: Node[]) => {
deleted.forEach((n) => {
const type = mapNameToType[n.id];
if (type === "plant") {
props.onRemovePlant(n.id);
} else if (type === "product") {
props.onRemoveProduct(n.id);
} else if (type === "center") {
props.onRemoveCenter!(n.id);
} }
}); };
}, [props, mapNameToType]); });
return { nodes: layouted, edges };
for (const [productName, product] of Object.entries(props.products) as [string, CircularProduct][]) { }
if(!product.x || !product.y) hasNullPositions = true;
mapNameToType[productName] = "product";
nodes.push({
id: product.uid,
type: "default",
data: {label: product.name, type: 'product'},
position: { x:product.x, y:product.y},
className: 'ProductNode'
});
}
for (const [plantName, plant] of Object.entries(props.plants) as [string, CircularPlant][]) {
if(!plant.x || !plant.y) hasNullPositions = true;
mapNameToType[plantName] = "plant";
nodes.push({
id: plant.uid,
type: "default",
data: {label: plant.name, type: 'plant'},
position: { x:plant.x, y:plant.y},
className: 'PlantNode'
});
if (plant) {
for (const inputProduct of plant.inputs){
edges.push({
id: `${inputProduct}-${plantName}`,
source: inputProduct,
target: plantName,
animated: true,
style: { stroke: "black" },
markerEnd: {
type: MarkerType.ArrowClosed,
},
}); const PipelineBlock: React.FC<PipelineBlockProps> = props => {
} const nodes: Node<CustomNodeData>[] = [];
for (const outputProduct of plant.outputs ?? []) { const edges: Edge[] = [];
edges.push({ const mapNameToType: Record<string, 'plant' | 'product' | 'center'> = {};
id: `${plantName}-${outputProduct}`, let hasNullPositions = false;
source: plantName, Object.entries(props.products).forEach(([key, product]) => {
target: outputProduct, if (!product.x || !product.y) hasNullPositions = true;
animated: true, mapNameToType[key] = 'product';
style: { stroke: 'black' }, nodes.push({
markerEnd: { type: MarkerType.ArrowClosed }, id: product.uid,
type: 'default',
data: { label: product.name, type: 'product' },
position: { x: product.x, y: product.y },
className: 'ProductNode'
}); });
} });
Object.entries(props.plants).forEach(([key, plant]) => {
} if (!plant.x || !plant.y) hasNullPositions = true;
mapNameToType[key] = 'plant';
} nodes.push({
for (const [centerName, center] of Object.entries(props.centers)) { id: plant.uid,
mapNameToType[centerName] = "center"; type: 'default',
data: { label: plant.name, type: 'plant' },
position: { x: plant.x, y: plant.y },
className: 'PlantNode'
});
plant.inputs.forEach(input => {
edges.push({
id: `${input}-${key}-in`,
source: input,
target: key,
animated: true,
style: { stroke: 'black' },
markerEnd: { type: MarkerType.ArrowClosed }
});
});
plant.outputs.forEach(output => {
edges.push({
id: `${key}-${output}-out`,
source: key,
target: output,
animated: true,
style: { stroke: 'black' },
markerEnd: { type: MarkerType.ArrowClosed }
});
});
});
Object.entries(props.centers).forEach(([key, center]) => {
if (!center.x || !center.y) hasNullPositions = true;
mapNameToType[key] = 'center';
nodes.push({ nodes.push({
id: center.uid, id: center.uid,
type: "default", type: 'default',
data: { label: center.name, type: "center"}, data: { label: center.name, type: 'center' },
position: {x: center.x, y: center.y}, position: { x: center.x, y: center.y },
className: 'CenterNode' className: 'CenterNode'
}); });
if (center.input) { if (center.input) {
edges.push({ edges.push({
id: `${center.input}-${centerName}`, id: `${center.input}-${key}-in`,
source: center.input, source: center.input,
target:centerName, target: key,
animated: true, animated: true,
style: { stroke: "black"}, style: { stroke: 'black' },
markerEnd: { type: MarkerType.ArrowClosed}, markerEnd: { type: MarkerType.ArrowClosed }
}); });
} }
for (const out of center.output) { center.output.forEach(out => {
edges.push({ edges.push({
id: `${centerName}-${out}`, id: `${key}-${out}-out`,
source: centerName, source: key,
target:out, target: out,
animated: true, animated: true,
style: { stroke: "black"}, style: { stroke: 'black' },
markerEnd: { type: MarkerType.ArrowClosed}, markerEnd: { type: MarkerType.ArrowClosed }
}); });
} });
} });
const onConnect = (params: Connection) => {
const { source, target } = params;
if (!source || !target) return;
useEffect(() => { const sourceType = mapNameToType[source];
if (hasNullPositions) onLayout(); const targetType = mapNameToType[target];
if (sourceType === 'product' && targetType === 'plant') props.onSetPlantInput(target, source);
}, [hasNullPositions]); else if (sourceType === 'plant' && targetType === 'product') props.onAddPlantOutput(source, target);
else if (sourceType === 'product' && targetType === 'center') props.onAddCenterInput(target, source);
else if (sourceType === 'center' && targetType === 'product') props.onAddCenterOutput(source, target);
return ( };
<>
<Section title="Pipeline" /> const onNodeDragStop = (_: any, node: Node<CustomNodeData>) => {
<Card> const { id, position, data } = node;
<div className={styles.PipelineBlock}> if (data.type === 'plant') props.onMovePlant(id, position.x, position.y);
<ReactFlow if (data.type === 'product') props.onMoveProduct(id, position.x, position.y);
if (data.type === 'center') props.onMoveCenter(id, position.x, position.y);
};
const handleNodesDelete = useCallback(
(deleted: Node<CustomNodeData>[]) => {
deleted.forEach(n => {
const type = mapNameToType[n.id];
if (type === 'plant') props.onRemovePlant(n.id);
else if (type === 'product') props.onRemoveProduct(n.id);
else if (type === 'center') props.onRemoveCenter(n.id);
});
},
[props]
);
const onLayout = () => {
const { nodes: ln, edges: le } = getLayoutedNodesAndEdges(nodes, edges);
ln.forEach(n => {
const { id, position, data } = n;
if (data.type === 'plant') props.onMovePlant(id, position.x, position.y);
else if (data.type === 'product') props.onMoveProduct(id, position.x, position.y);
else props.onMoveCenter(id, position.x, position.y);
});
};
nodes={nodes} useEffect(() => {
edges={edges} if (hasNullPositions) onLayout();
onNodeDoubleClick={onNodeDoubleClick} }, [hasNullPositions]);
onNodeDragStop={onNodeDragStop}
onConnect={onConnect} return (
onNodesDelete={handleNodesDelete} <>
deleteKeyCode="Delete" <Section title="Pipeline" />
maxZoom={1.25} <Card>
minZoom={0.5} <div className={styles.PipelineBlock}>
snapToGrid={true} <ReactFlow
preventScrolling={false} nodes={nodes}
nodeTypes={{ default: CustomNode }} edges={edges}
> onConnect={onConnect}
<Background /> onNodeDragStop={onNodeDragStop}
<Controls showInteractive={false} /> onNodesDelete={handleNodesDelete}
</ReactFlow> deleteKeyCode="delete"
</div> maxZoom={1.25}
<div style={{ textAlign: "center", marginTop: "1rem" }}> minZoom={0.5}
<button snapToGrid
style={{ margin: "0 8px" }} preventScrolling
onClick={props.onAddProduct} nodeTypes={{ default: CustomNode }}
> >
Add product <Background />
</button> <Controls showInteractive={false} />
<button </ReactFlow>
style={{ margin: "0 8px" }} </div>
onClick={props.onAddPlant} <div style={{ textAlign: 'center', marginTop: '1rem' }}>
> <button style={{ margin: '0 8px' }} onClick={props.onAddProduct}>
Add plant Add product
</button> </button>
<button <button style={{ margin: '0 8px' }} onClick={props.onAddPlant}>
style={{ margin: "0 8px" }} Add plant
onClick={props.onAddCenter} </button>
> <button style={{ margin: '0 8px' }} onClick={props.onAddCenter}>
Add center Add center
</button> </button>
<button <button style={{ margin: '0 8px' }} onClick={onLayout}>
style={{ margin: "0 8px" }} Auto Layout
onClick={onLayout} </button>
> <button
Auto Layout style={{ margin: '0 8px' }}
</button> title="Drag from one connector to another to create links. Double click to rename. Click to move. Press Delete to remove."
<button >
style={{ margin: "0 8px" }} ?
title="Drag from one connector to another to create links between products, plants, and centers. Double click to rename an element. Click an element to select and move it. Press the [Delete] key to remove it." </button>
> </div>
? </Card>
</button> </>
</div> );
</Card>
</>
);
}; };
export default PipelineBlock; export default PipelineBlock;
Loading…
Cancel
Save