Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xflow #1613

Merged
merged 7 commits into from
Jan 15, 2025
Merged

Xflow #1613

Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/xflow/FlowProvider.md
Original file line number Diff line number Diff line change
@@ -36,6 +36,9 @@ group:
- screenToFlowPosition:将屏幕坐标转换为画布坐标
- flowToScreenPosition:将画布坐标转换为屏幕坐标
- runAutoLayout:自动布局节点
- copyNode:复制单个节点
- pasteNode:粘贴单个节点
- deleteNode:删除单个节点

## useNodes

3 changes: 2 additions & 1 deletion docs/xflow/api.md
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ title: API
| onNodeClick | 节点点击事件 | `NodeMouseHandler` | |
| antdVersion | antd 的版本 | `V4 \| V5` | `V5` |
| readOnly | 只读模式 | `boolean` | `false` |
| onTesting | 单点调试方法 | `(node,nodes)=>void` | |


## TNodePanel
@@ -104,7 +105,7 @@ title: API
| nodePanel | 自定义节点的面板配置信息 | [TNodePanel](#tnodepanel) | |
| switchExtra | 条件节点属性配置 | [TSwitchExtra](#tswitchextra) | |
| parallelExtra | 并行节点属性配置 | [TParallelExtra](#tparallelextra) | |
| onTesting | 节点的单点调试方法 | `(node,nodes) => void` | |
| showTestingBtn | 是否展示节点的单点调试按钮 | `boolean` | `false` |



16 changes: 12 additions & 4 deletions packages/x-flow/src/XFlow.tsx
Original file line number Diff line number Diff line change
@@ -298,17 +298,25 @@ const XFlow: FC<FlowProps> = memo(props => {
onConnect={onConnect}
onNodesChange={changes => {
changes.forEach(change => {
if (change.type === 'remove' || change.type === 'add') {
if (change.type === 'remove') {
record(() => {
onNodesChange(changes);
onNodesChange([change]);
});
} else {
onNodesChange(changes);
onNodesChange([change]);
}
});
}}
onEdgesChange={changes => {
onEdgesChange(changes);
changes.forEach(change => {
if (change.type === 'remove') {
record(() => {
onEdgesChange([change]);
});
} else {
onEdgesChange([change]);
}
});
}}
onEdgeMouseEnter={(_, edge: any) => {
getUpdateEdgeConfig(edge, '#2970ff');
7 changes: 4 additions & 3 deletions packages/x-flow/src/components/CandidateNode/index.tsx
Original file line number Diff line number Diff line change
@@ -4,15 +4,15 @@ import React, { memo } from 'react';
import { shallow } from 'zustand/shallow';
import { useStore } from '../../hooks/useStore';
import CustomNode from '../CustomNode';
import { useFlow } from '../../hooks/useFlow';

const CandidateNode = () => {
const { zoom } = useViewport();
const reactflow = useReactFlow();
const { candidateNode, mousePosition, setIsAddingNode, setCandidateNode, addNodes } = useStore(
const { candidateNode, mousePosition, setIsAddingNode, setCandidateNode } = useStore(
(s: any) => ({
nodes: s.nodes,
edges: s.edges,
addNodes: s.addNodes,
candidateNode: s.candidateNode,
setIsAddingNode: s.setIsAddingNode,
mousePosition: s.mousePosition,
@@ -22,6 +22,7 @@ const CandidateNode = () => {
}),
shallow
);
const { addNodes } = useFlow();

useEventListener('click', ev => {
if (!candidateNode) {
@@ -42,7 +43,7 @@ const CandidateNode = () => {
},
position: { x, y },
};
addNodes(newNodes, false);
addNodes(newNodes);
setIsAddingNode(false)
setCandidateNode(null);
});
91 changes: 39 additions & 52 deletions packages/x-flow/src/components/CustomEdge/index.tsx
Original file line number Diff line number Diff line change
@@ -5,9 +5,9 @@ import {
getBezierPath,
useReactFlow,
} from '@xyflow/react';
import produce from 'immer';
import React, { memo, useContext, useState } from 'react';
import { shallow } from 'zustand/shallow';
import { useFlow } from '../../hooks/useFlow';
import { useStore } from '../../hooks/useStore';
import { ConfigContext } from '../../models/context';
import { uuid, uuid4 } from '../../utils';
@@ -41,26 +41,19 @@ export default memo((edge: any) => {
const hideEdgeDelBtn = globalConfig?.edge?.hideEdgeDelBtn ?? false;
const deletable = globalConfig?.edge?.deletable ?? true;

const {
nodes,
edges,
addNodes,
addEdges,
mousePosition,
onEdgesChange,
layout,
} = useStore(
(state: any) => ({
layout: state.layout,
nodes: state.nodes,
edges: state.edges,
mousePosition: state.mousePosition,
addNodes: state.addNodes,
addEdges: state.addEdges,
onEdgesChange: state.onEdgesChange,
}),
shallow
);
const { nodes, edges, addEdges, mousePosition, onEdgesChange, layout } =
useStore(
(state: any) => ({
layout: state.layout,
nodes: state.nodes,
edges: state.edges,
mousePosition: state.mousePosition,
addEdges: state.addEdges,
onEdgesChange: state.onEdgesChange,
}),
shallow
);
const { addNodes } = useFlow();

const handleAddNode = (data: any) => {
const { screenToFlowPosition } = reactflow;
@@ -72,39 +65,33 @@ export default memo((edge: any) => {
const targetId = uuid();
const title = settingMap[data?._nodeType]?.title || data?._nodeType;

const newNodes = produce(nodes, (draft: any) => {
draft.push({
id: targetId,
type: 'custom',
data: {
title: `${title}_${uuid4()}`,
...data,
},
position: { x, y },
});
});
const newNodes = {
id: targetId,
type: 'custom',
data: {
title: `${title}_${uuid4()}`,
...data,
},
position: { x, y },
};

const newEdges = produce(edges, (draft: any) => {
draft.push(
...[
{
id: uuid(),
source,
target: targetId,
deletable: deletable,
...(sourceHandleId && { sourceHandle: sourceHandleId }),
},
{
id: uuid(),
source: targetId,
deletable: deletable,
target,
},
]
);
});
const newEdges = [
{
id: uuid(),
source,
target: targetId,
deletable: deletable,
...(sourceHandleId && { sourceHandle: sourceHandleId }),
},
{
id: uuid(),
source: targetId,
deletable: deletable,
target,
},
];

addNodes(newNodes, false);
addNodes(newNodes as any);
addEdges(newEdges);
onEdgesChange([{ id, type: 'remove' }]);
};
28 changes: 13 additions & 15 deletions packages/x-flow/src/components/CustomNode/index.tsx
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import {
} from '../../utils';
import './index.less';
import SourceHandle from './sourceHandle';
import { useFlow } from '../../hooks/useFlow';

export default memo((props: any) => {
const { id, type, data, layout, isConnectable, selected, onClick, status } =
@@ -41,22 +42,20 @@ export default memo((props: any) => {
widgets[`${capitalize(type)}Node`] || widgets['CommonNode'];
const [isHovered, setIsHovered] = useState(false);
const reactflow = useReactFlow();
const { addNodes, addEdges, copyNode, pasteNode, deleteNode, mousePosition } =
const { addEdges, mousePosition } =
useStore(
(state: any) => ({
nodes: state.nodes,
edges: state.edges,
mousePosition: state.mousePosition,
addNodes: state.addNodes,
addEdges: state.addEdges,
copyNode: state.copyNode,
pasteNode: state.pasteNode,
deleteNode: state.deleteNode,
onEdgesChange: state.onEdgesChange,
}),
shallow
);
const { addNodes, pasteNode, copyNode, deleteNode } = useFlow();
const isNote = type === 'Note';
const isEnd = type === 'End';
const isSwitchNode = type === 'Switch' || type === 'Parallel' || isNote; // 判断是否为条件节点/并行节点/注释节点
const connectable = readOnly ? false : isConnectable;

@@ -69,7 +68,6 @@ export default memo((props: any) => {
});
const targetId = uuid();
const title = settingMap[data?._nodeType]?.title || data?._nodeType;

const newNodes = {
id: targetId,
type: 'custom',
@@ -86,7 +84,7 @@ export default memo((props: any) => {
deletable,
...(sourceHandle && { sourceHandle }),
};
addNodes(newNodes, false);
addNodes(newNodes as any);
addEdges(newEdges);
};

@@ -100,18 +98,18 @@ export default memo((props: any) => {
const handleCopyNode = useCallback(() => {
copyNode(id);
message.success('复制成功');
}, [copyNode]);
}, [copyNode, id]);

const handlePasteNode = useCallback(
(data?: { sourceHandle: string }) => {
pasteNode(id, data);
},
[pasteNode]
[pasteNode, id]
);

const handleDeleteNode = useCallback(() => {
deleteNode(id);
}, [pasteNode]);
}, [deleteNode, id]);

const defaultAction = (e, sourceHandle) => {
if (e.key === 'copy') {
@@ -196,7 +194,7 @@ export default memo((props: any) => {
key: 'paste',
},
];
}, [type, data]);
}, [type, data, isEnd]);

// 节点状态处理
const statusObj = transformNodeStatus(globalConfig?.nodeView?.status || []);
@@ -207,13 +205,13 @@ export default memo((props: any) => {
<Menu.Item key={'copy'} disabled={disabledCopy}>
复制
</Menu.Item>
{menuItem.map((r: any) => {
{!isEnd ? menuItem.map((r: any) => {
return (
<Menu.Item {...r} key={r.key}>
{r.label}
</Menu.Item>
);
})}
}) : null}
<Menu.Item key={'delete'} danger={true} disabled={disabledDelete}>
删除
</Menu.Item>
@@ -230,7 +228,7 @@ export default memo((props: any) => {
key: 'copy',
disabled: disabledCopy,
},
...menuItem,
...(isEnd ? [] : menuItem),
{
label: '删除',
key: 'delete',
@@ -246,7 +244,7 @@ export default memo((props: any) => {
return {
overlay: menu,
};
}, [menuItem]);
}, [menuItem, isEnd]);
return (
<div
className={classNames('xflow-node-container', {
8 changes: 4 additions & 4 deletions packages/x-flow/src/components/NodeEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -70,8 +70,8 @@ const NodeEditor: FC<INodeEditorProps> = (props: any) => {
if (node) {
// 更新节点的 data
if (
(node?.data?._nodeType === 'Switch' ||
node?.data?._nodeType === 'Parallel')
node?.data?._nodeType === 'Switch' ||
node?.data?._nodeType === 'Parallel'
) {
data['list'] = (data?.list || [])?.map((item, index) => {
if (item?._id) {
@@ -88,8 +88,8 @@ const NodeEditor: FC<INodeEditorProps> = (props: any) => {
}
});
}

node.data = { ...node.data, ...data };
const { _nodeType, _status } = node?.data;
node.data = { _nodeType, _status, ...data }; // form-render的list如果为空,不会返回list相应的字段,只能全部替换data
}
});
setNodes(newNodes, false);
7 changes: 4 additions & 3 deletions packages/x-flow/src/components/PanelContainer/index.tsx
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ const Panel: FC<IPanelProps> = (props: IPanelProps) => {
antdVersion,
readOnly,
logPanel,
onTesting,
}: any = useContext(ConfigContext);
const nodeSetting = settingMap[nodeType] || {};
const { nodes, setNodes } = useStore(
@@ -59,7 +60,7 @@ const Panel: FC<IPanelProps> = (props: IPanelProps) => {
const isDisabled = disabled; // 目前没用
const [descVal, setDescVal] = useState(data?.desc);
const [titleVal, setTitleVal] = useState(data?.title || nodeSetting?.title);
const { nodePanel, iconSvg, onTesting } = nodeSetting;
const { nodePanel, iconSvg, showTestingBtn } = nodeSetting;
const hideDesc =
nodePanel?.hideDesc ?? globalConfig?.nodePanel?.hideDesc ?? false;
const isShowStatusPanel = Boolean(isTruthy(node?._status) && openLogPanel);
@@ -151,14 +152,14 @@ const Panel: FC<IPanelProps> = (props: IPanelProps) => {
</div>
<div className="title-actions">
<Space size={[4, 4]}>
{!isDisabled && onTesting && (
{!isDisabled && showTestingBtn && (
<>
<IconView
type="icon-yunhang"
onClick={() => {
const n =
nodes?.find(item => item?.id === node?.id) || {};
onTesting(n, nodes);
onTesting && onTesting(n, nodes);
}}
style={{ fontSize: 16 }}
/>
Loading