feat(web): workflow node port view update
This commit is contained in:
@@ -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-30 15:11:56
|
* @Last Modified time: 2026-03-30 16:52:54
|
||||||
*/
|
*/
|
||||||
import LoopNode from './components/Nodes/LoopNode';
|
import LoopNode from './components/Nodes/LoopNode';
|
||||||
import NormalNode from './components/Nodes/NormalNode';
|
import NormalNode from './components/Nodes/NormalNode';
|
||||||
@@ -695,8 +695,59 @@ export const portArgs = { x: nodeWidth, y: portItemArgsY }
|
|||||||
|
|
||||||
const defaultPortGroup = {
|
const defaultPortGroup = {
|
||||||
position: { name: 'absolute' },
|
position: { name: 'absolute' },
|
||||||
markup: portMarkup,
|
markup: [
|
||||||
attrs: portAttrs
|
{ tagName: 'rect', selector: 'body' },
|
||||||
|
{ tagName: 'circle', selector: 'hoverBody' },
|
||||||
|
{ tagName: 'text', selector: 'label' },
|
||||||
|
],
|
||||||
|
attrs: {
|
||||||
|
body: {
|
||||||
|
width: 1,
|
||||||
|
height: 8,
|
||||||
|
x: -1,
|
||||||
|
magnet: true,
|
||||||
|
stroke: port_color,
|
||||||
|
strokeWidth: edge_width,
|
||||||
|
fill: port_color,
|
||||||
|
},
|
||||||
|
hoverBody: {
|
||||||
|
r: 6,
|
||||||
|
cy: 2,
|
||||||
|
magnet: true,
|
||||||
|
stroke: port_color,
|
||||||
|
strokeWidth: edge_width,
|
||||||
|
fill: port_color,
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
text: '+',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fill: '#FFFFFF',
|
||||||
|
textAnchor: 'middle',
|
||||||
|
textVerticalAnchor: 'middle',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
y: '0.15em',
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftPortGroup = {
|
||||||
|
position: { name: 'absolute' },
|
||||||
|
markup: [{ tagName: 'rect', selector: 'body' }],
|
||||||
|
attrs: {
|
||||||
|
body: {
|
||||||
|
width: 1,
|
||||||
|
height: 8,
|
||||||
|
x: -1,
|
||||||
|
y: -4,
|
||||||
|
magnet: true,
|
||||||
|
stroke: port_color,
|
||||||
|
strokeWidth: edge_width,
|
||||||
|
fill: port_color,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -705,7 +756,7 @@ const defaultPortGroup = {
|
|||||||
*/
|
*/
|
||||||
export const defaultAbsolutePortGroups = {
|
export const defaultAbsolutePortGroups = {
|
||||||
right: defaultPortGroup,
|
right: defaultPortGroup,
|
||||||
left: defaultPortGroup,
|
left: leftPortGroup,
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Default port items for standard nodes
|
* Default port items for standard nodes
|
||||||
@@ -799,7 +850,7 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
|
|||||||
height: 28,
|
height: 28,
|
||||||
shape: 'add-node',
|
shape: 'add-node',
|
||||||
ports: {
|
ports: {
|
||||||
groups: { left: defaultPortGroup },
|
groups: { left: leftPortGroup },
|
||||||
items: [{ group: 'left', args: { x: 0, y: 18 }}],
|
items: [{ group: 'left', args: { x: 0, y: 18 }}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -826,7 +877,7 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
|
|||||||
height: 28,
|
height: 28,
|
||||||
shape: 'add-node',
|
shape: 'add-node',
|
||||||
ports: {
|
ports: {
|
||||||
groups: { left: defaultPortGroup },
|
groups: { left: leftPortGroup },
|
||||||
items: [{ group: 'left', args: { x: 0, y: 14 } }],
|
items: [{ group: 'left', args: { x: 0, y: 14 } }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -835,7 +886,7 @@ export const graphNodeLibrary: Record<string, NodeConfig> = {
|
|||||||
height: 76,
|
height: 76,
|
||||||
shape: 'normal-node',
|
shape: 'normal-node',
|
||||||
ports: {
|
ports: {
|
||||||
groups: { left: defaultPortGroup },
|
groups: { left: leftPortGroup },
|
||||||
items: [defaultPortItems[0]],
|
items: [defaultPortItems[0]],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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-30 15:08:14
|
* @Last Modified time: 2026-03-30 17:18:11
|
||||||
*/
|
*/
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@@ -441,6 +441,7 @@ export const useWorkflowGraph = ({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (graphRef.current) {
|
if (graphRef.current) {
|
||||||
graphRef.current.centerContent()
|
graphRef.current.centerContent()
|
||||||
|
graphRef.current.getNodes().forEach(node => node.toFront());
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
@@ -719,6 +720,7 @@ export const useWorkflowGraph = ({
|
|||||||
};
|
};
|
||||||
const nodePortClickEvent = ({ e, node, port }: { e: MouseEvent, node: Node, port: string }) => {
|
const nodePortClickEvent = ({ e, node, port }: { e: MouseEvent, node: Node, port: string }) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
const portElement = e.target as HTMLElement;
|
const portElement = e.target as HTMLElement;
|
||||||
const rect = portElement.getBoundingClientRect();
|
const rect = portElement.getBoundingClientRect();
|
||||||
|
|
||||||
@@ -903,13 +905,65 @@ export const useWorkflowGraph = ({
|
|||||||
graphRef.current.on('edge:click', edgeClick);
|
graphRef.current.on('edge:click', edgeClick);
|
||||||
// Listen to port click event
|
// Listen to port click event
|
||||||
graphRef.current.on('node:port:click', nodePortClickEvent);
|
graphRef.current.on('node:port:click', nodePortClickEvent);
|
||||||
|
// Port hover: show circle style on right ports
|
||||||
|
graphRef.current.on('node:port:mouseenter', ({ node, port }) => {
|
||||||
|
if (!port) return;
|
||||||
|
const portData = node.getPort(port);
|
||||||
|
if (portData?.group !== 'right') return;
|
||||||
|
node.toFront();
|
||||||
|
node.setPortProp(port, 'attrs/body/opacity', 0);
|
||||||
|
node.setPortProp(port, 'attrs/hoverBody/opacity', 1);
|
||||||
|
node.setPortProp(port, 'attrs/label/opacity', 1);
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:port:mouseleave', ({ node, port }) => {
|
||||||
|
if (!port) return;
|
||||||
|
const portData = node.getPort(port);
|
||||||
|
if (portData?.group !== 'right') return;
|
||||||
|
node.setPortProp(port, 'attrs/body/opacity', 1);
|
||||||
|
node.setPortProp(port, 'attrs/hoverBody/opacity', 0);
|
||||||
|
node.setPortProp(port, 'attrs/label/opacity', 0);
|
||||||
|
});
|
||||||
// Listen to canvas click event, cancel selection
|
// Listen to canvas click event, cancel selection
|
||||||
graphRef.current.on('blank:click', blankClick);
|
graphRef.current.on('blank:click', blankClick);
|
||||||
|
// Node hover: highlight connected edges
|
||||||
|
graphRef.current.on('node:mouseenter', ({ node }) => {
|
||||||
|
graphRef.current?.getEdges().forEach(edge => {
|
||||||
|
const view = graphRef.current?.findViewByCell(edge);
|
||||||
|
view?.removeTools();
|
||||||
|
if (edge.getAttrByPath('line/stroke') !== edge_selected_color) {
|
||||||
|
edge.setAttrByPath('line/stroke', edge_color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
graphRef.current?.getConnectedEdges(node).forEach(edge => {
|
||||||
|
edge.setAttrByPath('line/stroke', edge_hover_color);
|
||||||
|
});
|
||||||
|
node.getPorts().filter(p => p.group === 'right').forEach(p => {
|
||||||
|
node.setPortProp(p.id!, 'attrs/body/opacity', 0);
|
||||||
|
node.setPortProp(p.id!, 'attrs/hoverBody/opacity', 1);
|
||||||
|
node.setPortProp(p.id!, 'attrs/label/opacity', 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
graphRef.current.on('node:mouseleave', ({ node }) => {
|
||||||
|
graphRef.current?.getConnectedEdges(node).forEach(edge => {
|
||||||
|
if (edge.getAttrByPath('line/stroke') !== edge_selected_color) {
|
||||||
|
edge.setAttrByPath('line/stroke', edge_color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
node.getPorts().filter(p => p.group === 'right').forEach(p => {
|
||||||
|
node.setPortProp(p.id!, 'attrs/body/opacity', 1);
|
||||||
|
node.setPortProp(p.id!, 'attrs/hoverBody/opacity', 0);
|
||||||
|
node.setPortProp(p.id!, 'attrs/label/opacity', 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
// Listen to zoom event
|
// Listen to zoom event
|
||||||
graphRef.current.on('scale', scaleEvent);
|
graphRef.current.on('scale', scaleEvent);
|
||||||
// Listen to node move event
|
// Listen to node move event
|
||||||
graphRef.current.on('node:moved', nodeMoved);
|
graphRef.current.on('node:moved', nodeMoved);
|
||||||
graphRef.current.on('node:removed', blankClick)
|
graphRef.current.on('node:removed', blankClick)
|
||||||
|
// When edge changes, bring connected nodes' ports to front
|
||||||
|
graphRef.current.on('edge:change', () => {
|
||||||
|
graphRef.current?.getNodes().forEach(node => node.toFront());
|
||||||
|
});
|
||||||
// Listen to copy keyboard event
|
// Listen to copy keyboard event
|
||||||
graphRef.current.bindKey(['ctrl+c', 'cmd+c'], copyEvent);
|
graphRef.current.bindKey(['ctrl+c', 'cmd+c'], copyEvent);
|
||||||
// Listen to paste keyboard event
|
// Listen to paste keyboard event
|
||||||
|
|||||||
Reference in New Issue
Block a user