refactoring
This commit is contained in:
62
frontend/src/app/canvas/CanvasContextMenuContent.tsx
Normal file
62
frontend/src/app/canvas/CanvasContextMenuContent.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Context menu content for the canvas: Create Node (grouped by classification) and Paste.
|
||||
* Used inside CanvasPage so the menu structure lives in one place.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuGroup,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
} from '@/components/ui/context-menu'
|
||||
import { ClipboardPaste } from 'lucide-react'
|
||||
import { getRegisteredNodeTypesGroupedByClassification } from '@/lib/graph/nodeRegistry'
|
||||
|
||||
export type CanvasContextMenuContentProps = {
|
||||
onCreateNode: (type: string) => void
|
||||
onPaste: () => void
|
||||
}
|
||||
|
||||
export function CanvasContextMenuContent({ onCreateNode, onPaste }: CanvasContextMenuContentProps) {
|
||||
const groups = getRegisteredNodeTypesGroupedByClassification()
|
||||
return (
|
||||
<ContextMenuContent className="w-48" aria-label="Canvas menu: create node or paste">
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger>Create Node</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent className="w-44">
|
||||
{groups.map(
|
||||
(group, groupIndex) =>
|
||||
group.types.length > 0 && (
|
||||
<React.Fragment key={group.classification}>
|
||||
{groupIndex > 0 && <ContextMenuSeparator />}
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuLabel className="text-muted-foreground">
|
||||
{group.label}
|
||||
</ContextMenuLabel>
|
||||
{group.types.map((desc) => (
|
||||
<ContextMenuItem key={desc.id} onSelect={() => onCreateNode(desc.id)}>
|
||||
{desc.menuIcon}
|
||||
{desc.menuLabel}
|
||||
</ContextMenuItem>
|
||||
))}
|
||||
</ContextMenuGroup>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem onSelect={onPaste}>
|
||||
<ClipboardPaste className="mr-2 h-4 w-4" />
|
||||
Paste
|
||||
</ContextMenuItem>
|
||||
</ContextMenuGroup>
|
||||
</ContextMenuContent>
|
||||
)
|
||||
}
|
||||
@@ -27,18 +27,9 @@ import FlowContext from '@/lib/graph/flowContext'
|
||||
import { useTheme } from '@/lib/themeContext'
|
||||
import { usePlatform } from '@/app/kosmos/KosmosContext'
|
||||
import { useGraphStateWithHistory } from '@/hooks/useGraphStateWithHistory'
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuGroup,
|
||||
} from '@/components/ui/context-menu'
|
||||
import { ContextMenu, ContextMenuTrigger } from '@/components/ui/context-menu'
|
||||
import { CanvasContextMenuContent } from '@/app/canvas/CanvasContextMenuContent'
|
||||
import { useCanvasConnectionPath } from '@/app/canvas/useCanvasConnectionPath'
|
||||
import { CanvasMenubar } from '@/app/canvas/CanvasMenubar'
|
||||
import { createContextualNode } from '@/app/canvas/ContextualZoomNode'
|
||||
import { FlowKeyboardShortcuts } from '@/components/graph/FlowKeyboardShortcuts'
|
||||
@@ -56,17 +47,15 @@ import {
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { ClipboardPaste, FolderOpen, FileStack, CircleDotDashed } from 'lucide-react'
|
||||
import { FolderOpen, FileStack, CircleDotDashed } from 'lucide-react'
|
||||
import { DEFAULT_NODE_STYLE, getDefaultDataForType, getNextNodeId } from '@/lib/graph/flowUtils'
|
||||
import {
|
||||
getRegisteredNodeTypes,
|
||||
getRegisteredNodeTypesGroupedByClassification,
|
||||
getRegisteredNodeTypeIds,
|
||||
getDefaultStyle,
|
||||
getNodeType,
|
||||
isConnectionAllowed,
|
||||
} from '@/lib/graph/nodeRegistry'
|
||||
import { getPathNodeIds, getPausedSegmentNodeIds } from '@/lib/graph/graphPath'
|
||||
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
@@ -78,8 +67,6 @@ import {
|
||||
|
||||
const SNAP_GRID: [number, number] = [15, 15]
|
||||
const DUPLICATE_OFFSET = { x: 30, y: 30 }
|
||||
/** Minimum time (ms) the connection ant trail runs when a path update is in progress. */
|
||||
const CONNECTION_PATH_UPDATE_MIN_MS = 1500
|
||||
const snapToGrid = (x: number, y: number): { x: number; y: number } => ({
|
||||
x: Math.round(x / SNAP_GRID[0]) * SNAP_GRID[0],
|
||||
y: Math.round(y / SNAP_GRID[1]) * SNAP_GRID[1],
|
||||
@@ -267,82 +254,7 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
||||
const [isSelecting, setIsSelecting] = React.useState(false)
|
||||
const [ariaAnnouncement, setAriaAnnouncement] = React.useState<string | null>(null)
|
||||
const [fullscreenNodeId, setFullscreenNodeId] = React.useState<string | null>(null)
|
||||
const [connectionPathUpdatingNodeIds, setConnectionPathUpdatingNodeIds] = React.useState<string[]>([])
|
||||
const [connectionPathTriggerNodeIds, setConnectionPathTriggerNodeIds] = React.useState<string[]>([])
|
||||
const pathUpdateNodeIdsRef = useRef<Set<string>>(new Set())
|
||||
const pathUpdateStartTimeRef = useRef<number | null>(null)
|
||||
const pathUpdateEndTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const [connectionPathPausedNodeIds, setConnectionPathPausedNodeIds] = React.useState<string[]>([])
|
||||
const [connectionPathErrorNodeIds, setConnectionPathErrorNodeIds] = React.useState<string[]>([])
|
||||
const connectionPathPausedNodeIdsRef = useRef<string[]>([])
|
||||
connectionPathPausedNodeIdsRef.current = connectionPathPausedNodeIds
|
||||
|
||||
const clearPathUpdateSession = useCallback(() => {
|
||||
setConnectionPathUpdatingNodeIds([])
|
||||
if (connectionPathPausedNodeIdsRef.current.length === 0) {
|
||||
setConnectionPathTriggerNodeIds([])
|
||||
setConnectionPathPausedNodeIds([])
|
||||
}
|
||||
}, [])
|
||||
|
||||
const startConnectionPathUpdate = useCallback((nodeId: string) => {
|
||||
const ref = pathUpdateNodeIdsRef.current
|
||||
ref.add(nodeId)
|
||||
if (ref.size === 1) {
|
||||
pathUpdateStartTimeRef.current = Date.now()
|
||||
if (pathUpdateEndTimeoutRef.current != null) {
|
||||
clearTimeout(pathUpdateEndTimeoutRef.current)
|
||||
pathUpdateEndTimeoutRef.current = null
|
||||
}
|
||||
}
|
||||
setConnectionPathUpdatingNodeIds(Array.from(ref))
|
||||
}, [])
|
||||
|
||||
const endConnectionPathUpdate = useCallback((nodeId: string) => {
|
||||
const ref = pathUpdateNodeIdsRef.current
|
||||
ref.delete(nodeId)
|
||||
if (ref.size > 0) {
|
||||
setConnectionPathUpdatingNodeIds(Array.from(ref))
|
||||
return
|
||||
}
|
||||
const startedAt = pathUpdateStartTimeRef.current ?? 0
|
||||
const elapsed = Date.now() - startedAt
|
||||
const remaining = Math.max(0, CONNECTION_PATH_UPDATE_MIN_MS - elapsed)
|
||||
if (remaining === 0) {
|
||||
clearPathUpdateSession()
|
||||
} else {
|
||||
pathUpdateEndTimeoutRef.current = setTimeout(() => {
|
||||
pathUpdateEndTimeoutRef.current = null
|
||||
clearPathUpdateSession()
|
||||
}, remaining)
|
||||
}
|
||||
}, [clearPathUpdateSession])
|
||||
|
||||
const pathTriggerBatchRef = useRef<Set<string>>(new Set())
|
||||
const pathTriggerScheduledRef = useRef(false)
|
||||
const addConnectionPathTrigger = useCallback((nodeId: string) => {
|
||||
pathTriggerBatchRef.current.add(nodeId)
|
||||
if (pathTriggerScheduledRef.current) return
|
||||
pathTriggerScheduledRef.current = true
|
||||
requestAnimationFrame(() => {
|
||||
pathTriggerScheduledRef.current = false
|
||||
const batch = new Set(pathTriggerBatchRef.current)
|
||||
pathTriggerBatchRef.current = new Set()
|
||||
if (batch.size === 0) return
|
||||
setConnectionPathTriggerNodeIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
batch.forEach((id) => next.add(id))
|
||||
return next.size === prev.length && prev.every((id) => next.has(id)) ? prev : Array.from(next)
|
||||
})
|
||||
})
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => () => {
|
||||
if (pathUpdateEndTimeoutRef.current != null) {
|
||||
clearTimeout(pathUpdateEndTimeoutRef.current)
|
||||
}
|
||||
}, [])
|
||||
const connectionPath = useCanvasConnectionPath(edges)
|
||||
|
||||
const nodesRef = useRef(nodes)
|
||||
nodesRef.current = nodes
|
||||
@@ -551,50 +463,6 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
||||
flowActionsRef.current?.pasteAtViewportCenter?.()
|
||||
}, [])
|
||||
|
||||
const connectionPathNodeIds = useMemo(
|
||||
() =>
|
||||
getPathNodeIds(
|
||||
edges,
|
||||
connectionPathUpdatingNodeIds,
|
||||
connectionPathTriggerNodeIds,
|
||||
connectionPathPausedNodeIds
|
||||
),
|
||||
[edges, connectionPathUpdatingNodeIds, connectionPathTriggerNodeIds, connectionPathPausedNodeIds]
|
||||
)
|
||||
|
||||
const connectionPathPausedSegmentNodeIds = useMemo(
|
||||
() =>
|
||||
getPausedSegmentNodeIds(
|
||||
edges,
|
||||
connectionPathNodeIds,
|
||||
connectionPathTriggerNodeIds,
|
||||
connectionPathPausedNodeIds
|
||||
),
|
||||
[edges, connectionPathNodeIds, connectionPathTriggerNodeIds, connectionPathPausedNodeIds]
|
||||
)
|
||||
|
||||
const connectionPathActiveSegmentNodeIds = useMemo(() => {
|
||||
const active = new Set(connectionPathNodeIds)
|
||||
connectionPathPausedSegmentNodeIds.forEach((id) => active.delete(id))
|
||||
return active
|
||||
}, [connectionPathNodeIds, connectionPathPausedSegmentNodeIds])
|
||||
|
||||
const addConnectionPathPausedNode = useCallback((nodeId: string) => {
|
||||
setConnectionPathPausedNodeIds((prev) => (prev.includes(nodeId) ? prev : [...prev, nodeId]))
|
||||
}, [])
|
||||
|
||||
const removeConnectionPathPausedNode = useCallback((nodeId: string) => {
|
||||
setConnectionPathPausedNodeIds((prev) => prev.filter((id) => id !== nodeId))
|
||||
}, [])
|
||||
|
||||
const addConnectionPathError = useCallback((nodeId: string) => {
|
||||
setConnectionPathErrorNodeIds((prev) => (prev.includes(nodeId) ? prev : [...prev, nodeId]))
|
||||
}, [])
|
||||
|
||||
const removeConnectionPathError = useCallback((nodeId: string) => {
|
||||
setConnectionPathErrorNodeIds((prev) => prev.filter((id) => id !== nodeId))
|
||||
}, [])
|
||||
|
||||
const flowContextValue = useMemo(
|
||||
() => ({
|
||||
nodes,
|
||||
@@ -609,20 +477,20 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
||||
flowActionsRef,
|
||||
fullscreenNodeId,
|
||||
setFullscreenNodeId,
|
||||
connectionPathUpdatingNodeIds,
|
||||
connectionPathTriggerNodeIds,
|
||||
addConnectionPathTrigger,
|
||||
connectionPathNodeIds,
|
||||
connectionPathPausedSegmentNodeIds,
|
||||
connectionPathActiveSegmentNodeIds,
|
||||
connectionPathPausedNodeIds,
|
||||
addConnectionPathPausedNode,
|
||||
removeConnectionPathPausedNode,
|
||||
connectionPathErrorNodeIds,
|
||||
addConnectionPathError,
|
||||
removeConnectionPathError,
|
||||
startConnectionPathUpdate,
|
||||
endConnectionPathUpdate,
|
||||
connectionPathUpdatingNodeIds: connectionPath.connectionPathUpdatingNodeIds,
|
||||
connectionPathTriggerNodeIds: connectionPath.connectionPathTriggerNodeIds,
|
||||
addConnectionPathTrigger: connectionPath.addConnectionPathTrigger,
|
||||
connectionPathNodeIds: connectionPath.connectionPathNodeIds,
|
||||
connectionPathPausedSegmentNodeIds: connectionPath.connectionPathPausedSegmentNodeIds,
|
||||
connectionPathActiveSegmentNodeIds: connectionPath.connectionPathActiveSegmentNodeIds,
|
||||
connectionPathPausedNodeIds: connectionPath.connectionPathPausedNodeIds,
|
||||
addConnectionPathPausedNode: connectionPath.addConnectionPathPausedNode,
|
||||
removeConnectionPathPausedNode: connectionPath.removeConnectionPathPausedNode,
|
||||
connectionPathErrorNodeIds: connectionPath.connectionPathErrorNodeIds,
|
||||
addConnectionPathError: connectionPath.addConnectionPathError,
|
||||
removeConnectionPathError: connectionPath.removeConnectionPathError,
|
||||
startConnectionPathUpdate: connectionPath.startConnectionPathUpdate,
|
||||
endConnectionPathUpdate: connectionPath.endConnectionPathUpdate,
|
||||
}),
|
||||
[
|
||||
nodes,
|
||||
@@ -637,20 +505,7 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
||||
flowActionsRef,
|
||||
fullscreenNodeId,
|
||||
setFullscreenNodeId,
|
||||
connectionPathUpdatingNodeIds,
|
||||
connectionPathTriggerNodeIds,
|
||||
addConnectionPathTrigger,
|
||||
connectionPathNodeIds,
|
||||
connectionPathPausedSegmentNodeIds,
|
||||
connectionPathActiveSegmentNodeIds,
|
||||
connectionPathPausedNodeIds,
|
||||
addConnectionPathPausedNode,
|
||||
removeConnectionPathPausedNode,
|
||||
connectionPathErrorNodeIds,
|
||||
addConnectionPathError,
|
||||
removeConnectionPathError,
|
||||
startConnectionPathUpdate,
|
||||
endConnectionPathUpdate,
|
||||
connectionPath,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -900,39 +755,7 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-48" aria-label="Canvas menu: create node or paste">
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger>Create Node</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent className="w-44">
|
||||
{getRegisteredNodeTypesGroupedByClassification().map(
|
||||
(group, groupIndex) =>
|
||||
group.types.length > 0 && (
|
||||
<React.Fragment key={group.classification}>
|
||||
{groupIndex > 0 && <ContextMenuSeparator />}
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuLabel className="text-muted-foreground">
|
||||
{group.label}
|
||||
</ContextMenuLabel>
|
||||
{group.types.map((desc) => (
|
||||
<ContextMenuItem key={desc.id} onSelect={() => createNode(desc.id)}>
|
||||
{desc.menuIcon}
|
||||
{desc.menuLabel}
|
||||
</ContextMenuItem>
|
||||
))}
|
||||
</ContextMenuGroup>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem onSelect={() => pasteNode()}>
|
||||
<ClipboardPaste className="mr-2 h-4 w-4" />
|
||||
Paste
|
||||
</ContextMenuItem>
|
||||
</ContextMenuGroup>
|
||||
</ContextMenuContent>
|
||||
<CanvasContextMenuContent onCreateNode={createNode} onPaste={pasteNode} />
|
||||
</ContextMenu>
|
||||
{fullscreenNodeId && (
|
||||
<FullscreenNodeOverlay
|
||||
|
||||
174
frontend/src/app/canvas/useCanvasConnectionPath.ts
Normal file
174
frontend/src/app/canvas/useCanvasConnectionPath.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* Hook that holds all connection-path state and callbacks for the canvas.
|
||||
* Used to show the "ant trail" along the path of an update (e.g. config → agent → render).
|
||||
* Extracted from CanvasPage to keep the page focused on composition.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { getPathNodeIds, getPausedSegmentNodeIds } from '@/lib/graph/graphPath'
|
||||
|
||||
export type EdgeLike = { source: string; target: string }
|
||||
|
||||
/** Minimum time (ms) the connection ant trail runs when a path update is in progress. */
|
||||
const CONNECTION_PATH_UPDATE_MIN_MS = 1500
|
||||
|
||||
export type UseCanvasConnectionPathResult = {
|
||||
connectionPathUpdatingNodeIds: string[]
|
||||
connectionPathTriggerNodeIds: string[]
|
||||
connectionPathPausedNodeIds: string[]
|
||||
connectionPathErrorNodeIds: string[]
|
||||
connectionPathNodeIds: Set<string>
|
||||
connectionPathPausedSegmentNodeIds: Set<string>
|
||||
connectionPathActiveSegmentNodeIds: Set<string>
|
||||
startConnectionPathUpdate: (nodeId: string) => void
|
||||
endConnectionPathUpdate: (nodeId: string) => void
|
||||
addConnectionPathTrigger: (nodeId: string) => void
|
||||
addConnectionPathPausedNode: (nodeId: string) => void
|
||||
removeConnectionPathPausedNode: (nodeId: string) => void
|
||||
addConnectionPathError: (nodeId: string) => void
|
||||
removeConnectionPathError: (nodeId: string) => void
|
||||
}
|
||||
|
||||
export function useCanvasConnectionPath(edges: EdgeLike[]): UseCanvasConnectionPathResult {
|
||||
const [connectionPathUpdatingNodeIds, setConnectionPathUpdatingNodeIds] = useState<string[]>([])
|
||||
const [connectionPathTriggerNodeIds, setConnectionPathTriggerNodeIds] = useState<string[]>([])
|
||||
const [connectionPathPausedNodeIds, setConnectionPathPausedNodeIds] = useState<string[]>([])
|
||||
const [connectionPathErrorNodeIds, setConnectionPathErrorNodeIds] = useState<string[]>([])
|
||||
|
||||
const pathUpdateNodeIdsRef = useRef<Set<string>>(new Set())
|
||||
const pathUpdateStartTimeRef = useRef<number | null>(null)
|
||||
const pathUpdateEndTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const connectionPathPausedNodeIdsRef = useRef<string[]>([])
|
||||
connectionPathPausedNodeIdsRef.current = connectionPathPausedNodeIds
|
||||
|
||||
const pathTriggerBatchRef = useRef<Set<string>>(new Set())
|
||||
const pathTriggerScheduledRef = useRef(false)
|
||||
|
||||
const clearPathUpdateSession = useCallback(() => {
|
||||
setConnectionPathUpdatingNodeIds([])
|
||||
if (connectionPathPausedNodeIdsRef.current.length === 0) {
|
||||
setConnectionPathTriggerNodeIds([])
|
||||
setConnectionPathPausedNodeIds([])
|
||||
}
|
||||
}, [])
|
||||
|
||||
const startConnectionPathUpdate = useCallback((nodeId: string) => {
|
||||
const ref = pathUpdateNodeIdsRef.current
|
||||
ref.add(nodeId)
|
||||
if (ref.size === 1) {
|
||||
pathUpdateStartTimeRef.current = Date.now()
|
||||
if (pathUpdateEndTimeoutRef.current != null) {
|
||||
clearTimeout(pathUpdateEndTimeoutRef.current)
|
||||
pathUpdateEndTimeoutRef.current = null
|
||||
}
|
||||
}
|
||||
setConnectionPathUpdatingNodeIds(Array.from(ref))
|
||||
}, [])
|
||||
|
||||
const endConnectionPathUpdate = useCallback((nodeId: string) => {
|
||||
const ref = pathUpdateNodeIdsRef.current
|
||||
ref.delete(nodeId)
|
||||
if (ref.size > 0) {
|
||||
setConnectionPathUpdatingNodeIds(Array.from(ref))
|
||||
return
|
||||
}
|
||||
const startedAt = pathUpdateStartTimeRef.current ?? 0
|
||||
const elapsed = Date.now() - startedAt
|
||||
const remaining = Math.max(0, CONNECTION_PATH_UPDATE_MIN_MS - elapsed)
|
||||
if (remaining === 0) {
|
||||
clearPathUpdateSession()
|
||||
} else {
|
||||
pathUpdateEndTimeoutRef.current = setTimeout(() => {
|
||||
pathUpdateEndTimeoutRef.current = null
|
||||
clearPathUpdateSession()
|
||||
}, remaining)
|
||||
}
|
||||
}, [clearPathUpdateSession])
|
||||
|
||||
const addConnectionPathTrigger = useCallback((nodeId: string) => {
|
||||
pathTriggerBatchRef.current.add(nodeId)
|
||||
if (pathTriggerScheduledRef.current) return
|
||||
pathTriggerScheduledRef.current = true
|
||||
requestAnimationFrame(() => {
|
||||
pathTriggerScheduledRef.current = false
|
||||
const batch = new Set(pathTriggerBatchRef.current)
|
||||
pathTriggerBatchRef.current = new Set()
|
||||
if (batch.size === 0) return
|
||||
setConnectionPathTriggerNodeIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
batch.forEach((id) => next.add(id))
|
||||
return next.size === prev.length && prev.every((id) => next.has(id)) ? prev : Array.from(next)
|
||||
})
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (pathUpdateEndTimeoutRef.current != null) {
|
||||
clearTimeout(pathUpdateEndTimeoutRef.current)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const connectionPathNodeIds = useMemo(
|
||||
() =>
|
||||
getPathNodeIds(
|
||||
edges,
|
||||
connectionPathUpdatingNodeIds,
|
||||
connectionPathTriggerNodeIds,
|
||||
connectionPathPausedNodeIds
|
||||
),
|
||||
[edges, connectionPathUpdatingNodeIds, connectionPathTriggerNodeIds, connectionPathPausedNodeIds]
|
||||
)
|
||||
|
||||
const connectionPathPausedSegmentNodeIds = useMemo(
|
||||
() =>
|
||||
getPausedSegmentNodeIds(
|
||||
edges,
|
||||
connectionPathNodeIds,
|
||||
connectionPathTriggerNodeIds,
|
||||
connectionPathPausedNodeIds
|
||||
),
|
||||
[edges, connectionPathNodeIds, connectionPathTriggerNodeIds, connectionPathPausedNodeIds]
|
||||
)
|
||||
|
||||
const connectionPathActiveSegmentNodeIds = useMemo(() => {
|
||||
const active = new Set(connectionPathNodeIds)
|
||||
connectionPathPausedSegmentNodeIds.forEach((id) => active.delete(id))
|
||||
return active
|
||||
}, [connectionPathNodeIds, connectionPathPausedSegmentNodeIds])
|
||||
|
||||
const addConnectionPathPausedNode = useCallback((nodeId: string) => {
|
||||
setConnectionPathPausedNodeIds((prev) => (prev.includes(nodeId) ? prev : [...prev, nodeId]))
|
||||
}, [])
|
||||
|
||||
const removeConnectionPathPausedNode = useCallback((nodeId: string) => {
|
||||
setConnectionPathPausedNodeIds((prev) => prev.filter((id) => id !== nodeId))
|
||||
}, [])
|
||||
|
||||
const addConnectionPathError = useCallback((nodeId: string) => {
|
||||
setConnectionPathErrorNodeIds((prev) => (prev.includes(nodeId) ? prev : [...prev, nodeId]))
|
||||
}, [])
|
||||
|
||||
const removeConnectionPathError = useCallback((nodeId: string) => {
|
||||
setConnectionPathErrorNodeIds((prev) => prev.filter((id) => id !== nodeId))
|
||||
}, [])
|
||||
|
||||
return {
|
||||
connectionPathUpdatingNodeIds,
|
||||
connectionPathTriggerNodeIds,
|
||||
connectionPathPausedNodeIds,
|
||||
connectionPathErrorNodeIds,
|
||||
connectionPathNodeIds,
|
||||
connectionPathPausedSegmentNodeIds,
|
||||
connectionPathActiveSegmentNodeIds,
|
||||
startConnectionPathUpdate,
|
||||
endConnectionPathUpdate,
|
||||
addConnectionPathTrigger,
|
||||
addConnectionPathPausedNode,
|
||||
removeConnectionPathPausedNode,
|
||||
addConnectionPathError,
|
||||
removeConnectionPathError,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user