feat(web) workflow edge center add add tool
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-09 18:31:30
|
* @Date: 2026-02-09 18:31:30
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-06 11:43:58
|
* @Last Modified time: 2026-03-30 11:55:10
|
||||||
*/
|
*/
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Popover, Flex } from 'antd';
|
import { Popover, Flex } from 'antd';
|
||||||
@@ -173,7 +173,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
|||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
gap={4}
|
gap={4}
|
||||||
className={clsx('rb:text-[#212332] rb:font-medium rb:text-[12px] rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:border rb:rounded-lg rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:border-[#DFE4ED] rb:flex rb:items-center rb:justify-center', {
|
className={clsx('rb:text-[#212332] rb:font-medium rb:text-[12px] rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:border rb:rounded-lg rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:border-[#FCFCFD] rb:flex rb:items-center rb:justify-center', {
|
||||||
'rb:border-orange-500 rb:border-[3px] rb:bg-[#FCFCFD] rb:text-[#475467]': data.isSelected,
|
'rb:border-orange-500 rb:border-[3px] rb:bg-[#FCFCFD] rb:text-[#475467]': data.isSelected,
|
||||||
'rb:border-[#d1d5db] rb:bg-[#FCFCFD] rb:text-[#374151]': !data.isSelected
|
'rb:border-[#d1d5db] rb:bg-[#FCFCFD] rb:text-[#374151]': !data.isSelected
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
|||||||
return (
|
return (
|
||||||
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:p-3 rb:border rb:rounded-2xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)]', {
|
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:p-3 rb:border rb:rounded-2xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)]', {
|
||||||
'rb:border-[#171719]': data.isSelected,
|
'rb:border-[#171719]': data.isSelected,
|
||||||
'rb:border-[#DFE4ED]': !data.isSelected
|
'rb:border-[#FCFCFD]': !data.isSelected
|
||||||
})}>
|
})}>
|
||||||
<NodeTools node={node} />
|
<NodeTools node={node} />
|
||||||
<Flex align="center" gap={8} className="rb:flex-1">
|
<Flex align="center" gap={8} className="rb:flex-1">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { ReactShapeConfig } from '@antv/x6-react-shape';
|
|||||||
|
|
||||||
const GroupStartNode: ReactShapeConfig['component'] = () => {
|
const GroupStartNode: ReactShapeConfig['component'] = () => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:border rb:rounded-xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:border-[#DFE4ED] rb:flex rb:items-center rb:justify-center')}>
|
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:border rb:rounded-xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:border-[#FCFCFD] rb:flex rb:items-center rb:justify-center')}>
|
||||||
<div className="rb:size-5 rb:bg-cover rb:bg-[url('@/assets/images/workflow/start.svg')]" />
|
<div className="rb:size-5 rb:bg-cover rb:bg-[url('@/assets/images/workflow/start.svg')]" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
|||||||
return (
|
return (
|
||||||
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:p-3 rb:border rb:rounded-2xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)]', {
|
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:p-3 rb:border rb:rounded-2xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)]', {
|
||||||
'rb:border-[#171719]': data.isSelected,
|
'rb:border-[#171719]': data.isSelected,
|
||||||
'rb:border-[#DFE4ED]': !data.isSelected
|
'rb:border-[#FCFCFD]': !data.isSelected
|
||||||
})}>
|
})}>
|
||||||
<NodeTools node={node} />
|
<NodeTools node={node} />
|
||||||
<Flex align="center" gap={8} className="rb:flex-1">
|
<Flex align="center" gap={8} className="rb:flex-1">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const NormalNode: ReactShapeConfig['component'] = ({ node }) => {
|
|||||||
return (
|
return (
|
||||||
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:p-3 rb:border rb:rounded-2xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)]', {
|
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:p-3 rb:border rb:rounded-2xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)]', {
|
||||||
'rb:border-[#171719]': data.isSelected,
|
'rb:border-[#171719]': data.isSelected,
|
||||||
'rb:border-[#DFE4ED]': !data.isSelected
|
'rb:border-[#FCFCFD]': !data.isSelected
|
||||||
})}>
|
})}>
|
||||||
<NodeTools node={node} />
|
<NodeTools node={node} />
|
||||||
<Flex align="center" gap={8} className="rb:flex-1">
|
<Flex align="center" gap={8} className="rb:flex-1">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-09 18:30:28
|
* @Date: 2026-02-09 18:30:28
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-24 11:11:56
|
* @Last Modified time: 2026-03-30 15:14:02
|
||||||
*/
|
*/
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Popover } from 'antd';
|
import { Popover } from 'antd';
|
||||||
@@ -20,13 +20,15 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
const [sourceNode, setSourceNode] = useState<any>(null);
|
const [sourceNode, setSourceNode] = useState<any>(null);
|
||||||
const [sourcePort, setSourcePort] = useState<string>('');
|
const [sourcePort, setSourcePort] = useState<string>('');
|
||||||
const [tempElement, setTempElement] = useState<HTMLElement | null>(null);
|
const [tempElement, setTempElement] = useState<HTMLElement | null>(null);
|
||||||
|
const [edgeInsertion, setEdgeInsertion] = useState<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handlePortClick = (event: CustomEvent) => {
|
const handlePortClick = (event: CustomEvent) => {
|
||||||
const { node, port, element, rect } = event.detail;
|
const { node, port, element, rect, edgeInsertion } = event.detail;
|
||||||
setSourceNode(node);
|
setSourceNode(node);
|
||||||
setSourcePort(port);
|
setSourcePort(port);
|
||||||
setTempElement(element);
|
setTempElement(element);
|
||||||
|
setEdgeInsertion(edgeInsertion || null);
|
||||||
setPopoverPosition({ x: rect.left, y: rect.top });
|
setPopoverPosition({ x: rect.left, y: rect.top });
|
||||||
setPopoverVisible(true);
|
setPopoverVisible(true);
|
||||||
};
|
};
|
||||||
@@ -72,15 +74,47 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
const sourcePortInfo = sourceNode.getPorts().find((p: any) => p.id === sourcePort);
|
const sourcePortInfo = sourceNode.getPorts().find((p: any) => p.id === sourcePort);
|
||||||
const sourcePortGroup = sourcePortInfo?.group || sourcePort;
|
const sourcePortGroup = sourcePortInfo?.group || sourcePort;
|
||||||
|
|
||||||
// If add-node position exists, use it; otherwise calculate new position
|
// Calculate new node position
|
||||||
let newX, newY;
|
let newX, newY;
|
||||||
if (addNodePosition) {
|
if (edgeInsertion) {
|
||||||
|
// Edge insertion: place new node on the same row as target, between source and target
|
||||||
|
const targetBBox = edgeInsertion.targetCell.getBBox();
|
||||||
|
const gap = targetBBox.x - (sourceBBox.x + sourceBBox.width);
|
||||||
|
const requiredSpace = nodeWidth + horizontalSpacing * 4;
|
||||||
|
|
||||||
|
// New node x: right after source + spacing
|
||||||
|
newX = sourceBBox.x + sourceBBox.width + horizontalSpacing;
|
||||||
|
// Same row as target node
|
||||||
|
newY = targetBBox.y + (targetBBox.height - nodeHeight) / 2;
|
||||||
|
|
||||||
|
// If not enough space, shift target and all downstream nodes to the right
|
||||||
|
if (gap < requiredSpace) {
|
||||||
|
const shiftX = requiredSpace - gap;
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const shiftDownstream = (cell: any) => {
|
||||||
|
const cellId = cell.id;
|
||||||
|
if (visited.has(cellId)) return;
|
||||||
|
visited.add(cellId);
|
||||||
|
const pos = cell.getPosition();
|
||||||
|
cell.setPosition(pos.x + shiftX, pos.y);
|
||||||
|
// Recursively shift nodes connected from right ports
|
||||||
|
graph.getConnectedEdges(cell, { outgoing: true }).forEach((e: any) => {
|
||||||
|
const tId = e.getTargetCellId();
|
||||||
|
if (tId && !visited.has(tId)) {
|
||||||
|
const tCell = graph.getCellById(tId);
|
||||||
|
if (tCell?.isNode()) shiftDownstream(tCell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
shiftDownstream(edgeInsertion.targetCell);
|
||||||
|
}
|
||||||
|
} else if (addNodePosition) {
|
||||||
newX = addNodePosition.x;
|
newX = addNodePosition.x;
|
||||||
newY = addNodePosition.y;
|
newY = addNodePosition.y;
|
||||||
} else {
|
} else {
|
||||||
// Determine node placement direction based on port position
|
// Determine node placement direction based on port position
|
||||||
if (sourcePortGroup === 'left') {
|
if (sourcePortGroup === 'left') {
|
||||||
// Left port: add node to the left
|
// Left port: add node to the left
|
||||||
newX = sourceBBox.x - nodeWidth*2 - horizontalSpacing;
|
newX = sourceBBox.x - nodeWidth*2 - horizontalSpacing;
|
||||||
newY = sourceBBox.y;
|
newY = sourceBBox.y;
|
||||||
} else {
|
} else {
|
||||||
@@ -91,7 +125,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
|
|
||||||
// Check if position overlaps with existing nodes (only consider connected nodes)
|
// Check if position overlaps with existing nodes (only consider connected nodes)
|
||||||
const checkOverlap = (x: number, y: number) => {
|
const checkOverlap = (x: number, y: number) => {
|
||||||
// Get nodes connected to the source node
|
// Get nodes connected to the source node
|
||||||
const connectedNodes = new Set();
|
const connectedNodes = new Set();
|
||||||
graph.getConnectedEdges(sourceNode).forEach((edge: any) => {
|
graph.getConnectedEdges(sourceNode).forEach((edge: any) => {
|
||||||
const sourceId = edge.getSourceCellId();
|
const sourceId = edge.getSourceCellId();
|
||||||
@@ -108,7 +142,7 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
y + nodeHeight < bbox.y || y > bbox.y + bbox.height);
|
y + nodeHeight < bbox.y || y > bbox.y + bbox.height);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// If position is occupied, search downward for empty space
|
// If position is occupied, search downward for empty space
|
||||||
while (checkOverlap(newX, newY)) {
|
while (checkOverlap(newX, newY)) {
|
||||||
newY += nodeHeight + verticalSpacing;
|
newY += nodeHeight + verticalSpacing;
|
||||||
@@ -140,28 +174,51 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edge insertion: remove old edge immediately before creating new edges
|
||||||
|
if (edgeInsertion) {
|
||||||
|
const { edge: oldEdge } = edgeInsertion;
|
||||||
|
if (oldEdge.id && graph.getCellById(oldEdge.id)) {
|
||||||
|
graph.removeCell(oldEdge.id);
|
||||||
|
} else {
|
||||||
|
graph.removeEdge(oldEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create edge connection
|
// Create edge connection
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const targetPorts = newNode.getPorts();
|
const newPorts = newNode.getPorts();
|
||||||
let targetPort;
|
|
||||||
|
if (edgeInsertion) {
|
||||||
if (sourcePortGroup === 'left') {
|
// Edge insertion: create source→new and new→target edges
|
||||||
|
const { targetCell, targetPort: origTargetPort } = edgeInsertion;
|
||||||
|
const newLeftPort = newPorts.find((p: any) => p.group === 'left')?.id || 'left';
|
||||||
|
const newRightPort = newPorts.find((p: any) => p.group === 'right')?.id || 'right';
|
||||||
|
graph.addEdge({
|
||||||
|
source: { cell: sourceNode.id, port: sourcePort },
|
||||||
|
target: { cell: newNode.id, port: newLeftPort },
|
||||||
|
...edgeAttrs
|
||||||
|
});
|
||||||
|
graph.addEdge({
|
||||||
|
source: { cell: newNode.id, port: newRightPort },
|
||||||
|
target: { cell: targetCell.id, port: origTargetPort },
|
||||||
|
...edgeAttrs
|
||||||
|
});
|
||||||
|
setEdgeInsertion(null);
|
||||||
|
} else if (sourcePortGroup === 'left') {
|
||||||
// Connect from left port to new node's right side
|
// Connect from left port to new node's right side
|
||||||
targetPort = targetPorts.find((port: any) => port.group === 'right')?.id || 'right';
|
const targetPort = newPorts.find((port: any) => port.group === 'right')?.id || 'right';
|
||||||
graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: newNode.id, port: targetPort },
|
source: { cell: newNode.id, port: targetPort },
|
||||||
target: { cell: sourceNode.id, port: sourcePort },
|
target: { cell: sourceNode.id, port: sourcePort },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
// zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Connect from right port to new node's left side
|
// Connect from right port to new node's left side
|
||||||
targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
const targetPort = newPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
||||||
graph.addEdge({
|
graph.addEdge({
|
||||||
source: { cell: sourceNode.id, port: sourcePort },
|
source: { cell: sourceNode.id, port: sourcePort },
|
||||||
target: { cell: newNode.id, port: targetPort },
|
target: { cell: newNode.id, port: targetPort },
|
||||||
...edgeAttrs
|
...edgeAttrs
|
||||||
// zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 15:06:18
|
* @Date: 2026-02-03 15:06:18
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-27 18:30:52
|
* @Last Modified time: 2026-03-30 15:11:56
|
||||||
*/
|
*/
|
||||||
import LoopNode from './components/Nodes/LoopNode';
|
import LoopNode from './components/Nodes/LoopNode';
|
||||||
import NormalNode from './components/Nodes/NormalNode';
|
import NormalNode from './components/Nodes/NormalNode';
|
||||||
@@ -642,6 +642,8 @@ interface NodeConfig {
|
|||||||
|
|
||||||
/** Edge color for normal state */
|
/** Edge color for normal state */
|
||||||
export const edge_color = '#D4D5D9';
|
export const edge_color = '#D4D5D9';
|
||||||
|
/** Edge color for hover state */
|
||||||
|
export const edge_hover_color = '#2E90FA';
|
||||||
/** Edge color for selected state */
|
/** Edge color for selected state */
|
||||||
export const edge_selected_color = '#171719'
|
export const edge_selected_color = '#171719'
|
||||||
export const edge_width = 2;
|
export const edge_width = 2;
|
||||||
@@ -884,4 +886,70 @@ export const edgeAttrs = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edge hover tool: circular "+" button shown at midpoint on hover
|
||||||
|
*/
|
||||||
|
export const edgeHoverTool = {
|
||||||
|
name: 'button',
|
||||||
|
args: {
|
||||||
|
markup: [
|
||||||
|
{
|
||||||
|
tagName: 'circle',
|
||||||
|
selector: 'button',
|
||||||
|
attrs: {
|
||||||
|
r: 6,
|
||||||
|
stroke: port_color,
|
||||||
|
strokeWidth: edge_width,
|
||||||
|
fill: port_color,
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tagName: 'text',
|
||||||
|
textContent: '+',
|
||||||
|
selector: 'icon',
|
||||||
|
attrs: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fill: '#FFFFFF',
|
||||||
|
textAnchor: 'middle',
|
||||||
|
textVerticalAnchor: 'middle',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
y: '0.3em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
distance: 0.5,
|
||||||
|
offset: { x: 0, y: 0 },
|
||||||
|
onClick({ e, cell: edge }: any) {
|
||||||
|
e.stopPropagation();
|
||||||
|
const graph = edge.model?.graph;
|
||||||
|
if (!graph) return;
|
||||||
|
const sourceCell = graph.getCellById(edge.getSourceCellId());
|
||||||
|
const targetCell = graph.getCellById(edge.getTargetCellId());
|
||||||
|
const sourcePort = edge.getSourcePortId();
|
||||||
|
const targetPort = edge.getTargetPortId();
|
||||||
|
if (!sourceCell || !targetCell) return;
|
||||||
|
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.style.position = 'fixed';
|
||||||
|
tempDiv.style.left = rect.left + 'px';
|
||||||
|
tempDiv.style.top = rect.top + 'px';
|
||||||
|
tempDiv.style.width = '1px';
|
||||||
|
tempDiv.style.height = '1px';
|
||||||
|
tempDiv.style.zIndex = '9999';
|
||||||
|
document.body.appendChild(tempDiv);
|
||||||
|
window.dispatchEvent(new CustomEvent('port:click', {
|
||||||
|
detail: {
|
||||||
|
node: sourceCell,
|
||||||
|
port: sourcePort,
|
||||||
|
element: tempDiv,
|
||||||
|
rect,
|
||||||
|
edgeInsertion: { edge, sourceCell, targetCell, sourcePort, targetPort }
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 15:17:48
|
* @Date: 2026-02-03 15:17:48
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-27 18:14:38
|
* @Last Modified time: 2026-03-30 15:08:14
|
||||||
*/
|
*/
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@@ -12,7 +12,7 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
|
|||||||
import { register } from '@antv/x6-react-shape';
|
import { register } from '@antv/x6-react-shape';
|
||||||
import type { PortMetadata } from '@antv/x6/lib/model/port';
|
import type { PortMetadata } from '@antv/x6/lib/model/port';
|
||||||
|
|
||||||
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, defaultPortItems, portItemArgsY, edge_width, conditionNodePortItemArgsY, conditionNodeItemHeight, conditionNodeHeight, notesConfig } from '../constant';
|
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edgeHoverTool, edge_color, edge_hover_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, defaultPortItems, portItemArgsY, edge_width, conditionNodePortItemArgsY, conditionNodeItemHeight, conditionNodeHeight, notesConfig } from '../constant';
|
||||||
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
|
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
|
||||||
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
|
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
|
||||||
import { useUser } from '@/store/user';
|
import { useUser } from '@/store/user';
|
||||||
@@ -881,12 +881,21 @@ export const useWorkflowGraph = ({
|
|||||||
});
|
});
|
||||||
// Use plugins
|
// Use plugins
|
||||||
setupPlugins();
|
setupPlugins();
|
||||||
// Listen to edge mouseleave event
|
// Listen to edge mouseenter event: show hover style and add button
|
||||||
|
graphRef.current.on('edge:mouseenter', ({ edge }: { edge: Edge }) => {
|
||||||
|
if (edge.getAttrByPath('line/stroke') !== edge_selected_color) {
|
||||||
|
edge.setAttrByPath('line/stroke', edge_hover_color);
|
||||||
|
edge.setAttrByPath('line/strokeWidth', edge_width);
|
||||||
|
}
|
||||||
|
edge.addTools([edgeHoverTool]);
|
||||||
|
});
|
||||||
|
// Listen to edge mouseleave event: revert style and remove add button
|
||||||
graphRef.current.on('edge:mouseleave', ({ edge }: { edge: Edge }) => {
|
graphRef.current.on('edge:mouseleave', ({ edge }: { edge: Edge }) => {
|
||||||
if (edge.getAttrByPath('line/stroke') !== edge_selected_color) {
|
if (edge.getAttrByPath('line/stroke') !== edge_selected_color) {
|
||||||
edge.setAttrByPath('line/stroke', edge_color);
|
edge.setAttrByPath('line/stroke', edge_color);
|
||||||
edge.setAttrByPath('line/strokeWidth', edge_width);
|
edge.setAttrByPath('line/strokeWidth', edge_width);
|
||||||
}
|
}
|
||||||
|
edge.removeTools();
|
||||||
});
|
});
|
||||||
// Listen to node selection event
|
// Listen to node selection event
|
||||||
graphRef.current.on('node:click', nodeClick);
|
graphRef.current.on('node:click', nodeClick);
|
||||||
|
|||||||
Reference in New Issue
Block a user