171 lines
4.7 KiB
TypeScript
171 lines
4.7 KiB
TypeScript
import { useCallback } from 'react';
|
|
import {
|
|
BaseNode,
|
|
internalsSymbol,
|
|
isNumeric,
|
|
errorMessages,
|
|
ConnectionMode,
|
|
Position,
|
|
getHandle,
|
|
OnError,
|
|
} from '@xyflow/system';
|
|
|
|
import { useStore } from '../hooks/useStore';
|
|
import { getEdgePositions, getNodeData, isEdgeVisible } from '../container/EdgeRenderer/utils';
|
|
import { type ReactFlowState, type NodeInternals, type Edge, EdgePosition } from '../types';
|
|
import { shallow } from 'zustand/shallow';
|
|
|
|
const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }];
|
|
|
|
type GroupedEdges = {
|
|
edges: Edge[];
|
|
level: number;
|
|
isMaxLevel: boolean;
|
|
};
|
|
|
|
function groupEdgesByZLevel(edges: Edge[], nodeInternals: NodeInternals, elevateEdgesOnSelect = false): GroupedEdges[] {
|
|
let maxLevel = -1;
|
|
|
|
const levelLookup = edges.reduce<Record<string, Edge[]>>((tree, edge) => {
|
|
const hasZIndex = isNumeric(edge.zIndex);
|
|
let z = hasZIndex ? edge.zIndex! : 0;
|
|
|
|
if (elevateEdgesOnSelect) {
|
|
z = hasZIndex
|
|
? edge.zIndex!
|
|
: Math.max(
|
|
nodeInternals.get(edge.source)?.[internalsSymbol]?.z || 0,
|
|
nodeInternals.get(edge.target)?.[internalsSymbol]?.z || 0
|
|
);
|
|
}
|
|
|
|
if (tree[z]) {
|
|
tree[z].push(edge);
|
|
} else {
|
|
tree[z] = [edge];
|
|
}
|
|
|
|
maxLevel = z > maxLevel ? z : maxLevel;
|
|
|
|
return tree;
|
|
}, {});
|
|
|
|
const edgeTree = Object.entries(levelLookup).map(([key, edges]) => {
|
|
const level = +key;
|
|
|
|
return {
|
|
edges,
|
|
level,
|
|
isMaxLevel: level === maxLevel,
|
|
};
|
|
});
|
|
|
|
if (edgeTree.length === 0) {
|
|
return defaultEdgeTree;
|
|
}
|
|
|
|
return edgeTree;
|
|
}
|
|
|
|
type LayoutEdgeParams = {
|
|
sourceNode: BaseNode;
|
|
sourceHandle: string | null;
|
|
targetNode: BaseNode;
|
|
targetHandle: string | null;
|
|
connectionMode: ConnectionMode;
|
|
onError?: OnError;
|
|
};
|
|
|
|
export function getEdgePosition(params: LayoutEdgeParams): EdgePosition | null {
|
|
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(params.sourceNode);
|
|
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(params.targetNode);
|
|
|
|
if (!sourceIsValid || !targetIsValid) {
|
|
return null;
|
|
}
|
|
|
|
// when connection type is loose we can define all handles as sources and connect source -> source
|
|
const targetNodeHandles =
|
|
params.connectionMode === ConnectionMode.Strict
|
|
? targetHandleBounds!.target
|
|
: (targetHandleBounds!.target ?? []).concat(targetHandleBounds!.source ?? []);
|
|
const sourceHandle = getHandle(sourceHandleBounds!.source!, params.sourceHandle);
|
|
const targetHandle = getHandle(targetNodeHandles!, params.targetHandle);
|
|
const sourcePosition = sourceHandle?.position || Position.Bottom;
|
|
const targetPosition = targetHandle?.position || Position.Top;
|
|
|
|
if (!sourceHandle || !targetHandle) {
|
|
params.onError?.('008', errorMessages['error008'](sourceHandle, {} as Edge));
|
|
|
|
return null;
|
|
}
|
|
|
|
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
|
sourceNodeRect,
|
|
sourceHandle,
|
|
sourcePosition,
|
|
targetNodeRect,
|
|
targetHandle,
|
|
targetPosition
|
|
);
|
|
|
|
return {
|
|
sourceX,
|
|
sourceY,
|
|
targetX,
|
|
targetY,
|
|
sourcePosition,
|
|
targetPosition,
|
|
};
|
|
}
|
|
|
|
function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): GroupedEdges[] {
|
|
const edges = useStore(
|
|
useCallback(
|
|
(s: ReactFlowState) => {
|
|
const visibleEdges = onlyRenderVisible
|
|
? s.edges.filter((e) => {
|
|
const sourceNode = s.nodeInternals.get(e.source);
|
|
const targetNode = s.nodeInternals.get(e.target);
|
|
|
|
return (
|
|
sourceNode?.width &&
|
|
sourceNode?.height &&
|
|
targetNode?.width &&
|
|
targetNode?.height &&
|
|
isEdgeVisible({
|
|
sourcePos: sourceNode.positionAbsolute || { x: 0, y: 0 },
|
|
targetPos: targetNode.positionAbsolute || { x: 0, y: 0 },
|
|
sourceWidth: sourceNode.width,
|
|
sourceHeight: sourceNode.height,
|
|
targetWidth: targetNode.width,
|
|
targetHeight: targetNode.height,
|
|
width: s.width,
|
|
height: s.height,
|
|
transform: s.transform,
|
|
})
|
|
);
|
|
})
|
|
: s.edges;
|
|
|
|
return groupEdgesByZLevel(visibleEdges, s.nodeInternals, elevateEdgesOnSelect);
|
|
},
|
|
[onlyRenderVisible, elevateEdgesOnSelect]
|
|
),
|
|
(groupA, groupB) => {
|
|
const unEqual = groupA.some(
|
|
(item, index) =>
|
|
item.isMaxLevel !== groupB[index].isMaxLevel ||
|
|
item.level !== groupB[index].level ||
|
|
!shallow(item.edges, groupB[index].edges)
|
|
);
|
|
|
|
return !unEqual;
|
|
}
|
|
);
|
|
|
|
return edges;
|
|
}
|
|
|
|
export default useVisibleEdges;
|