diff --git a/docs/xflow/FlowProvider.md b/docs/xflow/FlowProvider.md index 086b200ce..a64bb2581 100644 --- a/docs/xflow/FlowProvider.md +++ b/docs/xflow/FlowProvider.md @@ -1,12 +1,50 @@ --- order: 3 -title: '多实例画布' +title: '' mobile: false group: title: 最佳展示 order: 4 --- -# 基础交互 +## 基础交互 + +`` 组件是一个 Context Provider,使得在 `` 组件之外访问流的内部状态成为可能。我们提供的 `useFlow()`、`useNodes()` 和 `useEdges()` 钩子依赖于这个组件才能工作。 + +## useFlow + +`useFlow()` 钩子返回 XFlow 实例,包含了一些实用的内部方法。 + +- setNodes:设置节点 +- addNodes:添加一个或多个节点 +- setEdges:设置边 +- addEdges:添加一个或多个边 +- getNodes:获取节点数据 +- getEdges:获取边数据 +- toObject:将画布数据转换为对象返回 +- zoomIn:放大画布 +- zoomOut:缩小画布 +- zoomTo:缩放画布 +- getZoom:获取缩放比例 +- setViewport:设置视口 +- getViewport:获取视口 +- fitView:适应画布 +- setCenter:设置画布中心 +- fitBounds:适应边界 +- screenToFlowPosition:将屏幕坐标转换为画布坐标 +- flowToScreenPosition:将画布坐标转换为屏幕坐标 + +## useNodes + +`useFlow` 的 `getNodes` 是瞬时值。想要监听节点状态,请使用 `useNodes` 钩子来返回实时 nodes 状态。 + +## useEdges + +`useFlow` 的 `getEdges` 是瞬时值。想要监听节点状态,请使用 `useEdges` 钩子来返回实时 edges 状态。 + +## 注意 + +- 如果你正在使用路由器并且希望流程的状态在不同路由之间保持持久,那么将 `` 组件放置在路由器外部是至关重要的。 +- 如果在同一页面上有多个 ``,则需要为每个 `` 使用单独的 `` diff --git a/docs/xflow/api.md b/docs/xflow/api.md index 50c3f5fe6..33ca90cd9 100644 --- a/docs/xflow/api.md +++ b/docs/xflow/api.md @@ -7,15 +7,16 @@ title: API ## XFlow -| 属性 | 描述 | 类型 | 默认值 | -| ------------- | ------------------------------------ | ----------------------------------------------------------- | ------ | -| initialValues | 初始的节点和边数据 | `{nodes:any[],edges:any[]}` | - | - | -| layout | 节点布局的方向 | `LR \| TB` | LR | - | -| widgets | 自定义组件 | `Record` | - | - | -| settings | 节点配置,定义页面中可拖动的节点配置 | ( [TNodeGroup](#tnodegroup) \| [TNodeItem](#tnodeitem) )[ ] | | -| nodeSelector | 节点选择器配置,可控制节点的可搜索性 | `TNodeSelector` -| iconFontUrl | iconfont url,用于配置图标渲染 | `String` | | - | | +| 属性 | 描述 | 类型 | 默认值 | +| ----------------- | ------------------------------------------ | ----------------------------------------------------------- | ------ | +| initialValues | 初始的节点和边数据 | `{nodes:any[],edges:any[]}` | - | - | +| layout | 节点布局的方向 | `LR \| TB` | LR | - | +| widgets | 自定义组件 | `Record` | - | - | +| settings | 节点配置,定义页面中可拖动的节点配置 | ( [TNodeGroup](#tnodegroup) \| [TNodeItem](#tnodeitem) )[ ] | | +| nodeSelector | 节点选择器配置,可控制节点的可搜索性 | `TNodeSelector` | +| iconFontUrl | iconfont url,用于配置图标渲染 | `String` | | +| configPanelWidth | 统一设置配置面板宽度 | `number` | 400 | +| hideLineInsertBtn | 是否隐藏两个节点之间,连线上的增加节点按钮 | `boolean` | false | ### TNodeGroup @@ -31,17 +32,20 @@ title: API 单个节点配置 -| 属性 | 描述 | 类型 | 默认值 | -| ------------------ | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -| title | 节点名称 | `string` | | -| type | 节点类型 | `string` | | -| hidden | 是否在配置面板中显示节点 | `boolean` | false | -| targetHandleHidden | 是否隐藏左侧输入连接头 | `boolean` | false | -| sourceHandleHidden | 是否隐藏右侧输出连接头 | `boolean` | false | -| icon | 节点的图标配置 | `{type:string;bgColor:string}` | | -| settingSchema | 节点的业务配置信息,详见[form-render 文档](/form-render/api-schema)。同时设置`settingSchema`和`settingWidget`只生效`settingWidget` | SchemaBase | | -| settingWidget | 自定义节点的业务配置组件,在弹窗中展示。同时设置`settingSchema`和`settingWidget`只生效`settingWidget`。定义之后需要在`widgets`中引入自定义组件。 | `string` | | -| nodeWidget | 自定义节点的业务配置信息展示组件,在节点内部展示业务配置信息。定义之后需要在`widgets`中引入自定义组件。 | `string` | | +| 属性 | 描述 | 类型 | 默认值 | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| title | 节点名称 | `string` | | +| type | 节点类型 | `string` | | +| hidden | 是否在配置面板中显示节点 | `boolean` | false | +| hideDesc | 是否在配置面板中显示节点的描述信息 | `boolean` | false | +| targetHandleHidden | 是否隐藏左侧输入连接头 | `boolean` | false | +| sourceHandleHidden | 是否隐藏右侧输出连接头 | `boolean` | false | +| icon | 节点的图标配置 | `{type:string;bgColor:string}` | | +| iconSvg | 节点的图标配置Svg格式 | `SVGSVGElement` | | +| settingSchema | 节点的业务配置信息,详见[form-render 文档](/form-render/api-schema)。同时设置`settingSchema`和`settingWidget`只生效`settingWidget` | SchemaBase | | +| settingWidget | 自定义节点的业务配置组件,在弹窗中展示。同时设置`settingSchema`和`settingWidget`只生效`settingWidget`。定义之后需要在`widgets`中引入自定义组件。 | `string` | | +| nodeWidget | 自定义节点的业务配置信息展示组件,在节点内部展示业务配置信息。定义之后需要在`widgets`中引入自定义组件。 | `string` | | +| nodeConfigPanelWidth | 单独设置节点配置面板宽度 | `string` | 400 | ## TNodeSelector diff --git a/docs/xflow/demo/flow-provider/index.tsx b/docs/xflow/demo/flow-provider/index.tsx index 39287d968..126c7d086 100644 --- a/docs/xflow/demo/flow-provider/index.tsx +++ b/docs/xflow/demo/flow-provider/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import * as React from "react"; import XFlow, { FlowProvider, useNodes } from '@xrenders/xflow'; import { edges as initialEdges } from './edges'; @@ -7,18 +7,18 @@ import settings from './setting'; const App = () => { return ( - -
- -
- -
+ +
+ +
+ +
); }; @@ -30,9 +30,9 @@ function Sidebar() { return ( 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 (
{ fontSize: '12px', }} open={isShowTooltip} + getPopupContainer={() => document.getElementById('xflow-container') as HTMLElement} > diff --git a/packages/x-flow/src/components/FlowProvider/index.tsx b/packages/x-flow/src/components/FlowProvider/index.tsx index ed34fcf5c..69a630da0 100644 --- a/packages/x-flow/src/components/FlowProvider/index.tsx +++ b/packages/x-flow/src/components/FlowProvider/index.tsx @@ -1,12 +1,14 @@ +import { ReactFlowProvider } from '@xyflow/react'; +import React, { memo, ReactNode, useContext, useEffect, useState } from 'react'; + +import { useStore } from '../../hooks/useStore'; import StoreContext, { Provider } from '../../models/context'; import { createStore } from '../../models/store'; - -import type { ReactNode } from 'react'; -import React, { memo, useContext, useState } from 'react'; +import { transformNodes } from '../../utils'; export const FlowProvider = memo<{ - initialNodes: any[]; - initialEdges: any[]; + initialNodes?: any[]; + initialEdges?: any[]; children: ReactNode; }>(({ initialNodes: nodes = [], initialEdges: edges = [], children }) => { const [store] = useState(() => @@ -16,28 +18,53 @@ export const FlowProvider = memo<{ }) ); - return {children}; + return ( + + {children} + + ); }); +const InitialProvider = ({ nodes, edges, layout, children }) => { + const { setNodes, setEdges, setLayout } = useStore(s => ({ + setNodes: s.setNodes, + setEdges: s.setEdges, + setLayout: s.setLayout, + })); + useEffect(() => { + setNodes(transformNodes(nodes)); + setLayout(layout); + setEdges(edges); + }, []); + + // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051 + return <>{children}; +}; + export const FlowProviderWrapper = ({ children, nodes, edges, + layout, }: { children: React.ReactNode; nodes: any[]; edges: any[]; + layout?: 'LR' | 'TB'; }) => { const isWrapped = useContext(StoreContext); if (isWrapped) { - // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051 - return <>{children}; + return ( + + {children} + + ); } return ( - + {children} ); diff --git a/packages/x-flow/src/components/IconView/index.tsx b/packages/x-flow/src/components/IconView/index.tsx index 2ef5d36df..08d62cd8c 100644 --- a/packages/x-flow/src/components/IconView/index.tsx +++ b/packages/x-flow/src/components/IconView/index.tsx @@ -7,7 +7,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; */ const Icon = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/a/font_4069358_ihlwbi6uipk.js', + scriptUrl: '//at.alicdn.com/t/a/font_4069358_36flyyai0sm.js', }); export default Icon; diff --git a/packages/x-flow/src/components/NodeContainer/index.tsx b/packages/x-flow/src/components/NodeContainer/index.tsx index ec7ba8322..55dd2a8b9 100644 --- a/packages/x-flow/src/components/NodeContainer/index.tsx +++ b/packages/x-flow/src/components/NodeContainer/index.tsx @@ -1,15 +1,26 @@ -import { Typography } from 'antd'; +import { Popover, Tooltip, Typography } from 'antd'; import classNames from 'classnames'; import React, { memo, useMemo } from 'react'; import createIconFont from '../../utils/createIconFont'; +import { MenuTooltip } from '../NodesMenu'; import './index.less'; const { Text, Paragraph } = Typography; export default memo((props: any) => { - const { className, onClick, children, icon, title, desc, hideDesc, NodeWidget, iconFontUrl } = props; + const { + className, + onClick, + children, + icon, + title, + desc, + hideDesc, + NodeWidget, + iconFontUrl, + iconSvg + } = props; const IconBox = useMemo(() => createIconFont(iconFontUrl), [iconFontUrl]); - return (
{ onClick={onClick} >
- - - + } + placement='bottomLeft' + trigger='click' + getPopupContainer={() => document.getElementById('xflow-container') as HTMLElement} + > + document.getElementById('xflow-container') as HTMLElement} + > + + {iconSvg ? iconSvg : } + + + document.getElementById('xflow-container') as HTMLElement + }, + }} > {title}
-
{children}
- { - NodeWidget &&
- {NodeWidget} -
- } + +
{children}
+ {NodeWidget &&
{NodeWidget}
} {!hideDesc && !!desc && ( document.getElementById('xflow-container') as HTMLElement + }, }} className='node-desc' > diff --git a/packages/x-flow/src/components/NodesMenu/index.tsx b/packages/x-flow/src/components/NodesMenu/index.tsx index f090506a9..53ad88b99 100644 --- a/packages/x-flow/src/components/NodesMenu/index.tsx +++ b/packages/x-flow/src/components/NodesMenu/index.tsx @@ -36,13 +36,13 @@ const searchNodeList = (query: string, list: any[]) => { }; // 悬浮菜单项详细描述 -const MenuTooltip = ({ icon, title, description, iconFontUrl }: any) => { +export const MenuTooltip = ({ icon, title, description, iconFontUrl, iconSvg }: any) => { const IconBox = useMemo(() => createIconFont(iconFontUrl), [iconFontUrl]); return (
-
- +
+ {iconSvg ? iconSvg :}
{title} @@ -56,7 +56,8 @@ const MenuTooltip = ({ icon, title, description, iconFontUrl }: any) => { // 节点菜单项 const MenuItem = (props: any) => { - const { title, type, icon, onClick, iconFontUrl } = props; + + const { title, type, icon, onClick, iconFontUrl, iconSvg } = props; const IconBox = useMemo(() => createIconFont(iconFontUrl), [iconFontUrl]); return ( @@ -65,16 +66,18 @@ const MenuItem = (props: any) => { title={} placement='right' arrow={false} + getPopupContainer={() => document.getElementById('xflow-container') as HTMLElement} >
- - + {iconSvg ? iconSvg : + + />} {title}
@@ -107,7 +110,7 @@ const NodesMenu = (props: TNodeMenu, ref: Ref) => { } const handleSearch = (ev: any) => { - setState({ menuList: searchNodeList(ev.target.value, items)}) + setState({ menuList: searchNodeList(ev.target.value, items) }) }; return ( @@ -129,8 +132,8 @@ const NodesMenu = (props: TNodeMenu, ref: Ref) => { {filterHiddenMenu(item.items).map((data: any, index: number) => ( ))} diff --git a/packages/x-flow/src/components/NodesPopover/index.less b/packages/x-flow/src/components/NodesPopover/index.less new file mode 100644 index 000000000..17ec2d559 --- /dev/null +++ b/packages/x-flow/src/components/NodesPopover/index.less @@ -0,0 +1,5 @@ +.nodes-popover { + .ant-popover-inner-content { + padding: 0; + } +} \ No newline at end of file diff --git a/packages/x-flow/src/components/NodesPopover/index.tsx b/packages/x-flow/src/components/NodesPopover/index.tsx index 726ce81f3..c94f2e2eb 100644 --- a/packages/x-flow/src/components/NodesPopover/index.tsx +++ b/packages/x-flow/src/components/NodesPopover/index.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { useClickAway } from 'ahooks'; import { Popover } from 'antd'; import React, { @@ -8,17 +9,22 @@ import React, { useRef, useState, } from 'react'; +import { useStore } from '../../hooks/useStore'; import { ConfigContext } from '../../models/context'; import NodesMenu from '../NodesMenu'; +import { getAntdVersion } from '../../utils'; +import './index.less'; export default forwardRef((props: any, popoverRef) => { const { addNode, children, onNodeSelectPopoverChange } = props; - + const { setIsAddingNode } = useStore(s => ({ + setIsAddingNode: s.setIsAddingNode, + })); const ref = useRef(null); const closeRef: any = useRef(null); const [open, setOpen] = useState(false); - const { settings, nodeSelector } = useContext(ConfigContext); + const { settings, nodeSelector }: any = useContext(ConfigContext); const { showSearch, popoverProps = { placement: 'top' } } = nodeSelector || {}; @@ -42,16 +48,32 @@ export default forwardRef((props: any, popoverRef) => { onNodeSelectPopoverChange && onNodeSelectPopoverChange(false); }, []); + const popoverVersionProps = useMemo(() => { + const version = getAntdVersion(); + if (version === 'V5') { + return { + open, + }; + } + // V4 + return { + visible: open, + }; + }, [open]); + return ( document.getElementById('xflow-container')} zIndex={2000} arrow={false} overlayInnerStyle={{ padding: '12px 6px' }} {...popoverProps} - trigger="click" - open={open} + trigger='click' + {...popoverVersionProps} onOpenChange={() => { setTimeout(() => { + setIsAddingNode(true); closeRef.current = true; setOpen(true); }, 50); diff --git a/packages/x-flow/src/components/PanelContainer/index.less b/packages/x-flow/src/components/PanelContainer/index.less index 7679e2fa9..cef206332 100644 --- a/packages/x-flow/src/components/PanelContainer/index.less +++ b/packages/x-flow/src/components/PanelContainer/index.less @@ -26,10 +26,18 @@ display: flex; align-items: center; justify-content: space-between; - .ant-input-outlined { + .ant-input { border-color: #fff; font-weight: 600; } + + .ant-input:focus { + border-color: #40a9ff; + } + + .ant-input-outlined:focus { + border-color: #3b82f6; + } } .icon-box { @@ -56,7 +64,7 @@ border-color: #fff; } textarea { - margin: 12px 0; + margin-top: 12px; } } @@ -122,4 +130,4 @@ font-size: 12px; } } -} \ No newline at end of file +} diff --git a/packages/x-flow/src/components/PanelContainer/index.tsx b/packages/x-flow/src/components/PanelContainer/index.tsx index 6c39484ce..3c848950d 100644 --- a/packages/x-flow/src/components/PanelContainer/index.tsx +++ b/packages/x-flow/src/components/PanelContainer/index.tsx @@ -1,11 +1,12 @@ +import React, { FC, useContext, useEffect, useMemo, useState } from 'react'; import { Divider, Drawer, Input, Space } from 'antd'; import produce from 'immer'; import { debounce } from 'lodash'; -import React, { FC, useContext, useEffect, useState, useMemo } from 'react'; import { shallow } from 'zustand/shallow'; import { useStore } from '../../hooks/useStore'; import { ConfigContext } from '../../models/context'; import createIconFont from '../../utils/createIconFont'; +import { getAntdVersion } from '../../utils'; import IconView from '../IconView'; import './index.less'; @@ -25,7 +26,7 @@ const Panel: FC = (props: IPanelProps) => { const { onClose, children, nodeType, disabled, node, description, id, data } = props; // 1.获取节点配置信息 - const { settingMap, iconFontUrl } = useContext(ConfigContext); + const { settingMap, iconFontUrl, configPanelWidth }: any = useContext(ConfigContext); const nodeSetting = settingMap[nodeType] || {}; const { nodes, setNodes } = useStore( (state: any) => ({ @@ -38,9 +39,9 @@ const Panel: FC = (props: IPanelProps) => { const isDisabled = ['Input', 'Output'].includes(nodeType) || disabled; const [descVal, setDescVal] = useState(data?.desc); const [titleVal, setTitleVal] = useState(data?.title || nodeSetting?.title); + const { hideDesc, nodeConfigPanelWidth, iconSvg } = nodeSetting; // const description = getDescription(nodeType, props.description); - const handleNodeValueChange = debounce((data: any) => { const newNodes = produce(nodes, draft => { let node = null; @@ -66,33 +67,52 @@ const Panel: FC = (props: IPanelProps) => { const Icon = useMemo(() => createIconFont(iconFontUrl), [iconFontUrl]); + const drawerVersionProps = useMemo(() => { + const version = getAntdVersion(); + if (version === 'V5') { + return { + rootClassName: 'custom-node-panel', + open: true, + }; + } + // V4 + return { + className: 'custom-node-panel', + visible: true, + }; + }, []); + return ( 全局的width> 默认 400 mask={false} onClose={onClose} + headerStyle={{ paddingBottom: '12px' }} title={ <>
- + {iconSvg ? ( + iconSvg + ) : ( + + )} {isDisabled ? ( {nodeSetting?.title} ) : ( { setTitleVal(e.target.value); handleNodeValueChange({ title: e.target.value }); @@ -104,7 +124,7 @@ const Panel: FC = (props: IPanelProps) => { {!isDisabled && ( <> - + )} @@ -117,23 +137,23 @@ const Panel: FC = (props: IPanelProps) => {
-
- {isDisabled ? ( - description - ) : ( - { - setDescVal(e.target.value); - handleNodeValueChange({ desc: e.target.value }); - }} - /> - )} -
+ {!hideDesc && ( +
+ {isDisabled ? ( + description + ) : ( + { + setDescVal(e.target.value); + handleNodeValueChange({ desc: e.target.value }); + }} + /> + )} +
+ )} } > diff --git a/packages/x-flow/src/hooks/useFlow.ts b/packages/x-flow/src/hooks/useFlow.ts index baf2c8b49..eac2cb450 100644 --- a/packages/x-flow/src/hooks/useFlow.ts +++ b/packages/x-flow/src/hooks/useFlow.ts @@ -1,40 +1,65 @@ -import { useMemo } from 'react'; +import { Edge, useReactFlow } from '@xyflow/react'; import { useMemoizedFn } from 'ahooks'; -import { useStoreApi } from './useStore'; -import { useTemporalStore } from './useTemporalStore'; +import { useMemo } from 'react'; import { FlowNode } from '../models/store'; -import { Edge } from '@xyflow/react'; +import { useStoreApi } from './useStore'; +// useFlow 维护原则 +// 1. 尽量复用 reactflow 已有的方法,不要重复造轮子 +// 2. 非必要不暴露新的方法和状态 export const useFlow = () => { const storeApi = useStoreApi(); const instance = storeApi.getState(); - const temporalStore = useTemporalStore(); - - const getNodes = useMemoizedFn(() => storeApi.getState().nodes); - const getEdges = useMemoizedFn(() => storeApi.getState().edges); + const { + zoomIn, + zoomOut, + zoomTo, + getZoom, + setViewport, + getViewport, + fitView, + setCenter, + fitBounds, + toObject, + getNodes, + getEdges, + screenToFlowPosition, + flowToScreenPosition + } = useReactFlow(); const setNodes = useMemoizedFn((nodes: FlowNode[]) => { - temporalStore.record(() => { - storeApi.getState().setNodes(nodes); - }) - }) + storeApi.getState().setNodes(nodes); + }); const addNodes = useMemoizedFn((nodes: FlowNode[]) => { - temporalStore.record(() => { - storeApi.getState().addNodes(nodes); - }) - }) + storeApi.getState().addNodes(nodes); + }); const setEdges = useMemoizedFn((edges: Edge[]) => { storeApi.getState().setEdges(edges); - }) + }); const addEdges = useMemoizedFn((edges: Edge[]) => { storeApi.getState().addEdges(edges); - }) + }); - return useMemo(() => ({ - setNodes, - addNodes, - setEdges, - addEdges, - getNodes, - getEdges, - }), [instance]); + return useMemo( + () => ({ + setNodes, + addNodes, + setEdges, + addEdges, + getNodes, + getEdges, + toObject, + zoomIn, + zoomOut, + zoomTo, + getZoom, + setViewport, + getViewport, + fitView, + setCenter, + fitBounds, + screenToFlowPosition, + flowToScreenPosition + }), + [instance] + ); }; diff --git a/packages/x-flow/src/hooks/useTemporalStore.ts b/packages/x-flow/src/hooks/useTemporalStore.ts index baa26653e..a0a19e3ed 100644 --- a/packages/x-flow/src/hooks/useTemporalStore.ts +++ b/packages/x-flow/src/hooks/useTemporalStore.ts @@ -9,11 +9,13 @@ export const useTemporalStore = () => { '[XFlow]: Seems like you have not used zustand provider as an ancestor.' ); } + const temporalStore = store.temporal.getState(); + // 默认关闭时间机器 + temporalStore.pause(); return { ...store.temporal.getState(), record: (callback: () => void) => { - const temporalStore = store.temporal.getState(); temporalStore.resume(); callback(); temporalStore.pause(); diff --git a/packages/x-flow/src/index.ts b/packages/x-flow/src/index.ts index 85dc9d99a..85e4f4892 100644 --- a/packages/x-flow/src/index.ts +++ b/packages/x-flow/src/index.ts @@ -2,6 +2,7 @@ import XFlow from './XFlow'; import withProvider from './withProvider'; import * as nodes from './nodes'; +import FlowProps from './types'; export type { default as FR, @@ -12,4 +13,4 @@ export { useFlow } from './hooks/useFlow'; export { useNodes } from './hooks/useNodes'; export { useEdges } from './hooks/useEdges'; -export default withProvider(XFlow, nodes); +export default withProvider(XFlow, nodes); diff --git a/packages/x-flow/src/models/store.ts b/packages/x-flow/src/models/store.ts index 2f4833308..9e730a839 100644 --- a/packages/x-flow/src/models/store.ts +++ b/packages/x-flow/src/models/store.ts @@ -28,6 +28,7 @@ export type FlowState = { nodes?: FlowNode[]; edges?: Edge[]; panOnDrag?: boolean; + isAddingNode?: boolean; candidateNode: any; mousePosition: any; onNodesChange: OnNodesChange; @@ -38,6 +39,7 @@ export type FlowState = { addNodes: (nodes: FlowNode[]) => void; addEdges: (edges: Edge[]) => void; setLayout: (layout: 'LR' | 'TB') => void; + setIsAddingNode: (payload: boolean) => void; setCandidateNode: (candidateNode: any) => void; setMousePosition: (mousePosition: any) => void; }; @@ -55,6 +57,7 @@ const createStore = (initProps?: Partial) => { (set, get) => ({ ...DEFAULT_PROPS, ...initProps, + isAddingNode: false, candidateNode: null, // nodeMenus: [], mousePosition: { pageX: 0, pageY: 0, elementX: 0, elementY: 0 }, @@ -95,6 +98,9 @@ const createStore = (initProps?: Partial) => { // setNodeMenus: (nodeMenus: any) => { // set({ nodeMenus }); // }, + setIsAddingNode: payload => { + set({ isAddingNode: payload }); + }, setCandidateNode: candidateNode => { set({ candidateNode }); }, diff --git a/packages/x-flow/src/nodes/node-common/index.tsx b/packages/x-flow/src/nodes/node-common/index.tsx index 917e0cdcb..c4c76fa17 100644 --- a/packages/x-flow/src/nodes/node-common/index.tsx +++ b/packages/x-flow/src/nodes/node-common/index.tsx @@ -7,6 +7,7 @@ export default memo((props: any) => { const { settingMap, widgets, iconFontUrl } = useContext(ConfigContext); const nodeSetting = settingMap[type] || {}; const NodeWidget = widgets[nodeSetting?.nodeWidget] || undefined; + const nodeDescription = nodeSetting?.description || ''; return ( { desc={data?.desc} NodeWidget={NodeWidget ? : undefined} iconFontUrl={iconFontUrl} + description={nodeDescription} // 不允许用户更改的节点描述 + iconSvg={nodeSetting?.iconSvg} /> ); }); diff --git a/packages/x-flow/src/nodes/node-end/index.tsx b/packages/x-flow/src/nodes/node-end/index.tsx index a22708088..299c8d3d0 100644 --- a/packages/x-flow/src/nodes/node-end/index.tsx +++ b/packages/x-flow/src/nodes/node-end/index.tsx @@ -7,6 +7,7 @@ export default memo((props: any) => { const { settingMap,widgets, iconFontUrl } = useContext(ConfigContext); const nodeSetting = settingMap[type] || {}; const NodeWidget = widgets[nodeSetting?.nodeWidget] || undefined; + const nodeDescription = nodeSetting?.description || ''; return ( { desc={data?.desc} NodeWidget={NodeWidget ? : undefined} iconFontUrl={iconFontUrl} + description={nodeDescription} // 不允许用户更改的节点描述 + iconSvg={nodeSetting?.iconSvg} /> ); }); diff --git a/packages/x-flow/src/nodes/node-start/index.tsx b/packages/x-flow/src/nodes/node-start/index.tsx index 180808eca..673a7c0fb 100644 --- a/packages/x-flow/src/nodes/node-start/index.tsx +++ b/packages/x-flow/src/nodes/node-start/index.tsx @@ -7,6 +7,8 @@ export default memo((props: any) => { const { settingMap, widgets, iconFontUrl } = useContext(ConfigContext); const nodeSetting = settingMap[type] || {}; const NodeWidget = widgets[nodeSetting?.nodeWidget] || undefined; + const nodeDescription = nodeSetting?.description || ''; + return ( { desc={data?.desc} NodeWidget={NodeWidget ? : undefined} iconFontUrl={iconFontUrl} + description={nodeDescription} // 不允许用户更改的节点描述 + iconSvg={nodeSetting?.iconSvg} /> ); }); diff --git a/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx b/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx index 339934868..5e1043e68 100644 --- a/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx +++ b/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx @@ -1,8 +1,10 @@ -import { Flex } from 'antd'; +import { Space, Typography } from 'antd'; import React, { memo } from 'react'; import SourceHandle from '../../components/CustomNode/sourceHandle'; import './index.less'; +const { Paragraph } = Typography; + export default memo((props: any) => { const { data, @@ -15,8 +17,8 @@ export default memo((props: any) => { } = props; return ( - - {(data?.switchData || [{}])?.map((item, index) => ( + + {(data?.list || [{}])?.map((item, index) => (
{index === 0 ? 'IF' : 'ELIF'}
@@ -32,11 +34,27 @@ export default memo((props: any) => {
{CustomNodeWidget ? ( - + ) : ( <> {item?.value && ( -
{item?.value}
+ document.getElementById('xflow-container') + }, + }} + > + {item?.value} + )} )} @@ -57,6 +75,6 @@ export default memo((props: any) => { />
-
+ ); }); diff --git a/packages/x-flow/src/nodes/node-switch/index.less b/packages/x-flow/src/nodes/node-switch/index.less index 4bc4064ad..d240da6cc 100644 --- a/packages/x-flow/src/nodes/node-switch/index.less +++ b/packages/x-flow/src/nodes/node-switch/index.less @@ -27,6 +27,7 @@ } .node-switch-widget { + width: 100%; .item-header { position: relative; diff --git a/packages/x-flow/src/nodes/node-switch/index.tsx b/packages/x-flow/src/nodes/node-switch/index.tsx index ca6643820..023d4eed4 100644 --- a/packages/x-flow/src/nodes/node-switch/index.tsx +++ b/packages/x-flow/src/nodes/node-switch/index.tsx @@ -17,6 +17,7 @@ export default memo((props: any) => { const { settingMap, widgets, iconFontUrl } = useContext(ConfigContext); const nodeSetting = settingMap[type] || {}; const NodeWidget = widgets[nodeSetting?.nodeWidget] || undefined; + const nodeDescription = nodeSetting?.description || ''; return ( { CustomNodeWidget={NodeWidget} /> } + description={nodeDescription} // 不允许用户更改的节点描述 + iconSvg={nodeSetting?.iconSvg} /> ); }); diff --git a/packages/x-flow/src/nodes/node-switch/setting/index.tsx b/packages/x-flow/src/nodes/node-switch/setting/index.tsx index 46ee2b7a6..ab68f73a1 100644 --- a/packages/x-flow/src/nodes/node-switch/setting/index.tsx +++ b/packages/x-flow/src/nodes/node-switch/setting/index.tsx @@ -12,7 +12,7 @@ const schema: Schema = { span: 24, displayType: 'row', properties: { - switchData: { + list: { type: 'array', widget: 'simpleList', display: 'block', diff --git a/packages/x-flow/src/operator/Control/index.less b/packages/x-flow/src/operator/Control/index.less index 48986c4ea..65a1fdcc8 100644 --- a/packages/x-flow/src/operator/Control/index.less +++ b/packages/x-flow/src/operator/Control/index.less @@ -8,6 +8,12 @@ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); color: #667085; margin-left: 10px; + + .ant-btn { + display: inline-flex; + align-items: center; + justify-content: center; + } } .control-item { diff --git a/packages/x-flow/src/operator/Control/index.tsx b/packages/x-flow/src/operator/Control/index.tsx index bd39552aa..09294272f 100644 --- a/packages/x-flow/src/operator/Control/index.tsx +++ b/packages/x-flow/src/operator/Control/index.tsx @@ -7,9 +7,11 @@ import { useStore, useStoreApi } from '../../hooks/useStore'; import { useEventEmitterContextContext } from '../../models/event-emitter'; import './index.less'; +import { useFullscreen } from 'ahooks'; const Control = (props: any) => { - const { addNode } = props; + const { addNode, xflowRef } = props; + const [isFullscreen, {toggleFullscreen }] = useFullscreen(xflowRef); const addNote = (e: MouseEvent) => { e.stopPropagation(); @@ -24,59 +26,76 @@ const Control = (props: any) => { }; return ( -
+
- + document.getElementById('xflow-container') as HTMLElement}>
+ style={{ backgroundColor: !panOnDrag ? 'rgb(239,244,255)' : '' }} + /> - -
document.getElementById('xflow-container') as HTMLElement}> +
+ style={{ + backgroundColor: panOnDrag ? 'rgb(239,244,255)' : '', + marginLeft: '1px', + }} + />
-
- +
+ document.getElementById('xflow-container') as HTMLElement}>
); }; diff --git a/packages/x-flow/src/operator/UndoRedo/index.less b/packages/x-flow/src/operator/UndoRedo/index.less index 5994dc756..9e19b5dff 100644 --- a/packages/x-flow/src/operator/UndoRedo/index.less +++ b/packages/x-flow/src/operator/UndoRedo/index.less @@ -8,4 +8,10 @@ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); color: #667085; margin-left: 10px; + + .ant-btn { + display: inline-flex; + align-items: center; + justify-content: center; + } } \ No newline at end of file diff --git a/packages/x-flow/src/operator/UndoRedo/index.tsx b/packages/x-flow/src/operator/UndoRedo/index.tsx index 392cf27ac..0aed34c68 100644 --- a/packages/x-flow/src/operator/UndoRedo/index.tsx +++ b/packages/x-flow/src/operator/UndoRedo/index.tsx @@ -13,18 +13,18 @@ export type UndoRedoProps = { export default memo(({ handleUndo, handleRedo, pastStates, futureStates }: UndoRedoProps) => { return (
- + document.getElementById('xflow-container') as HTMLElement}>
- + document.getElementById('xflow-container') as HTMLElement}>