mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 23:06:40 +08:00
# 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
127 lines
4.0 KiB
TypeScript
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)
|
|
}
|
|
}
|