From cd7553bba08d803caee8f82facd7060f88f54492 Mon Sep 17 00:00:00 2001 From: ModestFun <61576426+ModestFun@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:24:59 +0800 Subject: [PATCH] :sparkles: feat: New capabilities for node and edge (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :bug: fix: build * :bug: fix: react-flow-attribution remove * :sparkles: feat: edge border raduis type * :bug: feat: eslint config * :sparkles: feat: border radius edge * :memo: fix: 删除不必要的demo配置 * :memo: feat: radius edge memo update * :memo: feat: 简化圆角线段组件的demo示例 * :sparkles: feat: 更新proflow配置的类型 * :sparkles: feat: 更新proflow点击 拖拽事件触发结构 * :sparkles: feat: add reactflow css * :sparkles: feat: add component docs * :sparkles: feat: add transfer font with zoom * :sparkles: feat: add transfer font with zoom for blood group node * :sparkles: feat: add edge select * :sparkles: feat: add edge select type * :bug: fix: ci bug --------- Co-authored-by: jiangchu --- docs/useDocs/baseInrro.md | 11 ++++ docs/useDocs/drawer.md | 10 ++++ docs/useDocs/editorview.md | 11 ++++ docs/useDocs/flowview.md | 12 ++++ docs/useDocs/intro.md | 10 ++++ docs/useDocs/layoud.md | 10 ++++ docs/useDocs/quickDoc.md | 11 ++++ docs/useDocs/zoom.md | 10 ++++ package.json | 1 + src/BasicNode/index.md | 4 +- src/BloodGroupNode/index.tsx | 69 ++++++++++++++-------- src/BloodNode/index.tsx | 47 ++++++++++++--- src/CanvasLoading/index.md | 4 +- src/NodeField/index.md | 3 +- src/ProFlow/constants.tsx | 21 +------ src/ProFlow/demos/ProFlowDemo.tsx | 8 ++- src/ProFlow/helper.tsx | 46 ++++++++------- src/ProFlow/index.tsx | 95 ++++++++++++++++++++++--------- src/constants.tsx | 29 ++++++++++ tsconfig.json | 30 +++------- 20 files changed, 316 insertions(+), 126 deletions(-) create mode 100644 docs/useDocs/baseInrro.md create mode 100644 docs/useDocs/drawer.md create mode 100644 docs/useDocs/editorview.md create mode 100644 docs/useDocs/flowview.md create mode 100644 docs/useDocs/intro.md create mode 100644 docs/useDocs/layoud.md create mode 100644 docs/useDocs/quickDoc.md create mode 100644 docs/useDocs/zoom.md diff --git a/docs/useDocs/baseInrro.md b/docs/useDocs/baseInrro.md new file mode 100644 index 0000000..268eb82 --- /dev/null +++ b/docs/useDocs/baseInrro.md @@ -0,0 +1,11 @@ +--- +nav: 使用文档 +group: + title: 基础介绍 + order: 1 +title: 根本性概念 +order: 1 +description: +--- + +dsfsaf diff --git a/docs/useDocs/drawer.md b/docs/useDocs/drawer.md new file mode 100644 index 0000000..72108b4 --- /dev/null +++ b/docs/useDocs/drawer.md @@ -0,0 +1,10 @@ +--- +nav: 使用文档 +group: + title: 进阶使用 + order: 3 +title: 侧边栏 +description: +--- + +dsfsaf diff --git a/docs/useDocs/editorview.md b/docs/useDocs/editorview.md new file mode 100644 index 0000000..69792fb --- /dev/null +++ b/docs/useDocs/editorview.md @@ -0,0 +1,11 @@ +--- +nav: 使用文档 +group: + title: 快速上手 + order: 1 +title: 画布编辑 +order: 3 +description: +--- + +### 123123 diff --git a/docs/useDocs/flowview.md b/docs/useDocs/flowview.md new file mode 100644 index 0000000..d0629ea --- /dev/null +++ b/docs/useDocs/flowview.md @@ -0,0 +1,12 @@ +--- +nav: 使用文档 +group: + title: 快速上手 + order: 1 +title: 画布预览 +order: 2 +description: +--- + +s +ssdadaf diff --git a/docs/useDocs/intro.md b/docs/useDocs/intro.md new file mode 100644 index 0000000..b3bca47 --- /dev/null +++ b/docs/useDocs/intro.md @@ -0,0 +1,10 @@ +--- +nav: 使用文档 +group: + title: 基础介绍 + order: 1 +title: 为什么选择 ReactFlow ? +description: +--- + +dsfsaf diff --git a/docs/useDocs/layoud.md b/docs/useDocs/layoud.md new file mode 100644 index 0000000..f710aba --- /dev/null +++ b/docs/useDocs/layoud.md @@ -0,0 +1,10 @@ +--- +nav: 使用文档 +group: + title: 进阶使用 + order: 3 +title: 布局 +description: 自动布局 、 手动布局 +--- + +dsfsaf diff --git a/docs/useDocs/quickDoc.md b/docs/useDocs/quickDoc.md new file mode 100644 index 0000000..ce0fff5 --- /dev/null +++ b/docs/useDocs/quickDoc.md @@ -0,0 +1,11 @@ +--- +nav: 使用文档 +group: + title: 快速上手 + order: 2 +title: 安装使用 +order: 1 +description: +--- + +asd aws diff --git a/docs/useDocs/zoom.md b/docs/useDocs/zoom.md new file mode 100644 index 0000000..2805195 --- /dev/null +++ b/docs/useDocs/zoom.md @@ -0,0 +1,10 @@ +--- +nav: 使用文档 +group: + title: 进阶使用 + order: 3 +title: 缩放 +description: +--- + +dsfsaf diff --git a/package.json b/package.json index 59f521a..5b7699d 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "react-dom": "^18", "semantic-release": "^20", "semantic-release-config-gitmoji": "^1", + "styled-components": "^6.1.0", "stylelint": "^14", "typescript": "^5", "vitest": "latest" diff --git a/src/BasicNode/index.md b/src/BasicNode/index.md index 8d39e58..b6cff85 100644 --- a/src/BasicNode/index.md +++ b/src/BasicNode/index.md @@ -1,5 +1,7 @@ --- -group: 节点 +group: + title: 节点 + order: 1 title: BasicNode 基础节点 description: 画布中的基础节点容器 --- diff --git a/src/BloodGroupNode/index.tsx b/src/BloodGroupNode/index.tsx index 780199b..e6acb70 100644 --- a/src/BloodGroupNode/index.tsx +++ b/src/BloodGroupNode/index.tsx @@ -1,9 +1,19 @@ -import { getClsFromSelectType } from '@/BloodNode'; -import { NodeMapItem, NodeSelect, ProFlowNode, ProFlowNodeData } from '@/ProFlow/constants'; +import { ArtboardTitle, getClsFromSelectType } from '@/BloodNode'; +import { NodeMapItem, NodeSelect } from '@/ProFlow/constants'; +import { ProFlowNode, ProFlowNodeData } from '@/constants'; import { cx } from 'antd-style'; import React from 'react'; import { useStyles } from './styles'; +export interface BloodNodeGroupProps { + id?: string; + zoom?: number; + label?: string; + select?: NodeSelect; + data: ProFlowNode[]; + group: boolean; +} + const convertMappingNode = (nodeList: ProFlowNode[]): NodeMapItem[] => { return nodeList.map((node) => { return node; @@ -25,7 +35,13 @@ const GroupItem = (node: NodeMapItem) => { ); }; -const BloodNodeGroup: React.FC = ({ group, data, select = NodeSelect.SELECT }) => { +const BloodNodeGroup: React.FC = ({ + group, + data, + select = NodeSelect.SELECT, + zoom = 1, + label, +}) => { const { styles } = useStyles(); if (!group) { @@ -39,27 +55,34 @@ const BloodNodeGroup: React.FC = ({ group, data, select = NodeSelec const nodeList = convertMappingNode(data as ProFlowNode[]); return ( -
- {nodeList!.map((_node, index) => { - const data = _node.data as ProFlowNodeData; - _node.position = { - x: 0, - y: 100 * index, - }; - _node.title = data.title; - _node.logo = data.logo; - _node.des = data.describe; - return GroupItem(_node); - })} -
-
- 查看更多 - + <> + {label && ( + + {zoom <= 0.1 ? `${String(label).substring(0, 3)}...` : label} + + )} +
+ {nodeList!.map((_node, index) => { + const data = _node.data as ProFlowNodeData; + _node.position = { + x: 0, + y: 100 * index, + }; + _node.title = data.title; + _node.logo = data.logo; + _node.des = data.describe; + return GroupItem(_node); + })} +
+
+ 查看更多 + +
-
+ ); }; diff --git a/src/BloodNode/index.tsx b/src/BloodNode/index.tsx index e41dd31..0b365b4 100644 --- a/src/BloodNode/index.tsx +++ b/src/BloodNode/index.tsx @@ -1,5 +1,6 @@ import { NODE_DANGER, NODE_SELECT, NODE_WARNING, NodeSelect } from '@/ProFlow/constants'; import React from 'react'; +import styled from 'styled-components'; import { useStyles } from './styles'; interface BloodNodeProps { @@ -11,8 +12,27 @@ interface BloodNodeProps { icon?: string; className?: string; selectType?: NodeSelect; + zoom?: number; + label?: string; } +const zoomNum = (num: number, zoom: number, limitMax?: boolean) => { + if (limitMax) return zoom > 1 ? num : num / zoom; + + return num / zoom; +}; + +export const ArtboardTitle = styled.div<{ zoom: number }>` + position: absolute; + z-index: 10; + top: -${({ zoom }) => zoomNum(24, zoom, true)}px; + padding: ${({ zoom }) => `${2 / zoom}px ${1 / zoom}px ${2 / zoom}px 0`}; + color: rgba(0, 0, 0, 0.45); + border-radius: 4px; + font-size: ${({ zoom }) => `${14 / zoom}px`}; + white-space: nowrap; +`; + export function getClsFromSelectType(select: NodeSelect) { switch (select) { case NodeSelect.SELECT: @@ -32,21 +52,32 @@ const BloodNode: React.FC> = ({ description, className, selectType = NodeSelect.SELECT, + zoom = 1, + label, }) => { const { styles, cx } = useStyles(); + console.log(zoom); + return ( -
- -
-
- {title} - {/* {mainDanger && } + <> + {label && ( + + {zoom <= 0.1 ? `${String(label).substring(0, 3)}...` : label} + + )} +
+ +
+
+ {title} + {/* {mainDanger && } {qualityScore && } */} +
+
{description}
-
{description}
-
+ ); }; diff --git a/src/CanvasLoading/index.md b/src/CanvasLoading/index.md index 345a646..904abb7 100644 --- a/src/CanvasLoading/index.md +++ b/src/CanvasLoading/index.md @@ -1,5 +1,7 @@ --- -group: 基础组件 +group: + title: 辅助 + order: 2 title: CanvasLoading 画布加载状态 description: 画布加载状态 --- diff --git a/src/NodeField/index.md b/src/NodeField/index.md index df1c95d..13eed31 100644 --- a/src/NodeField/index.md +++ b/src/NodeField/index.md @@ -1,5 +1,6 @@ --- -group: 节点 +group: + title: 节点 title: NodeField 节点字段 description: 展示节点中的单个字段的组件 --- diff --git a/src/ProFlow/constants.tsx b/src/ProFlow/constants.tsx index 8207ad6..84c6371 100644 --- a/src/ProFlow/constants.tsx +++ b/src/ProFlow/constants.tsx @@ -1,3 +1,4 @@ +import { ProFlowNode, ProFlowNodeData } from '@/constants'; import { Node } from 'reactflow'; export enum NodeSelect { @@ -6,24 +7,6 @@ export enum NodeSelect { WARNING = 'WARNING', DEFAULT = 'DEFAULT', } -export interface ProFlowNode { - id: string; - group?: boolean; - select?: NodeSelect; - data: ProFlowNodeData | ProFlowNode[]; -} - -export interface ProFlowNodeData { - title: string; - describe: string; - logo: string; -} - -export interface ProFLowEdge { - id: string; - source: string; - target: string; -} export interface InitialNode extends Node { width?: number; @@ -53,6 +36,8 @@ export interface NodeMapItem { logo?: string; data: ProFlowNodeData | ProFlowNode[]; nodeType?: string; + zoom?: number; + label?: string; position?: { x: number; y: number; diff --git a/src/ProFlow/demos/ProFlowDemo.tsx b/src/ProFlow/demos/ProFlowDemo.tsx index 32452ea..57dae0a 100644 --- a/src/ProFlow/demos/ProFlowDemo.tsx +++ b/src/ProFlow/demos/ProFlowDemo.tsx @@ -1,7 +1,7 @@ +import { NodeSelect, ProFlowNode } from '@/index'; import { createStyles } from 'antd-style'; import { memo } from 'react'; import ProFlow from '..'; -import { NodeSelect, ProFlowNode } from '../constants'; const useStyles = createStyles(({ css }) => ({ container: css` @@ -14,6 +14,7 @@ const nodes: ProFlowNode[] = [ { id: 'a1', select: NodeSelect.SELECT, + label: '123', data: { title: 'XXX数据源', describe: 'cksadjfnf', @@ -51,6 +52,7 @@ const nodes: ProFlowNode[] = [ id: 'a4', group: true, select: NodeSelect.WARNING, + label: '456', data: [ { id: 'a5', @@ -123,21 +125,25 @@ const edges = [ { id: 'a1-a2', source: 'a1', + select: NodeSelect.WARNING, target: 'a2', }, { id: 'a1-b1', source: 'a1', + select: NodeSelect.WARNING, target: 'b1', }, { id: 'a2-a3', source: 'a2', + select: NodeSelect.WARNING, target: 'a3', }, { id: 'a3-a4', source: 'a3', + select: NodeSelect.WARNING, target: 'a4', }, ]; diff --git a/src/ProFlow/helper.tsx b/src/ProFlow/helper.tsx index 8271ff6..17db6b1 100644 --- a/src/ProFlow/helper.tsx +++ b/src/ProFlow/helper.tsx @@ -1,5 +1,6 @@ import BloodNodeGroup from '@/BloodGroupNode'; import BloodNode from '@/BloodNode'; +import { ProFlowEdge, ProFlowNode, ProFlowNodeData } from '@/constants'; import Dagre from '@dagrejs/dagre'; import { cx } from 'antd-style'; import { Edge, Node, Position } from 'reactflow'; @@ -12,9 +13,6 @@ import { NodeMapItem, NodeMapping, NodeSelect, - ProFLowEdge, - ProFlowNode, - ProFlowNodeData, } from './constants'; function getTypeFromEdge(node: NodeMapItem) { @@ -30,7 +28,7 @@ function getTypeFromEdge(node: NodeMapItem) { return 'default'; } -export function convertMappingFrom(nodes: ProFlowNode[], edges: ProFLowEdge[]) { +export function convertMappingFrom(nodes: ProFlowNode[], edges: ProFlowEdge[], zoom: number) { const mapping: NodeMapping = {}; nodes.forEach((node) => { mapping[node.id] = { @@ -40,6 +38,8 @@ export function convertMappingFrom(nodes: ProFlowNode[], edges: ProFLowEdge[]) { select: node.select, right: [], left: [], + zoom, + label: node.label, }; }); @@ -106,33 +106,37 @@ function getEdgeClsFromNodeSelect(select: NodeSelect) { } } -function getRenderEdge(node: NodeMapItem, targetNode: NodeMapItem) { - const { id } = node; - const { id: targetId, select = NodeSelect.DEFAULT } = targetNode; +export function getRenderEdges(edges: ProFlowEdge[]) { + return edges.map((edge) => { + const { source, target, select = NodeSelect.DEFAULT } = edge; - return { - id: `${id}-${targetId}`, - source: id!, - target: targetId!, - type: 'radiusEdge', - className: getEdgeClsFromNodeSelect(select), - }; + return { + id: `${source}-${target}`, + source, + target, + type: 'radiusEdge', + className: getEdgeClsFromNodeSelect(select), + }; + }); } export const getRenderData = ( mapping: NodeMapping, + edges: ProFlowEdge[], ): { nodes: Node[]; edges: Edge[]; } => { const renderNodes: Node[] = []; - const renderEdges: Edge[] = []; + const renderEdges: Edge[] = getRenderEdges(edges); // const { styles, cx } = useStyles(); Object.keys(mapping).forEach((id) => { const node = mapping[id]; const { select = NodeSelect.DEFAULT } = node; - console.log(node); + + console.log(node.zoom); + renderNodes.push({ sourcePosition: Position.Right, targetPosition: Position.Left, @@ -149,6 +153,8 @@ export const getRenderData = ( group={node.group} data={node.data! as ProFlowNode[]} select={select} + zoom={node.zoom} + label={node.label} /> ) : ( ), }, }); - - if (node.right!.length) { - node.right!.forEach((targetId: string) => { - renderEdges.push(getRenderEdge(node, mapping[targetId])); - }); - } }); const { _nodes, _edges } = setNodePosition(renderNodes, renderEdges); diff --git a/src/ProFlow/index.tsx b/src/ProFlow/index.tsx index 46efe50..2bdb718 100644 --- a/src/ProFlow/index.tsx +++ b/src/ProFlow/index.tsx @@ -1,37 +1,42 @@ -import React, { useMemo, type CSSProperties, type MouseEvent as ReactMouseEvent } from 'react'; -import ReactFlow, { Background, BackgroundVariant, Edge, Node, useEdgesState } from 'reactflow'; -import { ProFlowController, RadiusEdge } from '../index'; -import { ProFLowEdge, ProFlowNode } from './constants'; +import React, { useCallback, useMemo, type MouseEvent as ReactMouseEvent } from 'react'; +import ReactFlow, { + Background, + BackgroundVariant, + Edge, + Node, + ReactFlowProvider, + useViewport, +} from 'reactflow'; +import 'reactflow/dist/style.css'; +import { ProFlowController, ProFlowProps, RadiusEdge } from '../index'; import { convertMappingFrom, getRenderData } from './helper'; import { useStyles } from './styles'; const MIN_ZOOM = 0.1; -interface ProFlowProps { - onNodeDragStart: (event: ReactMouseEvent, node: Node, nodes: Node[]) => void; - onPaneClick: (event: ReactMouseEvent) => void; - onNodeClick: (event: ReactMouseEvent, node: Node) => void; - nodes: ProFlowNode[]; - edges: ProFLowEdge[]; - className?: string; - style?: CSSProperties; - miniMap?: boolean; -} +const initFn = () => {}; -const ProFlow: React.FC> = (props) => { - const { onNodeDragStart, onPaneClick, onNodeClick, nodes, edges, miniMap = true } = props; +const Flow: React.FC> = (props) => { + const { + onNodeDragStart = initFn, + onPaneClick = initFn, + onNodeClick = initFn, + nodes, + edges, + miniMap = true, + } = props; const { styles, cx } = useStyles(); - const mapping = convertMappingFrom(nodes!, edges!); + const { zoom } = useViewport(); + const mapping = useMemo(() => convertMappingFrom(nodes!, edges!, zoom), [nodes, edges, zoom]); const renderData = useMemo((): { nodes: Node[]; edges: Edge[]; } => { - if (mapping) { - const { nodes, edges } = getRenderData(mapping); - + if (mapping && edges!.length) { + const { nodes, edges: _edges } = getRenderData(mapping, edges!); return { nodes, - edges, + edges: _edges, }; } else { return { @@ -39,18 +44,44 @@ const ProFlow: React.FC> = (props) => { edges: [], }; } - }, [mapping]); - const [_edges] = useEdgesState(renderData.edges); + }, [mapping, edges]); - console.log(renderData.edges); - console.log(_edges); + // const [_edges] = useEdgesState(renderData.edges); + + const handleNodeDragStart = useCallback( + (event: ReactMouseEvent, node: Node, nodes: Node[]) => { + // TODO: 应当把事件中的 node 转换为 ProFlowNode 透出给用户 + // const {node} = transformNode(node); + onNodeDragStart(event, node, nodes); + }, + [onNodeDragStart], + ); + const handlePaneClick = useCallback( + (event: ReactMouseEvent) => { + // TODO: 应当把事件中的 node 转换为 ProFlowNode 透出给用户 + // const {node} = transformNode(node); + onPaneClick(event); + }, + [onPaneClick], + ); + + const handleNodeClick = useCallback( + (event: ReactMouseEvent, node: Node) => { + // TODO: 应当把事件中的 node 转换为 ProFlowNode 透出给用户 + // const {node} = transformNode(node); + onNodeClick(event, node); + }, + [onNodeClick], + ); + + // TODO: 要把loading状态包掉,要把空状态包掉。 return ( > = (props) => { ); }; +const ProFlow: React.FC> = (props) => { + return ( + + + + ); +}; + export default ProFlow; diff --git a/src/constants.tsx b/src/constants.tsx index 1d85021..ab72426 100644 --- a/src/constants.tsx +++ b/src/constants.tsx @@ -1,14 +1,32 @@ +import { type CSSProperties, type MouseEvent as ReactMouseEvent } from 'react'; +import { Node } from 'reactflow'; + export enum NodeSelect { SELECT = 'SELECT', DANGER = 'DANGER', WARNING = 'WARNING', DEFAULT = 'DEFAULT', } + +export enum EdgeType { + default = 'default', + radius = 'radius', +} + export interface ProFlowNode { id: string; group?: boolean; select?: NodeSelect; data: ProFlowNodeData | ProFlowNode[]; + label?: string; +} + +export interface ProFlowEdge { + id: string; + source: string; + target: string; + select?: NodeSelect; + type?: EdgeType; } export interface ProFlowNodeData { @@ -16,3 +34,14 @@ export interface ProFlowNodeData { describe: string; logo: string; } + +export interface ProFlowProps { + onNodeDragStart?: (event: ReactMouseEvent, node: Node, nodes: Node[]) => void; + onPaneClick?: (event: ReactMouseEvent) => void; + onNodeClick?: (event: ReactMouseEvent, node: Node) => void; + nodes: ProFlowNode[]; + edges: ProFlowEdge[]; + className?: string; + style?: CSSProperties; + miniMap?: boolean; +} diff --git a/tsconfig.json b/tsconfig.json index 99181ed..43cf874 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,5 @@ { - "include": [ - "src", - "tests", - ".dumirc.ts", - "*.ts" - ], + "include": ["src", "tests", ".dumirc.ts", "*.ts"], "compilerOptions": { "strict": true, "declaration": true, @@ -13,23 +8,12 @@ "resolveJsonModule": true, "jsx": "react-jsx", "baseUrl": ".", - "types": [ - "vitest/globals" - ], + "types": ["vitest/globals"], "paths": { - "@@/*": [ - ".dumi/tmp/*" - ], - "@/*": [ - "./src/*" - ], - "@ant-design/pro-flow": [ - "src" - ], - "@ant-design/pro-flow/*": [ - "src/*", - "*" - ] + "@@/*": [".dumi/tmp/*"], + "@/*": ["./src/*"], + "@ant-design/pro-flow": ["src"], + "@ant-design/pro-flow/*": ["src/*", "*"] } } -} \ No newline at end of file +}