Skip to content

Commit

Permalink
Merge branch 'xflow' of https://github.com/alibaba/x-render into xflow
Browse files Browse the repository at this point in the history
  • Loading branch information
昔梦 committed Jan 15, 2025
2 parents efd8dd0 + cb293e8 commit beeab0d
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 111 deletions.
3 changes: 3 additions & 0 deletions docs/xflow/FlowProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ group:
- screenToFlowPosition:将屏幕坐标转换为画布坐标
- flowToScreenPosition:将画布坐标转换为屏幕坐标
- runAutoLayout:自动布局节点
- copyNode:复制单个节点
- pasteNode:粘贴单个节点
- deleteNode:删除单个节点

## useNodes

Expand Down
16 changes: 12 additions & 4 deletions packages/x-flow/src/XFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
7 changes: 4 additions & 3 deletions packages/x-flow/src/components/CandidateNode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,6 +22,7 @@ const CandidateNode = () => {
}),
shallow
);
const { addNodes } = useFlow();

useEventListener('click', ev => {
if (!candidateNode) {
Expand All @@ -42,7 +43,7 @@ const CandidateNode = () => {
},
position: { x, y },
};
addNodes(newNodes, false);
addNodes(newNodes);
setIsAddingNode(false)
setCandidateNode(null);
});
Expand Down
91 changes: 39 additions & 52 deletions packages/x-flow/src/components/CustomEdge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -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' }]);
};
Expand Down
28 changes: 13 additions & 15 deletions packages/x-flow/src/components/CustomNode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } =
Expand All @@ -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;

Expand All @@ -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',
Expand All @@ -86,7 +84,7 @@ export default memo((props: any) => {
deletable,
...(sourceHandle && { sourceHandle }),
};
addNodes(newNodes, false);
addNodes(newNodes as any);
addEdges(newEdges);
};

Expand All @@ -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') {
Expand Down Expand Up @@ -196,7 +194,7 @@ export default memo((props: any) => {
key: 'paste',
},
];
}, [type, data]);
}, [type, data, isEnd]);

// 节点状态处理
const statusObj = transformNodeStatus(globalConfig?.nodeView?.status || []);
Expand All @@ -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>
Expand All @@ -230,7 +228,7 @@ export default memo((props: any) => {
key: 'copy',
disabled: disabledCopy,
},
...menuItem,
...(isEnd ? [] : menuItem),
{
label: '删除',
key: 'delete',
Expand All @@ -246,7 +244,7 @@ export default memo((props: any) => {
return {
overlay: menu,
};
}, [menuItem]);
}, [menuItem, isEnd]);
return (
<div
className={classNames('xflow-node-container', {
Expand Down
54 changes: 52 additions & 2 deletions packages/x-flow/src/hooks/useFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { useMemoizedFn } from 'ahooks';
import { useMemo } from 'react';
import { FlowNode } from '../models/store';
import { useStoreApi } from './useStore';
import { useTemporalStore } from './useTemporalStore';
import autoLayoutNodes from '../utils/autoLayoutNodes';
import { generateCopyNodes, uuid } from '../utils';
import { message } from 'antd';

// useFlow 维护原则
// 1. 尽量复用 reactflow 已有的方法,不要重复造轮子
Expand All @@ -27,18 +30,62 @@ export const useFlow = () => {
screenToFlowPosition,
flowToScreenPosition
} = useReactFlow();
const { record } = useTemporalStore();

const setNodes = useMemoizedFn((nodes: FlowNode[], isTransform = false) => {
storeApi.getState().setNodes(nodes, isTransform);
});
const addNodes = useMemoizedFn((nodes: FlowNode[], isTransform = false) => {
storeApi.getState().addNodes(nodes, isTransform);
record(() => {
storeApi.getState().addNodes(nodes, isTransform);
})
});
const setEdges = useMemoizedFn((edges: Edge[]) => {
storeApi.getState().setEdges(edges);
});
const addEdges = useMemoizedFn((edges: Edge[]) => {
storeApi.getState().addEdges(edges);
});
const copyNode = useMemoizedFn((nodeId) => {
const copyNodes = generateCopyNodes(
storeApi.getState().nodes.find((node) => node.id === nodeId),
);
storeApi.setState({
copyNodes,
});
});
const pasteNode = useMemoizedFn((nodeId: string, data: any) => {
if (storeApi.getState().copyNodes.length > 0) {
const newEdges = {
id: uuid(),
source: nodeId,
target: storeApi.getState().copyNodes[0].id,
...data
};
record(() => {
storeApi.getState().addNodes(storeApi.getState().copyNodes, false);
})
storeApi.getState().addEdges(newEdges);
storeApi.setState({
copyNodes: [],
});
}else{
message.warning('请先复制节点!')
}
});
const deleteNode = useMemoizedFn((nodeId) => {
record(() => {
storeApi.setState({
edges: storeApi.getState().edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId),
});
})
record(() => {
storeApi.setState({
nodes: storeApi.getState().nodes.filter((node) => node.id !== nodeId),
});
})
})

const runAutoLayout = useMemoizedFn(() => {
const newNodes: any = autoLayoutNodes(storeApi.getState().nodes, storeApi.getState().edges, storeApi.getState().layout);
setNodes(newNodes, false);
Expand All @@ -64,7 +111,10 @@ export const useFlow = () => {
fitBounds,
screenToFlowPosition,
flowToScreenPosition,
runAutoLayout
runAutoLayout,
copyNode,
pasteNode,
deleteNode
}),
[instance]
);
Expand Down
Loading

0 comments on commit beeab0d

Please sign in to comment.