refactor: performance
This commit is contained in:
@@ -31,7 +31,7 @@ import {
|
|||||||
import { useTheme } from '@/lib/themeContext'
|
import { useTheme } from '@/lib/themeContext'
|
||||||
import { usePlatform } from '@/app/kosmos/KosmosContext'
|
import { usePlatform } from '@/app/kosmos/KosmosContext'
|
||||||
import { useCanvasGraph } from '@/app/canvas/useCanvasGraph'
|
import { useCanvasGraph } from '@/app/canvas/useCanvasGraph'
|
||||||
import { getExampleGraph } from '@/app/canvas/canvasGraphUtils'
|
import { getExampleGraph, backfillEdgeTargetTypes } from '@/app/canvas/canvasGraphUtils'
|
||||||
import { ContextMenu, ContextMenuTrigger } from '@/components/ui/context-menu'
|
import { ContextMenu, ContextMenuTrigger } from '@/components/ui/context-menu'
|
||||||
import { CanvasContextMenuContent } from '@/app/canvas/CanvasContextMenuContent'
|
import { CanvasContextMenuContent } from '@/app/canvas/CanvasContextMenuContent'
|
||||||
import { useCanvasConnectionPath } from '@/app/canvas/useCanvasConnectionPath'
|
import { useCanvasConnectionPath } from '@/app/canvas/useCanvasConnectionPath'
|
||||||
@@ -189,6 +189,9 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
|||||||
|
|
||||||
const nodesRef = useRef(nodes)
|
const nodesRef = useRef(nodes)
|
||||||
nodesRef.current = nodes
|
nodesRef.current = nodes
|
||||||
|
const graphRef = useRef<{ nodes: AppNode[]; edges: AppEdge[] }>({ nodes: [], edges: [] })
|
||||||
|
graphRef.current.nodes = nodes
|
||||||
|
graphRef.current.edges = edges
|
||||||
|
|
||||||
const [apiTodosCount, setApiTodosCount] = React.useState<number | null>(null)
|
const [apiTodosCount, setApiTodosCount] = React.useState<number | null>(null)
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -244,7 +247,12 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
|
(params: Connection) => {
|
||||||
|
const targetType =
|
||||||
|
nodesRef.current.find((n) => n.id === params.target)?.type ?? ''
|
||||||
|
const conn = { ...params, data: { targetType } as Record<string, unknown> }
|
||||||
|
setEdges((eds) => addEdge(conn, eds))
|
||||||
|
},
|
||||||
[setEdges]
|
[setEdges]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -324,7 +332,9 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
|||||||
toast.error('Invalid file: expected nodes and edges arrays')
|
toast.error('Invalid file: expected nodes and edges arrays')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStateImmediate({ nodes: state.nodes as AppNode[], edges: state.edges as AppEdge[] })
|
const nodes = state.nodes as AppNode[]
|
||||||
|
const edges = backfillEdgeTargetTypes(nodes, state.edges as AppEdge[])
|
||||||
|
setStateImmediate({ nodes, edges })
|
||||||
if (state.version != null && state.version > PROJECT_VERSION) {
|
if (state.version != null && state.version > PROJECT_VERSION) {
|
||||||
toast.error('Project was created with a newer app version')
|
toast.error('Project was created with a newer app version')
|
||||||
} else {
|
} else {
|
||||||
@@ -393,8 +403,8 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const graphContextValue = useMemo(
|
const graphContextValue = useMemo(
|
||||||
() => ({ nodes, setNodes, edges, setEdges }),
|
() => ({ setNodes, setEdges, graphRef, edges }),
|
||||||
[nodes, setNodes, edges, setEdges]
|
[setNodes, setEdges, edges]
|
||||||
)
|
)
|
||||||
const connectionPathContextValue = useMemo(
|
const connectionPathContextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -438,18 +448,36 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const nodesForFlow = useMemo(
|
const prevNodesRef = useRef<AppNode[]>([])
|
||||||
() =>
|
const prevNodesForFlowRef = useRef<Node[]>([])
|
||||||
nodes.map((n) => ({
|
const nodesForFlow = useMemo(() => {
|
||||||
|
const prev = prevNodesRef.current
|
||||||
|
if (nodes === prev) return prevNodesForFlowRef.current
|
||||||
|
const prevById = new Map(prev.map((n) => [n.id, n]))
|
||||||
|
const prevWrappedById = new Map(
|
||||||
|
prevNodesForFlowRef.current.map((w, i) => [prev[i]?.id, w])
|
||||||
|
)
|
||||||
|
const result = nodes.map((n) => {
|
||||||
|
const prevNode = prevById.get(n.id)
|
||||||
|
if (prevNode === n && prevWrappedById.has(n.id)) {
|
||||||
|
return prevWrappedById.get(n.id)!
|
||||||
|
}
|
||||||
|
return {
|
||||||
...n,
|
...n,
|
||||||
className: [n.className, 'nowheel'].filter(Boolean).join(' '),
|
className: [n.className, 'nowheel'].filter(Boolean).join(' '),
|
||||||
})),
|
}
|
||||||
[nodes]
|
})
|
||||||
)
|
prevNodesRef.current = nodes
|
||||||
|
prevNodesForFlowRef.current = result
|
||||||
|
return result
|
||||||
|
}, [nodes])
|
||||||
const edgesForFlow = useMemo(
|
const edgesForFlow = useMemo(
|
||||||
() =>
|
() =>
|
||||||
edges.map((e) => {
|
edges.map((e) => {
|
||||||
const targetType = nodes.find((nd) => nd.id === e.target)?.type ?? ''
|
const targetType =
|
||||||
|
(typeof e.data === 'object' && e.data !== null && (e.data as Record<string, unknown>).targetType != null
|
||||||
|
? (e.data as Record<string, unknown>).targetType
|
||||||
|
: '') as string
|
||||||
const connectionLabel = getConnectionLabelForTarget(targetType)
|
const connectionLabel = getConnectionLabelForTarget(targetType)
|
||||||
const baseData =
|
const baseData =
|
||||||
typeof e.data === 'object' && e.data !== null ? (e.data as Record<string, unknown>) : {}
|
typeof e.data === 'object' && e.data !== null ? (e.data as Record<string, unknown>) : {}
|
||||||
@@ -458,7 +486,7 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
|||||||
data: { ...baseData, connectionLabel },
|
data: { ...baseData, connectionLabel },
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[edges, nodes]
|
[edges]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onContextMenuCapture = useCallback((ev: React.MouseEvent) => {
|
const onContextMenuCapture = useCallback((ev: React.MouseEvent) => {
|
||||||
|
|||||||
@@ -17,25 +17,34 @@ export { ViewportDisplayContext }
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be rendered inside ReactFlowProvider. Subscribes to viewport once,
|
* Must be rendered inside ReactFlowProvider. Subscribes to viewport once,
|
||||||
* maps zoom to displayMode with hysteresis, and provides it to descendants.
|
* maps zoom to displayMode with hysteresis. Throttles updates via rAF to avoid
|
||||||
|
* re-rendering all contextual nodes on every zoom tick.
|
||||||
*/
|
*/
|
||||||
export function ViewportDisplayProvider({ children }: { children: React.ReactNode }) {
|
export function ViewportDisplayProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { zoom } = useViewport()
|
const { zoom } = useViewport()
|
||||||
const [displayMode, setDisplayMode] = useState<ViewportDisplayMode>(() =>
|
const [displayMode, setDisplayMode] = useState<ViewportDisplayMode>(() =>
|
||||||
zoom <= CONTEXTUAL_ZOOM_THRESHOLD ? 'compact' : 'full'
|
zoom <= CONTEXTUAL_ZOOM_THRESHOLD ? 'compact' : 'full'
|
||||||
)
|
)
|
||||||
const lastRef = useRef(displayMode)
|
const lastModeRef = useRef(displayMode)
|
||||||
|
const zoomRef = useRef(zoom)
|
||||||
|
const rafRef = useRef<number | null>(null)
|
||||||
|
zoomRef.current = zoom
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const low = CONTEXTUAL_ZOOM_THRESHOLD - HYSTERESIS
|
if (rafRef.current !== null) return
|
||||||
const high = CONTEXTUAL_ZOOM_THRESHOLD + HYSTERESIS
|
rafRef.current = requestAnimationFrame(() => {
|
||||||
let next: ViewportDisplayMode = lastRef.current
|
rafRef.current = null
|
||||||
if (zoom <= low) next = 'compact'
|
const z = zoomRef.current
|
||||||
else if (zoom >= high) next = 'full'
|
const low = CONTEXTUAL_ZOOM_THRESHOLD - HYSTERESIS
|
||||||
if (next !== lastRef.current) {
|
const high = CONTEXTUAL_ZOOM_THRESHOLD + HYSTERESIS
|
||||||
lastRef.current = next
|
let next: ViewportDisplayMode = lastModeRef.current
|
||||||
setDisplayMode(next)
|
if (z <= low) next = 'compact'
|
||||||
}
|
else if (z >= high) next = 'full'
|
||||||
|
if (next !== lastModeRef.current) {
|
||||||
|
lastModeRef.current = next
|
||||||
|
setDisplayMode(next)
|
||||||
|
}
|
||||||
|
})
|
||||||
}, [zoom])
|
}, [zoom])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,6 +7,20 @@ import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
|
|||||||
import { DEFAULT_NODE_STYLE } from '@/lib/graph/flowUtils'
|
import { DEFAULT_NODE_STYLE } from '@/lib/graph/flowUtils'
|
||||||
import { loadGraphFromStorage } from '@/app/pleroma/projectGraphStorage'
|
import { loadGraphFromStorage } from '@/app/pleroma/projectGraphStorage'
|
||||||
|
|
||||||
|
/** Ensure each edge has data.targetType from the target node (for labels without depending on nodes in edgesForFlow). */
|
||||||
|
export function backfillEdgeTargetTypes(
|
||||||
|
nodes: AppNode[],
|
||||||
|
edges: AppEdge[]
|
||||||
|
): AppEdge[] {
|
||||||
|
const typeById = new Map(nodes.map((n) => [n.id, n.type ?? '']))
|
||||||
|
return edges.map((e) => {
|
||||||
|
const targetType = typeById.get(e.target) ?? (e.data as Record<string, unknown>)?.targetType ?? ''
|
||||||
|
const data = typeof e.data === 'object' && e.data !== null ? (e.data as Record<string, unknown>) : {}
|
||||||
|
if (data.targetType === targetType) return e
|
||||||
|
return { ...e, data: { ...data, targetType } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const NODE_GAP = 150
|
const NODE_GAP = 150
|
||||||
|
|
||||||
const EXAMPLE_NODES: AppNode[] = [
|
const EXAMPLE_NODES: AppNode[] = [
|
||||||
@@ -45,20 +59,24 @@ const EXAMPLE_EDGES: AppEdge[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export function getExampleGraph(): { nodes: AppNode[]; edges: AppEdge[] } {
|
export function getExampleGraph(): { nodes: AppNode[]; edges: AppEdge[] } {
|
||||||
return {
|
const nodes = EXAMPLE_NODES.map((n) => ({
|
||||||
nodes: EXAMPLE_NODES.map((n) => ({
|
...n,
|
||||||
...n,
|
data: n.data && typeof n.data === 'object' ? { ...(n.data as object) } : n.data,
|
||||||
data: n.data && typeof n.data === 'object' ? { ...(n.data as object) } : n.data,
|
}))
|
||||||
})),
|
const edges = backfillEdgeTargetTypes(
|
||||||
edges: EXAMPLE_EDGES.map((e) => ({ ...e })),
|
nodes,
|
||||||
}
|
EXAMPLE_EDGES.map((e) => ({ ...e }))
|
||||||
|
)
|
||||||
|
return { nodes, edges }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInitialGraph(projectId: string | undefined): { nodes: AppNode[]; edges: AppEdge[] } {
|
export function getInitialGraph(projectId: string | undefined): { nodes: AppNode[]; edges: AppEdge[] } {
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
const stored = loadGraphFromStorage(projectId)
|
const stored = loadGraphFromStorage(projectId)
|
||||||
if (stored && (stored.nodes.length > 0 || stored.edges.length > 0)) {
|
if (stored && (stored.nodes.length > 0 || stored.edges.length > 0)) {
|
||||||
return { nodes: stored.nodes as AppNode[], edges: stored.edges as AppEdge[] }
|
const nodes = stored.nodes as AppNode[]
|
||||||
|
const edges = backfillEdgeTargetTypes(nodes, stored.edges as AppEdge[])
|
||||||
|
return { nodes, edges }
|
||||||
}
|
}
|
||||||
return { nodes: [], edges: [] }
|
return { nodes: [], edges: [] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export function FlowKeyboardShortcuts() {
|
|||||||
const { fitView, screenToFlowPosition } = useReactFlow()
|
const { fitView, screenToFlowPosition } = useReactFlow()
|
||||||
const graphCtx = useContext(GraphContext)
|
const graphCtx = useContext(GraphContext)
|
||||||
const uiCtx = useContext(FlowUIContext)
|
const uiCtx = useContext(FlowUIContext)
|
||||||
const nodes = graphCtx?.nodes ?? []
|
|
||||||
const setNodes = graphCtx?.setNodes
|
const setNodes = graphCtx?.setNodes
|
||||||
const setConnectionFrom = uiCtx?.setConnectionFrom
|
const setConnectionFrom = uiCtx?.setConnectionFrom
|
||||||
const flowActionsRef = uiCtx?.flowActionsRef
|
const flowActionsRef = uiCtx?.flowActionsRef
|
||||||
@@ -80,6 +79,7 @@ export function FlowKeyboardShortcuts() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (ev: KeyboardEvent) => {
|
const onKeyDown = (ev: KeyboardEvent) => {
|
||||||
|
const nodes = graphCtx?.graphRef?.current?.nodes ?? []
|
||||||
if (ev.key === 'Escape') {
|
if (ev.key === 'Escape') {
|
||||||
const openDialog = document.querySelector('[role="dialog"]')
|
const openDialog = document.querySelector('[role="dialog"]')
|
||||||
if (openDialog && ev.target instanceof Node && openDialog.contains(ev.target)) return
|
if (openDialog && ev.target instanceof Node && openDialog.contains(ev.target)) return
|
||||||
@@ -152,7 +152,7 @@ export function FlowKeyboardShortcuts() {
|
|||||||
window.addEventListener('keydown', onKeyDown, true)
|
window.addEventListener('keydown', onKeyDown, true)
|
||||||
return () => window.removeEventListener('keydown', onKeyDown, true)
|
return () => window.removeEventListener('keydown', onKeyDown, true)
|
||||||
}, [
|
}, [
|
||||||
nodes,
|
graphCtx?.graphRef,
|
||||||
setNodes,
|
setNodes,
|
||||||
setConnectionFrom,
|
setConnectionFrom,
|
||||||
pasteAtViewportCenter,
|
pasteAtViewportCenter,
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ type Props = {
|
|||||||
export function NodeHeaderTitle({ nodeId, displayTitle }: Props) {
|
export function NodeHeaderTitle({ nodeId, displayTitle }: Props) {
|
||||||
const graphCtx = useContext(GraphContext)
|
const graphCtx = useContext(GraphContext)
|
||||||
const uiCtx = useContext(FlowUIContext)
|
const uiCtx = useContext(FlowUIContext)
|
||||||
const nodes = graphCtx?.nodes ?? []
|
|
||||||
const setNodes = graphCtx?.setNodes
|
const setNodes = graphCtx?.setNodes
|
||||||
const edges = graphCtx?.edges ?? []
|
|
||||||
const setEdges = graphCtx?.setEdges
|
const setEdges = graphCtx?.setEdges
|
||||||
const renamingNodeId = uiCtx?.renamingNodeId ?? null
|
const renamingNodeId = uiCtx?.renamingNodeId ?? null
|
||||||
const setRenamingNodeId = uiCtx?.setRenamingNodeId
|
const setRenamingNodeId = uiCtx?.setRenamingNodeId
|
||||||
@@ -32,7 +30,9 @@ export function NodeHeaderTitle({ nodeId, displayTitle }: Props) {
|
|||||||
}, [isRenaming, nodeId])
|
}, [isRenaming, nodeId])
|
||||||
|
|
||||||
const applyRename = useCallback(() => {
|
const applyRename = useCallback(() => {
|
||||||
if (!setNodes || !setEdges || !setRenamingNodeId) return
|
if (!setNodes || !setEdges || !setRenamingNodeId || !graphCtx?.graphRef) return
|
||||||
|
const nodes = graphCtx.graphRef.current.nodes
|
||||||
|
const edges = graphCtx.graphRef.current.edges
|
||||||
const newId = inputValue.trim()
|
const newId = inputValue.trim()
|
||||||
if (!newId || newId === nodeId) {
|
if (!newId || newId === nodeId) {
|
||||||
setRenamingNodeId(null)
|
setRenamingNodeId(null)
|
||||||
@@ -46,7 +46,7 @@ export function NodeHeaderTitle({ nodeId, displayTitle }: Props) {
|
|||||||
setNodes(nextNodes as AppNode[])
|
setNodes(nextNodes as AppNode[])
|
||||||
setEdges(nextEdges)
|
setEdges(nextEdges)
|
||||||
setRenamingNodeId(null)
|
setRenamingNodeId(null)
|
||||||
}, [nodeId, inputValue, nodes, edges, setNodes, setEdges, setRenamingNodeId])
|
}, [nodeId, inputValue, graphCtx?.graphRef, setNodes, setEdges, setRenamingNodeId])
|
||||||
|
|
||||||
const cancelRename = useCallback(() => {
|
const cancelRename = useCallback(() => {
|
||||||
setRenamingNodeId?.(null)
|
setRenamingNodeId?.(null)
|
||||||
|
|||||||
@@ -45,10 +45,9 @@ type Props = {
|
|||||||
export function NodeMenubar({ nodeId, nodeType, editInputsContent, inputsMenuContent, insertTagsContent, insertMenuLabel = 'Insert', insertContentDirect, insertTagsLabel = 'Tags', nodeMenuExtraContent: nodeMenuExtraContentProp, outputMenuContent, dataMenuContent }: Props) {
|
export function NodeMenubar({ nodeId, nodeType, editInputsContent, inputsMenuContent, insertTagsContent, insertMenuLabel = 'Insert', insertContentDirect, insertTagsLabel = 'Tags', nodeMenuExtraContent: nodeMenuExtraContentProp, outputMenuContent, dataMenuContent }: Props) {
|
||||||
const graphCtx = useContext(GraphContext)
|
const graphCtx = useContext(GraphContext)
|
||||||
const uiCtx = useContext(FlowUIContext)
|
const uiCtx = useContext(FlowUIContext)
|
||||||
const nodes = graphCtx?.nodes ?? []
|
const nodes = graphCtx?.graphRef?.current?.nodes ?? []
|
||||||
const setNodes = graphCtx?.setNodes
|
const setNodes = graphCtx?.setNodes
|
||||||
const setEdges = graphCtx?.setEdges
|
const setEdges = graphCtx?.setEdges
|
||||||
|
|
||||||
const edges = graphCtx?.edges ?? []
|
const edges = graphCtx?.edges ?? []
|
||||||
const node = nodes.find((n: any) => n.id === nodeId)
|
const node = nodes.find((n: any) => n.id === nodeId)
|
||||||
const nodeMenuExtraContent = useMemo(
|
const nodeMenuExtraContent = useMemo(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* pipeline interface.
|
* pipeline interface.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useContext, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useAbstractNode } from '@/lib/graph/abstractNode'
|
import { useAbstractNode } from '@/lib/graph/abstractNode'
|
||||||
import { ConnectionPathContext } from '@/lib/graph/flowContext'
|
import { ConnectionPathContext } from '@/lib/graph/flowContext'
|
||||||
import { useSyncConnectionStatus } from '@/lib/graph/nodeLifecycle'
|
import { useSyncConnectionStatus } from '@/lib/graph/nodeLifecycle'
|
||||||
@@ -118,6 +118,8 @@ export function useRenderingNodeState(
|
|||||||
const viewportWidth = data?.viewportWidth ?? DEFAULT_VIEWPORT_WIDTH
|
const viewportWidth = data?.viewportWidth ?? DEFAULT_VIEWPORT_WIDTH
|
||||||
const viewportHeight = data?.viewportHeight ?? DEFAULT_VIEWPORT_HEIGHT
|
const viewportHeight = data?.viewportHeight ?? DEFAULT_VIEWPORT_HEIGHT
|
||||||
|
|
||||||
|
const deferredNodes = useDeferredValue(nodes)
|
||||||
|
const deferredEdges = useDeferredValue(edges)
|
||||||
const srcId = incomingIds.length > 0 ? incomingIds[0] : null
|
const srcId = incomingIds.length > 0 ? incomingIds[0] : null
|
||||||
const srcNode = useMemo(
|
const srcNode = useMemo(
|
||||||
() => (srcId ? nodes.find((n: { id: string }) => n.id === srcId) : null),
|
() => (srcId ? nodes.find((n: { id: string }) => n.id === srcId) : null),
|
||||||
@@ -150,8 +152,8 @@ export function useRenderingNodeState(
|
|||||||
: ''
|
: ''
|
||||||
|
|
||||||
const signatures = useMemo(
|
const signatures = useMemo(
|
||||||
() => buildSourceSignatures(nodes as NodeLike[], edges as EdgeLike[], id, incomingIds),
|
() => buildSourceSignatures(deferredNodes as NodeLike[], deferredEdges as EdgeLike[], id, incomingIds),
|
||||||
[nodes, edges, id, incomingIds]
|
[deferredNodes, deferredEdges, id, incomingIds]
|
||||||
)
|
)
|
||||||
const {
|
const {
|
||||||
connectedNodeIds,
|
connectedNodeIds,
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function useAbstractNode<TData = Record<string, unknown>>(
|
|||||||
): AbstractNodeContext<TData> {
|
): AbstractNodeContext<TData> {
|
||||||
const graphCtx = useContext(GraphContext)
|
const graphCtx = useContext(GraphContext)
|
||||||
const pathCtx = useContext(ConnectionPathContext)
|
const pathCtx = useContext(ConnectionPathContext)
|
||||||
const nodes = graphCtx?.nodes ?? []
|
const nodes = graphCtx?.graphRef?.current?.nodes ?? []
|
||||||
const edges = graphCtx?.edges ?? []
|
const edges = graphCtx?.edges ?? []
|
||||||
const setNodes = graphCtx?.setNodes
|
const setNodes = graphCtx?.setNodes
|
||||||
const setEdges = graphCtx?.setEdges
|
const setEdges = graphCtx?.setEdges
|
||||||
|
|||||||
@@ -25,14 +25,20 @@ export type FlowActions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Graph context (nodes, edges, setters)
|
// Graph context (setters + graphRef for reads; edges in context so edge changes trigger re-renders)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export type GraphContextRef = {
|
||||||
|
current: { nodes: AppNode[]; edges: AppEdge[] }
|
||||||
|
}
|
||||||
|
|
||||||
export type GraphContextValue = {
|
export type GraphContextValue = {
|
||||||
nodes: AppNode[]
|
|
||||||
setNodes: (updater: AppNode[] | ((prev: AppNode[]) => AppNode[])) => void
|
setNodes: (updater: AppNode[] | ((prev: AppNode[]) => AppNode[])) => void
|
||||||
edges: AppEdge[]
|
|
||||||
setEdges: (updater: AppEdge[] | ((prev: AppEdge[]) => AppEdge[])) => void
|
setEdges: (updater: AppEdge[] | ((prev: AppEdge[]) => AppEdge[])) => void
|
||||||
|
/** Current nodes/edges; updated every render. Read from here to avoid re-rendering on position-only changes. */
|
||||||
|
graphRef: GraphContextRef
|
||||||
|
/** Edges in context so consumers (e.g. edge indicators, useAbstractNode) re-render when edges change. */
|
||||||
|
edges: AppEdge[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const GraphContext = React.createContext<GraphContextValue | null>(null)
|
const GraphContext = React.createContext<GraphContextValue | null>(null)
|
||||||
|
|||||||
Reference in New Issue
Block a user