diff --git a/docs/useDocs/autoLayout.md b/docs/useDocs/autoLayout.md new file mode 100644 index 0000000..543ae3e --- /dev/null +++ b/docs/useDocs/autoLayout.md @@ -0,0 +1,143 @@ +--- +nav: 使用文档 +group: + title: 进阶使用 + order: 2 +title: 自动布局 +description: +--- + +## 自动布局 + +当你使用 FlowView 组件传入 nodes 和 edges 列表后,FlowView 会自动梳理节点之间的逻辑关系,并给出一个自动布局结果渲染在画布上,比如下面这段关系渲染后的结果如下。 + +```js +const nodes = [ + { + id: 'a1', + data: { + title: '节点1', + }, + }, + { + id: 'a2', + data: { + title: '节点2', + }, + }, + { + id: 'a3', + data: { + title: '节点3', + }, + }, + { + id: 'a4', + data: { + title: '节点4', + }, + }, + { + id: 'a5', + data: { + title: '节点5', + }, + }, + { + id: 'a6', + data: { + title: '节点6', + }, + }, +]; + +const edges = [ + { + source: 'a1', + target: 'a2', + }, + { + source: 'a1', + target: 'a3', + }, + { + source: 'a2', + target: 'a3', + }, + { + source: 'a3', + target: 'a6', + }, + { + source: 'a2', + target: 'a4', + }, + { + source: 'a3', + target: 'a5', + }, + { + source: 'a2', + target: 'a6', + }, +]; + +function App() { + return ( + + + + ); +} + +export default App; +``` + +布局效果如下所示: + + +## 自定义布局 + +如果希望自己设置坐标,可以给节点添加 position 属性来控制。 + +```js +const nodes = [ + { + id: 'a1', + position: { + x: 100, + y: 300, + }, + data: { + title: '节点1', + }, + }, + { + id: 'a2', + position: { + x: 300, + y: 600, + }, + data: { + title: '节点2', + }, + }, + ... +]; +``` + + + +## 关闭自动布局 + +你也可以在 FlowView 中直接关闭自动布局功能 + +```js + +``` + +:::info +当你关掉自动布局能力,并且没有为节点单独设置坐标,那么 FlowView 会将节点的坐标初始化为 1,1。效果上就是所有的节点重合在一起。 +::: + + diff --git a/docs/useDocs/baseInrro.md b/docs/useDocs/baseInrro.md index 7d29631..7db97f6 100644 --- a/docs/useDocs/baseInrro.md +++ b/docs/useDocs/baseInrro.md @@ -7,3 +7,42 @@ title: 根本性概念 order: 1 description: --- + +## 术语和定义 + +### Nodes + +ProFlow 中的节点是一个 React 组件。这意味着它可以渲染您喜欢的任何内容。每个节点都有一个 x 和 y 坐标,这告诉它它在视口中的位置。在不设置 type 的情况下,FlowView 组件默认会把组件设置为[Lineage](/components/lineage-node)类型的节点。你也可以自定义节点类型。 + + + +### Custom Nodes + +[自定义节点使用说明](/components/customNodeDoc) + + +### Handles + +`Handle` 可以翻译为 “**句柄**” 或者 “**端口**”,是边缘连接到节点的位置。`Handle`可以放置在任何地方。 + +可以用以下方式式引入 `Handle` 与 `Position`,来自定义 `Handle` 在节点中的位置。 + +```ts +import { Handle, type Position } from '@ant-design/pro-flow'; +``` + + + +### Edges + +一条 Edge 连接两个节点。每个边缘都需要一个`source` 和 一个`target`。 ProFlow 内置了五种边缘类型: + +- 'straight' +- 'step' + -'smoothstep' + -'bezier' +- 'radius' 。 + +FlowView 把 `smoothstep` 设置为默认类型。 你也可以自定义边缘类型。 + + diff --git a/docs/useDocs/customNodeDoc.md b/docs/useDocs/customNodeDoc.md new file mode 100644 index 0000000..12b45ec --- /dev/null +++ b/docs/useDocs/customNodeDoc.md @@ -0,0 +1,161 @@ +--- +nav: 使用文档 +group: + title: 进阶使用 + order: 2 +title: 自定义节点 +description: +--- + +## 自定义节点 + +ProFlow 的一个强大功能是能够添加自定义节点。在自定义节点中,您可以渲染所需的所有内容。例如,您可以定义多个源句柄和目标句柄,并呈现表单输入或图表。在本节中,我们将实现一个带有输入字段的节点,该输入字段更新应用程序另一部分中的一些文本。 + +## 实现自定义节点 + +让我们开始实现:声明一个自定义节点`TextUpdaterNode`组件,它的结构由 3 个 Handle 组件和一块内容区组成。其中一个 Handle 的位置在顶部,两个在底部。 + +```js +import { useCallback } from 'react'; +import { Handle, Position } from '@ant-design/pro-flow'; + +const handleStyle = { left: 10 }; + +function TextUpdaterNode({ data }) { + const onChange = useCallback((evt) => { + console.log(evt.target.value); + }, []); + + return ( + <> + +
+ + +
+ + + + ); +} +``` + +## 添加节点类型 + +你可以将新的节点类型添加到 prop 中传递给 FlowView。 + +:::warning +**在组件外部定义或缓存`nodeTypes`非常重要。** 否则 React 会在每次渲染时创建一个新的对象,这会导致性能问题和错误。 +::: + +```js +const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }) ,[]); + +return +``` + +定义新节点类型后,可以在 nodes 列表中使用这个类型了。 + +```js +const nodes = [ + { + id: 'n1', + type: 'textUpdater', + data: { value: 123 }, + }, +]; +``` + +然后你就可以得到如下的自定义节点效果。 + + +## 使用多个 Handle + +当一个节点拥有多个 Handle 时,在连接时只传递节点 Id 是不够的,还需要传递特定的 HandleId。 + +```js +const initialEdge = [ + { id: 'edge-1', source: 'n1', sourceHandle: 'a', target: 'n2' }, + { id: 'edge-2', source: 'n1', sourceHandle: 'b', target: 'n3' }, +]; +``` + +效果如下: + + +## 添加节点交互 + +在自定义节点的 data 中,FlowView 还会透传 2 个属性 selectType 与 zoom。你可以通过这两个属性来给节点配置一些交互。 + +```js +import { + FlowView, + FlowViewProvider, + Handle, + Position, + SelectType, + useFlowViewer, +} from '@ant-design/pro-flow'; + +const edges = [ + { id: 'edge-1', source: 'n1', target: 'n2', sourceHandle: 'a' }, + { id: 'edge-2', source: 'n1', target: 'n3', sourceHandle: 'b' }, +]; + +const CustomNode: FC<{ + data: { + title: string, + selectType: SelectType, + }, +}> = ({ data }) => { + console.log(data); + const onChange = useCallback((evt) => { + console.log(evt.target.value); + }, []); + + return ( + + +
+ + +
+ + +
+ ); +}; + +const nodeTypes = { customNode: CustomNode }; + +function App() { + const flowViewer = useFlowViewer(); + + return ( + + { + flowViewer?.selectNode(n.id, SelectType.SELECT); + }} + onPaneClick={() => { + flowViewer?.selectNodes(['n1', 'n2', 'n3'], SelectType.DEFAULT); + }} + nodes={nodes} + edges={edges} + nodeTypes={nodeTypes} + miniMap={false} + /> + + ); +} +function ProApp() { + return ( + + + + ); +} +``` + +效果如下: + diff --git a/docs/useDocs/demos/CoreEdge.tsx b/docs/useDocs/demos/CoreEdge.tsx new file mode 100644 index 0000000..6ca5afe --- /dev/null +++ b/docs/useDocs/demos/CoreEdge.tsx @@ -0,0 +1,108 @@ +import styled from 'styled-components'; +import { FlowView } from '../../../src/index'; + +const Container = styled.div` + width: 800px; + height: 300px; +`; + +const nodes = [ + { + id: 'a1', + data: { + title: '节点1', + }, + }, + { + id: 'a2', + data: { + title: '节点2', + }, + }, + { + id: 'a3', + data: { + title: '节点3', + }, + }, + { + id: 'a4', + data: { + title: '节点4', + }, + }, + { + id: 'a5', + data: { + title: '节点5', + }, + }, + { + id: 'a6', + data: { + title: '节点6', + }, + }, +]; + +const edges = [ + { + source: 'a1', + target: 'a2', + type: 'straight', + label: 'straight', + }, + { + source: 'a1', + target: 'a3', + type: 'step', + label: 'step', + }, + { + source: 'a2', + target: 'a3', + type: 'straight', + label: 'straight', + }, + { + source: 'a3', + target: 'a6', + type: 'bezier', + label: 'bezier', + }, + { + source: 'a2', + target: 'a4', + type: 'smmothstep', + label: 'smmothstep', + }, + { + source: 'a3', + target: 'a5', + type: 'bezier', + label: 'bezier', + }, + { + source: 'a2', + target: 'a6', + type: 'radius', + label: 'radius', + }, +]; + +function App() { + return ( + + { + console.log(node); + }} + nodes={nodes} + edges={edges} + miniMap={false} + /> + + ); +} + +export default App; diff --git a/docs/useDocs/demos/CoreHandle.tsx b/docs/useDocs/demos/CoreHandle.tsx new file mode 100644 index 0000000..b4da5d5 --- /dev/null +++ b/docs/useDocs/demos/CoreHandle.tsx @@ -0,0 +1,71 @@ +import { FC } from 'react'; +import styled from 'styled-components'; +import { FlowView, Handle, Position } from '../../../src/index'; + +const Wrap = styled.div` + width: 200px; + height: 83px; + background-color: white; + display: flex; + align-items: center; + justify-content: center; + position: relative; +`; + +const Container = styled.div` + width: 800px; + height: 300px; +`; + +const CustomNode: FC<{ + data: { + title: string; + }; +}> = (props) => { + const { data } = props; + + return ( + + + + +
+ +
+ + + +
+ ); +}; + +const nodes = [ + { + id: 'b1', + type: 'customNode', + data: { + title: '一堆 Handle', + }, + }, +]; + +const nodeTypes = { customNode: CustomNode }; + +function App() { + return ( + + { + console.log(node); + }} + nodes={nodes} + edges={[]} + nodeTypes={nodeTypes} + miniMap={false} + autoLayout={false} + /> + + ); +} + +export default App; diff --git a/docs/useDocs/demos/CoreNode.tsx b/docs/useDocs/demos/CoreNode.tsx new file mode 100644 index 0000000..0e95a9c --- /dev/null +++ b/docs/useDocs/demos/CoreNode.tsx @@ -0,0 +1,31 @@ +/** + * compact: true + */ +import { FlowView } from '@ant-design/pro-flow'; +import styled from 'styled-components'; + +const nodes = [ + { + id: 'a1', + data: { + title: 'XXX_API_b3', + logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', + description: 'XXX_XXX_XXX_API', + }, + }, +]; + +function App() { + return ( + + + + ); +} + +export default App; + +const Container = styled.div` + width: 100%; + height: 300px; +`; diff --git a/docs/useDocs/demos/CustomerNode.tsx b/docs/useDocs/demos/CustomerNode.tsx new file mode 100644 index 0000000..e4fade6 --- /dev/null +++ b/docs/useDocs/demos/CustomerNode.tsx @@ -0,0 +1,61 @@ +import { FC, useCallback } from 'react'; +import styled from 'styled-components'; +import { FlowView, Handle, Position } from '../../../src/index'; + +const Wrap = styled.div` + width: 200px; + height: 83px; + background-color: white; + display: flex; + align-items: center; + justify-content: center; +`; + +const Container = styled.div` + width: 800px; + height: 300px; +`; + +const CustomNode: FC<{ + data: { + title: string; + }; +}> = ({ data }) => { + const onChange = useCallback((evt) => { + console.log(evt.target.value); + }, []); + + return ( + + +
+ + +
+ + +
+ ); +}; + +const nodes = [ + { + id: 'b1', + type: 'customNode', + data: { + title: 'Text', + }, + }, +]; + +const nodeTypes = { customNode: CustomNode }; + +function App() { + return ( + + + + ); +} + +export default App; diff --git a/docs/useDocs/demos/autoLayoutDemo1.tsx b/docs/useDocs/demos/autoLayoutDemo1.tsx new file mode 100644 index 0000000..96e22ac --- /dev/null +++ b/docs/useDocs/demos/autoLayoutDemo1.tsx @@ -0,0 +1,87 @@ +import styled from 'styled-components'; +import { FlowView } from '../../../src/index'; + +const Container = styled.div` + width: 800px; + height: 300px; +`; + +const nodes = [ + { + id: 'a1', + data: { + title: '节点1', + }, + }, + { + id: 'a2', + data: { + title: '节点2', + }, + }, + { + id: 'a3', + data: { + title: '节点3', + }, + }, + { + id: 'a4', + data: { + title: '节点4', + }, + }, + { + id: 'a5', + data: { + title: '节点5', + }, + }, + { + id: 'a6', + data: { + title: '节点6', + }, + }, +]; + +const edges = [ + { + source: 'a1', + target: 'a2', + }, + { + source: 'a1', + target: 'a3', + }, + { + source: 'a2', + target: 'a3', + }, + { + source: 'a3', + target: 'a6', + }, + { + source: 'a2', + target: 'a4', + }, + { + source: 'a3', + target: 'a5', + }, + { + source: 'a2', + target: 'a6', + }, +]; + +function App() { + return ( + + + + ); +} + +export default App; diff --git a/docs/useDocs/demos/autoLayoutDemo2.tsx b/docs/useDocs/demos/autoLayoutDemo2.tsx new file mode 100644 index 0000000..6e612c6 --- /dev/null +++ b/docs/useDocs/demos/autoLayoutDemo2.tsx @@ -0,0 +1,95 @@ +import styled from 'styled-components'; +import { FlowView } from '../../../src/index'; + +const Container = styled.div` + width: 800px; + height: 300px; +`; + +const nodes = [ + { + id: 'a1', + position: { + x: 100, + y: 300, + }, + data: { + title: '节点1', + }, + }, + { + id: 'a2', + position: { + x: 300, + y: 600, + }, + data: { + title: '节点2', + }, + }, + { + id: 'a3', + data: { + title: '节点3', + }, + }, + { + id: 'a4', + data: { + title: '节点4', + }, + }, + { + id: 'a5', + data: { + title: '节点5', + }, + }, + { + id: 'a6', + data: { + title: '节点6', + }, + }, +]; + +const edges = [ + { + source: 'a1', + target: 'a2', + }, + { + source: 'a1', + target: 'a3', + }, + { + source: 'a2', + target: 'a3', + }, + { + source: 'a3', + target: 'a6', + }, + { + source: 'a2', + target: 'a4', + }, + { + source: 'a3', + target: 'a5', + }, + { + source: 'a2', + target: 'a6', + }, +]; + +function App() { + return ( + + + + ); +} + +export default App; diff --git a/docs/useDocs/demos/autoLayoutDemo3.tsx b/docs/useDocs/demos/autoLayoutDemo3.tsx new file mode 100644 index 0000000..0d14be1 --- /dev/null +++ b/docs/useDocs/demos/autoLayoutDemo3.tsx @@ -0,0 +1,87 @@ +import styled from 'styled-components'; +import { FlowView } from '../../../src/index'; + +const Container = styled.div` + width: 800px; + height: 300px; +`; + +const nodes = [ + { + id: 'a1', + data: { + title: '节点1', + }, + }, + { + id: 'a2', + data: { + title: '节点2', + }, + }, + { + id: 'a3', + data: { + title: '节点3', + }, + }, + { + id: 'a4', + data: { + title: '节点4', + }, + }, + { + id: 'a5', + data: { + title: '节点5', + }, + }, + { + id: 'a6', + data: { + title: '节点6', + }, + }, +]; + +const edges = [ + { + source: 'a1', + target: 'a2', + }, + { + source: 'a1', + target: 'a3', + }, + { + source: 'a2', + target: 'a3', + }, + { + source: 'a3', + target: 'a6', + }, + { + source: 'a2', + target: 'a4', + }, + { + source: 'a3', + target: 'a5', + }, + { + source: 'a2', + target: 'a6', + }, +]; + +function App() { + return ( + + + + ); +} + +export default App; diff --git a/docs/useDocs/demos/index.less b/docs/useDocs/demos/index.less new file mode 100644 index 0000000..05ed87f --- /dev/null +++ b/docs/useDocs/demos/index.less @@ -0,0 +1,205 @@ +.pipeNodeWrap { + width: 260px; + min-height: 100px; + background-color: #f6f8fa; + padding: 16px; + box-sizing: border-box; + border-radius: 8px; + + .handle { + top: 0; + } + + .stepTitle { + overflow: hidden; + color: #8c8c8c; + white-space: nowrap; + text-overflow: ellipsis; + } + .pipeNode { + margin-top: 10px; + width: 232px; + box-sizing: border-box; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + + .mainBox { + width: 100%; + padding: 12px; + height: 70px; + background-color: white; + display: flex; + border-bottom: none; + border-radius: 8px; + box-sizing: border-box; + .logo { + img { + width: 16px; + height: 16px; + margin-top: 4px; + } + } + .wrap { + margin-left: 8px; + display: flex; + flex-direction: column; + .title { + color: #000; + font-weight: 500; + font-size: 14px; + line-height: 22px; + white-space: nowrap; + } + .des { + margin-top: 8px; + color: #00000073; + font-size: 12px; + } + } + } + } + + .children { + display: flex; + flex-direction: column; + align-items: center; + padding-bottom: 10px; + + .childrenBox { + width: 200px; + padding: 12px; + height: 70px; + background-color: white; + display: flex; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + box-sizing: border-box; + margin-top: 10px; + + .logo { + img { + width: 16px; + height: 16px; + margin-top: 4px; + } + } + .wrap { + margin-left: 8px; + display: flex; + flex-direction: column; + .title { + color: #000; + font-weight: 500; + font-size: 14px; + line-height: 22px; + white-space: nowrap; + } + .des { + margin-top: 8px; + color: #00000073; + font-size: 12px; + } + } + } + } +} +.container { + width: 100%; + height: 600px; +} + +.techUIpipeNodeWrap { + width: 260px; + min-height: 100px; + background-color: #f6f8fa; + padding: 12px 6px; + box-sizing: border-box; + border-radius: 8px; + + .pipeNode { + width: 100%; + border-radius: 4px; + background-color: white; + padding-bottom: 12px; + + .mainBox { + width: 100%; + padding: 12px; + height: 45px; + background-color: white; + display: flex; + border-bottom: none; + border-radius: 8px; + box-sizing: border-box; + position: relative; + .logo { + img { + width: 16px; + height: 16px; + margin-top: 4px; + } + } + .title { + margin-left: 8px; + color: #000; + font-weight: 500; + font-size: 14px; + line-height: 22px; + white-space: nowrap; + } + .subLogo { + position: absolute; + right: 6px; + top: 8px; + img { + width: 80px; + height: 25px; + } + } + } + + .children { + padding-bottom: 8px; + display: flex; + flex-direction: column; + align-items: center; + + .childrenBox { + width: 220px; + height: 30px; + margin-top: 3px; + border: 1px solid #f1f1f1; + box-sizing: border-box; + padding-left: 5px; + display: flex; + align-items: center; + border-radius: 3px; + position: relative; + + .logo { + display: flex; + align-items: center; + img { + width: 16px; + height: 16px; + } + } + .title { + margin-left: 8px; + color: #000; + font-weight: 500; + font-size: 14px; + line-height: 22px; + white-space: nowrap; + } + } + } + } + + .des { + margin-left: 16px; + + color: #00000073; + font-size: 10px; + } +} diff --git a/docs/useDocs/demos/multiHandle.tsx b/docs/useDocs/demos/multiHandle.tsx new file mode 100644 index 0000000..8202929 --- /dev/null +++ b/docs/useDocs/demos/multiHandle.tsx @@ -0,0 +1,119 @@ +import { FC, useCallback } from 'react'; +import styled from 'styled-components'; +import { + FlowView, + FlowViewProvider, + Handle, + Position, + SelectType, + useFlowViewer, +} from '../../../src/index'; + +const Wrap = styled.div` + width: 200px; + height: 83px; + background-color: white; + display: flex; + align-items: center; + justify-content: center; + + &.default { + border: none; + } + + &.select { + border: 1px solid red; + } +`; + +const Container = styled.div` + width: 800px; + height: 300px; +`; + +const CustomNode: FC<{ + data: { + title: string; + selectType: SelectType; + }; +}> = ({ data }) => { + console.log(data); + const onChange = useCallback((evt) => { + console.log(evt.target.value); + }, []); + + return ( + + +
+ + +
+ + +
+ ); +}; + +const nodes = [ + { + id: 'n1', + type: 'customNode', + data: { + title: 'Text', + }, + }, + { + id: 'n2', + data: { + title: 'n2 node', + }, + }, + { + id: 'n3', + data: { + title: 'n3 node', + }, + }, +]; + +const edges = [ + { id: 'edge-1', source: 'n1', target: 'n2', sourceHandle: 'a' }, + { id: 'edge-2', source: 'n1', target: 'n3', sourceHandle: 'b' }, +]; + +const nodeTypes = { customNode: CustomNode }; + +function App() { + const flowViewer = useFlowViewer(); + + console.log(flowViewer); + + return ( + + { + console.log(n); + flowViewer?.selectNode(n.id, SelectType.SELECT); + }} + onPaneClick={() => { + flowViewer?.selectNodes(['n1', 'n2', 'n3'], SelectType.DEFAULT); + }} + nodes={nodes} + edges={edges} + nodeTypes={nodeTypes} + miniMap={false} + /> + + ); +} + +function ProApp() { + return ( + + + + ); +} + +export default ProApp; diff --git a/docs/useDocs/demos/pipelineDemo.tsx b/docs/useDocs/demos/pipelineDemo.tsx new file mode 100644 index 0000000..592c157 --- /dev/null +++ b/docs/useDocs/demos/pipelineDemo.tsx @@ -0,0 +1,318 @@ +/** + * compact: true + */ +import { + Background, + FlowView, + FlowViewProvider, + Handle, + Position, + useFlowViewer, +} from '@ant-design/pro-flow'; +import { FC, useCallback } from 'react'; +import { SelectType } from '../../../src'; +import './index.less'; + +interface PipeNodeChild { + title: string; + des: string; + logo: string; +} + +interface PipeNode { + stepTitle: string; + title: string; + des: string; + logo: string; + needSwitch?: boolean; + open?: boolean; + children?: PipeNodeChild[]; + selectType?: SelectType; +} + +const nodeWidth = 170; +const nodeHeight = 500; + +const PipeNode: FC<{ + data: PipeNode; +}> = ({ data }) => { + const { + stepTitle, + title, + des, + logo, + needSwitch = false, + open = false, + children = [], + selectType, + } = data; + + return ( +
+ +
{stepTitle}
+
+
+
+ +
+
+
{title}
+
{des}
+
+ {needSwitch && ( +
+
+
+
+
+ )} +
+ {children.length > 0 && ( +
+ {children.map((item, index) => ( +
+
+ +
+
+
{item.title}
+
{item.des}
+
+
+ ))} +
+ )} +
+ +
+ ); +}; + +const nodeTypes = { pipeNode: PipeNode }; + +const nodes = [ + { + id: 'a1', + type: 'pipeNode', + width: nodeWidth, + height: nodeHeight, + data: { + stepTitle: '阶段 1: 部署平台 tnpmregistry@...', + title: 'tnpmregistry@DEFAULT ...', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '34秒', + needSwitch: true, + open: true, + children: [ + { + title: '参数初始化', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '1秒', + }, + { + title: 'NPM 组件初始化', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '30秒', + }, + { + title: '同步成员(仅子组件生...', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '0秒', + }, + { + title: '注册部署平台', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '0秒', + }, + ], + }, + }, + { + id: 'a2', + type: 'pipeNode', + width: nodeWidth, + height: nodeHeight, + data: { + stepTitle: '阶段 2: 部署平台 hitu@DEFAULT ...', + title: 'hitu@DEFAULT 初始化', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '2秒', + needSwitch: true, + open: true, + children: [ + { + title: '初始化海图组件', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '1秒', + }, + { + title: '注册部署平台', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '0秒', + }, + ], + }, + }, + { + id: 'a3', + type: 'pipeNode', + width: nodeWidth, + height: nodeHeight, + data: { + stepTitle: '阶段 3: 三方关联初始化', + title: '监控初始化', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '1秒', + }, + }, + { + id: 'a4', + type: 'pipeNode', + width: nodeWidth, + height: nodeHeight, + data: { + stepTitle: '阶段 4: 准备组件仓库初始化', + title: '重命名 name', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '1秒', + }, + }, + { + id: 'a5', + type: 'pipeNode', + width: nodeWidth, + height: nodeHeight, + data: { + stepTitle: '阶段 5: 代码初始化', + title: '仓库初始化', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*7gzVQJ63mfEAAAAAAAAAAAAADvl6AQ/original', + des: '6秒', + needSwitch: true, + open: true, + children: [ + { + title: '创建仓库', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '3秒', + }, + { + title: '接触分支保护', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*sko9RoPu-HgAAAAAAAAAAAAADvl6AQ/original', + des: '1秒', + }, + { + title: '初始化代码模板', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*7gzVQJ63mfEAAAAAAAAAAAAADvl6AQ/original', + des: '1秒', + }, + { + title: '创建代码基线', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*Em_PQoTrMDgAAAAAAAAAAAAADvl6AQ/original', + des: '未开始', + }, + ], + }, + }, + { + id: 'a6', + type: 'pipeNode', + width: nodeWidth, + height: nodeHeight, + data: { + stepTitle: '阶段 6: 仓库权限同步', + title: '同步至埋点平台', + logo: 'https://mdn.alipayobjects.com/huamei_d2ejos/afts/img/A*Em_PQoTrMDgAAAAAAAAAAAAADvl6AQ/original', + des: '未开始', + }, + }, +]; + +const edges = [ + { + id: 'e1', + source: 'a1', + target: 'a2', + animated: true, + }, + { + id: 'e2', + source: 'a2', + target: 'a3', + animated: true, + }, + { + id: 'e3', + source: 'a3', + target: 'a4', + animated: true, + }, + { + id: 'e4', + source: 'a4', + target: 'a5', + animated: true, + }, + { + id: 'e5', + source: 'a5', + target: 'a6', + animated: true, + }, +]; + +function App() { + const flowViewer = useFlowViewer(); + + const handleClick = useCallback( + (e, n) => { + flowViewer?.zoomToNode(n.id, 1000); + }, + [flowViewer], + ); + + const handlePaneClick = useCallback(() => { + // flowViewer?.zoomToNode(n.id, 1000); + }, [flowViewer]); + + return ( +
+ + + +
+ ); +} + +function ProApp() { + return ( + + + + ); +} + +export default ProApp; diff --git a/docs/useDocs/demos/selectFlow.tsx b/docs/useDocs/demos/selectFlow.tsx index a359b43..6aded35 100644 --- a/docs/useDocs/demos/selectFlow.tsx +++ b/docs/useDocs/demos/selectFlow.tsx @@ -48,19 +48,19 @@ const initEdges = [ ]; function App() { - const { updateSelectNode, updateSelectEdges, updateSelectNodes } = useFlowViewer(); + const { selectNode, selectEdges, selectNodes } = useFlowViewer(); return ( { - updateSelectNodes!(['a1', 'a2', 'a3'], SelectType.SUB_SELECT); - updateSelectNode!(node.id, SelectType.SELECT); - updateSelectEdges!(['a1-a2', 'a1-a3'], SelectType.SUB_SELECT); + selectNodes(['a1', 'a2', 'a3'], SelectType.SUB_SELECT); + selectNode(node.id, SelectType.SELECT); + selectEdges(['a1-a2', 'a1-a3'], SelectType.SUB_SELECT); }} onPaneClick={() => { - updateSelectNodes!(['a1', 'a2', 'a3'], SelectType.DEFAULT); - updateSelectEdges!(['a1-a2', 'a1-a3'], SelectType.DEFAULT); + selectNodes(['a1', 'a2', 'a3'], SelectType.DEFAULT); + selectEdges(['a1-a2', 'a1-a3'], SelectType.DEFAULT); }} nodes={initNodes} edges={initEdges} diff --git a/docs/useDocs/demos/useFlowViewer.tsx b/docs/useDocs/demos/useFlowViewer.tsx new file mode 100644 index 0000000..8257375 --- /dev/null +++ b/docs/useDocs/demos/useFlowViewer.tsx @@ -0,0 +1,113 @@ +import { Button } from 'antd'; +import styled from 'styled-components'; +import { + EdgeType, + FlowPanel, + FlowView, + FlowViewProvider, + SelectType, + useFlowViewer, +} from '../../../src/index'; + +const initNodes = [ + { + id: 'a1', + data: { + title: 'a1 节点', + logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', + describe: 'XXX_XXX_XXX_API', + }, + }, + { + id: 'a2', + data: { + title: 'XXX_API_b4', + logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', + describe: 'XXX_XXX_XXX_API', + }, + }, + { + id: 'a3', + data: { + title: 'XXX_API_b4', + logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', + describe: 'XXX_XXX_XXX_API', + }, + }, +]; +const initEdges = [ + { + id: 'a1-a2', + source: 'a1', + target: 'a2', + }, + { + id: 'a1-a3', + source: 'a1', + target: 'a3', + type: EdgeType.radius, + }, +]; + +function App() { + const { selectNode, selectEdges, selectNodes, zoomToNode } = useFlowViewer(); + + return ( + + { + selectNodes(['a1', 'a2', 'a3'], SelectType.SUB_SELECT); + selectNode(node.id, SelectType.SELECT); + selectEdges(['a1-a2', 'a1-a3'], SelectType.SUB_SELECT); + }} + onPaneClick={() => { + selectNodes(['a1', 'a2', 'a3'], SelectType.DEFAULT); + selectEdges(['a1-a2', 'a1-a3'], SelectType.DEFAULT); + }} + nodes={initNodes} + edges={initEdges} + > + +
+ + + +
+
+
+
+ ); +} + +function ProApp() { + return ( + + + + ); +} + +export default ProApp; + +const Container = styled.div` + width: 800px; + height: 500px; +`; diff --git a/docs/useDocs/intro.md b/docs/useDocs/intro.md index 3071289..dc31311 100644 --- a/docs/useDocs/intro.md +++ b/docs/useDocs/intro.md @@ -3,7 +3,7 @@ nav: 使用文档 group: title: 基础介绍 order: 1 -title: 为什么选择 ProFlow ? +title: ProFlow 特性 description: --- diff --git a/docs/useDocs/layoud.md b/docs/useDocs/layoud.md deleted file mode 100644 index 6f829bd..0000000 --- a/docs/useDocs/layoud.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -nav: 使用文档 -group: - title: 进阶使用 - order: 3 -title: 布局 -description: 自动布局 、 手动布局 ---- - -正在加急编写中... diff --git a/docs/useDocs/pipleLineDemo.md b/docs/useDocs/pipleLineDemo.md new file mode 100644 index 0000000..2e03487 --- /dev/null +++ b/docs/useDocs/pipleLineDemo.md @@ -0,0 +1,10 @@ +--- +nav: 使用文档 +group: + title: 案例展示 + order: 10 +title: 雨燕 PipeLine +description: +--- + + diff --git a/docs/useFlowEditor.md b/docs/useDocs/useFlowEditor.md similarity index 97% rename from docs/useFlowEditor.md rename to docs/useDocs/useFlowEditor.md index 00bbabf..bcff950 100644 --- a/docs/useFlowEditor.md +++ b/docs/useDocs/useFlowEditor.md @@ -1,4 +1,11 @@ -# useFlowEditor +--- +nav: 使用文档 +group: + title: 进阶使用 + order: 3 +title: useFlowEditor +description: +--- ```ts import { useFlowEditor } from '@ant-design/pro-flow'; diff --git a/docs/useDocs/useFlowViewer.md b/docs/useDocs/useFlowViewer.md new file mode 100644 index 0000000..ac313e3 --- /dev/null +++ b/docs/useDocs/useFlowViewer.md @@ -0,0 +1,211 @@ +--- +nav: 使用文档 +group: + title: 进阶使用 + order: 2 +title: useFlowViewer +description: +--- + +## 使用方式 + +使用`FlowViewProvider`来包裹你的组件,即可使用`FlowView`提供的 hook 能力 + +```js +import { FlowViewProvider, useFlowViewer } from '@ant-design/pro-flow'; + +function App() { + const viewer = useFlowViewer(); + + return ( + + + + ); +} + +function ProApp() { + return ( + + + + ); +} +``` + +## 功能展示 + + + +## 节点选中 + +提供了一组公共节点选中方法,包括: + +1. selectNode: 选中节点; +2. selectNodes: 批量选中节点; +3. selectEdge: 选中边缘; +4. selectEdges: 批量选中边缘; + +这些方法可以用于在前端为一个或多个节点(边缘)标记选中状态。 + +### selectNode + +```js +selectNode: (nodeId: string, selectType: SelectType) => void; +``` + +#### 参数 + +- `nodeId` - 节点 Id + - 类型: `string` +- `selectType` - 选中样式 + - 类型: [SelectType](/components/flow-view#selecttype) + +#### 返回值 + +- 类型: `void` + +### selectNodes + +```js +selectNodes: (nodeIds: string[], selectType: SelectType) => void; +``` + +#### 参数 + +- `nodeIds` - 节点 Id 的数组 + - 类型: `string[]` +- `selectType` - 选中样式 + + - 类型: [SelectType](/components/flow-view#selecttype) + +### selectEdge + +```js +selectEdge: (edgeId: string, selectType: SelectType) => void; +``` + +#### 参数 + +- `edgeId` - 边 Id + - 类型: `string` +- `selectType` - 选中样式 + + - 类型: [SelectType](/components/flow-view#selecttype) + +### selectEdges + +```js +selectEdges: (edgeIds: string[], selectType: SelectType) => void; +``` + +#### 参数 + +- `edgeIds` - 边 Id 的数组 + - 类型: `string[]` +- `selectType` - 选中样式 + - 类型: [SelectType](/components/flow-view#selecttype) + +#### 返回值 + +- 类型: `void` + +## 画布聚焦 + +1. zoomTo: 画布缩放; +2. zoomToNode: 快速聚焦到某个节点; +3. getViewport: 获取当前窗口坐标及缩放等级; +4. setViewport: 设置窗口坐标及缩放等级; + +### selectNode + +```js +zoomTo: (zoomNumber: number, duration?: number) => void; +``` + +#### 参数 + +- `zoomNumber` - zoom 等级 + - 类型: `number` +- `duration` - 持续时间 + - 类型: `number` + - 可选参数 + +#### 返回值 + +- 类型: `void` + +### selectNode + +```js +zoomToNode: (nodeId: string, duration?: number) => void; +``` + +#### 参数 + +- `nodeId` - 节点 Id + - 类型: `string` +- `duration` - 持续时间 + - 类型: `number` + - 可选参数 + +#### 返回值 + +- 类型: `void` + +### getViewport + +```js +getViewport: () => Viewport; +``` + +#### 参数 + +- 无 + +#### 返回值 + +- 类型: `Viewport` + +```js +type Viewport = { + x: number, + y: number, + zoom: number, +}; +``` + +### setViewport + +```js +setViewport: (viewport: Viewport, duration?: number) => void; +``` + +#### 参数 + +- `viewport` - 视窗 + - 类型: `Viewport` +- `duration` - 持续时间 + - 类型: `number` + - 可选参数 + +#### 返回值 + +- 类型: `void` + +## 小地图 + +1. setMiniMapPosition: 设置 MiniMap 在窗口的坐标位置 + +### setMiniMapPosition + +```js +setMiniMapPosition: (x: number, y: number) => void; +``` + +#### 参数 + +- `x` - x 轴坐标 + - 类型: `number` +- `y` - y 轴坐标 + - 类型: `number` diff --git a/docs/useDocs/zoom.md b/docs/useDocs/zoom.md deleted file mode 100644 index 06c3822..0000000 --- a/docs/useDocs/zoom.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -nav: 使用文档 -group: - title: 进阶使用 - order: 3 -title: 缩放 -description: ---- - -正在加急编写中... diff --git a/src/Background/demos/index.tsx b/src/Background/demos/index.tsx index 32f2cde..acbfd77 100644 --- a/src/Background/demos/index.tsx +++ b/src/Background/demos/index.tsx @@ -1,8 +1,7 @@ -import { FlowView } from '@/index'; +import { Background, BackgroundVariant, FlowPanel, FlowView } from '@ant-design/pro-flow'; +import { Button } from 'antd'; import { createStyles } from 'antd-style'; import { memo, useState } from 'react'; -import { Panel } from 'reactflow'; -import Background, { BackgroundVariant } from '..'; const useStyles = createStyles(({ css }) => ({ container: css` @@ -18,18 +17,12 @@ const BackgroundDemo = memo(() => { return (
- +
variant:
- - - -
+ + + +
diff --git a/src/FlowEditor/demos/index.tsx b/src/FlowEditor/demos/index.tsx index 06fb080..7c049ed 100644 --- a/src/FlowEditor/demos/index.tsx +++ b/src/FlowEditor/demos/index.tsx @@ -5,7 +5,7 @@ import { Handle, Position } from 'reactflow'; import styled from 'styled-components'; const StringNode = styled.div` - width: 120px; + width: 150px; height: 30px; text-align: center; background-color: white; diff --git a/src/FlowEditor/index.md b/src/FlowEditor/index.md index f4a6845..922cf92 100644 --- a/src/FlowEditor/index.md +++ b/src/FlowEditor/index.md @@ -7,3 +7,7 @@ description: ## default + +## API + +FlowEditor 为数据驱动解决方案,需搭配 [useFlowEditor](/use-docs/use-flow-editor) 使用。 diff --git a/src/FlowPanel/demos/index.tsx b/src/FlowPanel/demos/index.tsx index e626fb9..f5e5254 100644 --- a/src/FlowPanel/demos/index.tsx +++ b/src/FlowPanel/demos/index.tsx @@ -1,7 +1,6 @@ -import { FlowView } from '@/index'; +import { FlowPanel, FlowView } from '@ant-design/pro-flow'; import { createStyles } from 'antd-style'; import { memo } from 'react'; -import FlowPanel from '..'; const useStyles = createStyles(({ css }) => ({ container: css` diff --git a/src/FlowView/components/DefaultNode.tsx b/src/FlowView/components/DefaultNode.tsx index 1dce259..a9d9293 100644 --- a/src/FlowView/components/DefaultNode.tsx +++ b/src/FlowView/components/DefaultNode.tsx @@ -2,9 +2,11 @@ import { DefaultNodeData } from '@/constants'; import { FC } from 'react'; import { useStyles } from '../styles'; -const DefaultNode: FC = (props) => { +const DefaultNode: FC<{ + data: DefaultNodeData; +}> = ({ data }) => { const { styles, cx } = useStyles(); - const { className, children } = props; + const { className, children } = data; return
{children}
; }; diff --git a/src/FlowView/constants.tsx b/src/FlowView/constants.tsx index c100eb4..6534c1b 100644 --- a/src/FlowView/constants.tsx +++ b/src/FlowView/constants.tsx @@ -29,7 +29,7 @@ export interface NodeMapItem = ({ data }) => { + const onChange = useCallback((evt: { target: { value: any } }) => { + console.log(evt.target.value); + }, []); + + return ( + + +
+ + +
+ + +
+ ); +}; + +export default CustomNode; diff --git a/src/FlowView/demos/ProFlowDemo.tsx b/src/FlowView/demos/ProFlowDemo.tsx index d4393c5..62c22c2 100644 --- a/src/FlowView/demos/ProFlowDemo.tsx +++ b/src/FlowView/demos/ProFlowDemo.tsx @@ -1,10 +1,16 @@ -import { EdgeType, FlowViewEdge, FlowViewNode, SelectType } from '@/index'; -import { FlowView } from '@ant-design/pro-flow'; +import { + EdgeType, + FlowViewEdge, + FlowViewNode, + FlowViewProvider, + SelectType, +} from '@ant-design/pro-flow'; import { Progress } from 'antd'; import { createStyles } from 'antd-style'; import React, { useState } from 'react'; import styled from 'styled-components'; -import { FlowViewProvider } from '../provider/FlowViewProvider'; +import { FlowView } from '../../index'; +import CustomNode from './CustomerNode'; const useStyles = createStyles(({ css }) => ({ container: css` @@ -53,17 +59,19 @@ const nodes: FlowViewNode[] = [ { id: 'a1', label: '123', - type: 'default', data: { - children:
default node, 123123
, + title: 'XXX_API_ddddddddddddddddddddddddddddddddddddddddddddddddddddddb1', + logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', + description: 'XXX_XXX_XXX_API', }, }, { id: 'b1', + label: 'label', data: { title: 'XXX_API_ddddddddddddddddddddddddddddddddddddddddddddddddddddddb1', logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', - describe: 'XXX_XXX_XXX_API', + description: 'XXX_XXX_XXX_API', titleSlot: { type: 'left', value: ( @@ -84,7 +92,7 @@ const nodes: FlowViewNode[] = [ data: { title: 'XXX_APIddddddddddddddddddddddddddddddddddddddddddddddddddd_b2', logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', - describe: 'XXX_XXX_XXX_API', + description: 'XXX_XXX_XXX_API', titleSlot: { type: 'right', value: , @@ -94,9 +102,9 @@ const nodes: FlowViewNode[] = [ { id: 'b3', data: { - title: 'XXX_API_b3', + title: 'XXX_APIddddddddddddddddddddddddddddddddddddddddddddddddddd_b2', logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', - describe: 'XXX_XXX_XXX_API', + description: 'XXX_XXX_XXX_API', }, }, { @@ -104,7 +112,7 @@ const nodes: FlowViewNode[] = [ data: { title: 'XXX_API_b4', logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original', - describe: 'XXX_XXX_XXX_API', + description: 'XXX_XXX_XXX_API', }, }, { @@ -112,7 +120,7 @@ const nodes: FlowViewNode[] = [ data: { title: 'XXXX产品', logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*ezaYT4wYRBwAAAAAAAAAAAAADvuvAQ/original', - describe: '2031030213014', + description: '2031030213014', }, }, { @@ -185,25 +193,21 @@ const edges: FlowViewEdge[] = [ id: 'a1-b1', source: 'a1', target: 'b1', - type: EdgeType.radius, }, { id: 'a1-b2', source: 'a1', target: 'b2', - type: EdgeType.radius, }, { id: 'a1-b3', source: 'a1', target: 'b3', - type: EdgeType.radius, }, { id: 'a1-b4', source: 'a1', target: 'b4', - type: EdgeType.radius, }, { @@ -244,6 +248,7 @@ const ProFlowDemo = () => { const { styles } = useStyles(); const handleHighlight = (node: FlowViewNode) => { + console.log(node); _nodes.forEach((_node) => { if (_node.id === node.id) { _node.select = SelectType.SELECT; @@ -263,8 +268,18 @@ const ProFlowDemo = () => { }; const handleUnHighlight = () => { - setNodes(nodes); - setEdges(edges); + setNodes( + _nodes.map((_node) => { + _node.select = SelectType.DEFAULT; + return _node; + }), + ); + setEdges( + edges.map((edge) => { + edge.select = SelectType.DEFAULT; + return edge; + }), + ); }; return ( @@ -274,6 +289,7 @@ const ProFlowDemo = () => { onPaneClick={handleUnHighlight} nodes={_nodes} edges={_edges} + nodeTypes={{ textCustomNode: CustomNode }} >
); diff --git a/src/FlowView/helper.tsx b/src/FlowView/helper.tsx index aa85f12..9892f45 100644 --- a/src/FlowView/helper.tsx +++ b/src/FlowView/helper.tsx @@ -1,18 +1,7 @@ -import LineageNodeGroup from '@/LineageGroupNode'; -import LineageNode from '@/LineageNode'; -import { - DefaultNodeData, - EdgeType, - FlowViewEdge, - FlowViewNode, - LineageGroupNodeData, - LineageNodeData, - NodeHandler, -} from '@/constants'; +import { FlowViewEdge, FlowViewNode } from '@/constants'; import Dagre from '@dagrejs/dagre'; import { cx } from 'antd-style'; import { Edge, Node, Position } from 'reactflow'; -import DefaultNode from './components/DefaultNode'; import { EDGE_DANGER, EDGE_SELECT, @@ -27,35 +16,28 @@ import { SelectType, } from './constants'; -// 这里的type是指节点的连接点在哪里,input是在左边,output是在右边,default是左右两边都有 -function getTypeFromEdge(node: NodeMapItem) { - if (node.left?.length && node.right?.length) { - return 'default'; - } - if (node.left?.length) { - return 'output'; - } - if (node.right?.length) { - return 'input'; - } - return 'default'; -} - export function convertMappingFrom(nodes: FlowViewNode[], edges: FlowViewEdge[], zoom: number) { const mapping: NodeMapping = {}; + nodes.forEach((node) => { - const { type = 'lineage', position = { x: NaN, y: NaN } } = node; + const { + width, + height, + select = SelectType.DEFAULT, + type = 'lineage', + position = { x: NaN, y: NaN }, + } = node; mapping[node.id] = { id: node.id, - // width: width ? width : node.group ? 355 : 322, - // height: height ? height : node.group ? 1100 : 85, data: node.data, - select: node.select, + select, flowNodeType: type, right: [], left: [], position, + width, + height, zoom, label: node.label, }; @@ -70,7 +52,23 @@ export function convertMappingFrom(nodes: FlowViewNode[], edges: FlowViewEdge[], return mapping; } -export function setNodePosition(nodes: Node[], edges: Edge[]) { +export function setNodePosition(nodes: Node[], edges: Edge[], autoLayout: boolean) { + if (!autoLayout) { + return { + _nodes: nodes.map((node) => { + const { x: _x, y: _y } = node.position; + return { + ...node, + position: { + x: isNaN(_x) ? 1 : _x, + y: isNaN(_y) ? 1 : _y, + }, + }; + }) as unknown as Node[], + _edges: edges, + }; + } + const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); g.setGraph({ @@ -86,9 +84,7 @@ export function setNodePosition(nodes: Node[], edges: Edge[]) { return { _nodes: nodes.map((node) => { const { x, y } = g.node(node.id); - console.log(x, y); const { x: _x, y: _y } = node.position; - console.log(_x, _y); return { ...node, position: { @@ -101,7 +97,7 @@ export function setNodePosition(nodes: Node[], edges: Edge[]) { }; } -function sortEdges(edges: Edge[]) { +export function sortEdges(edges: Edge[]) { const highEdges: Edge[] = edges.filter((item) => { return item.className?.includes('edgeSelected') || item.className?.includes('edgeSubSelected'); }); @@ -159,13 +155,26 @@ function getEdgeClsFromSelectType(select: SelectType) { export function getRenderEdges(edges: FlowViewEdge[]) { return edges.map((edge) => { - const { source, target, select = SelectType.DEFAULT, type } = edge; + const { + source, + target, + select = SelectType.DEFAULT, + type = 'smoothstep', + label, + animated, + sourceHandle, + targetHandle, + } = edge; return { id: `${source}-${target}`, source, target, - type: type === EdgeType.default ? 'smoothstep' : 'radiusEdge', + sourceHandle, + targetHandle, + type, + animated, + label, className: getEdgeClsFromSelectType(select), }; }); @@ -177,69 +186,126 @@ export function getRenderEdges(edges: FlowViewEdge[]) { // }) } -const NodeComponentHandler: NodeHandler = { - default: (node: NodeMapItem) => , - lineage: (node: NodeMapItem) => { - const { select = SelectType.DEFAULT } = node; - - return ( - - ); - }, - lineageGroup: (node: NodeMapItem) => { - const { select = SelectType.DEFAULT } = node; - - return ( - - ); - }, +// const NodeComponentHandler: NodeHandler = { +// default: (node: NodeMapItem) => , +// lineage: (node: NodeMapItem) => { +// const { select = SelectType.DEFAULT } = node; + +// return ( +// +// ); +// }, +// lineageGroup: (node: NodeMapItem) => { +// const { select = SelectType.DEFAULT } = node; + +// return ( +// +// ); +// }, +// }; + +const getWidthAndHeight = (node: NodeMapItem) => { + if (['lineage', 'default'].includes(node.flowNodeType!)) { + return { + width: 320, + height: 83, + }; + } else if (node.flowNodeType === 'lineageGroup') { + return { + width: 355, + height: 1100, + }; + } else { + return { + width: node.width || 1, + height: node.height || 1, + }; + } +}; + +const getHandleType = (node: NodeMapItem) => { + if (node.left?.length === 0 && node.right?.length === 0) { + return 'none'; + } else if (node.left?.length === 0) { + return 'input'; + } else if (node.right?.length === 0) { + return 'output'; + } else { + return 'both'; + } +}; + +// 只有pro flow节点才有的额外属性 +const getProFlowNodeData = (node: NodeMapItem) => { + if (['lineage'].includes(node.flowNodeType!)) { + return { + ...node.data, + selectType: node.select, + label: node.label, + zoom: node.zoom, + handleType: getHandleType(node), + }; + } else if (node.flowNodeType === 'lineageGroup') { + return { + data: node.data, + selectType: node.select, + label: node.label, + zoom: node.zoom, + handleType: getHandleType(node), + }; + } else { + return { + ...node.data, + selectType: node.select, + zoom: node.zoom, + }; + } }; export const getRenderData = ( mapping: NodeMapping, edges: FlowViewEdge[], + autoLayout: boolean, ): { nodes: Node[]; edges: Edge[]; } => { const renderNodes: Node[] = []; const renderEdges: Edge[] = getRenderEdges(edges); - // const { styles, cx } = useStyles(); Object.keys(mapping).forEach((id) => { const node = mapping[id]; const { flowNodeType } = node; + const { width, height } = getWidthAndHeight(node); renderNodes.push({ sourcePosition: Position.Right, targetPosition: Position.Left, id: node.id!, position: node.position!, - type: getTypeFromEdge(node), - width: node.group ? 355 : 320, - height: node.group ? 1100 : 83, + type: flowNodeType, + width: width, + height: height, className: cx(INIT_NODE), - data: { - label: NodeComponentHandler[flowNodeType!](node), - }, + data: getProFlowNodeData(node), }); }); - const { _nodes, _edges } = setNodePosition(renderNodes, renderEdges); + const { _nodes, _edges } = setNodePosition(renderNodes, renderEdges, autoLayout); return { nodes: _nodes, diff --git a/src/FlowView/hooks/useFlowView.ts b/src/FlowView/hooks/useFlowView.ts index 9026bcb..e23e748 100644 --- a/src/FlowView/hooks/useFlowView.ts +++ b/src/FlowView/hooks/useFlowView.ts @@ -1,4 +1,5 @@ import { useContext } from 'react'; +import { Viewport } from 'reactflow'; import { FlowViewContext } from '../provider/provider'; export const useFlowView = () => { @@ -22,13 +23,60 @@ export const useMiniMap = () => { }; export const useFlowViewer = () => { - const { updateSelectNode, updateSelectEdge, updateSelectEdges, updateSelectNodes } = - useContext(FlowViewContext); - - return { + const { updateSelectNode, updateSelectEdge, updateSelectEdges, updateSelectNodes, + setMiniMapPosition: setPosition, + reactFlowInstance, + } = useContext(FlowViewContext); + + const getNode = (nodeId: string) => { + return reactFlowInstance?.getNode(nodeId); + }; + + const getNodes = () => { + return reactFlowInstance?.getNodes(); + }; + + const zoomTo = (zoomNumber: number, duration?: number) => { + reactFlowInstance?.zoomTo(zoomNumber, { duration }); + }; + + const zoomToNode = (nodeId: string, duration?: number) => { + const node = getNode(nodeId); + if (node) { + reactFlowInstance?.fitView({ + nodes: [{ id: nodeId }], + duration, + }); + } + }; + + const setMiniMapPosition = (x: number, y: number) => { + setPosition!({ x, y }); + }; + + const getViewport = () => { + return reactFlowInstance?.getViewport!(); + }; + + const setViewport = (viewport: Viewport, duration?: number) => { + return reactFlowInstance?.setViewport!(viewport, { duration }); + }; + + return { + selectNode: updateSelectNode!, + selectEdge: updateSelectEdge!, + selectEdges: updateSelectEdges!, + selectNodes: updateSelectNodes!, + getNode, + getNodes, + zoomTo, + getViewport, + setViewport, + zoomToNode, + setMiniMapPosition, }; }; diff --git a/src/FlowView/index.md b/src/FlowView/index.md index 82a11f0..127e6e1 100644 --- a/src/FlowView/index.md +++ b/src/FlowView/index.md @@ -19,20 +19,21 @@ description: | className | `string` | 边数据 | - | - | | style | `CSSProperties` | 节点数据 | - | - | | miniMap | `boolean` | 边数据 | - | - | +| autoLayout | `boolean` | 自动布局 | true | - | | background | `boolean` | 节点数据 | - | - | | children | `React.ReactNode` | 边数据 | - | - | ### FlowViewNode -| 属性名 | 类型 | 描述 | 默认值 | 必选 | -| ------ | ---------------------------------------------- | ------------ | ------ | ---- | -| id | `string` | 边数据 | - | - | -| select | `SelectType` | 节点数据 | - | - | -| data | `NodeTypeDataMap[T]` | 边数据 | - | - | -| type | `T = 'default' \| 'lineage' \| 'lineageGroup'` | 节点类型() | - | - | -| label | `string` | 边数据 | - | - | -| width | `number` | 节点数据 | - | - | -| height | `number` | 边数据 | - | - | +| 属性名 | 类型 | 描述 | 默认值 | 必选 | +| ------ | ---------------------------------------------------------- | -------- | ------ | ---- | +| id | `string` | 边数据 | - | - | +| select | `SelectType` | 节点数据 | - | - | +| data | `NodeTypeDataMap[T]` | 边数据 | - | - | +| type | `T = 'default' \| 'lineage' \| 'lineageGroup' \| 'string'` | 节点类型 | - | - | +| label | `string` | 边数据 | - | - | +| width | `number` | 节点数据 | - | - | +| height | `number` | 边数据 | - | - | #### NodeTypeDataMap[T] diff --git a/src/FlowView/index.tsx b/src/FlowView/index.tsx index 152add6..bdc9ab0 100644 --- a/src/FlowView/index.tsx +++ b/src/FlowView/index.tsx @@ -1,14 +1,17 @@ +import LineageNodeGroup from '@/LineageGroupNode'; +import LineageNode from '@/LineageNode'; import React, { createContext, useCallback, useContext, useEffect, + useMemo, type MouseEvent as ReactMouseEvent, } from 'react'; -import ReactFlow, { Edge, Node } from 'reactflow'; +import ReactFlow, { BackgroundVariant, Edge, Node, useViewport } from 'reactflow'; import 'reactflow/dist/style.css'; -import Background, { BackgroundVariant } from '../Background'; -import { FlowViewProps, ProFlowController, RadiusEdge } from '../index'; +import { Background, FlowViewProps, ProFlowController, RadiusEdge } from '../index'; +import DefaultNode from './components/DefaultNode'; import { FlowViewContext } from './provider/provider'; import { useStyles } from './styles'; @@ -24,9 +27,12 @@ const FlowView: React.FC> = (props) => { onEdgeClick = initFn, nodes = [], edges = [], + nodeTypes = {}, + edgeTypes = {}, miniMap = true, children, background = true, + autoLayout = true, } = props; const { miniMapPosition, @@ -35,10 +41,25 @@ const FlowView: React.FC> = (props) => { edges: renderEdges, } = useContext(FlowViewContext); const { styles, cx } = useStyles(); + const nodeTypesMemo = useMemo(() => { + return { + ...nodeTypes, + lineage: LineageNode, + lineageGroup: LineageNodeGroup, + default: DefaultNode, + }; + }, []); + const edgeTypesMemo = useMemo(() => { + return { + ...edgeTypes, + radius: RadiusEdge, + }; + }, []); + const { zoom } = useViewport(); useEffect(() => { - flowDataAdapter!(nodes, edges); - }, [nodes, edges]); + flowDataAdapter!(nodes, edges, zoom, autoLayout); + }, [nodes, edges, zoom]); const handleNodeDragStart = useCallback( (event: ReactMouseEvent, node: Node, nodes: Node[]) => { @@ -71,12 +92,11 @@ const FlowView: React.FC> = (props) => { (event: ReactMouseEvent, edge: Edge) => { // TODO: 应当把事件中的 node 转换为 FlowViewNode 透出给用户 // const {node} = transformNode(node); - handleEdgeClick(event, edge); + onEdgeClick(event, edge); }, [onEdgeClick], ); - // TODO: 要把loading状态包掉,要把空状态包掉。 return ( > = (props) => { onEdgeClick={handleEdgeClick} nodes={renderNodes} edges={renderEdges} - edgeTypes={{ - radiusEdge: RadiusEdge, - }} + nodeTypes={nodeTypesMemo} + edgeTypes={edgeTypesMemo} panOnScroll fitView minZoom={MIN_ZOOM} diff --git a/src/FlowView/provider/FlowViewProvider.tsx b/src/FlowView/provider/FlowViewProvider.tsx index 706f921..ad63cbc 100644 --- a/src/FlowView/provider/FlowViewProvider.tsx +++ b/src/FlowView/provider/FlowViewProvider.tsx @@ -1,6 +1,6 @@ import { FlowViewEdge, FlowViewNode, MiniMapPosition, NodeTypeDataMap } from '@/constants'; import { FC, ReactNode, useCallback, useEffect, useState } from 'react'; -import { Edge, Node, ReactFlowProvider, useReactFlow, useViewport } from 'reactflow'; +import { Edge, Node, ReactFlowProvider, useReactFlow } from 'reactflow'; import { NodeMapping, SelectType } from '../constants'; import { convertMappingFrom, getRenderData } from '../helper'; import { FlowViewContext } from './provider'; @@ -13,20 +13,26 @@ const ProviderInner: FC<{ children: ReactNode }> = ({ children }) => { const [edges, setEdges] = useState([]); const [initEdges, setInitEdges] = useState(undefined); const [mapping, setMapping] = useState({}); - const { zoom } = useViewport(); + const [autoLayout, setAutoLayout] = useState(true); const convertRenderData = useCallback(() => { - const { nodes: _nodes, edges: _edges } = getRenderData(mapping, initEdges!); + const { nodes: _nodes, edges: _edges } = getRenderData(mapping, initEdges!, autoLayout); setNodes(_nodes); setEdges(_edges); - }, [initEdges]); + }, [mapping, initEdges, autoLayout]); const flowDataAdapter = useCallback( - (initNodes: FlowViewNode[], initEdges: FlowViewEdge[]) => { + ( + initNodes: FlowViewNode[], + initEdges: FlowViewEdge[], + zoom: number, + autoLayout: boolean, + ) => { if (initNodes.length === 0) return; setMapping(convertMappingFrom(initNodes!, initEdges!, zoom)); setInitEdges(initEdges); + setAutoLayout(autoLayout); }, [], ); @@ -86,6 +92,7 @@ const ProviderInner: FC<{ children: ReactNode }> = ({ children }) => { value={{ nodes, edges, + // dargePosition, flowDataAdapter, isUseProvider: true, reactFlowInstance, diff --git a/src/FlowView/provider/provider.ts b/src/FlowView/provider/provider.ts index 604b9ff..e97ca00 100644 --- a/src/FlowView/provider/provider.ts +++ b/src/FlowView/provider/provider.ts @@ -4,7 +4,12 @@ import { Edge, Node, ReactFlowInstance } from 'reactflow'; import { NodeMapping, SelectType } from '../constants'; interface FlowViewContextProps { - flowDataAdapter?: (nodes: FlowViewNode[], edges: FlowViewEdge[]) => void; + flowDataAdapter?: ( + nodes: FlowViewNode[], + edges: FlowViewEdge[], + zoom: number, + autoLayout: boolean, + ) => void; nodes?: Node[]; edges?: Edge[]; mapping?: NodeMapping; diff --git a/src/FlowView/styles.tsx b/src/FlowView/styles.tsx index d513251..fe7eb81 100644 --- a/src/FlowView/styles.tsx +++ b/src/FlowView/styles.tsx @@ -21,8 +21,6 @@ export const useStyles = createStyles(({ css, cx }) => ({ .${INIT_NODE} { padding: 0; box-sizing: border-box; - width: 320px; - height: 83px; border: none; border-radius: 8px; cursor: pointer; @@ -75,8 +73,6 @@ export const useStyles = createStyles(({ css, cx }) => ({ `, nodeWrap: cx( css` - width: 320px; - height: 85px; display: flex; z-index: 10 !important; position: absolute; diff --git a/src/LineageGroupNode/demos/index.tsx b/src/LineageGroupNode/demos/index.tsx index a6cab64..916c979 100644 --- a/src/LineageGroupNode/demos/index.tsx +++ b/src/LineageGroupNode/demos/index.tsx @@ -1,5 +1,4 @@ -import { FlowViewEdge, FlowViewNode, SelectType } from '@/index'; -import { FlowView } from '@ant-design/pro-flow'; +import { FlowView, FlowViewEdge, FlowViewNode, SelectType } from '@/index'; import { createStyles } from 'antd-style'; import { useState } from 'react'; diff --git a/src/LineageGroupNode/index.tsx b/src/LineageGroupNode/index.tsx index 8a3f981..68d059a 100644 --- a/src/LineageGroupNode/index.tsx +++ b/src/LineageGroupNode/index.tsx @@ -4,14 +4,22 @@ import { LineageGroupNodeData, LineageNodeData } from '@/constants'; import { getClsFromSelectType } from '@/utils'; import { cx } from 'antd-style'; import React from 'react'; +import { Handle, Position } from 'reactflow'; +import styled from 'styled-components'; import { useStyles } from './styles'; +const Wrap = styled.div` + width: 357px; + height: 632px; + position: relative; +`; export interface LineageNodeGroupProps { id?: string; zoom?: number; label?: string; - select?: SelectType; + selectType?: SelectType; data: LineageGroupNodeData[]; + handleType?: 'input' | 'output' | 'none' | ' both'; } const convertMappingNode = (nodeList: LineageGroupNodeData[]): NodeMapItem[] => { @@ -35,49 +43,61 @@ const GroupItem = (node: NodeMapItem) => { ); }; -const LineageNodeGroup: React.FC = ({ - data, - select = SelectType.SELECT, - zoom = 1, - label, -}) => { +const LineageNodeGroup: React.FC<{ + data: LineageNodeGroupProps; +}> = ({ data }) => { const { styles } = useStyles(); + const { + handleType = 'both', + selectType = SelectType.SELECT, + zoom = 1, + label, + data: _data, + } = data; - if ((data as LineageGroupNodeData[]).length < 7) { + if ((_data as LineageGroupNodeData[]).length < 7) { return
数组长度必须大于等于7!
; } - const nodeList = convertMappingNode(data as LineageGroupNodeData[]); + const nodeList = convertMappingNode(_data as LineageGroupNodeData[]); return ( - <> - {label && ( - - {zoom <= 0.1 ? `${String(label).substring(0, 3)}...` : label} - - )} -
- {nodeList!.map((_node, index) => { - const data = _node.data as LineageNodeData; - _node.position = { - x: 0, - y: 100 * index, - }; - _node.title = data.title; - _node.logo = data.logo; - _node.des = data.describe; - return GroupItem(_node); - })} -
-
- 查看更多 - + + {handleType === 'output' || handleType === 'both' ? ( + + ) : null} +
+ {label && ( + + {zoom <= 0.1 ? `${String(label).substring(0, 3)}...` : label} + + )} +
+ {nodeList!.map((_node, index) => { + const data = _node.data as LineageNodeData; + _node.position = { + x: 0, + y: 100 * index, + }; + _node.title = data.title; + _node.logo = data.logo; + _node.des = data.describe; + return GroupItem(_node); + })} +
+
+ 查看更多 + +
- + {handleType === 'input' || handleType === 'both' ? ( + + ) : null} +
); }; diff --git a/src/LineageGroupNode/styles.ts b/src/LineageGroupNode/styles.ts index d78e204..ab400e5 100644 --- a/src/LineageGroupNode/styles.ts +++ b/src/LineageGroupNode/styles.ts @@ -40,17 +40,16 @@ export const useStyles = createStyles(({ css }) => ({ line-height: 32px; bottom: 16px; left: 112px; + text-align: center; img { width: 14px; height: 14px; margin-left: 4px; - transform: translateY(-1px); + transform: translateY(2px); } `, groupItemWrap: css` - width: 320px; - height: 83px; display: flex; border-radius: 12px; box-sizing: border-box; diff --git a/src/LineageNode/demos/DataViewList.tsx b/src/LineageNode/demos/DataViewList.tsx index 82e64d8..4d87a38 100644 --- a/src/LineageNode/demos/DataViewList.tsx +++ b/src/LineageNode/demos/DataViewList.tsx @@ -1,6 +1,6 @@ import { createStyles } from 'antd-style'; import { memo } from 'react'; -import BloodNode from '..'; +import LineageNode from '..'; const useStyles = createStyles(({ css }) => ({ container: css` @@ -51,12 +51,14 @@ const NodeList = memo(() => {
{nodeList.map((item) => { return ( - ); })} diff --git a/src/LineageNode/demos/index.tsx b/src/LineageNode/demos/index.tsx index ebd4f81..b4eb77e 100644 --- a/src/LineageNode/demos/index.tsx +++ b/src/LineageNode/demos/index.tsx @@ -1,5 +1,4 @@ -import { FlowViewEdge, FlowViewNode, SelectType } from '@/index'; -import { FlowView } from '@ant-design/pro-flow'; +import { FlowView, FlowViewEdge, FlowViewNode, SelectType } from '@ant-design/pro-flow'; import { Progress } from 'antd'; import { createStyles } from 'antd-style'; import React, { useState } from 'react'; diff --git a/src/LineageNode/index.tsx b/src/LineageNode/index.tsx index 5462a82..a79a33e 100644 --- a/src/LineageNode/index.tsx +++ b/src/LineageNode/index.tsx @@ -1,9 +1,16 @@ import { SelectType } from '@/FlowView/constants'; import { getClsFromSelectType } from '@/utils'; import React from 'react'; +import { Handle, Position } from 'reactflow'; import styled from 'styled-components'; import { useStyles } from './styles'; +const Wrap = styled.div` + width: 320px; + height: 83px; + position: relative; +`; + interface BloodNodeProps { logo: string; title?: string; @@ -19,6 +26,7 @@ interface BloodNodeProps { type: 'left' | 'right'; value: React.ReactNode; }; + handleType?: 'input' | 'output' | 'none' | ' both'; } const zoomNum = (num: number, zoom: number, limitMax?: boolean) => { @@ -52,46 +60,58 @@ const TitleSlotRight = styled.div` top: 9px; `; -const BloodNode: React.FC> = ({ - title, - logo, - description, - className, - selectType = SelectType.SELECT, - zoom = 1, - label, - titleSlot, -}) => { +const LineageNode: React.FC<{ + data: BloodNodeProps; +}> = ({ data }) => { const { styles, cx } = useStyles(); + const { + title, + logo, + description, + className, + selectType = SelectType.SELECT, + zoom = 1, + label, + titleSlot, + handleType = 'both', + } = data; return ( - <> - {label && ( - - {zoom <= 0.1 ? `${String(label).substring(0, 3)}...` : label} - - )} -
- -
-
- {title} - {!!titleSlot && !!titleSlot.value && titleSlot.type === 'left' && ( - {titleSlot.value} - )} - {!!titleSlot && !!titleSlot.value && titleSlot.type === 'right' && ( - -
- {titleSlot.value} -
- )} -
+ + {handleType === 'output' || handleType === 'both' ? ( + + ) : null} +
+ {label && ( + + {zoom <= 0.1 ? `${String(label).substring(0, 3)}...` : label} + + )} +
+ +
+
+ {title} + {!!titleSlot && !!titleSlot.value && titleSlot.type === 'left' && ( + {titleSlot.value} + )} + {!!titleSlot && !!titleSlot.value && titleSlot.type === 'right' && ( + +
+ {titleSlot.value} +
+ )} +
-
{description}
+
{description}
+
- + {handleType === 'input' || handleType === 'both' ? ( + + ) : null} +
); }; -export default BloodNode; +export default LineageNode; diff --git a/src/LineageNode/styles.ts b/src/LineageNode/styles.ts index f6315af..6c203b6 100644 --- a/src/LineageNode/styles.ts +++ b/src/LineageNode/styles.ts @@ -4,8 +4,6 @@ export const useStyles = createStyles(({ css, cx, prefixCls }) => ({ nodeWrap: cx( `${prefixCls}-xx`, css` - width: 320px; - height: 85px; display: flex; z-index: 10 !important; position: absolute; diff --git a/src/ProFlowController/demos/FlowControllerDemo.tsx b/src/ProFlowController/demos/FlowControllerDemo.tsx index 1f671c0..de7fc2a 100644 --- a/src/ProFlowController/demos/FlowControllerDemo.tsx +++ b/src/ProFlowController/demos/FlowControllerDemo.tsx @@ -1,4 +1,4 @@ -import { FlowView, ProFlowController } from '@/index'; +import { FlowView, ProFlowController } from '@ant-design/pro-flow'; import { createStyles } from 'antd-style'; import { memo } from 'react'; @@ -13,7 +13,21 @@ const FlowControllerDemo = memo(() => { const { styles } = useStyles(); return (
- +
diff --git a/src/constants.tsx b/src/constants.tsx index 85c5874..23c2493 100644 --- a/src/constants.tsx +++ b/src/constants.tsx @@ -1,5 +1,5 @@ import React, { type CSSProperties, type MouseEvent as ReactMouseEvent } from 'react'; -import { Node } from 'reactflow'; +import { Edge, EdgeProps, Node, NodeProps } from 'reactflow'; import { NodeMapItem } from './FlowView/constants'; export enum SelectType { @@ -53,8 +53,8 @@ export type DefaultNodeType = T extends FlowNodeType ? T : 'lineage'; export interface FlowViewNode> { id: string; select?: SelectType; - data: NodeTypeDataMap[T]; - type?: T; + data: NodeTypeDataMap[T] | any; + type?: T | string; label?: string; width?: number; height?: number; @@ -68,7 +68,11 @@ export interface FlowViewEdge { id: string; source: string; target: string; + sourceHandle?: string; + targetHandle?: string; + animated?: boolean; select?: SelectType; + label?: string; type?: EdgeType; } @@ -76,14 +80,16 @@ export interface FlowViewProps { onNodeDragStart?: (event: ReactMouseEvent, node: Node, nodes: Node[]) => void; onPaneClick?: (event: ReactMouseEvent) => void; onNodeClick?: (event: ReactMouseEvent, node: Node) => void; - onEdgeClick?: (event: ReactMouseEvent) => void; + onEdgeClick?: (event: ReactMouseEvent, edge: Edge) => void; nodes: FlowViewNode[]; edges: FlowViewEdge[]; - className?: string; + nodeTypes?: Record>; + edgeTypes?: Record>; style?: CSSProperties; miniMap?: boolean; background?: boolean; children?: React.ReactNode; + autoLayout?: boolean; } export interface MiniMapPosition { diff --git a/src/index.ts b/src/index.ts index e40787c..c47e990 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,14 @@ +import { Handle, Position } from 'reactflow'; + export * from './Background'; +export { default as Background } from './Background'; export { default as BasicNode } from './BasicNode'; export { default as CanvasLoading } from './CanvasLoading'; export * from './ControlInput'; export { default as EditableText } from './EditableText'; export * from './FlowEditor'; export * from './FlowPanel'; +export { default as FlowPanel } from './FlowPanel'; export { FlowStoreProvider, type FlowEditorStoreProviderProps } from './FlowStoreProvider'; export { default as FlowView } from './FlowView/FlowView'; export * from './FlowView/hooks/useFlowView'; @@ -16,3 +20,4 @@ export * from './ProFlowController'; export { default as ProFlowController } from './ProFlowController'; export { default as RadiusEdge } from './RadiusEdge'; export * from './constants'; +export { Handle, Position };