Skip to content

Commit

Permalink
feat: Optimize edge center calculation and improve node addition UI (#62
Browse files Browse the repository at this point in the history
)

* feat:add node from eges center plus button and auto connect
* feat:feat: Optimize edge center calculation and improve node addition UI
This commit includes the following improvements:
1. Moved edge center calculation logic to a separate utility function
Enhanced edge selection and node addition functionality
Improved UI for adding nodes to edges
Refined edge and node selection behavior
Updated React Flow configuration for better performance and appearance
  • Loading branch information
Onelevenvy authored Oct 4, 2024
1 parent f70203e commit c10f8ee
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 2 deletions.
161 changes: 159 additions & 2 deletions web/src/components/WorkFlow/FlowVisualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import ReactFlow, {
MarkerType,
Panel,
useViewport,
EdgeLabelRenderer,
} from "reactflow";

import { FaPlus } from "react-icons/fa";
import { useContextMenu } from "@/hooks/graphs/useContextMenu";
import { useFlowState } from "@/hooks/graphs/useFlowState";
import { useGraphConfig } from "@/hooks/graphs/useUpdateGraphConfig";
Expand All @@ -30,6 +31,8 @@ import {
MenuList,
Text,
useColorModeValue,
IconButton,
VStack,
} from "@chakra-ui/react";
import { MdBuild, MdOutlineHelp } from "react-icons/md";
import { VscTriangleRight } from "react-icons/vsc";
Expand All @@ -39,6 +42,7 @@ import NodePalette from "./NodePalette";
import BaseProperties from "./nodes/Base/Properties";
import { type NodeType, nodeConfig } from "./nodes/nodeConfig";
import type { CustomNode, FlowVisualizerProps } from "./types";
import { calculateEdgeCenter } from './utils';

const FlowVisualizer: React.FC<FlowVisualizerProps> = ({
nodeTypes,
Expand Down Expand Up @@ -70,6 +74,7 @@ const FlowVisualizer: React.FC<FlowVisualizerProps> = ({
const onNodeClick = useCallback(
(event: React.MouseEvent, node: Node) => {
setSelectedNodeId(node.id);
setSelectedEdge(null); // 取消选中的边
},
[setSelectedNodeId]
);
Expand Down Expand Up @@ -309,6 +314,96 @@ 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);

const sourceNode = nodes.find((node) => node.id === edge.source);
const targetNode = nodes.find((node) => node.id === edge.target);
if (sourceNode && targetNode) {
const centerPoint = calculateEdgeCenter(sourceNode, targetNode);
setMenuPosition(centerPoint);
}
},
[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);
setSelectedNodeId(null); // 取消选中的节点
}, [setSelectedNodeId]);

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

Expand Down Expand Up @@ -389,6 +486,29 @@ 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}
isRound={true} // 使按钮变成圆形
_hover={{ bg: "blue.500" }} // 悬停时的样式
_active={{ bg: "blue.600" }} // 点击时的样式
/>
</div>
)}
</EdgeLabelRenderer>
</ReactFlow>
{contextMenu.nodeId && (
<Menu isOpen={true} onClose={closeContextMenu}>
Expand Down Expand Up @@ -486,8 +606,45 @@ 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>
);
};

export default FlowVisualizer;
export default FlowVisualizer;
13 changes: 13 additions & 0 deletions web/src/components/WorkFlow/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Node } from 'reactflow';

export const calculateEdgeCenter = (sourceNode: Node, targetNode: Node): { x: number, y: number } => {
const sourceX = sourceNode.position.x + (sourceNode.width ?? 0) / 2;
const sourceY = sourceNode.position.y + (sourceNode.height ?? 0) / 2;
const targetX = targetNode.position.x + (targetNode.width ?? 0) / 2;
const targetY = targetNode.position.y + (targetNode.height ?? 0) / 2;

return {
x: (sourceX + targetX) / 2,
y: (sourceY + targetY) / 2
};
};

0 comments on commit c10f8ee

Please sign in to comment.