feat: performance 1

This commit is contained in:
2026-03-12 18:01:27 +01:00
parent 4bd2f1b458
commit 923bf4bff0
18 changed files with 404 additions and 146 deletions

View File

@@ -1,18 +1,17 @@
import React, { useContext, useMemo } from 'react'
import React, { useContext, useMemo, memo } from 'react'
import {
BaseEdge,
getBezierPath,
type EdgeProps,
} from '@xyflow/react'
import FlowContext from '@/lib/graph/flowContext'
import { ConnectionPathContext } from '@/lib/graph/flowContext'
import { getConnectionStatus, CONNECTION_STATUS_CLASS } from '@/lib/graph/connectionStatus'
import { getConnectionLabelForTarget } from '@/lib/graph/nodeRegistry'
const EDGE_STROKE_WIDTH = 2
const DOT_MARKER_R = 1.5
const EMPTY_PATH_NODE_IDS = new Set<string>()
export function AnimatedEdge({
function AnimatedEdgeInner({
id,
source,
sourceX,
@@ -25,9 +24,9 @@ export function AnimatedEdge({
label: labelProp,
interactionWidth,
target,
data,
}: EdgeProps) {
const ctx = useContext(FlowContext)
const nodes = ctx?.nodes ?? []
const ctx = useContext(ConnectionPathContext)
const pathNodeIds = ctx?.connectionPathNodeIds ?? EMPTY_PATH_NODE_IDS
const pausedSegmentNodeIds = ctx?.connectionPathPausedSegmentNodeIds ?? EMPTY_PATH_NODE_IDS
const activeSegmentNodeIds = ctx?.connectionPathActiveSegmentNodeIds ?? EMPTY_PATH_NODE_IDS
@@ -35,12 +34,8 @@ export function AnimatedEdge({
() => new Set(ctx?.connectionPathErrorNodeIds ?? []),
[ctx?.connectionPathErrorNodeIds]
)
const targetNode = useMemo(() => nodes.find((n: any) => n.id === target), [nodes, target])
const derivedLabel = useMemo(
() => (targetNode?.type != null ? getConnectionLabelForTarget(targetNode.type) : undefined),
[targetNode?.type]
)
const label = labelProp ?? derivedLabel
const label = labelProp ?? (data as { connectionLabel?: string } | undefined)?.connectionLabel
const connectionStatus = useMemo(
() =>
@@ -146,3 +141,23 @@ export function AnimatedEdge({
</>
)
}
function edgePropsAreEqual(prev: EdgeProps, next: EdgeProps): boolean {
return (
prev.id === next.id &&
prev.source === next.source &&
prev.target === next.target &&
prev.sourceX === next.sourceX &&
prev.sourceY === next.sourceY &&
prev.targetX === next.targetX &&
prev.targetY === next.targetY &&
prev.sourcePosition === next.sourcePosition &&
prev.targetPosition === next.targetPosition &&
prev.style === next.style &&
prev.label === next.label &&
(prev.data as { connectionLabel?: string } | undefined)?.connectionLabel ===
(next.data as { connectionLabel?: string } | undefined)?.connectionLabel
)
}
export const AnimatedEdge = memo(AnimatedEdgeInner, edgePropsAreEqual)

View File

@@ -2,8 +2,7 @@ import type { ComponentProps, ReactNode } from "react";
import { NodeResizer } from "@xyflow/react";
import { useContext } from "react";
import FlowContext from "@/lib/graph/flowContext";
import { useConnectionPathRole } from "@/lib/graph/flowContext";
import { FlowUIContext, useConnectionPathRole } from "@/lib/graph/flowContext";
import { cn } from "@/lib/utils";
/** Default min size for resizable nodes (used by NodeResizer). */
@@ -37,8 +36,8 @@ export function BaseNode({
resizeConstraints,
...props
}: BaseNodeProps) {
const flowContext = useContext(FlowContext);
const isFullscreenInstance = Boolean(nodeId && flowContext?.fullscreenNodeId === nodeId);
const flowUIContext = useContext(FlowUIContext);
const isFullscreenInstance = Boolean(nodeId && flowUIContext?.fullscreenNodeId === nodeId);
const connectionPathRole = useConnectionPathRole(nodeId);
const hasSize =
dimensions &&

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useContext, useEffect } from 'react'
import { useReactFlow } from '@xyflow/react'
import type { Node } from '@xyflow/react'
import FlowContext from '@/lib/graph/flowContext'
import { GraphContext, FlowUIContext } from '@/lib/graph/flowContext'
import { getNextNodeId, getDefaultDataForType } from '@/lib/graph/flowUtils'
import { getDefaultStyle, getRegisteredNodeTypeIds } from '@/lib/graph/nodeRegistry'
@@ -14,11 +14,12 @@ function isMod(ev: KeyboardEvent) {
/** Must be rendered inside ReactFlowProvider and FlowContext. Handles Escape, ⌘C, ⌘V, ⌘D, ⌘0. */
export function FlowKeyboardShortcuts() {
const { fitView, screenToFlowPosition } = useReactFlow()
const ctx = useContext(FlowContext)
const nodes = ctx?.nodes ?? []
const setNodes = ctx?.setNodes
const setConnectionFrom = ctx?.setConnectionFrom
const flowActionsRef = ctx?.flowActionsRef
const graphCtx = useContext(GraphContext)
const uiCtx = useContext(FlowUIContext)
const nodes = graphCtx?.nodes ?? []
const setNodes = graphCtx?.setNodes
const setConnectionFrom = uiCtx?.setConnectionFrom
const flowActionsRef = uiCtx?.flowActionsRef
const pasteAtViewportCenter = useCallback(async () => {
if (!setNodes || !screenToFlowPosition) return

View File

@@ -1,5 +1,5 @@
import React, { useContext, useMemo } from 'react'
import FlowContext from '@/lib/graph/flowContext'
import { GraphContext } from '@/lib/graph/flowContext'
import { ArrowDownLeft, ArrowUpRight } from 'lucide-react'
import { NodeHelpPopover } from '@/components/graph/NodeHelpPopover'
import { getNodeType, getNodeClassificationLabel } from '@/lib/graph/nodeRegistry'
@@ -11,7 +11,7 @@ type Props = {
}
export function NodeFooterEdgeIndicators({ nodeId, nodeType, children }: Props) {
const ctx = useContext(FlowContext)
const ctx = useContext(GraphContext)
const edges = ctx?.edges ?? []
const { inputs, outputs } = useMemo(() => {

View File

@@ -1,7 +1,7 @@
import React, { useContext } from 'react'
import { Handle, Position } from '@xyflow/react'
import { ArrowDownLeft, ArrowUpRight } from 'lucide-react'
import FlowContext from '@/lib/graph/flowContext'
import { FlowUIContext } from '@/lib/graph/flowContext'
import { cn } from '@/lib/utils'
type NodeHandleProps = {
@@ -11,7 +11,7 @@ type NodeHandleProps = {
}
export function InputHandle({ id, nodeId }: NodeHandleProps) {
const ctx = useContext(FlowContext)
const ctx = useContext(FlowUIContext)
const connectionFrom = ctx?.connectionFrom ?? null
const isValidConnection = ctx?.isValidConnection
const isConnecting = Boolean(nodeId && connectionFrom && connectionFrom.nodeId !== nodeId)

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'
import FlowContext from '@/lib/graph/flowContext'
import { GraphContext, FlowUIContext } from '@/lib/graph/flowContext'
import type { AppNode } from '@/lib/graph/nodeTypes'
import { replaceNodeIdInGraph } from '@/lib/graph/flowUtils'
@@ -9,13 +9,14 @@ type Props = {
}
export function NodeHeaderTitle({ nodeId, displayTitle }: Props) {
const ctx = useContext(FlowContext)
const nodes = ctx?.nodes ?? []
const setNodes = ctx?.setNodes
const edges = ctx?.edges ?? []
const setEdges = ctx?.setEdges
const renamingNodeId = ctx?.renamingNodeId ?? null
const setRenamingNodeId = ctx?.setRenamingNodeId
const graphCtx = useContext(GraphContext)
const uiCtx = useContext(FlowUIContext)
const nodes = graphCtx?.nodes ?? []
const setNodes = graphCtx?.setNodes
const edges = graphCtx?.edges ?? []
const setEdges = graphCtx?.setEdges
const renamingNodeId = uiCtx?.renamingNodeId ?? null
const setRenamingNodeId = uiCtx?.setRenamingNodeId
const [inputValue, setInputValue] = useState(nodeId)
const inputRef = useRef<HTMLInputElement>(null)

View File

@@ -4,7 +4,7 @@
* it is taken from getNodeType(nodeType)?.getNodeMenuExtraContent?.(nodeId, data).
*/
import React, { useCallback, useContext, useMemo } from 'react'
import FlowContext from '@/lib/graph/flowContext'
import { GraphContext, FlowUIContext } from '@/lib/graph/flowContext'
import { getNodeType } from '@/lib/graph/nodeRegistry'
import {
Menubar,
@@ -43,12 +43,13 @@ type Props = {
}
export function NodeMenubar({ nodeId, nodeType, editInputsContent, inputsMenuContent, insertTagsContent, insertMenuLabel = 'Insert', insertContentDirect, insertTagsLabel = 'Tags', nodeMenuExtraContent: nodeMenuExtraContentProp, outputMenuContent, dataMenuContent }: Props) {
const ctx = useContext(FlowContext)
const nodes = ctx?.nodes ?? []
const setNodes = ctx?.setNodes
const setEdges = ctx?.setEdges
const graphCtx = useContext(GraphContext)
const uiCtx = useContext(FlowUIContext)
const nodes = graphCtx?.nodes ?? []
const setNodes = graphCtx?.setNodes
const setEdges = graphCtx?.setEdges
const edges = ctx?.edges ?? []
const edges = graphCtx?.edges ?? []
const node = nodes.find((n: any) => n.id === nodeId)
const nodeMenuExtraContent = useMemo(
() => nodeMenuExtraContentProp ?? getNodeType(nodeType)?.getNodeMenuExtraContent?.(nodeId, node?.data ?? {}),
@@ -64,8 +65,8 @@ export function NodeMenubar({ nodeId, nodeType, editInputsContent, inputsMenuCon
}, [nodeId, setNodes, setEdges])
const onRename = useCallback(() => {
ctx?.setRenamingNodeId?.(nodeId)
}, [nodeId, ctx])
uiCtx?.setRenamingNodeId?.(nodeId)
}, [nodeId, uiCtx])
return (
<Menubar className="h-auto min-h-0 flex items-center bg-none p-1 border-t-0 border-l-0 border-r-0 border-b border-b-secondary shadow-none rounded-none text-muted-foreground">