123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- import { PlusOutlined } from "@ant-design/icons";
- import { Edge, EventArgs, Graph, Node } from "@antv/x6";
- import React, { useEffect, useRef } from "react";
- import { Dropdown, Popover } from "antd";
- import NodeMenu from "../NodeMenu";
- export default function Port(props: {
- hovered: boolean;
- out?: boolean;
- style?: React.CSSProperties;
- node?: Node;
- graph?: Graph;
- type?: "in" | "out" | "extra";
- }) {
- const { hovered, style = {}, out = true, node, graph, type } = props;
- const [canAdd, setCanAdd] = React.useState(false);
- const [open, setOpen] = React.useState(false);
- const isDown = useRef(false);
- const isMove = useRef(false);
- const newEdge = React.useRef<Edge>();
- const extraStyle = React.useMemo(() => {
- if (out && canAdd) {
- return {
- transform: "scale(2) translateY(-50%)",
- };
- }
- if (!out) {
- return {
- width: 8,
- height: 16,
- borderRadius: 0,
- border: "none",
- left: -5,
- };
- } else {
- return {};
- }
- }, [canAdd]);
- const handleSetCanAdd = (value: boolean) => {
- out && setCanAdd(value);
- graph?.togglePanning(!value);
- };
- // step1: 鼠标按下拖拽开始
- const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
- isDown.current = true;
- node?.setData({ lock: true });
- };
- useEffect(() => {
- // step2: 移动鼠标添加连线
- // 检测移动 添加边 设置边目标位置
- graph?.on("node:mousemove", (args) => {
- if (isDown.current) {
- // 按下还没移动时创建边
- if (!isMove.current && node) {
- isMove.current = true;
- const ports = node.getPorts();
- const rightPort = ports.find((item) => type ? item.group === 'bottom' : item.group === "right");
- newEdge.current = graph?.addEdge({
- source: { cell: node.id, port: rightPort?.id },
- router: {
- name: "manhattan",
- args: {
- padding: 20,
- excludeShapes: ["notice-node"],
- },
- },
- connector: { name: "rounded" },
- target: {
- x: args.x,
- y: args.y,
- },
- attrs: {
- line: {
- stroke: "#37d0ff",
- strokeWidth: 2,
- },
- },
- });
- } else {
- // 判断是否进入其他节点内
- // 查找到最顶层的节点 然后修改位置为节点
- const nodes = graph.getNodesInArea(args.x, args.y, 5, 5);
- const targetNode = nodes.length
- ? (nodes || [])?.reduce((prev, curr) => {
- const prevZ = prev.zIndex || -1;
- const currZ = curr?.zIndex || -1;
- return prevZ >= currZ ? prev : curr;
- })
- : null;
- // 进入节点 不存在已连接边 则添加一条边
- if (targetNode && targetNode.shape !== "notice-node") {
- const ports = targetNode.getPorts();
- const targetPort = ports.find((item) => item.group === "left");
- newEdge.current?.setTarget({
- cell: targetNode.id,
- port: targetPort?.id,
- });
- } else {
- // 否则修改边的终点
- newEdge.current?.setTarget({
- x: args.x,
- y: args.y,
- });
- }
- }
- }
- });
- // step3: 鼠标抬起,展示节点菜单或者连线连接到目标节点
- graph?.on("node:mouseup", (args) => {
- console.log("node:mouseup", newEdge.current);
- node?.setData({ lock: false });
- isDown.current = false;
- isMove.current = false;
- // 拖拽过程中释放鼠标 检测当前是否有节点
- if (newEdge.current) {
- // 判断是否连接到了节点
- if (!Object.hasOwn(newEdge.current.target, "x")) {
- newEdge.current.setAttrs({
- line: {
- stroke: "#7e8186",
- },
- });
- newEdge.current.setZIndex(0);
- newEdge.current = undefined;
- return;
- }
- // 判断是否存在menu-popover
- const els = document.querySelectorAll("[data-shape='menu-popover']");
- if (els.length) {
- return;
- }
- graph.addNode({
- shape: "menu-popover",
- x: args.x,
- y: args.y,
- });
- }
- });
- // 添加节点完成设置连线目标节点
- graph?.on("node:change:addedNode", (args: EventArgs["cell:change:*"]) => {
- const { current } = args;
- if (newEdge.current && current) {
- const addNode = current?.addNode as Node;
- const ports = addNode.getPorts();
- const leftPort = ports?.find((item) => item.group === "left");
- const bottomPort = ports?.find((item) => item.group === "bottom");
- newEdge.current.setTarget({
- cell: addNode.id,
- port: type ? bottomPort?.id : leftPort?.id,
- });
- newEdge.current.setAttrs({
- line: {
- stroke: "#7e8186",
- },
- });
- newEdge.current = undefined;
- }
- });
- // 节点菜单menu-popver关闭, 如果连线目标没有节点信息移除连线
- graph?.on("node:change:closedPopover", () => {
- setTimeout(() => {
- if (Object.hasOwn(newEdge.current?.target || {}, "x")) {
- graph?.removeCells([newEdge.current!]);
- newEdge.current = undefined;
- }
- }, 300);
- });
- }, []);
- const { x, y } = node?.position() || { x: 0, y: 0 };
- const x1 = (node?.getBBox()?.width || 0) + x + 50;
- // 点击添加节点成功后,设置连线
- const handleAddChange = (addNode?: Node) => {
- setOpen(false);
- if (addNode && node) {
- const sourcePorts = node?.getPorts();
- const sourcePort = sourcePorts?.find((item) => type ? item.group === 'bottom' : item.group === "right");
- const targetPorts = addNode?.getPorts();
- const targetPort = targetPorts?.find((item) => item.group === "left");
- graph?.addEdge({
- source: {
- cell: node.id,
- port: sourcePort?.id,
- },
- target: {
- cell: addNode.id,
- port: targetPort?.id,
- },
- router: {
- name: "manhattan",
- args: {
- padding: 20,
- excludeShapes: ["notice-node"],
- },
- },
- zIndex: 0,
- connector: { name: "rounded", args: {} },
- attrs: {
- line: {
- stroke: "#7e8186",
- strokeWidth: 2,
- },
- },
- });
- }
- };
- return (
- <Popover
- content={
- <NodeMenu
- graph={graph}
- onChange={handleAddChange}
- position={{ x: x1, y }}
- />
- }
- trigger={"click"}
- placement="right"
- arrow={false}
- open={open}
- onOpenChange={(open) => {
- setOpen(open);
- }}
- >
- {type === "extra" ? (
- <div
- className="node-port flex items-center justify-center"
- style={{
- transform: hovered
- ? "scale(1.2) translateY(-50%) rotate(45deg)"
- : "scale(1) translateY(-50%) rotate(45deg)",
- width: 16,
- height: 16,
- border: 'none',
- borderRadius: 0,
- ...style,
- }}
- onMouseEnter={() => handleSetCanAdd(true)}
- onMouseLeave={() => handleSetCanAdd(false)}
- onMouseDown={handleMouseDown}
- >
- {out && (
- <PlusOutlined
- className="transform scale-0 transition duration-300 color-#fff text-8px"
- style={{ transform: canAdd ? "scale(1)" : "scale(0)" }}
- />
- )}
- </div>
- ) : (
- <div
- className="node-port flex items-center justify-center"
- style={{
- transform: hovered
- ? "scale(1.2) translateY(-50%)"
- : "scale(1) translateY(-50%)",
- ...style,
- ...extraStyle,
- }}
- onMouseEnter={() => handleSetCanAdd(true)}
- onMouseLeave={() => handleSetCanAdd(false)}
- onMouseDown={handleMouseDown}
- >
- {out && (
- <PlusOutlined
- className="transform scale-0 transition duration-300 color-#fff text-8px"
- style={{ transform: canAdd ? "scale(1)" : "scale(0)" }}
- />
- )}
- </div>
- )}
- </Popover>
- );
- }
|