Skip to content

Commit

Permalink
✨ feat: node edge onchange, add minimap config (#52)
Browse files Browse the repository at this point in the history
* ✨ feat: node edge onchange, add minimap config

* ✨ feat: add useNodesState function

* 🐛 fix: build

---------

Co-authored-by: jiangchu <jiangchu.wzy@antgroup.com>
  • Loading branch information
ModestFun and jiangchu committed Dec 18, 2023
1 parent cfeffd1 commit 4dced96
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 70 deletions.
9 changes: 1 addition & 8 deletions src/BasicNode/index.tsx
Expand Up @@ -87,14 +87,7 @@ const BasicNode: React.FC<{
{zoom <= 0.1 ? `${String(label).substring(0, 3)}...` : label}
</ArtboardTitle>
)}
<div
className={cx(
styles.nodeWrap,
styles[getClsFromSelectType(selectType)],
className,
'nodrag',
)}
>
<div className={cx(styles.nodeWrap, styles[getClsFromSelectType(selectType)], className)}>
<img className={'img'} src={logo} alt="" />
<div className={'content'}>
<div className={'title'}>
Expand Down
11 changes: 9 additions & 2 deletions src/FlowView/constants.tsx
Expand Up @@ -23,8 +23,8 @@ export interface NodeMapItem<T = any, U extends string | undefined = string | un
title?: string;
group?: boolean;
des?: string;
width?: number;
height?: number;
width?: number | null;
height?: number | null;
danger?: boolean;
dangerCount?: number;
type?: 'input' | 'output' | 'default';
Expand All @@ -48,6 +48,13 @@ export interface NodeMapItem<T = any, U extends string | undefined = string | un
};
}

export interface LayoutOptions {
rankdir?: 'TB' | 'BT' | 'LR' | 'RL';
align?: 'UL' | 'DL' | 'UR' | 'DR';
nodesep?: number;
ranksep?: number;
}

export type NodeMapping = Record<string, NodeMapItem>;

export const NODE_SELECT = 'nodeSelected';
Expand Down
18 changes: 12 additions & 6 deletions src/FlowView/demos/ProFlowDemo.tsx
@@ -1,16 +1,20 @@
/**
* compact: true
*/
import { FlowView, FlowViewEdge, FlowViewNode, SelectType } from '@ant-design/pro-flow';
import { useState } from 'react';
import CustomNode from './CustomerNode';
import {
FlowView,
FlowViewNode,
SelectType,
useEdgesState,
useNodesState,
} from '@ant-design/pro-flow';
import { edges, nodes } from './data';
import useStyles from './index.style';

