Skip to content

Commit

Permalink
feat:add node from eges center plus button and auto connect
Browse files Browse the repository at this point in the history
  • Loading branch information
Onelevenvy committed Oct 4, 2024
1 parent 3cac4e9 commit d7312be
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 1 deletion.
163 changes: 162 additions & 1 deletion web/src/components/WorkFlow/FlowVisualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ import ReactFlow, {
MarkerType,
Panel,
useViewport,
EdgeLabelRenderer,
Position,
// getEdgeCenter
} from "reactflow";

import { FaPlus } from "react-icons/fa";
import getEdgeCenter from "reactflow";
import { useContextMenu } from "@/hooks/graphs/useContextMenu";
import { useFlowState } from "@/hooks/graphs/useFlowState";
import { useGraphConfig } from "@/hooks/graphs/useUpdateGraphConfig";
Expand All @@ -30,6 +34,9 @@ import {
MenuList,
Text,
useColorModeValue,
IconButton,
Icon,
VStack,
} from "@chakra-ui/react";
import { MdBuild, MdOutlineHelp } from "react-icons/md";
import { VscTriangleRight } from "react-icons/vsc";
Expand Down Expand Up @@ -309,6 +316,101 @@ const FlowVisualizer: React.FC<FlowVisualizerProps> = ({

const [showDebugPreview, setShowDebugPreview] = useState(false);

const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
const [showNodeMenu, setShowNodeMenu] = useState(false);
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });

const onEdgeClick = useCallback(
(event: React.MouseEvent, edge: Edge) => {
event.stopPropagation();
setSelectedEdge(edge);
setShowNodeMenu(false);

const sourceNode = nodes.find((node) => node.id === edge.source);
const targetNode = nodes.find((node) => node.id === edge.target);
if (sourceNode && targetNode) {
const edgeCenter = getEdgeCenter({
sourceX: sourceNode.position.x,
sourceY: sourceNode.position.y,
targetX: targetNode.position.x,
targetY: targetNode.position.y,
});
setMenuPosition({ x: edgeCenter[0], y: edgeCenter[1] });
}
},
[nodes]
);

const handleAddNodeClick = useCallback((event: React.MouseEvent) => {
event.stopPropagation();
setShowNodeMenu(true);
}, []);

const addNodeToEdge = useCallback(
(nodeType: NodeType) => {
if (!selectedEdge) return;

const sourceNode = nodes.find((node) => node.id === selectedEdge.source);
const targetNode = nodes.find((node) => node.id === selectedEdge.target);
if (!sourceNode || !targetNode) return;

const newNodeId = `${nodeType}-${nodes.length + 1}`;
const centerX = (sourceNode.position.x + targetNode.position.x) / 2;
const centerY = (sourceNode.position.y + targetNode.position.y) / 2;

const newNode: CustomNode = {
id: newNodeId,
type: nodeType,
position: { x: centerX, y: centerY },
data: {
label: generateUniqueName(nodeConfig[nodeType].display),
customName: generateUniqueName(nodeConfig[nodeType].display),
onChange: (key: string, value: any) =>
onNodeDataChange(newNodeId, key, value),
...nodeConfig[nodeType].initialData,
},
};

const newEdge1: Edge = {
id: `e${selectedEdge.source}-${newNodeId}`,
source: selectedEdge.source,
target: newNodeId,
sourceHandle: "right",
targetHandle: "left",
type: selectedEdge.type,
};

const newEdge2: Edge = {
id: `e${newNodeId}-${selectedEdge.target}`,
source: newNodeId,
target: selectedEdge.target,
sourceHandle: "right",
targetHandle: "left",
type: selectedEdge.type,
};

setNodes((nds) => nds.concat(newNode));
setEdges((eds) =>
eds.filter((e) => e.id !== selectedEdge.id).concat(newEdge1, newEdge2)
);
setSelectedEdge(null);
setShowNodeMenu(false);
},
[
selectedEdge,
nodes,
setNodes,
setEdges,
onNodeDataChange,
generateUniqueName,
]
);

const onPaneClick = useCallback(() => {
setSelectedEdge(null);
setShowNodeMenu(false);
}, []);

return (
<Box
display="flex"
Expand Down Expand Up @@ -362,6 +464,8 @@ const FlowVisualizer: React.FC<FlowVisualizerProps> = ({
onDragOver={onDragOver}
onDrop={onDrop}
deleteKeyCode={["Backspace", "Delete"]}
onEdgeClick={onEdgeClick}
onPaneClick={onPaneClick}
>
<Controls />

Expand Down Expand Up @@ -389,6 +493,26 @@ const FlowVisualizer: React.FC<FlowVisualizerProps> = ({
)}
</Panel>
<ZoomDisplay />
<EdgeLabelRenderer>
{selectedEdge && (
<div
style={{
position: "absolute",
transform: `translate(-50%, -50%) translate(${menuPosition.x}px, ${menuPosition.y}px)`,
pointerEvents: "all",
zIndex: 1000,
}}
>
<IconButton
aria-label="Add node"
icon={<FaPlus />}
size="xs"
colorScheme="blue"
onClick={handleAddNodeClick}
/>
</div>
)}
</EdgeLabelRenderer>
</ReactFlow>
{contextMenu.nodeId && (
<Menu isOpen={true} onClose={closeContextMenu}>
Expand Down Expand Up @@ -486,6 +610,43 @@ const FlowVisualizer: React.FC<FlowVisualizerProps> = ({
/>
</Box>
)}
{showNodeMenu && (
<Box
position="fixed"
left={`${menuPosition.x}px`}
top={`${menuPosition.y}px`}
zIndex={1000}
bg="white"
borderRadius="md"
boxShadow="md"
p={2}
>
<VStack spacing={2} align="stretch">
{Object.entries(nodeConfig).map(
([nodeType, { display, icon: Icon, colorScheme }]) => (
<Box
key={nodeType}
border="1px solid #ddd"
borderRadius="md"
padding={2}
cursor="pointer"
onClick={() => addNodeToEdge(nodeType as NodeType)}
_hover={{ bg: "gray.100" }}
>
<IconButton
aria-label={display}
icon={<Icon />}
colorScheme={colorScheme}
size="xs"
mr={2}
/>
<Text display="inline">{display}</Text>
</Box>
)
)}
</VStack>
</Box>
)}
</Box>
);
};
Expand Down
25 changes: 25 additions & 0 deletions web/src/components/WorkFlow/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Node } from 'reactflow';

export const getEdgeParams = (source: Node, target: Node) => {
const sourceCenter = {
x: source.position.x + (source.width || 0) / 2,
y: source.position.y + (source.height || 0) / 2,
};

const targetCenter = {
x: target.position.x + (target.width || 0) / 2,
y: target.position.y + (target.height || 0) / 2,
};

const sx = sourceCenter.x;
const sy = sourceCenter.y;
const tx = targetCenter.x;
const ty = targetCenter.y;

return {
sx,
sy,
tx,
ty,
};
};

0 comments on commit d7312be

Please sign in to comment.