- Node {node.id} - x: {node.position.x.toFixed(2)}, y:{' '}
- {node.position.y.toFixed(2)}
+
+ Node {node?.id} - x: {node?.position?.x?.toFixed(2)}, y:{' '}
+ {node?.position?.y?.toFixed(2)}
))}
diff --git a/docs/xflow/demo/layout/TB/index.tsx b/docs/xflow/demo/layout/TB/index.tsx
index 9b717eb3b..5c7b091f6 100644
--- a/docs/xflow/demo/layout/TB/index.tsx
+++ b/docs/xflow/demo/layout/TB/index.tsx
@@ -62,11 +62,11 @@ export default () => {
);
diff --git a/docs/xflow/demo/nodeSetting/data.tsx b/docs/xflow/demo/nodeSetting/data.tsx
new file mode 100644
index 000000000..3f55218d5
--- /dev/null
+++ b/docs/xflow/demo/nodeSetting/data.tsx
@@ -0,0 +1,209 @@
+export const settings = [
+ {
+ title: '开始', // 节点名称
+ type: 'Start',
+ hidden: true,
+ targetHandleHidden: true,
+ icon: {
+ // 图标描述
+ type: 'icon-start', // icon-font
+ bgColor: '#17B26A', // 图标背景颜色
+ },
+ nodeConfigPanelWidth: 500,
+ settingSchema: {
+ // 自定义节点配置
+ type: 'object',
+ properties: {
+ input: {
+ title: '变量一',
+ type: 'string',
+ widget: 'input',
+ },
+ select: {
+ title: '变量二',
+ type: 'string',
+ widget: 'select',
+ props: {
+ options: [
+ { label: 'a', value: 'a' },
+ { label: 'b', value: 'b' },
+ { label: 'c', value: 'c' },
+ ],
+ },
+ },
+ radio1: {
+ title: '点击单选',
+ type: 'string',
+ widget: 'radio',
+ props: {
+ options: [
+ { label: '早', value: 'a' },
+ { label: '中', value: 'b' },
+ { label: '晚', value: 'c' },
+ ],
+ },
+ },
+ textarea1: {
+ title: '长文本',
+ type: 'string',
+ widget: 'textArea',
+ },
+ date1: {
+ title: '日期选择',
+ type: 'string',
+ widget: 'datePicker',
+ },
+ dateRange1: {
+ title: '日期范围',
+ type: 'range',
+ widget: 'dateRange',
+ },
+ time1: {
+ title: '时间选择',
+ type: 'string',
+ widget: 'timePicker',
+ },
+ timeRange1: {
+ title: '时间范围',
+ type: 'range',
+ widget: 'timeRange',
+ },
+ },
+ },
+ },
+ {
+ title: '结束',
+ type: 'End',
+ hidden: true,
+ sourceHandleHidden: true,
+ icon: {
+ type: 'icon-end',
+ bgColor: '#F79009',
+ },
+ settingSchema: {
+ type: 'object',
+ properties: {
+ input: {
+ title: '变量一',
+ type: 'string',
+ widget: 'input',
+ },
+ select: {
+ title: '变量二',
+ type: 'string',
+ widget: 'select',
+ props: {
+ options: [
+ { label: 'a', value: 'a' },
+ { label: 'b', value: 'b' },
+ { label: 'c', value: 'c' },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ title: 'Switch',
+ type: 'Switch',
+ description: '允许你根据 if/else 条件将 workflow 拆分成两个分支',
+ icon: {
+ type: 'icon-switch',
+ bgColor: '#06AED4',
+ },
+ },
+ {
+ title: '工具',
+ type: 'tool',
+ description: '允许使用工具能力',
+ icon: {
+ type: 'icon-gongju',
+ bgColor: '#2E90FA',
+ },
+ },
+ {
+ title: '工具',
+ type: '_group', // 节点分组
+ items: [
+ {
+ title: '代码执行',
+ type: 'Code',
+ description: '执行一段 Groovy 或 Python 或 NodeJS 代码实现自定义逻辑',
+ iconSvg: (
+
+ ),
+ },
+ {
+ title: '工具',
+ type: 'tool',
+ description: '允许使用工具能力',
+ icon: {
+ type: 'icon-gongju',
+ bgColor: '#2E90FA',
+ },
+ },
+ ],
+ },
+];
+
+export const nodes = [
+ {
+ type: 'Start',
+ id: '1',
+ position: { x: -35, y: 268 },
+ },
+ {
+ type: 'Switch',
+ id: '2',
+ position: { x: 277.5, y: 268 },
+ },
+ {
+ type: 'Code',
+ id: '3',
+ position: { x: 675, y: 123.75 },
+ },
+ {
+ type: 'tool',
+ id: '4',
+ position: { x: 686.25, y: 495 },
+ },
+ {
+ type: 'End',
+ id: '5',
+ position: { x: 1176.2499999999998, y: 281.25 },
+ },
+];
+export const edges = [
+ { source: '1', target: '2', id: 'e1-2' },
+ {
+ source: '2',
+ target: '3',
+ id: 'e2-3',
+ },
+ { source: '2', target: '4', id: 'e2-4' },
+ {
+ source: '3',
+ target: '5',
+ id: 'e3-5',
+ },
+ {
+ source: '4',
+ target: '5',
+ id: 'e4-5',
+ },
+];
diff --git a/docs/xflow/demo/nodeSetting/index.tsx b/docs/xflow/demo/nodeSetting/index.tsx
index 5deb83592..49083cd52 100644
--- a/docs/xflow/demo/nodeSetting/index.tsx
+++ b/docs/xflow/demo/nodeSetting/index.tsx
@@ -1,219 +1,40 @@
-import XFlow from '@xrenders/xflow';
+import XFlow, { FlowProvider, useFlow } from '@xrenders/xflow';
+import { Button } from 'antd';
// import settings from './setting';
import React from 'react';
+import { settings, nodes, edges } from "./data";
-const settings = [
- {
- title: '开始', // 节点名称
- type: 'Start',
- hidden: true,
- targetHandleHidden: true,
- icon: { // 图标描述
- type: 'icon-start', // icon-font
- bgColor: '#17B26A', // 图标背景颜色
- },
- settingSchema: { // 自定义节点配置
- type: 'object',
- properties: {
- input: {
- title: '变量一',
- type: 'string',
- widget: 'input',
- },
- select: {
- title: '变量二',
- type: 'string',
- widget: 'select',
- props: {
- options: [
- { label: 'a', value: 'a' },
- { label: 'b', value: 'b' },
- { label: 'c', value: 'c' },
- ],
- },
- },
- radio1: {
- title: '点击单选',
- type: 'string',
- widget: 'radio',
- props: {
- options: [
- { label: '早', value: 'a' },
- { label: '中', value: 'b' },
- { label: '晚', value: 'c' }
- ]
- }
- },
- textarea1: {
- title: '长文本',
- type: 'string',
- widget: 'textArea'
- },
- date1: {
- title: '日期选择',
- type: 'string',
- widget: 'datePicker'
- },
- dateRange1: {
- title: '日期范围',
- type: 'range',
- widget: 'dateRange'
- },
- time1: {
- title: '时间选择',
- type: 'string',
- widget: 'timePicker'
- },
- timeRange1: {
- title: '时间范围',
- type: 'range',
- widget: 'timeRange'
- },
- },
- },
- },
- {
- title: '结束',
- type: 'End',
- hidden: true,
- sourceHandleHidden: true,
- icon: {
- type: 'icon-end',
- bgColor: '#F79009',
- },
- settingSchema: {
- type: "object",
- properties: {
- input: {
- title: '变量一',
- type: 'string',
- widget: 'input',
- },
- select: {
- title: '变量二',
- type: 'string',
- widget: 'select',
- props: {
- options: [
- { label: 'a', value: 'a' },
- { label: 'b', value: 'b' },
- { label: 'c', value: 'c' },
- ],
- },
- },
- }
- }
- },
- {
- title: 'Switch',
- type: 'Switch',
- description: '允许你根据 if/else 条件将 workflow 拆分成两个分支',
- icon: {
- type: 'icon-switch',
- bgColor: '#06AED4',
- },
- },
- {
- title: '代码执行',
- type: 'Code',
- description: '执行一段 Groovy 或 Python 或 NodeJS 代码实现自定义逻辑',
- icon: {
- type: 'icon-code',
- bgColor: '#2E90FA',
- },
- },
- {
- title: '工具',
- type: 'tool',
- description: '允许使用工具能力',
- icon: {
- type: 'icon-gongju',
- bgColor: '#2E90FA',
- },
- },
- {
- title: '工具',
- type: '_group', // 节点分组
- items: [
- {
- title: '代码执行',
- type: 'Code',
- description: '执行一段 Groovy 或 Python 或 NodeJS 代码实现自定义逻辑',
- icon: {
- type: 'icon-code',
- bgColor: '#2E90FA',
- },
- },
- {
- title: '工具',
- type: 'tool',
- description: '允许使用工具能力',
- icon: {
- type: 'icon-gongju',
- bgColor: '#2E90FA',
- },
- },
- ],
- },
-]
-export default () => {
- const nodes = [
- {
- type: 'Start',
- id: '1',
- position: { x: -35, y: 268 },
- },
- {
- type: 'Switch',
- id: '2',
- position: { x: 277.5, y: 268 },
- },
- {
- type: 'Code',
- id: '3',
- position: { x: 675, y: 123.75 },
- },
- {
- type: 'tool',
- id: '4',
- position: { x: 686.25, y: 495 },
- },
- {
- type: 'End',
- id: '5',
- position: { x: 1176.2499999999998, y: 281.25 },
- },
- ];
- const edges = [
- { source: '1', target: '2', id: 'e1-2' },
- {
- source: '2',
- target: '3',
- id: 'e2-3',
- },
- { source: '2', target: '4', id: 'e2-4' },
- {
- source: '3',
- target: '5',
- id: 'e3-5',
- },
- {
- source: '4',
- target: '5',
- id: 'e4-5',
- },
- ];
+export const Flow = () => {
+ const { getNodes, getEdges, getViewport, toObject } = useFlow();
return (
-
+ <>
+
+ >
+ )
+}
+
+export default () => {
+ return (
+
+
+
+
);
};
diff --git a/docs/xflow/demo/switchNode/customSwitchNode/index.tsx b/docs/xflow/demo/switchNode/customSwitchNode/index.tsx
index 04a700cd1..79f9d3f17 100644
--- a/docs/xflow/demo/switchNode/customSwitchNode/index.tsx
+++ b/docs/xflow/demo/switchNode/customSwitchNode/index.tsx
@@ -2,8 +2,8 @@ import XFlow from '@xrenders/xflow';
import settings from './setting';
import React from 'react';
-const customWidget = ({ data, key, item }) => {
- return
{item?.value}
;
+const customWidget = ({ data, index }) => {
+ return
{data?.value}-{index}
;
};
export default () => {
@@ -12,7 +12,7 @@ export default () => {
type: 'Switch',
id: '2',
position: { x: 171.25, y: 218.75 },
- data: { switchData:[{value:"条件1"}]}
+ data: { list:[{value:"条件1"}]}
},
];
diff --git a/docs/xflow/demo/switchNode/index.tsx b/docs/xflow/demo/switchNode/index.tsx
index 0ade042e5..41f2ac56d 100644
--- a/docs/xflow/demo/switchNode/index.tsx
+++ b/docs/xflow/demo/switchNode/index.tsx
@@ -12,7 +12,7 @@ export default () => {
type: 'Switch',
id: '2',
position: { x: 171.25, y: 218.75 },
- data: { switchData: [{ value: '条件1' }] },
+ data: { list: [{ value: '条件1' }] },
},
{
type: 'End',
diff --git a/docs/xflow/nodeBuildIn.md b/docs/xflow/nodeBuildIn.md
index 127d9109b..301782b8c 100644
--- a/docs/xflow/nodeBuildIn.md
+++ b/docs/xflow/nodeBuildIn.md
@@ -9,7 +9,7 @@ group:
# 内置节点
## 条件内置节点
-内置条件节点,可以直接设置type为`Switch`使用,条件节点的数据格式为` switchData:[{value:"条件1"}]`
+内置条件节点,可以直接设置type为`Switch`使用,条件节点的数据格式为` list:[{value:"条件1"}]`
diff --git a/docs/xflow/schema/custom-settings.ts b/docs/xflow/schema/custom-settings.ts
index e6461213f..01028f88a 100644
--- a/docs/xflow/schema/custom-settings.ts
+++ b/docs/xflow/schema/custom-settings.ts
@@ -59,6 +59,7 @@ export default [
type: 'icon-model',
bgColor: '#6172F3',
},
+ hideDesc:true // 隐藏配置面板描述信息
},
{
title: 'Prompt',
diff --git a/docs/xflow/schema/settings.ts b/docs/xflow/schema/settings.ts
index 97e5a27ac..ef686db53 100644
--- a/docs/xflow/schema/settings.ts
+++ b/docs/xflow/schema/settings.ts
@@ -108,6 +108,16 @@ export default [
type: 'icon-model',
bgColor: '#6172F3',
},
+ settingSchema: {
+ type: "object",
+ properties: {
+ input: {
+ title: '变量一',
+ type: 'string',
+ widget: 'input',
+ },
+ }
+ }
},
{
title: 'Prompt',
diff --git a/packages/x-flow/package.json b/packages/x-flow/package.json
index 2c44e7349..71d5e2147 100644
--- a/packages/x-flow/package.json
+++ b/packages/x-flow/package.json
@@ -1,6 +1,6 @@
{
"name": "@xrenders/xflow",
- "version": "1.0.0-beta.4",
+ "version": "1.0.0-beta.5",
"description": "一款功能强大、易用灵活的流程编辑器框架,帮助你轻松构建复杂的工作流和流程产品",
"keywords": [
"xflow"
diff --git a/packages/x-flow/src/XFlow.tsx b/packages/x-flow/src/XFlow.tsx
index 9f8e5e590..8d15fb39c 100644
--- a/packages/x-flow/src/XFlow.tsx
+++ b/packages/x-flow/src/XFlow.tsx
@@ -18,17 +18,16 @@ import { useEventEmitterContextContext } from './models/event-emitter';
import CustomNodeComponent from './components/CustomNode';
import { useStore, useStoreApi } from './hooks/useStore';
-import { useTemporalStore } from './hooks/useTemporalStore';
import Operator from './operator';
import FlowProps from './types';
-import { transformNodes, uuid } from './utils';
+import { uuid } from './utils';
import autoLayoutNodes from './utils/autoLayoutNodes';
import { shallow } from 'zustand/shallow';
import NodeEditor from './components/NodeEditor';
-import { useFlow } from './hooks/useFlow';
import './index.less';
+import { useTemporalStore } from './hooks/useTemporalStore';
const CustomNode = memo(CustomNodeComponent);
const edgeTypes = { buttonedge: memo(CustomEdge) };
@@ -38,8 +37,7 @@ const edgeTypes = { buttonedge: memo(CustomEdge) };
* XFlow 入口
*
*/
-const XFlow: FC
= memo(props => {
- const { initialValues, settings } = props;
+const XFlow: FC = memo(() => {
const workflowContainerRef = useRef(null);
const store = useStoreApi();
const { zoomTo } = useReactFlow();
@@ -47,31 +45,34 @@ const XFlow: FC = memo(props => {
layout,
nodes,
edges,
+ setNodes,
+ setEdges,
panOnDrag,
onNodesChange,
onEdgesChange,
onConnect,
- setLayout,
setCandidateNode,
+ isAddingNode,
setMousePosition,
} = useStore(
- state => ({
- nodes: state.nodes,
- edges: state.edges,
- layout: state.layout,
- panOnDrag: state.panOnDrag,
- setLayout: state.setLayout,
- setMousePosition: state.setMousePosition,
- setCandidateNode: state.setCandidateNode,
- onNodesChange: state.onNodesChange,
- onEdgesChange: state.onEdgesChange,
- onConnect: state.onConnect,
+ s => ({
+ nodes: s.nodes,
+ edges: s.edges,
+ setNodes: s.setNodes,
+ setEdges: s.setEdges,
+ layout: s.layout,
+ panOnDrag: s.panOnDrag,
+ setMousePosition: s.setMousePosition,
+ isAddingNode: s.isAddingNode,
+ setCandidateNode: s.setCandidateNode,
+ onNodesChange: s.onNodesChange,
+ onEdgesChange: s.onEdgesChange,
+ onConnect: s.onConnect,
}),
shallow
);
- const { setNodes, setEdges } = useFlow();
+ const { record } = useTemporalStore();
const [activeNode, setActiveNode] = useState(null);
- const temporalStore = useTemporalStore();
useEffect(() => {
zoomTo(0.8);
setAutoFreeze(false);
@@ -80,14 +81,6 @@ const XFlow: FC = memo(props => {
};
}, []);
- useEffect(() => {
- setLayout(props.layout);
- // TODO: 默认关闭时间机器,可以向 zundo 贡献一个配置
- temporalStore.pause();
- setNodes(transformNodes(initialValues?.nodes));
- setEdges(initialValues?.edges);
- }, []);
-
useEventListener('keydown', e => {
if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey))
e.preventDefault();
@@ -110,6 +103,9 @@ const XFlow: FC = memo(props => {
elementY: e.clientY - containerClientRect.top,
});
}
+ }, {
+ target: workflowContainerRef.current,
+ enable: isAddingNode
});
const { eventEmitter } = useEventEmitterContextContext();
@@ -149,7 +145,6 @@ const XFlow: FC = memo(props => {
// y: 0,
// },
// };
- // // record(() => {
// addNodes(newNode);
// addEdges({
// id: uuid(),
@@ -160,7 +155,6 @@ const XFlow: FC = memo(props => {
// updateEdge(targetEdge?.id as string, {
// source: newNode.id,
// });
- // // });
// };
// edge 移入/移出效果
@@ -201,6 +195,7 @@ const XFlow: FC = memo(props => {
return (
= memo(props => {
};
}, [layout]);
- // const edgeTypes = { buttonedge: (edgeProps: any) => };
- // const { icon, description } =
- // settings.find(
- // item => item.type?.toLowerCase() === activeNode?.node?.toLowerCase()
- // ) || {};
-
const NodeEditorWrap = useMemo(() => {
return (
= memo(props => {
}}
onConnect={onConnect}
onNodesChange={changes => {
- onNodesChange(changes);
+ changes.forEach(change => {
+ if (change.type === 'remove' || change.type === 'add') {
+ record(() => {
+ onNodesChange(changes);
+ })
+ } else {
+ onNodesChange(changes);
+ }
+ })
}}
onEdgesChange={changes => {
onEdgesChange(changes);
@@ -261,9 +258,12 @@ const XFlow: FC = memo(props => {
onEdgeMouseLeave={(_, edge) => {
getUpdateEdgeConfig(edge, '#c9c9c9');
}}
+ onNodesDelete={() => {
+ // setActiveNode(null);
+ }}
>
-
+
{
const { zoom } = useViewport();
const reactflow = useReactFlow();
- const { candidateNode, mousePosition, setCandidateNode, addNodes } = useStore(
- (state: any) => ({
- nodes: state.nodes,
- edges: state.edges,
- addNodes: state.addNodes,
- candidateNode: state.candidateNode,
- mousePosition: state.mousePosition,
- setCandidateNode: state.setCandidateNode,
- onNodesChange: state.onNodesChange,
- onEdgesChange: state.onEdgesChange,
+ const { candidateNode, mousePosition, setIsAddingNode, setCandidateNode, addNodes } = useStore(
+ (s: any) => ({
+ nodes: s.nodes,
+ edges: s.edges,
+ addNodes: s.addNodes,
+ candidateNode: s.candidateNode,
+ setIsAddingNode: s.setIsAddingNode,
+ mousePosition: s.mousePosition,
+ setCandidateNode: s.setCandidateNode,
+ onNodesChange: s.onNodesChange,
+ onEdgesChange: s.onEdgesChange,
}),
shallow
);
@@ -41,8 +42,8 @@ const CandidateNode = () => {
},
position: { x, y },
};
- // @ts-ignore
addNodes(newNodes);
+ setIsAddingNode(false)
setCandidateNode(null);
});
diff --git a/packages/x-flow/src/components/CustomEdge/index.tsx b/packages/x-flow/src/components/CustomEdge/index.tsx
index 975595e52..713df1095 100644
--- a/packages/x-flow/src/components/CustomEdge/index.tsx
+++ b/packages/x-flow/src/components/CustomEdge/index.tsx
@@ -6,12 +6,13 @@ import {
useReactFlow,
} from '@xyflow/react';
import produce from 'immer';
-import React, { memo, useState } from 'react';
+import React, { memo, useContext, useState } from 'react';
import { shallow } from 'zustand/shallow';
import { useStore } from '../../hooks/useStore';
import { uuid } from '../../utils';
import NodeSelectPopover from '../NodesPopover';
import './index.less';
+import { ConfigContext } from '../../models/context';
export default memo((edge: any) => {
const { id, selected, sourceX, sourceY, targetX, targetY, source, target } =
@@ -26,6 +27,9 @@ export default memo((edge: any) => {
targetY,
});
+ const { hideLineInsertBtn } =
+ useContext(ConfigContext);
+
const {
nodes,
edges,
@@ -45,7 +49,7 @@ export default memo((edge: any) => {
onEdgesChange: state.onEdgesChange,
}),
shallow
- );
+ );
const handleAddNode = (data: any) => {
const { screenToFlowPosition } = reactflow;
@@ -122,11 +126,13 @@ export default memo((edge: any) => {
>
-
-
-
+ {
+ !hideLineInsertBtn &&
+
+
+ }
diff --git a/packages/x-flow/src/components/CustomNode/index.tsx b/packages/x-flow/src/components/CustomNode/index.tsx
index a712fc9c9..54b574772 100644
--- a/packages/x-flow/src/components/CustomNode/index.tsx
+++ b/packages/x-flow/src/components/CustomNode/index.tsx
@@ -15,7 +15,7 @@ export default memo((props: any) => {
widgets[`${capitalize(type)}Node`] || widgets['CommonNode'];
const [isHovered, setIsHovered] = useState(false);
const reactflow = useReactFlow();
- const { addNodes, addEdges, mousePosition,nodes,edges } = useStore(
+ const { addNodes, addEdges, mousePosition } = useStore(
(state: any) => ({
nodes: state.nodes,
edges: state.edges,
@@ -27,7 +27,6 @@ export default memo((props: any) => {
shallow
);
const isSwitchNode = type === 'Switch';
-
// 增加节点并进行联系
const handleAddNode = (data: any) => {
const { screenToFlowPosition } = reactflow;
@@ -58,7 +57,6 @@ export default memo((props: any) => {
targetPosition = Position.Top;
sourcePosition = Position.Bottom;
}
- console.log("1", JSON.stringify(nodes), '=====',JSON.stringify(edges))
return (