mirror of https://github.com/langgenius/dify.git
fix: correct entry node alignment for wrapper offset
- Add ENTRY_NODE_WRAPPER_OFFSET constant (x: 0, y: 21) for Start/Trigger nodes - Implement getNodeAlignPosition() to calculate actual inner node positions - Fix horizontal/vertical helpline rendering to account for wrapper offset - Fix snap-to-align logic to properly align inner nodes instead of wrapper - Correct helpline width/height calculation by subtracting offset for entry nodes - Ensure backward compatibility: only affects Start/Trigger nodes with EntryNodeContainer wrapper This fix ensures that Start and Trigger nodes (which have an EntryNodeContainer wrapper with status indicator) align based on their inner node boundaries rather than the wrapper boundaries, matching the alignment behavior of regular nodes.
This commit is contained in:
parent
bea11b08d7
commit
7c97ea4a9e
|
|
@ -1,12 +1,40 @@
|
|||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import type { Node } from '../types'
|
||||
import { BlockEnum, TRIGGER_NODE_TYPES } from '../types'
|
||||
import { useWorkflowStore } from '../store'
|
||||
|
||||
// Entry node (Start/Trigger) wrapper offsets
|
||||
// The EntryNodeContainer adds a wrapper with status indicator above the actual node
|
||||
// These offsets ensure alignment happens on the inner node, not the wrapper
|
||||
const ENTRY_NODE_WRAPPER_OFFSET = {
|
||||
x: 0, // No horizontal padding on wrapper (px-0)
|
||||
y: 21, // Actual measured: pt-0.5 (2px) + status bar height (~19px)
|
||||
} as const
|
||||
|
||||
export const useHelpline = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
// Check if a node is an entry node (Start or Trigger)
|
||||
const isEntryNode = useCallback((node: Node): boolean => {
|
||||
return TRIGGER_NODE_TYPES.includes(node.data.type as any) || node.data.type === BlockEnum.Start
|
||||
}, [])
|
||||
|
||||
// Get the actual alignment position of a node (accounting for wrapper offset)
|
||||
const getNodeAlignPosition = useCallback((node: Node) => {
|
||||
if (isEntryNode(node)) {
|
||||
return {
|
||||
x: node.position.x + ENTRY_NODE_WRAPPER_OFFSET.x,
|
||||
y: node.position.y + ENTRY_NODE_WRAPPER_OFFSET.y,
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: node.position.x,
|
||||
y: node.position.y,
|
||||
}
|
||||
}, [isEntryNode])
|
||||
|
||||
const handleSetHelpline = useCallback((node: Node) => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
|
|
@ -29,6 +57,9 @@ export const useHelpline = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// Get the actual alignment position for the dragging node
|
||||
const nodeAlignPos = getNodeAlignPosition(node)
|
||||
|
||||
const showHorizontalHelpLineNodes = nodes.filter((n) => {
|
||||
if (n.id === node.id)
|
||||
return false
|
||||
|
|
@ -39,33 +70,52 @@ export const useHelpline = () => {
|
|||
if (n.data.isInLoop)
|
||||
return false
|
||||
|
||||
const nY = Math.ceil(n.position.y)
|
||||
const nodeY = Math.ceil(node.position.y)
|
||||
// Get actual alignment position for comparison node
|
||||
const nAlignPos = getNodeAlignPosition(n)
|
||||
const nY = Math.ceil(nAlignPos.y)
|
||||
const nodeY = Math.ceil(nodeAlignPos.y)
|
||||
|
||||
if (nY - nodeY < 5 && nY - nodeY > -5)
|
||||
return true
|
||||
|
||||
return false
|
||||
}).sort((a, b) => a.position.x - b.position.x)
|
||||
}).sort((a, b) => {
|
||||
const aPos = getNodeAlignPosition(a)
|
||||
const bPos = getNodeAlignPosition(b)
|
||||
return aPos.x - bPos.x
|
||||
})
|
||||
|
||||
const showHorizontalHelpLineNodesLength = showHorizontalHelpLineNodes.length
|
||||
if (showHorizontalHelpLineNodesLength > 0) {
|
||||
const first = showHorizontalHelpLineNodes[0]
|
||||
const last = showHorizontalHelpLineNodes[showHorizontalHelpLineNodesLength - 1]
|
||||
|
||||
// Use actual alignment positions for help line rendering
|
||||
const firstPos = getNodeAlignPosition(first)
|
||||
const lastPos = getNodeAlignPosition(last)
|
||||
|
||||
// For entry nodes, we need to subtract the offset from width since lastPos already includes it
|
||||
const lastIsEntryNode = isEntryNode(last)
|
||||
const lastNodeWidth = lastIsEntryNode ? last.width! - ENTRY_NODE_WRAPPER_OFFSET.x : last.width!
|
||||
|
||||
const helpLine = {
|
||||
top: first.position.y,
|
||||
left: first.position.x,
|
||||
width: last.position.x + last.width! - first.position.x,
|
||||
top: firstPos.y,
|
||||
left: firstPos.x,
|
||||
width: lastPos.x + lastNodeWidth - firstPos.x,
|
||||
}
|
||||
|
||||
if (node.position.x < first.position.x) {
|
||||
helpLine.left = node.position.x
|
||||
helpLine.width = first.position.x + first.width! - node.position.x
|
||||
if (nodeAlignPos.x < firstPos.x) {
|
||||
const firstIsEntryNode = isEntryNode(first)
|
||||
const firstNodeWidth = firstIsEntryNode ? first.width! - ENTRY_NODE_WRAPPER_OFFSET.x : first.width!
|
||||
helpLine.left = nodeAlignPos.x
|
||||
helpLine.width = firstPos.x + firstNodeWidth - nodeAlignPos.x
|
||||
}
|
||||
|
||||
if (node.position.x > last.position.x)
|
||||
helpLine.width = node.position.x + node.width! - first.position.x
|
||||
if (nodeAlignPos.x > lastPos.x) {
|
||||
const nodeIsEntryNode = isEntryNode(node)
|
||||
const nodeWidth = nodeIsEntryNode ? node.width! - ENTRY_NODE_WRAPPER_OFFSET.x : node.width!
|
||||
helpLine.width = nodeAlignPos.x + nodeWidth - firstPos.x
|
||||
}
|
||||
|
||||
setHelpLineHorizontal(helpLine)
|
||||
}
|
||||
|
|
@ -81,33 +131,52 @@ export const useHelpline = () => {
|
|||
if (n.data.isInLoop)
|
||||
return false
|
||||
|
||||
const nX = Math.ceil(n.position.x)
|
||||
const nodeX = Math.ceil(node.position.x)
|
||||
// Get actual alignment position for comparison node
|
||||
const nAlignPos = getNodeAlignPosition(n)
|
||||
const nX = Math.ceil(nAlignPos.x)
|
||||
const nodeX = Math.ceil(nodeAlignPos.x)
|
||||
|
||||
if (nX - nodeX < 5 && nX - nodeX > -5)
|
||||
return true
|
||||
|
||||
return false
|
||||
}).sort((a, b) => a.position.x - b.position.x)
|
||||
}).sort((a, b) => {
|
||||
const aPos = getNodeAlignPosition(a)
|
||||
const bPos = getNodeAlignPosition(b)
|
||||
return aPos.x - bPos.x
|
||||
})
|
||||
const showVerticalHelpLineNodesLength = showVerticalHelpLineNodes.length
|
||||
|
||||
if (showVerticalHelpLineNodesLength > 0) {
|
||||
const first = showVerticalHelpLineNodes[0]
|
||||
const last = showVerticalHelpLineNodes[showVerticalHelpLineNodesLength - 1]
|
||||
|
||||
// Use actual alignment positions for help line rendering
|
||||
const firstPos = getNodeAlignPosition(first)
|
||||
const lastPos = getNodeAlignPosition(last)
|
||||
|
||||
// For entry nodes, we need to subtract the offset from height since lastPos already includes it
|
||||
const lastIsEntryNode = isEntryNode(last)
|
||||
const lastNodeHeight = lastIsEntryNode ? last.height! - ENTRY_NODE_WRAPPER_OFFSET.y : last.height!
|
||||
|
||||
const helpLine = {
|
||||
top: first.position.y,
|
||||
left: first.position.x,
|
||||
height: last.position.y + last.height! - first.position.y,
|
||||
top: firstPos.y,
|
||||
left: firstPos.x,
|
||||
height: lastPos.y + lastNodeHeight - firstPos.y,
|
||||
}
|
||||
|
||||
if (node.position.y < first.position.y) {
|
||||
helpLine.top = node.position.y
|
||||
helpLine.height = first.position.y + first.height! - node.position.y
|
||||
if (nodeAlignPos.y < firstPos.y) {
|
||||
const firstIsEntryNode = isEntryNode(first)
|
||||
const firstNodeHeight = firstIsEntryNode ? first.height! - ENTRY_NODE_WRAPPER_OFFSET.y : first.height!
|
||||
helpLine.top = nodeAlignPos.y
|
||||
helpLine.height = firstPos.y + firstNodeHeight - nodeAlignPos.y
|
||||
}
|
||||
|
||||
if (node.position.y > last.position.y)
|
||||
helpLine.height = node.position.y + node.height! - first.position.y
|
||||
if (nodeAlignPos.y > lastPos.y) {
|
||||
const nodeIsEntryNode = isEntryNode(node)
|
||||
const nodeHeight = nodeIsEntryNode ? node.height! - ENTRY_NODE_WRAPPER_OFFSET.y : node.height!
|
||||
helpLine.height = nodeAlignPos.y + nodeHeight - firstPos.y
|
||||
}
|
||||
|
||||
setHelpLineVertical(helpLine)
|
||||
}
|
||||
|
|
@ -119,7 +188,7 @@ export const useHelpline = () => {
|
|||
showHorizontalHelpLineNodes,
|
||||
showVerticalHelpLineNodes,
|
||||
}
|
||||
}, [store, workflowStore])
|
||||
}, [store, workflowStore, getNodeAlignPosition])
|
||||
|
||||
return {
|
||||
handleSetHelpline,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from 'reactflow'
|
||||
import type { PluginDefaultValue } from '../block-selector/types'
|
||||
import type { Edge, Node, OnNodeAdd } from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import { BlockEnum, TRIGGER_NODE_TYPES } from '../types'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import {
|
||||
CUSTOM_EDGE,
|
||||
|
|
@ -65,6 +65,13 @@ import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'
|
|||
|
||||
// Entry node deletion restriction has been removed to allow empty workflows
|
||||
|
||||
// Entry node (Start/Trigger) wrapper offsets for alignment
|
||||
// Must match the values in use-helpline.ts
|
||||
const ENTRY_NODE_WRAPPER_OFFSET = {
|
||||
x: 0,
|
||||
y: 21, // Adjusted based on visual testing feedback
|
||||
} as const
|
||||
|
||||
export const useNodesInteractions = () => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
|
|
@ -140,21 +147,51 @@ export const useNodesInteractions = () => {
|
|||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(n => n.id === node.id)!
|
||||
|
||||
if (showVerticalHelpLineNodesLength > 0)
|
||||
currentNode.position.x = showVerticalHelpLineNodes[0].position.x
|
||||
else if (restrictPosition.x !== undefined)
|
||||
currentNode.position.x = restrictPosition.x
|
||||
else if (restrictLoopPosition.x !== undefined)
|
||||
currentNode.position.x = restrictLoopPosition.x
|
||||
else currentNode.position.x = node.position.x
|
||||
// Check if current dragging node is an entry node
|
||||
const isCurrentEntryNode = TRIGGER_NODE_TYPES.includes(node.data.type as any) || node.data.type === BlockEnum.Start
|
||||
|
||||
if (showHorizontalHelpLineNodesLength > 0)
|
||||
currentNode.position.y = showHorizontalHelpLineNodes[0].position.y
|
||||
else if (restrictPosition.y !== undefined)
|
||||
// X-axis alignment with offset consideration
|
||||
if (showVerticalHelpLineNodesLength > 0) {
|
||||
const targetNode = showVerticalHelpLineNodes[0]
|
||||
const isTargetEntryNode = TRIGGER_NODE_TYPES.includes(targetNode.data.type as any) || targetNode.data.type === BlockEnum.Start
|
||||
|
||||
// Calculate the wrapper position needed to align the inner nodes
|
||||
// Target inner position = target.position + target.offset
|
||||
// Current inner position should equal target inner position
|
||||
// So: current.position + current.offset = target.position + target.offset
|
||||
// Therefore: current.position = target.position + target.offset - current.offset
|
||||
const targetOffset = isTargetEntryNode ? ENTRY_NODE_WRAPPER_OFFSET.x : 0
|
||||
const currentOffset = isCurrentEntryNode ? ENTRY_NODE_WRAPPER_OFFSET.x : 0
|
||||
currentNode.position.x = targetNode.position.x + targetOffset - currentOffset
|
||||
}
|
||||
else if (restrictPosition.x !== undefined) {
|
||||
currentNode.position.x = restrictPosition.x
|
||||
}
|
||||
else if (restrictLoopPosition.x !== undefined) {
|
||||
currentNode.position.x = restrictLoopPosition.x
|
||||
}
|
||||
else {
|
||||
currentNode.position.x = node.position.x
|
||||
}
|
||||
|
||||
// Y-axis alignment with offset consideration
|
||||
if (showHorizontalHelpLineNodesLength > 0) {
|
||||
const targetNode = showHorizontalHelpLineNodes[0]
|
||||
const isTargetEntryNode = TRIGGER_NODE_TYPES.includes(targetNode.data.type as any) || targetNode.data.type === BlockEnum.Start
|
||||
|
||||
const targetOffset = isTargetEntryNode ? ENTRY_NODE_WRAPPER_OFFSET.y : 0
|
||||
const currentOffset = isCurrentEntryNode ? ENTRY_NODE_WRAPPER_OFFSET.y : 0
|
||||
currentNode.position.y = targetNode.position.y + targetOffset - currentOffset
|
||||
}
|
||||
else if (restrictPosition.y !== undefined) {
|
||||
currentNode.position.y = restrictPosition.y
|
||||
else if (restrictLoopPosition.y !== undefined)
|
||||
}
|
||||
else if (restrictLoopPosition.y !== undefined) {
|
||||
currentNode.position.y = restrictLoopPosition.y
|
||||
else currentNode.position.y = node.position.y
|
||||
}
|
||||
else {
|
||||
currentNode.position.y = node.position.y
|
||||
}
|
||||
})
|
||||
setNodes(newNodes)
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue