From c1e24990282171c1f9d0193fe4616ac06d99051b Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 8 Jun 2023 15:46:35 +0200 Subject: [PATCH] refactor(svelte/react): use edge layouting vanilla helpers --- .../vite-app/src/examples/Stress/index.tsx | 1 + .../react/src/components/Edges/wrapEdge.tsx | 12 +- .../react/src/container/EdgeRenderer/utils.ts | 113 +--------------- packages/react/src/hooks/useVisibleEdges.ts | 120 +---------------- packages/react/src/types/edges.ts | 10 +- .../components/EdgeWrapper/EdgeWrapper.svelte | 26 ++-- .../EdgeRenderer/EdgeRenderer.svelte | 23 +++- .../src/lib/container/EdgeRenderer/utils.ts | 58 -------- packages/svelte/src/lib/store/edge-tree.ts | 88 +++++++++++++ .../svelte/src/lib/store/edges-layouted.ts | 69 ---------- packages/svelte/src/lib/store/index.ts | 11 +- .../svelte/src/lib/store/initial-store.ts | 8 +- packages/svelte/src/lib/store/types.ts | 4 +- packages/svelte/src/lib/types/edges.ts | 46 +++---- .../svelte/src/routes/stress/+page.svelte | 4 +- .../svelte/src/routes/subflows/+page.svelte | 4 +- packages/system/src/constants.ts | 13 +- packages/system/src/types/edges.ts | 11 ++ packages/system/src/utils/edges/general.ts | 124 +++++++++++++++++- packages/system/src/utils/edges/index.ts | 1 + packages/system/src/utils/edges/positions.ts | 82 ++++++++++++ 21 files changed, 392 insertions(+), 436 deletions(-) delete mode 100644 packages/svelte/src/lib/container/EdgeRenderer/utils.ts create mode 100644 packages/svelte/src/lib/store/edge-tree.ts delete mode 100644 packages/svelte/src/lib/store/edges-layouted.ts create mode 100644 packages/system/src/utils/edges/positions.ts diff --git a/examples/vite-app/src/examples/Stress/index.tsx b/examples/vite-app/src/examples/Stress/index.tsx index 125c2764..ec1fd2fa 100644 --- a/examples/vite-app/src/examples/Stress/index.tsx +++ b/examples/vite-app/src/examples/Stress/index.tsx @@ -74,6 +74,7 @@ const StressFlow = () => { onConnect={onConnect} onNodesChange={onNodesChange} onEdgesChange={onEdgeChange} + minZoom={0.2} > diff --git a/packages/react/src/components/Edges/wrapEdge.tsx b/packages/react/src/components/Edges/wrapEdge.tsx index 3562b0f6..ea66d988 100644 --- a/packages/react/src/components/Edges/wrapEdge.tsx +++ b/packages/react/src/components/Edges/wrapEdge.tsx @@ -1,14 +1,13 @@ -import { memo, useState, useMemo, useRef, type ComponentType, type KeyboardEvent, useEffect } from 'react'; +import { memo, useState, useMemo, useRef, type ComponentType, type KeyboardEvent } from 'react'; import cc from 'classcat'; -import { getMarkerId, elementSelectionKeys, XYHandle, type Connection } from '@xyflow/system'; +import { shallow } from 'zustand/shallow'; +import { getMarkerId, elementSelectionKeys, XYHandle, type Connection, getEdgePosition } from '@xyflow/system'; import { useStoreApi, useStore } from '../../hooks/useStore'; import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions'; import { EdgeAnchor } from './EdgeAnchor'; import { getMouseHandler } from './utils'; import type { EdgeProps, WrapEdgeProps } from '../../types'; -import { getEdgePosition } from '../../hooks/useVisibleEdges'; -import { shallow } from 'zustand/shallow'; export default (EdgeComponent: ComponentType) => { const EdgeWrapper = ({ @@ -63,6 +62,7 @@ export default (EdgeComponent: ComponentType) => { } const pos = getEdgePosition({ + id, sourceNode, targetNode, sourceHandle: sourceHandleId || null, @@ -74,10 +74,6 @@ export default (EdgeComponent: ComponentType) => { return pos; }, shallow); - useEffect(() => { - // console.log(edgePosition); - }, [edgePosition]); - const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]); const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]); diff --git a/packages/react/src/container/EdgeRenderer/utils.ts b/packages/react/src/container/EdgeRenderer/utils.ts index 403e5f80..0e8f39d3 100644 --- a/packages/react/src/container/EdgeRenderer/utils.ts +++ b/packages/react/src/container/EdgeRenderer/utils.ts @@ -1,19 +1,8 @@ import type { ComponentType } from 'react'; -import { - internalsSymbol, - Position, - rectToBox, - getHandlePosition, - type HandleElement, - type NodeHandleBounds, - type Rect, - type Transform, - type XYPosition, -} from '@xyflow/system'; import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge } from '../../components/Edges'; import wrapEdge from '../../components/Edges/wrapEdge'; -import type { EdgeProps, EdgeTypes, EdgeTypesWrapped, Node } from '../../types'; +import type { EdgeProps, EdgeTypes, EdgeTypesWrapped } from '../../types'; export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped; @@ -40,103 +29,3 @@ export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypesWrapped { ...specialTypes, }; } - -interface EdgePositions { - sourceX: number; - sourceY: number; - targetX: number; - targetY: number; -} - -export const getEdgePositions = ( - sourceNodeRect: Rect, - sourceHandle: HandleElement, - sourcePosition: Position, - targetNodeRect: Rect, - targetHandle: HandleElement, - targetPosition: Position -): EdgePositions => { - const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle); - const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle); - - return { - sourceX: sourceHandlePos.x, - sourceY: sourceHandlePos.y, - targetX: targetHandlePos.x, - targetY: targetHandlePos.y, - }; -}; - -interface 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; -} - -export function getNodeData(node?: Node): [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, - ]; -} diff --git a/packages/react/src/hooks/useVisibleEdges.ts b/packages/react/src/hooks/useVisibleEdges.ts index e59fd337..10a387fb 100644 --- a/packages/react/src/hooks/useVisibleEdges.ts +++ b/packages/react/src/hooks/useVisibleEdges.ts @@ -1,125 +1,11 @@ import { useCallback } from 'react'; -import { - BaseNode, - internalsSymbol, - isNumeric, - errorMessages, - ConnectionMode, - Position, - getHandle, - OnError, -} from '@xyflow/system'; +import { GroupedEdges, groupEdgesByZLevel, isEdgeVisible } 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 { Edge, type ReactFlowState } 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>((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[] { +function useVisibleEdges(onlyRenderVisible: boolean, elevateEdgesOnSelect: boolean): GroupedEdges[] { const edges = useStore( useCallback( (s: ReactFlowState) => { diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 6f13d97c..e470b918 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -12,6 +12,7 @@ import type { ConnectionLineType, HandleElement, ConnectionStatus, + EdgePosition, } from '@xyflow/system'; import { Node } from '.'; @@ -124,12 +125,3 @@ export type ConnectionLineComponentProps = { }; export type ConnectionLineComponent = ComponentType; - -export type EdgePosition = { - sourceX: number; - sourceY: number; - targetX: number; - targetY: number; - sourcePosition: Position; - targetPosition: Position; -}; diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte index 9ee06fca..d1191a45 100644 --- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte +++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte @@ -1,10 +1,10 @@ - - {#each $edgesLayouted as edge (edge.id)} - - {/each} +{#each $edgeTree as group (group.level)} + + {#if group.isMaxLevel} {/if} + + {#each group.edges as edge (edge.id)} + {@const edgeType = edge.type || 'default'} + {@const selectable = !!( + edge.selectable || + ($elementsSelectable && typeof edge.selectable === 'undefined') + )} - - + + {/each} + + +{/each}