refactor(svelte/react): use edge layouting vanilla helpers

This commit is contained in:
moklick
2023-06-08 15:46:35 +02:00
parent 4707a07c8b
commit c1e2499028
21 changed files with 392 additions and 436 deletions
+123 -1
View File
@@ -1,4 +1,14 @@
import { Position, type HandleElement, type MarkerType, type Rect, type XYPosition } from '../../types';
import { Transform, internalsSymbol } from '../..';
import {
Position,
type HandleElement,
type MarkerType,
type Rect,
type XYPosition,
BaseEdge,
BaseNode,
} from '../../types';
import { isNumeric, rectToBox } from '../utils';
// this is used for straight edges and simple smoothstep edges (LTR, RTL, BTT, TTB)
export function getEdgeCenter({
@@ -72,3 +82,115 @@ export function getHandle(bounds: HandleElement[], handleId?: string | null): Ha
return null;
}
const defaultEdgeTree = [{ level: 0, isMaxLevel: true, edges: [] }];
export type GroupedEdges<EdgeType extends BaseEdge> = {
edges: EdgeType[];
level: number;
isMaxLevel: boolean;
};
export function groupEdgesByZLevel<EdgeType extends BaseEdge>(
edges: EdgeType[],
nodes: Map<string, BaseNode> | BaseNode[],
elevateEdgesOnSelect = false
): GroupedEdges<EdgeType>[] {
let maxLevel = -1;
const isNodeInternals = 'get' in nodes;
const levelLookup = edges.reduce<Record<string, EdgeType[]>>((tree, edge) => {
const hasZIndex = isNumeric(edge.zIndex);
let z = hasZIndex ? edge.zIndex! : 0;
if (elevateEdgesOnSelect) {
z = hasZIndex
? edge.zIndex!
: Math.max(
(isNodeInternals ? nodes.get(edge.source) : nodes.find((n) => n.id === edge.source))?.[internalsSymbol]
?.z || 0,
(isNodeInternals ? nodes.get(edge.target) : nodes.find((n) => n.id === 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 IsEdgeVisibleParams = {
sourcePos: XYPosition;
targetPos: XYPosition;
sourceWidth: number;
sourceHeight: number;
targetWidth: number;
targetHeight: number;
width: number;
height: number;
transform: Transform;
};
export function isEdgeVisible({
sourcePos,
targetPos,
sourceWidth,
sourceHeight,
targetWidth,
targetHeight,
width,
height,
transform,
}: IsEdgeVisibleParams): boolean {
const edgeBox = {
x: Math.min(sourcePos.x, targetPos.x),
y: Math.min(sourcePos.y, targetPos.y),
x2: Math.max(sourcePos.x + sourceWidth, targetPos.x + targetWidth),
y2: Math.max(sourcePos.y + sourceHeight, targetPos.y + targetHeight),
};
if (edgeBox.x === edgeBox.x2) {
edgeBox.x2 += 1;
}
if (edgeBox.y === edgeBox.y2) {
edgeBox.y2 += 1;
}
const viewBox = rectToBox({
x: (0 - transform[0]) / transform[2],
y: (0 - transform[1]) / transform[2],
width: width / transform[2],
height: height / transform[2],
});
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x));
const yOverlap = Math.max(0, Math.min(viewBox.y2, edgeBox.y2) - Math.max(viewBox.y, edgeBox.y));
const overlappingArea = Math.ceil(xOverlap * yOverlap);
return overlappingArea > 0;
}
+1
View File
@@ -2,3 +2,4 @@ export * from './bezier-edge';
export * from './straight-edge';
export * from './smoothstep-edge';
export * from './general';
export * from './positions';
@@ -0,0 +1,82 @@
import { EdgePosition } from '../../types/edges';
import { ConnectionMode, OnError } from '../../types/general';
import { BaseNode, NodeHandleBounds } from '../../types/nodes';
import { Position, Rect } from '../../types/utils';
import { errorMessages, internalsSymbol } from '../../constants';
import { getHandle, getHandlePosition } from './general';
export function getHandleDataByNode(node?: BaseNode): [Rect, NodeHandleBounds | null, boolean] {
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
const isValid =
handleBounds &&
node?.width &&
node?.height &&
typeof node?.positionAbsolute?.x !== 'undefined' &&
typeof node?.positionAbsolute?.y !== 'undefined';
return [
{
x: node?.positionAbsolute?.x || 0,
y: node?.positionAbsolute?.y || 0,
width: node?.width || 0,
height: node?.height || 0,
},
handleBounds,
!!isValid,
];
}
export type GetEdgePositionParams = {
id: string;
sourceNode: BaseNode;
sourceHandle: string | null;
targetNode: BaseNode;
targetHandle: string | null;
connectionMode: ConnectionMode;
onError?: OnError;
};
export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null {
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getHandleDataByNode(params.sourceNode);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getHandleDataByNode(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 ? 'source' : 'target', {
id: params.id,
sourceHandle: params.sourceHandle,
targetHandle: params.targetHandle,
})
);
return null;
}
const { x: sourceX, y: sourceY } = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
const { x: targetX, y: targetY } = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
return {
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
};
}