Files
dify/web/app/components/workflow/utils/node-navigation.ts
Joel ebe9836dca Merge remote-tracking branch 'origin/main' into feat/end-user-auth-fe
# Conflicts:
#	web/app/components/header/account-dropdown/__tests__/index.spec.tsx
#	web/app/components/plugins/plugin-auth/__tests__/plugin-auth.spec.tsx
#	web/app/components/plugins/plugin-auth/authorize/__tests__/add-oauth-button.spec.tsx
#	web/app/components/plugins/plugin-auth/authorize/__tests__/authorize-components.spec.tsx
#	web/app/components/plugins/plugin-auth/authorize/__tests__/index.spec.tsx
#	web/app/components/plugins/plugin-auth/authorize/__tests__/oauth-client-settings.spec.tsx
#	web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx
#	web/app/components/plugins/plugin-auth/authorize/api-key-modal.tsx
#	web/app/components/plugins/plugin-auth/authorize/oauth-client-settings.tsx
#	web/app/components/workflow-app/components/workflow-header/__tests__/features-trigger.spec.tsx
#	web/app/components/workflow/hooks/__tests__/use-checklist.spec.ts
#	web/app/components/workflow/node-actions-menu/__tests__/index.spec.tsx
#	web/app/components/workflow/nodes/_base/components/__tests__/node-control.spec.tsx
#	web/app/components/workflow/nodes/_base/components/workflow-panel/__tests__/index.spec.tsx
#	web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx
#	web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/__tests__/index.spec.tsx
#	web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/__tests__/use-last-run.spec.ts
#	web/app/components/workflow/nodes/tool/__tests__/node.spec.tsx
#	web/app/components/workflow/nodes/tool/__tests__/panel.spec.tsx
#	web/app/components/workflow/utils/__tests__/node-navigation.spec.ts
#	web/app/components/workflow/utils/node-navigation.ts
2026-10-19 10:44:31 +08:00

127 lines
4.0 KiB
TypeScript

import type { Node, ReactFlowInstance } from 'reactflow'
/**
* Node navigation utilities for workflow
* This module provides functions for node selection, focusing and scrolling in workflow
*/
/**
* Interface for node selection event detail
*/
type NodeSelectionDetail = {
nodeId: string
focus?: boolean
}
/**
* Select a node in the workflow
* @param nodeId - The ID of the node to select
* @param focus - Whether to focus/scroll to the node
*/
export function selectWorkflowNode(nodeId: string, focus = false): void {
// Create and dispatch a custom event for node selection
const event = new CustomEvent('workflow:select-node', {
detail: {
nodeId,
focus,
},
})
document.dispatchEvent(event)
}
/**
* Scroll to a specific node in the workflow
* @param nodeId - The ID of the node to scroll to
*/
export function scrollToWorkflowNode(nodeId: string): void {
// Create and dispatch a custom event for scrolling to node
const event = new CustomEvent('workflow:scroll-to-node', {
detail: { nodeId },
})
document.dispatchEvent(event)
}
/**
* Setup node selection event listener
* @param handleNodeSelect - Function to handle node selection
* @returns Cleanup function
*/
export function setupNodeSelectionListener(handleNodeSelect: (nodeId: string) => void): () => void {
// Event handler for node selection
const handleNodeSelection = (event: CustomEvent<NodeSelectionDetail>) => {
const { nodeId, focus } = event.detail
if (nodeId) {
// Select the node
handleNodeSelect(nodeId)
// If focus is requested, scroll to the node
if (focus) {
// Use a small timeout to ensure node selection happens first
setTimeout(() => {
scrollToWorkflowNode(nodeId)
}, 100)
}
}
}
// Add event listener
document.addEventListener('workflow:select-node', handleNodeSelection as EventListener)
// Return cleanup function
return () => {
document.removeEventListener('workflow:select-node', handleNodeSelection as EventListener)
}
}
/**
* Setup scroll to node event listener with ReactFlow
* @param nodes - The workflow nodes
* @param reactflow - The ReactFlow instance
* @returns Cleanup function
*/
export function setupScrollToNodeListener(
nodes: Pick<Node, 'id' | 'position' | 'positionAbsolute' | 'parentId'>[],
reactflow: Pick<ReactFlowInstance, 'setCenter'>,
): () => void {
// Event handler for scrolling to node
const handleScrollToNode = (event: CustomEvent<NodeSelectionDetail>) => {
const { nodeId } = event.detail
if (nodeId) {
// Find the target node
const node = nodes.find((n) => n.id === nodeId)
if (node) {
// Use ReactFlow's fitView API to scroll to the node
const nodePosition = { ...(node.positionAbsolute ?? node.position) }
// Container children store relative positions before React Flow computes their absolute position.
if (!node.positionAbsolute) {
let parentId = node.parentId
const visited = new Set([node.id])
while (parentId && !visited.has(parentId)) {
visited.add(parentId)
const parent = nodes.find((candidate) => candidate.id === parentId)
if (!parent) break
nodePosition.x += parent.position.x
nodePosition.y += parent.position.y
parentId = parent.parentId
}
}
// Calculate position to place node in top-left area
// Move the center point right and down to show node in top-left
const targetX = nodePosition.x + window.innerWidth * 0.25
const targetY = nodePosition.y + window.innerHeight * 0.25
reactflow.setCenter(targetX, targetY, { zoom: 1, duration: 800 })
}
}
}
// Add event listener
document.addEventListener('workflow:scroll-to-node', handleScrollToNode as EventListener)
// Return cleanup function
return () => {
document.removeEventListener('workflow:scroll-to-node', handleScrollToNode as EventListener)
}
}