const ProFlowDemo = () => {
const { styles } = useStyles();
const [_nodes, setNodes] = useState<FlowViewNode[]>([...nodes]);
const [_edges, setEdges] = useState<FlowViewEdge[]>([...edges]);
const [_nodes, setNodes, onNodesChange] = useNodesState([...nodes]);
const [_edges, setEdges, onEdgesChange] = useEdgesState([...edges]);

const handleHighlight = (node: FlowViewNode) => {
_nodes.forEach((_node) => {
Expand Down Expand Up @@ -50,10 +54,12 @@ const ProFlowDemo = () => {
<div className={styles.container}>
<FlowView
onNodeDragStart={(e, node: any) => handleHighlight(node)}
onNodeClick={(e, node: any) => handleHighlight(node)}
onPaneClick={handleUnHighlight}
nodes={_nodes}
edges={_edges}
nodeTypes={{ textCustomNode: CustomNode }}
onEdgesChange={onEdgesChange}
onNodesChange={onNodesChange}
></FlowView>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/FlowView/demos/data.tsx
Expand Up @@ -37,7 +37,7 @@ const DangerLogo: React.FC = () => {
export const nodes: FlowViewNode[] = [
{
id: 'a1',
label: '123',
label: '12345',
data: {
title: 'XXX_API_ddddddddddddddddddddddddddddddddddddddddddddddddddddddb1',
logo: 'https://mdn.alipayobjects.com/huamei_ntgeqc/afts/img/A*kgyiRKi04eUAAAAAAAAAAAAADvuvAQ/original',
Expand Down
18 changes: 12 additions & 6 deletions src/FlowView/helper.tsx
Expand Up @@ -11,6 +11,7 @@ import {
EDGE_WARNING,
INIT_NODE,
InitialNode,
LayoutOptions,
NodeMapItem,
NodeMapping,
SelectType,
Expand Down Expand Up @@ -52,7 +53,12 @@ export function convertMappingFrom(nodes: FlowViewNode[], edges: FlowViewEdge[],
return mapping;
}

export function setNodePosition(nodes: Node[], edges: Edge[], autoLayout: boolean) {
export function setNodePosition(
nodes: Node[],
edges: Edge[],
autoLayout: boolean,
layoutOptions: LayoutOptions,
) {
if (!autoLayout) {
return {
_nodes: nodes.map((node) => {
Expand All @@ -72,8 +78,7 @@ export function setNodePosition(nodes: Node[], edges: Edge[], autoLayout: boolea
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));

g.setGraph({
rankdir: 'LR',
align: 'UL',
...layoutOptions,
});

edges.forEach((edge) => g.setEdge(edge.source, edge.target));
Expand All @@ -88,8 +93,8 @@ export function setNodePosition(nodes: Node[], edges: Edge[], autoLayout: boolea
return {
...node,
position: {
x: (isNaN(_x) ? x : _x) * 1.3,
y: (isNaN(_y) ? y : _y) * 1,
x: isNaN(_x) ? x : _x,
y: isNaN(_y) ? y : _y,
},
};
}) as unknown as Node[],
Expand Down Expand Up @@ -266,6 +271,7 @@ export const getRenderData = (
mapping: NodeMapping,
edges: FlowViewEdge[],
autoLayout: boolean,
layoutOptions: LayoutOptions,
): {
nodes: Node[];
edges: Edge[];
Expand All @@ -291,7 +297,7 @@ export const getRenderData = (
});
});

const { _nodes, _edges } = setNodePosition(renderNodes, renderEdges, autoLayout);
const { _nodes, _edges } = setNodePosition(renderNodes, renderEdges, autoLayout, layoutOptions);

return {
nodes: _nodes,
Expand Down
24 changes: 24 additions & 0 deletions src/FlowView/hooks/useFlowState.ts
@@ -0,0 +1,24 @@
import { FlowViewNode } from '@/constants';
import { useCallback, useState } from 'react';
import { Node, NodeChange, applyNodeChanges, useEdgesState } from 'reactflow';

const useNodesState = (
beforeNodes: FlowViewNode[],
): [
FlowViewNode[],
React.Dispatch<React.SetStateAction<FlowViewNode[]>>,
(changes: NodeChange[]) => void,
] => {
const [items, setItems] = useState(beforeNodes);

const onItemsChange = useCallback(
(changes: NodeChange[]) => {
setItems((items) => applyNodeChanges(changes, items as Node[]));
},
[items],
);

return [items, setItems, onItemsChange];
};

export { useEdgesState, useNodesState };
53 changes: 45 additions & 8 deletions src/FlowView/index.tsx
Expand Up @@ -8,9 +8,16 @@ import React, {
useMemo,
type MouseEvent as ReactMouseEvent,
} from 'react';
import ReactFlow, { BackgroundVariant, Edge, Node, useViewport } from 'reactflow';
import ReactFlow, {
BackgroundVariant,
Edge,
EdgeChange,
Node,
NodeChange,
useViewport,
} from 'reactflow';
import 'reactflow/dist/style.css';
import { Background, FlowViewProps, MiniMap, RadiusEdge } from '../index';
import { Background, FlowViewProps, Language, MiniMap, RadiusEdge } from '../index';
import DefaultNode from './components/DefaultNode';
import { FlowViewContext } from './provider/provider';
import { useStyles } from './styles';
Expand Down Expand Up @@ -38,6 +45,13 @@ const FlowView: React.FC<Partial<FlowViewProps>> = (props) => {
flowProps,
minZoom = 0.1,
maxZoom = 2,
className,
layoutOptions = {
rankdir: 'LR',
align: 'UL',
nodesep: 100,
ranksep: 200,
},
} = props;
const {
miniMapPosition,
Expand All @@ -63,14 +77,28 @@ const FlowView: React.FC<Partial<FlowViewProps>> = (props) => {
const { zoom } = useViewport();

useEffect(() => {
flowDataAdapter!(nodes, edges, 1, autoLayout);
flowDataAdapter!(nodes, edges, 1, autoLayout, layoutOptions);
}, [nodes, edges]);

useEffect(() => {
if (!stepLessZooming) return;
flowDataAdapter!(nodes, edges, zoom, autoLayout);
flowDataAdapter!(nodes, edges, zoom, autoLayout, layoutOptions);
}, [zoom]);

const handleNodesChange = useCallback(
(nodes: NodeChange[]) => {
onNodesChange(nodes);
},
[onNodesChange],
);

const handleEdgesChange = useCallback(
(edges: EdgeChange[]) => {
onEdgesChange(edges);
},
[onEdgesChange],
);

const handleNodeDragStart = useCallback(
(event: ReactMouseEvent, node: Node, nodes: Node[]) => {
// TODO: 应当把事件中的 node 转换为 FlowViewNode 透出给用户
Expand All @@ -80,6 +108,8 @@ const FlowView: React.FC<Partial<FlowViewProps>> = (props) => {
[onNodeDragStart],
);

const handleNodeDragStop = useCallback(() => {}, []);

const handlePaneClick = useCallback(
(event: ReactMouseEvent) => {
// TODO: 应当把事件中的 node 转换为 FlowViewNode 透出给用户
Expand Down Expand Up @@ -109,13 +139,14 @@ const FlowView: React.FC<Partial<FlowViewProps>> = (props) => {

return (
<ReactFlow
className={cx(styles.container)}
className={cx(styles.container, className)}
onNodeDragStart={handleNodeDragStart}
onNodeDragStop={handleNodeDragStop}
onPaneClick={handlePaneClick}
onNodeClick={handleNodeClick}
onEdgeClick={handleEdgeClick}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodesChange={handleNodesChange}
onEdgesChange={handleEdgesChange}
nodes={renderNodes}
edges={renderEdges}
nodeTypes={nodeTypesMemo}
Expand All @@ -126,7 +157,13 @@ const FlowView: React.FC<Partial<FlowViewProps>> = (props) => {
maxZoom={maxZoom}
{...flowProps}
>
{miniMap && <MiniMap position={miniMapPosition} className={'pro-flow-controller'} />}
{miniMap && (
<MiniMap
language={Language.zh_CN}
position={miniMapPosition}
className={'pro-flow-controller'}
/>
)}
{children}
{background && (
<Background
Expand Down
19 changes: 16 additions & 3 deletions src/FlowView/provider/FlowViewProvider.tsx
@@ -1,7 +1,7 @@
import { FlowViewEdge, FlowViewNode, MiniMapPosition, NodeTypeDataMap } from '@/constants';
import { FC, ReactNode, useCallback, useEffect, useState } from 'react';
import { Edge, Node, ReactFlowProvider, useReactFlow } from 'reactflow';
import { NodeMapping, SelectType } from '../constants';
import { LayoutOptions, NodeMapping, SelectType } from '../constants';
import { convertMappingFrom, getRenderData } from '../helper';
import { FlowViewContext } from './provider';

Expand All @@ -14,25 +14,38 @@ const ProviderInner: FC<{ children: ReactNode }> = ({ children }) => {
const [initEdges, setInitEdges] = useState<FlowViewEdge[] | undefined>(undefined);
const [mapping, setMapping] = useState<NodeMapping>({});
const [autoLayout, setAutoLayout] = useState<boolean>(true);
const [layoutOptions, setLayoutOptions] = useState<LayoutOptions>({
rankdir: 'LR',
align: 'UL',
nodesep: 100,
ranksep: 200,
});

const convertRenderData = useCallback(() => {
const { nodes: _nodes, edges: _edges } = getRenderData(mapping, initEdges!, autoLayout);
const { nodes: _nodes, edges: _edges } = getRenderData(
mapping,
initEdges!,
autoLayout,
layoutOptions,
);
setNodes(_nodes);
setEdges(_edges);
}, [mapping, initEdges, autoLayout]);
}, [mapping, initEdges, autoLayout, layoutOptions]);

const flowDataAdapter = useCallback(
(
initNodes: FlowViewNode<keyof NodeTypeDataMap>[],
initEdges: FlowViewEdge[],
zoom: number,
autoLayout: boolean,
layoutOptions: LayoutOptions,
) => {
if (initNodes.length === 0) return;

setMapping(convertMappingFrom(initNodes!, initEdges!, zoom));
setInitEdges(initEdges);
setAutoLayout(autoLayout);
setLayoutOptions(layoutOptions);
},
[],
);
Expand Down
2 changes: 2 additions & 0 deletions src/FlowView/provider/provider.ts
Expand Up @@ -2,13 +2,15 @@ import { FlowViewEdge, FlowViewNode, MiniMapPosition, NodeTypeDataMap } from '@/
import { createContext } from 'react';
import { Edge, Node, ReactFlowInstance } from 'reactflow';
import { NodeMapping, SelectType } from '../constants';
import { LayoutOptions } from './../constants';

interface FlowViewContextProps {
flowDataAdapter?: (
nodes: FlowViewNode<keyof NodeTypeDataMap>[],
edges: FlowViewEdge[],
zoom: number,
autoLayout: boolean,
layoutOptions: LayoutOptions,
) => void;
nodes?: Node[];
edges?: Edge[];
Expand Down

0 comments on commit 4dced96

Please sign in to comment.