refactor(web): normalize z-index layering (#38796)

This commit is contained in:
yyh 2026-07-12 22:23:14 +08:00 committed by GitHub
parent 3defd50709
commit 1246aa7f3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 324 additions and 221 deletions

View File

@ -523,20 +523,6 @@
"count": 1
}
},
"web/app/components/app/configuration/config/agent/agent-setting/index.tsx": {
"jsx-a11y/click-events-have-key-events": {
"count": 1
},
"jsx-a11y/no-static-element-interactions": {
"count": 1
},
"react/set-state-in-effect": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
},
"web/app/components/app/configuration/config/agent/agent-tools/index.tsx": {
"jsx-a11y/mouse-events-have-key-events": {
"count": 2

View File

@ -8,7 +8,7 @@ import AgentSettingButton from '../agent-setting-button'
let latestAgentSettingProps: any
vi.mock('../agent/agent-setting', () => ({
default: (props: any) => {
AgentSetting: (props: any) => {
latestAgentSettingProps = props
return (
<div data-testid="agent-setting">

View File

@ -5,7 +5,7 @@ import { Button } from '@langgenius/dify-ui/button'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import AgentSetting from './agent/agent-setting'
import { AgentSetting } from './agent/agent-setting'
type Props = Readonly<{
isFunctionCall: boolean

View File

@ -1,16 +1,7 @@
import type { AgentConfig } from '@/models/debug'
import { act, fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import { MAX_ITERATIONS_NUM } from '@/config'
import AgentSetting from '../index'
vi.mock('ahooks', async (importOriginal) => {
const actual = await importOriginal<typeof import('ahooks')>()
return {
...actual,
useClickAway: vi.fn(),
}
})
import { AgentSetting } from '../index'
vi.mock('@langgenius/dify-ui/slider', () => ({
Slider: (props: { className?: string, min?: number, max?: number, value: number, onValueChange: (value: number) => void }) => (
@ -65,8 +56,8 @@ describe('AgentSetting', () => {
})
it('should update iteration via slider and number input', () => {
const { container } = renderModal()
const slider = container.querySelector('.slider') as HTMLInputElement
renderModal()
const slider = screen.getByRole('slider')
const numberInput = screen.getByRole('spinbutton')
fireEvent.change(slider, { target: { value: '7' } })
@ -94,6 +85,34 @@ describe('AgentSetting', () => {
expect(onCancel).toHaveBeenCalled()
})
it('should call onCancel when Escape is pressed', () => {
const { onCancel } = renderModal()
fireEvent.keyDown(document, { key: 'Escape' })
expect(onCancel).toHaveBeenCalledTimes(1)
})
it('should call onCancel when the backdrop is clicked', () => {
const { onCancel } = renderModal()
const dialog = screen.getByRole('dialog')
fireEvent.pointerDown(document.body, { target: document.body })
fireEvent.mouseDown(document.body, { target: document.body })
fireEvent.click(document.body, { target: document.body })
expect(dialog).toBeInTheDocument()
expect(onCancel).toHaveBeenCalledTimes(1)
})
it('should call onCancel when close button clicked', () => {
const { onCancel } = renderModal()
fireEvent.click(screen.getByRole('button', { name: 'common.operation.close' }))
expect(onCancel).toHaveBeenCalledTimes(1)
})
it('should call onSave with updated payload', async () => {
const { onSave } = renderModal()
const numberInput = screen.getByRole('spinbutton')

View File

@ -1,13 +1,10 @@
'use client'
import type { FC } from 'react'
import type { AgentConfig } from '@/models/debug'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Slider } from '@langgenius/dify-ui/slider'
import { RiCloseLine } from '@remixicon/react'
import { useClickAway } from 'ahooks'
import * as React from 'react'
import { useEffect, useRef, useState } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication'
import { Unblur } from '@/app/components/base/icons/src/vender/solid/education'
@ -19,60 +16,43 @@ type Props = Readonly<{
payload: AgentConfig
isFunctionCall: boolean
onCancel: () => void
onSave: (payload: any) => void
onSave: (payload: AgentConfig) => void
}>
const maxIterationsMin = 1
const AgentSetting: FC<Props> = ({
export function AgentSetting({
isChatModel,
payload,
isFunctionCall,
onCancel,
onSave,
}) => {
}: Props) {
const { t } = useTranslation()
const [tempPayload, setTempPayload] = useState(payload)
const ref = useRef(null)
const [mounted, setMounted] = useState(false)
const maximumIterationsLabel = t($ => $['agent.setting.maximumIterations.name'], { ns: 'appDebug' })
useClickAway(() => {
if (mounted)
onCancel()
}, ref)
useEffect(() => {
setMounted(true)
}, [])
const handleSave = () => {
onSave(tempPayload)
}
return (
<div
className="fixed inset-0 z-100 flex justify-end overflow-hidden p-2"
style={{
backgroundColor: 'rgba(16, 24, 40, 0.20)',
<Dialog
open
onOpenChange={(open) => {
if (!open)
onCancel()
}}
>
<div
ref={ref}
className="flex h-full w-[640px] flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl"
>
<DialogContent className="top-2 right-2 bottom-2 left-auto flex h-auto max-h-none w-[640px] max-w-[calc(100vw-1rem)] translate-x-0 translate-y-0 flex-col overflow-hidden rounded-xl p-0">
<div className="flex h-14 shrink-0 items-center justify-between border-b border-divider-regular pr-5 pl-6">
<div className="flex flex-col text-base font-semibold text-text-primary">
<div className="leading-6">{t($ => $['agent.setting.name'], { ns: 'appDebug' })}</div>
</div>
<div className="flex items-center">
<div
onClick={onCancel}
className="flex size-6 cursor-pointer items-center justify-center"
>
<RiCloseLine className="size-4 text-text-tertiary" />
</div>
</div>
<DialogTitle className="text-base leading-6 font-semibold text-text-primary">
{t($ => $['agent.setting.name'], { ns: 'appDebug' })}
</DialogTitle>
<DialogCloseButton
className="static z-auto size-6 shrink-0"
aria-label={t($ => $['operation.close'], { ns: 'common' })}
/>
</div>
{/* Body */}
<div
@ -158,20 +138,21 @@ const AgentSetting: FC<Props> = ({
className="sticky bottom-0 z-5 flex w-full justify-end border-t border-divider-regular bg-background-section-burn px-6 py-4"
>
<Button
type="button"
onClick={onCancel}
className="mr-2"
>
{t($ => $['operation.cancel'], { ns: 'common' })}
</Button>
<Button
type="button"
variant="primary"
onClick={handleSave}
>
{t($ => $['operation.save'], { ns: 'common' })}
</Button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}
export default React.memo(AgentSetting)

View File

@ -540,7 +540,7 @@ const Flowchart = (props: FlowchartProps) => {
{svgString && (
<div className={themeClasses.mermaidDiv} style={{ objectFit: 'cover' }} onClick={handlePreviewClick}>
<div className="absolute bottom-2 left-2 z-100">
<div className="absolute bottom-2 left-2">
<button
type="button"
onClick={(e) => {

View File

@ -227,18 +227,6 @@ export default function AccountSetting({
</div>
</div>
<div className="relative flex min-h-0 w-[824px]">
<div className="fixed top-6 right-6 z-9999 flex flex-col items-center">
<Button
variant="tertiary"
size="large"
className="px-2"
aria-label={t($ => $['operation.close'], { ns: 'common' })}
onClick={handleClose}
>
<span className="i-ri-close-line size-5" />
</Button>
<div className="mt-1 system-2xs-medium-uppercase text-text-tertiary">ESC</div>
</div>
<ScrollArea
ref={scrollContainerRef}
className="h-full min-h-0 flex-1 bg-components-panel-bg"
@ -254,6 +242,18 @@ export default function AccountSetting({
<div className="mt-1 system-sm-regular wrap-break-word whitespace-normal text-text-tertiary">{activeItem?.description}</div>
)}
</div>
<div className="fixed top-6 right-6 flex shrink-0 flex-col items-center">
<Button
variant="tertiary"
size="large"
className="px-2"
aria-label={t($ => $['operation.close'], { ns: 'common' })}
onClick={handleClose}
>
<span className="i-ri-close-line size-5" />
</Button>
<div className="mt-1 system-2xs-medium-uppercase text-text-tertiary">ESC</div>
</div>
</div>
<div className="px-4 pt-6 sm:px-8">
{activeMenu === ACCOUNT_SETTING_TAB.PROVIDER && (

View File

@ -29,9 +29,9 @@ const MenuDialog = ({
}}
>
<DialogContent
backdropClassName={cn('z-40 bg-transparent', backdropClassName)}
backdropClassName={cn('bg-transparent', backdropClassName)}
className={cn(
'top-0 left-0 z-40 h-full max-h-none w-full max-w-none translate-x-0 translate-y-0 scale-100 overflow-hidden rounded-none border-none bg-background-sidenav-bg p-0 shadow-none backdrop-blur-md transition-opacity data-ending-style:scale-100 data-starting-style:scale-100',
'top-0 left-0 h-full max-h-none w-full max-w-none translate-x-0 translate-y-0 scale-100 overflow-hidden rounded-none border-none bg-background-sidenav-bg p-0 shadow-none backdrop-blur-md transition-opacity data-ending-style:scale-100 data-starting-style:scale-100',
className,
)}
>

View File

@ -13,7 +13,6 @@ const ConfigurationButton = ({ modelProvider, handleOpenModal }: ConfigurationBu
return (
<Button
size="small"
className="z-100"
onClick={(e) => {
e.stopPropagation()
handleOpenModal(modelProvider, ConfigurationMethodEnum.predefinedModel, undefined)

View File

@ -54,7 +54,7 @@ const StatusIndicators = ({ needsConfiguration, modelProvider, inModelList, disa
</div>
)}
{linkText && linkHref && (
<div className="z-100 cursor-pointer body-xs-regular text-text-accent">
<div className="cursor-pointer body-xs-regular text-text-accent">
<Link
href={linkHref}
onClick={(e) => {

View File

@ -60,7 +60,7 @@ const UpdateDSLModal = ({
</button>
</div>
<div className="relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs">
<div className="absolute top-0 left-0 size-full bg-toast-warning-bg opacity-40" />
<div className="pointer-events-none absolute top-0 left-0 size-full bg-toast-warning-bg opacity-40" />
<div className="flex items-start justify-center p-1">
<RiAlertFill className="size-4 shrink-0 text-text-warning-secondary" />
</div>
@ -70,7 +70,7 @@ const UpdateDSLModal = ({
<Button
size="small"
variant="secondary"
className="z-1000"
className="relative"
onClick={onBackup}
>
<RiFileDownloadLine className="size-3.5 text-components-button-secondary-text" />

View File

@ -41,7 +41,12 @@ export default function IntegrationsSettingModal({
)}
>
<div className="relative flex min-h-0 w-full shrink-0 overflow-hidden rounded-2xl border border-divider-subtle bg-components-panel-bg shadow-2xl">
<div className="fixed top-6 right-6 z-9999 flex flex-col items-center">
<IntegrationsPage
section={section}
onSectionChange={onSectionChange}
onSwitchToMarketplace={handleSwitchToMarketplace}
/>
<div className="fixed top-6 right-6 flex shrink-0 flex-col items-center">
<Button
variant="tertiary"
size="large"
@ -53,11 +58,6 @@ export default function IntegrationsSettingModal({
</Button>
<div className="mt-1 system-2xs-medium-uppercase text-text-tertiary">ESC</div>
</div>
<IntegrationsPage
section={section}
onSectionChange={onSectionChange}
onSwitchToMarketplace={handleSwitchToMarketplace}
/>
</div>
</div>
</MenuDialog>

View File

@ -141,6 +141,7 @@ describe('CustomEdge', () => {
expect(screen.getByTestId('block-selector').parentElement).toHaveStyle({
transform: 'translate(-50%, -50%) translate(24px, 48px)',
opacity: '0.7',
zIndex: '1001',
})
fireEvent.click(screen.getByTestId('block-selector'))

View File

@ -15,8 +15,6 @@ describe('SyncingDataModal', () => {
},
})
const overlay = container.firstElementChild
expect(overlay).toHaveClass('absolute', 'inset-0')
expect(overlay).toHaveClass('z-9999')
expect(container.firstElementChild).toBeInTheDocument()
})
})

View File

@ -1,4 +1,5 @@
import { act, renderHook } from '@testing-library/react'
import { NESTED_ELEMENT_Z_INDEX } from '../../../constants'
import { useInsertSnippet } from '../use-insert-snippet'
type TestNode = {
@ -6,7 +7,9 @@ type TestNode = {
position: { x: number, y: number }
selected?: boolean
parentId?: string
zIndex?: number
data: {
type?: string
selected?: boolean
_children?: { nodeId: string, nodeType: string }[]
_connectedSourceHandleIds?: string[]
@ -20,6 +23,13 @@ type TestEdge = {
sourceHandle?: string
target: string
targetHandle?: string
zIndex?: number
data?: {
isInIteration?: boolean
iteration_id?: string
isInLoop?: boolean
loop_id?: string
}
}
const mockFetchQuery = vi.fn()
@ -93,14 +103,16 @@ describe('useInsertSnippet', () => {
nodes: [
{
id: 'snippet-node-1',
zIndex: 1,
position: { x: 10, y: 20 },
data: { selected: false, _children: [{ nodeId: 'snippet-node-2', nodeType: 'code' }] },
data: { type: 'iteration', selected: false, _children: [{ nodeId: 'snippet-node-2', nodeType: 'code' }] },
},
{
id: 'snippet-node-2',
parentId: 'snippet-node-1',
zIndex: 1002,
position: { x: 30, y: 40 },
data: { selected: false },
data: { type: 'code', selected: false },
},
],
edges: [
@ -110,6 +122,7 @@ describe('useInsertSnippet', () => {
sourceHandle: 'source',
target: 'snippet-node-2',
targetHandle: 'target',
zIndex: 1002,
data: {},
},
],
@ -132,12 +145,15 @@ describe('useInsertSnippet', () => {
expect(nextNodes).toHaveLength(3)
expect(nextNodes[1]!.id).not.toBe('snippet-node-1')
expect(nextNodes[2]!.parentId).toBe(nextNodes[1]!.id)
expect(nextNodes[1]!.zIndex).toBe(0)
expect(nextNodes[2]!.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(nextNodes[1]!.data._children![0]!.nodeId).toBe(nextNodes[2]!.id)
const nextEdges = mockSetEdges.mock.calls[0]![0] as TestEdge[]
expect(nextEdges).toHaveLength(2)
expect(nextEdges[1]!.source).toBe(nextNodes[1]!.id)
expect(nextEdges[1]!.target).toBe(nextNodes[2]!.id)
expect(nextEdges[1]!.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(mockSaveStateToHistory).toHaveBeenCalledWith('NodePaste', {
nodeId: nextNodes[1]!.id,
@ -148,18 +164,32 @@ describe('useInsertSnippet', () => {
})
})
it('should connect inserted snippet nodes to the requested edge position', async () => {
it.each(['iteration', 'loop'] as const)('should connect inserted snippet nodes inside the %s container', async (containerType) => {
mockGetNodes.mockReturnValue([
{
id: 'container-node',
position: { x: 0, y: 0 },
data: {
type: containerType,
selected: false,
_children: [
{ nodeId: 'prev-node', nodeType: 'code' },
{ nodeId: 'next-node', nodeType: 'code' },
],
},
},
{
id: 'prev-node',
parentId: 'container-node',
position: { x: 0, y: 0 },
width: 240,
data: { type: 'start', selected: true, _connectedSourceHandleIds: ['source'] },
data: { type: 'code', selected: true, _connectedSourceHandleIds: ['source'] },
},
{
id: 'next-node',
parentId: 'container-node',
position: { x: 300, y: 0 },
data: { type: 'answer', selected: false, _connectedTargetHandleIds: ['target'] },
data: { type: 'code', selected: false, _connectedTargetHandleIds: ['target'] },
},
])
mockEdges = [
@ -170,8 +200,8 @@ describe('useInsertSnippet', () => {
target: 'next-node',
targetHandle: 'target',
data: {
sourceType: 'start',
targetType: 'answer',
sourceType: 'code',
targetType: 'code',
},
},
]
@ -221,6 +251,8 @@ describe('useInsertSnippet', () => {
const insertedExit = nextNodes.find(node => node.id !== 'prev-node' && node.id !== 'next-node' && node.id.includes('snippet-exit'))!
const shiftedNextNode = nextNodes.find(node => node.id === 'next-node')!
expect(insertedEntry.position).toEqual({ x: 300, y: 0 })
expect(insertedEntry.parentId).toBe('container-node')
expect(insertedExit.parentId).toBe('container-node')
expect(shiftedNextNode.position.x).toBe(600)
expect(nextNodes.find(node => node.id === 'prev-node')!.data._connectedSourceHandleIds).toEqual(['source'])
expect(insertedEntry.data._connectedTargetHandleIds).toEqual(['target'])
@ -248,6 +280,18 @@ describe('useInsertSnippet', () => {
targetHandle: 'target',
}),
]))
const incomingEdge = nextEdges.find(edge => edge.source === 'prev-node' && edge.target === insertedEntry.id)
const insertedInternalEdge = nextEdges.find(edge => edge.source === insertedEntry.id && edge.target === insertedExit.id)
const outgoingEdge = nextEdges.find(edge => edge.source === insertedExit.id && edge.target === 'next-node')
expect(incomingEdge?.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(insertedInternalEdge?.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(outgoingEdge?.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(insertedInternalEdge?.data).toEqual(expect.objectContaining({
isInIteration: containerType === 'iteration',
iteration_id: containerType === 'iteration' ? 'container-node' : undefined,
isInLoop: containerType === 'loop',
loop_id: containerType === 'loop' ? 'container-node' : undefined,
}))
expect(mockIncrementSnippetUseCount).toHaveBeenCalledWith({
params: { snippetId: 'snippet-1' },
})

View File

@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
import { useStoreApi } from 'reactflow'
import { consoleQuery } from '@/service/client'
import { useIncrementSnippetUseCountMutation } from '@/service/use-snippets'
import { CUSTOM_EDGE, ITERATION_CHILDREN_Z_INDEX, LOOP_CHILDREN_Z_INDEX, NODE_WIDTH_X_OFFSET, X_OFFSET } from '../../constants'
import { CUSTOM_EDGE, NESTED_ELEMENT_Z_INDEX, NODE_WIDTH_X_OFFSET, X_OFFSET } from '../../constants'
import { useNodesSyncDraft, useWorkflowHistory, WorkflowHistoryEvent } from '../../hooks'
import { BlockEnum } from '../../types'
import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../../utils'
@ -212,9 +212,7 @@ const createBoundaryEdges = ({
const parentNode = getParentNode(currentNodes, insertPayload)
const isInIteration = parentNode?.data.type === BlockEnum.Iteration
const isInLoop = parentNode?.data.type === BlockEnum.Loop
const zIndex = parentNode
? isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX
: 0
const zIndex = parentNode ? NESTED_ELEMENT_Z_INDEX : 0
const incomingEdges: Edge[] = []
const outgoingEdges: Edge[] = []
@ -315,6 +313,7 @@ export const useInsertSnippet = () => {
changes,
[...currentNodes, ...remappedGraph.nodes],
)
const remappedNodesById = new Map(remappedGraph.nodes.map(node => [node.id, node]))
const firstEntryNode = entryNodes.find(canConnectToTarget) ?? entryNodes[0]
const clearedNodes = currentNodes.map(node => ({
...node,
@ -354,14 +353,15 @@ export const useInsertSnippet = () => {
const shouldMoveIntoParent = !!parentNode && rootNodeIds.has(node.id)
const isInIteration = parentNode?.data.type === BlockEnum.Iteration
const isInLoop = parentNode?.data.type === BlockEnum.Loop
const snippetParentNode = node.parentId ? remappedNodesById.get(node.parentId) : undefined
const isNestedInSnippet = snippetParentNode?.data.type === BlockEnum.Iteration
|| snippetParentNode?.data.type === BlockEnum.Loop
return {
...node,
parentId: shouldMoveIntoParent ? parentNode.id : node.parentId,
extent: shouldMoveIntoParent ? parentNode.extent : node.extent,
zIndex: shouldMoveIntoParent
? isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX
: node.zIndex,
zIndex: shouldMoveIntoParent || isNestedInSnippet ? NESTED_ELEMENT_Z_INDEX : 0,
data: {
...node.data,
...(nodesConnectedSourceOrTargetHandleIdsMap[node.id] ?? {}),
@ -372,8 +372,31 @@ export const useInsertSnippet = () => {
},
}
})
const nextNodes = [...clearedNodes, ...insertedNodes]
const finalNodesById = new Map(nextNodes.map(node => [node.id, node]))
const insertedEdges = remappedGraph.edges.map((edge) => {
const sourceNode = finalNodesById.get(edge.source)
const targetNode = finalNodesById.get(edge.target)
const sourceParentNode = sourceNode?.parentId ? finalNodesById.get(sourceNode.parentId) : undefined
const targetParentNode = targetNode?.parentId ? finalNodesById.get(targetNode.parentId) : undefined
const nestedParentNode = [sourceParentNode, targetParentNode].find(node => node?.data.type === BlockEnum.Iteration || node?.data.type === BlockEnum.Loop)
const isInIteration = nestedParentNode?.data.type === BlockEnum.Iteration
const isInLoop = nestedParentNode?.data.type === BlockEnum.Loop
setNodes([...clearedNodes, ...insertedNodes])
return {
...edge,
data: {
...edge.data,
isInIteration,
iteration_id: isInIteration ? nestedParentNode.id : undefined,
isInLoop,
loop_id: isInLoop ? nestedParentNode.id : undefined,
},
zIndex: nestedParentNode ? NESTED_ELEMENT_Z_INDEX : 0,
}
})
setNodes(nextNodes)
setEdges([
...edges
.filter(edge => edge.id !== currentEdge?.id)
@ -384,7 +407,7 @@ export const useInsertSnippet = () => {
_connectedNodeIsSelected: false,
},
})),
...remappedGraph.edges,
...insertedEdges,
...boundaryEdges,
])
saveStateToHistory(WorkflowHistoryEvent.NodePaste, {

View File

@ -135,7 +135,7 @@ export const CommentInput: FC<CommentInputProps> = memo(({
return (
<div
className={cn(
'absolute z-60 w-96',
'absolute z-40 w-96',
disabled && 'pointer-events-none opacity-80',
)}
style={{

View File

@ -14,7 +14,7 @@ export const CommentCursor: FC = memo(() => {
return (
<div
className="pointer-events-none absolute z-50 flex size-6 items-center justify-center"
className="pointer-events-none absolute z-30 flex size-6 items-center justify-center"
style={{
left: mousePosition.elementX,
top: mousePosition.elementY,

View File

@ -622,7 +622,7 @@ const MentionInputInner = forwardRef<HTMLTextAreaElement, MentionInputProps>(({
{showMentionDropdown && filteredMentionUsers.length > 0 && typeof document !== 'undefined' && createPortal(
<div
className="fixed z-9999 max-h-[248px] w-[280px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg/95 shadow-lg backdrop-blur-[10px]"
className="fixed z-50 max-h-[248px] w-[280px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg/95 shadow-lg backdrop-blur-[10px]"
style={{
left: dropdownPosition.x,
[dropdownPosition.placement === 'top' ? 'bottom' : 'top']: dropdownPosition.placement === 'top'

View File

@ -414,7 +414,7 @@ export const CommentThread: FC<CommentThreadProps> = memo(({
return (
<div
className="absolute z-50 w-[360px] max-w-[360px]"
className="absolute z-30 w-[360px] max-w-[360px]"
style={{
left: canvasPosition.x + 40,
top: canvasPosition.y,

View File

@ -9,8 +9,8 @@ export const X_OFFSET = 60
export const NODE_WIDTH_X_OFFSET = NODE_WIDTH + X_OFFSET
export const Y_OFFSET = 39
export const START_INITIAL_POSITION = { x: 80, y: 282 }
export const ITERATION_NODE_Z_INDEX = 1
export const ITERATION_CHILDREN_Z_INDEX = 1002
// React Flow elevates selected nodes by 1000, so nested graph elements use the next layer.
export const NESTED_ELEMENT_Z_INDEX = 1001
export const ITERATION_PADDING = {
top: 65,
right: 16,
@ -18,8 +18,6 @@ export const ITERATION_PADDING = {
left: 16,
}
export const LOOP_NODE_Z_INDEX = 1
export const LOOP_CHILDREN_Z_INDEX = 1002
export const LOOP_PADDING = {
top: 65,
right: 16,

View File

@ -3,7 +3,6 @@ import type {
Edge,
OnSelectBlock,
} from './types'
import { cn } from '@langgenius/dify-ui/cn'
import { intersection } from 'es-toolkit/array'
import {
memo,
@ -19,7 +18,7 @@ import {
} from 'reactflow'
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
import BlockSelector from './block-selector'
import { ITERATION_CHILDREN_Z_INDEX, LOOP_CHILDREN_Z_INDEX } from './constants'
import { NESTED_ELEMENT_Z_INDEX } from './constants'
import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render'
import {
useAvailableBlocks,
@ -143,17 +142,15 @@ const CustomEdge = ({
/>
<EdgeLabelRenderer>
<div
className={cn(
'nopan nodrag',
'transition-opacity duration-150',
data.isInIteration && `z-[${ITERATION_CHILDREN_Z_INDEX}]`,
data.isInLoop && `z-[${LOOP_CHILDREN_Z_INDEX}]`,
)}
className="nopan nodrag transition-opacity duration-150"
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
pointerEvents: isTriggerVisible ? 'all' : 'none',
opacity: isTriggerVisible ? (data._waitingRun ? 0.7 : 1) : 0,
zIndex: data.isInIteration || data.isInLoop
? NESTED_ELEMENT_Z_INDEX
: undefined,
}}
onMouseEnter={() => setIsTriggerHovered(true)}
onMouseLeave={() => setIsTriggerHovered(false)}

View File

@ -1112,6 +1112,7 @@ describe('useNodesInteractions', () => {
createNode({
id: nodeId,
position: { x: 120, y: 120 },
zIndex: 1002,
data: {
type: containerType,
title: containerType === BlockEnum.Iteration ? 'Iteration' : 'Loop',
@ -1136,6 +1137,7 @@ describe('useNodesInteractions', () => {
expect(newContainer).toBeDefined()
expect(newContainer?.parentId).toBeUndefined()
expect(newContainer?.zIndex).toBe(0)
expect(newContainer?.data.isInIteration).toBeFalsy()
expect(newContainer?.data.isInLoop).toBeFalsy()
})

View File

@ -27,10 +27,9 @@ import { consoleQuery } from '@/service/client'
import { collaborationManager } from '../collaboration/core/collaboration-manager'
import {
CUSTOM_EDGE,
ITERATION_CHILDREN_Z_INDEX,
ITERATION_PADDING,
LOOP_CHILDREN_Z_INDEX,
LOOP_PADDING,
NESTED_ELEMENT_Z_INDEX,
NODE_WIDTH_X_OFFSET,
X_OFFSET,
Y_OFFSET,
@ -597,11 +596,7 @@ export const useNodesInteractions = () => {
isInLoop,
loop_id: isInLoop ? targetNode?.parentId : undefined,
},
zIndex: targetNode?.parentId
? isInIteration
? ITERATION_CHILDREN_Z_INDEX
: LOOP_CHILDREN_Z_INDEX
: 0,
zIndex: targetNode?.parentId ? NESTED_ELEMENT_Z_INDEX : 0,
}
const nodesConnectedSourceOrTargetHandleIdsMap
= getNodesConnectedSourceOrTargetHandleIdsMap(
@ -998,11 +993,11 @@ export const useNodesInteractions = () => {
newNode.data.isInLoop = isInLoop
if (isInIteration) {
newNode.data.iteration_id = parentNode.id
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
newNode.zIndex = NESTED_ELEMENT_Z_INDEX
}
if (isInLoop) {
newNode.data.loop_id = parentNode.id
newNode.zIndex = LOOP_CHILDREN_Z_INDEX
newNode.zIndex = NESTED_ELEMENT_Z_INDEX
}
if (
isInIteration
@ -1042,11 +1037,7 @@ export const useNodesInteractions = () => {
loop_id: isInLoop ? prevNode!.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: prevNode!.parentId
? isInIteration
? ITERATION_CHILDREN_Z_INDEX
: LOOP_CHILDREN_Z_INDEX
: 0,
zIndex: prevNode!.parentId ? NESTED_ELEMENT_Z_INDEX : 0,
}
}
@ -1156,11 +1147,11 @@ export const useNodesInteractions = () => {
newNode.data.isInLoop = isInLoop
if (isInIteration) {
newNode.data.iteration_id = parentNode.id
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
newNode.zIndex = NESTED_ELEMENT_Z_INDEX
}
if (isInLoop) {
newNode.data.loop_id = parentNode.id
newNode.zIndex = LOOP_CHILDREN_Z_INDEX
newNode.zIndex = NESTED_ELEMENT_Z_INDEX
}
}
@ -1188,11 +1179,7 @@ export const useNodesInteractions = () => {
loop_id: isInLoop ? nextNode.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: nextNode.parentId
? isInIteration
? ITERATION_CHILDREN_Z_INDEX
: LOOP_CHILDREN_Z_INDEX
: 0,
zIndex: nextNode.parentId ? NESTED_ELEMENT_Z_INDEX : 0,
}
}
@ -1309,11 +1296,11 @@ export const useNodesInteractions = () => {
newNode.data.isInLoop = isInLoop
if (isInIteration) {
newNode.data.iteration_id = parentNode.id
newNode.zIndex = ITERATION_CHILDREN_Z_INDEX
newNode.zIndex = NESTED_ELEMENT_Z_INDEX
}
if (isInLoop) {
newNode.data.loop_id = parentNode.id
newNode.zIndex = LOOP_CHILDREN_Z_INDEX
newNode.zIndex = NESTED_ELEMENT_Z_INDEX
}
}
@ -1339,11 +1326,7 @@ export const useNodesInteractions = () => {
loop_id: isInLoop ? prevNode.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: prevNode.parentId
? isInIteration
? ITERATION_CHILDREN_Z_INDEX
: LOOP_CHILDREN_Z_INDEX
: 0,
zIndex: prevNode.parentId ? NESTED_ELEMENT_Z_INDEX : 0,
}
}
@ -1382,11 +1365,7 @@ export const useNodesInteractions = () => {
loop_id: isNextNodeInLoop ? nextNode.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: nextNode.parentId
? isNextNodeInIteration
? ITERATION_CHILDREN_Z_INDEX
: LOOP_CHILDREN_Z_INDEX
: 0,
zIndex: nextNode.parentId ? NESTED_ELEMENT_Z_INDEX : 0,
}
}
const nodesConnectedSourceOrTargetHandleIdsMap
@ -1627,11 +1606,7 @@ export const useNodesInteractions = () => {
loop_id: isInLoop ? targetNodeForEdge.parentId : undefined,
_connectedNodeIsSelected: false,
},
zIndex: targetNodeForEdge.parentId
? isInIteration
? ITERATION_CHILDREN_Z_INDEX
: LOOP_CHILDREN_Z_INDEX
: 0,
zIndex: targetNodeForEdge.parentId ? NESTED_ELEMENT_Z_INDEX : 0,
})
}
@ -1667,11 +1642,7 @@ export const useNodesInteractions = () => {
loop_id: newNodeIsInLoop ? newCurrentNode.parentId : undefined,
_connectedNodeIsSelected: false,
},
zIndex: newCurrentNode.parentId
? newNodeIsInIteration
? ITERATION_CHILDREN_Z_INDEX
: LOOP_CHILDREN_Z_INDEX
: 0,
zIndex: newCurrentNode.parentId ? NESTED_ELEMENT_Z_INDEX : 0,
})
}
@ -2075,7 +2046,7 @@ export const useNodesInteractions = () => {
y: nodeToPaste.position.y + offsetY,
},
extent: nodeToPaste.extent,
zIndex: nodeToPaste.zIndex,
zIndex: 0,
})
newNode.id = newNode.id + index
@ -2145,7 +2116,7 @@ export const useNodesInteractions = () => {
positionAbsolute: child.positionAbsolute,
parentId: newNode.id,
extent: child.extent,
zIndex: ITERATION_CHILDREN_Z_INDEX,
zIndex: NESTED_ELEMENT_Z_INDEX,
})
newChild.id = `${newNode.id}${newChild.id + childIndex}`
idMapping[child.id] = newChild.id
@ -2245,7 +2216,7 @@ export const useNodesInteractions = () => {
positionAbsolute: child.positionAbsolute,
parentId: newNode.id,
extent: child.extent,
zIndex: LOOP_CHILDREN_Z_INDEX,
zIndex: NESTED_ELEMENT_Z_INDEX,
})
newChild.id = `${newNode.id}${newChild.id + childIndex}`
idMapping[child.id] = newChild.id
@ -2290,7 +2261,7 @@ export const useNodesInteractions = () => {
newNode.data.loop_id = !isIteration ? selectedContainerNode.id : undefined
newNode.parentId = selectedContainerNode.id
newNode.zIndex = isIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX
newNode.zIndex = NESTED_ELEMENT_Z_INDEX
newNode.positionAbsolute = {
x: newNode.position.x,
y: newNode.position.y,
@ -2348,13 +2319,7 @@ export const useNodesInteractions = () => {
loop_id: isInLoop ? parentNode?.id : undefined,
_connectedNodeIsSelected: false,
},
zIndex: parentNode
? isInIteration
? ITERATION_CHILDREN_Z_INDEX
: isInLoop
? LOOP_CHILDREN_Z_INDEX
: 0
: 0,
zIndex: parentNode && (isInIteration || isInLoop) ? NESTED_ELEMENT_Z_INDEX : 0,
}
edgesToPaste.push(newEdge)
}

View File

@ -73,7 +73,6 @@ import { CommentThread } from './comment/thread'
import {
CUSTOM_EDGE,
CUSTOM_NODE,
ITERATION_CHILDREN_Z_INDEX,
WORKFLOW_DATA_UPDATE,
} from './constants'
import CustomConnectionLine from './custom-connection-line'
@ -618,13 +617,12 @@ export const Workflow: FC<WorkflowProps> = memo(({
<div
id="workflow-container"
className={cn(
'relative h-full w-full min-w-[960px] overflow-hidden',
'relative isolate h-full w-full min-w-[960px] overflow-hidden',
workflowReadOnly && 'workflow-panel-animation',
nodeAnimation && 'workflow-node-animation',
)}
ref={workflowContainerRef}
>
<SyncingDataModal />
<CandidateNode />
<CommentManager />
<div
@ -750,8 +748,6 @@ export const Workflow: FC<WorkflowProps> = memo(({
onPaneContextMenu={handlePaneContextMenu}
onSelectionContextMenu={handleSelectionContextMenu}
connectionLineComponent={CustomConnectionLine}
// NOTE: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same?
connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
defaultViewport={viewport}
multiSelectionKeyCode={null}
deleteKeyCode={null}
@ -785,6 +781,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
)}
</ReactFlow>
</WorkflowContextmenu>
<SyncingDataModal />
</div>
)
})

View File

@ -151,12 +151,10 @@ const CodeEditor: FC<Props> = ({
{isShowVarPicker && createPortal(
<div
ref={popupRef}
className="w-[228px] space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg"
className="fixed z-50 w-[228px] space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg"
style={{
position: 'fixed',
top: popupPosition.y,
left: popupPosition.x,
zIndex: 100,
}}
>
<VarReferenceVars

View File

@ -85,7 +85,7 @@ describe('iteration interaction helpers', () => {
position: { x: 12, y: 24 },
positionAbsolute: { x: 12, y: 24 },
extent: 'parent',
zIndex: 7,
zIndex: 1002,
data: { type: BlockEnum.Code, title: 'Original', desc: 'child', selected: true },
})
@ -99,7 +99,7 @@ describe('iteration interaction helpers', () => {
expect(result).toEqual(expect.objectContaining({
parentId: 'iteration-2',
zIndex: 7,
zIndex: 1001,
data: expect.objectContaining({
title: 'blocks.code 3',
iteration_id: 'iteration-2',

View File

@ -5,6 +5,7 @@ import type {
} from '../../types'
import {
ITERATION_PADDING,
NESTED_ELEMENT_Z_INDEX,
} from '../../constants'
import { CUSTOM_ITERATION_START_NODE } from '../iteration-start/constants'
@ -108,6 +109,6 @@ export const buildIterationChildCopy = ({
positionAbsolute: child.positionAbsolute,
parentId: newNodeId,
extent: child.extent,
zIndex: child.zIndex,
zIndex: NESTED_ELEMENT_Z_INDEX,
}
}

View File

@ -87,7 +87,7 @@ describe('loop interaction helpers', () => {
expect(result.newId).toBe('loop-23')
expect(result.params).toEqual(expect.objectContaining({
parentId: 'loop-2',
zIndex: 1002,
zIndex: 1001,
data: expect.objectContaining({
title: 'Code 3',
isInLoop: true,

View File

@ -3,8 +3,8 @@ import type {
Node,
} from '../../types'
import {
LOOP_CHILDREN_Z_INDEX,
LOOP_PADDING,
NESTED_ELEMENT_Z_INDEX,
} from '../../constants'
import { CUSTOM_LOOP_START_NODE } from '../loop-start/constants'
@ -99,7 +99,7 @@ export const buildLoopChildCopy = ({
positionAbsolute: child.positionAbsolute,
parentId: newNodeId,
extent: child.extent,
zIndex: LOOP_CHILDREN_Z_INDEX,
zIndex: NESTED_ELEMENT_Z_INDEX,
}
return {

View File

@ -24,7 +24,7 @@
}
#workflow-container .react-flow__node-custom-note {
z-index: -1000 !important;
z-index: -1 !important;
}
#workflow-container .react-flow__attribution {

View File

@ -7,7 +7,7 @@ const SyncingDataModal = () => {
return null
return (
<div className="absolute inset-0 z-9999">
<div className="absolute inset-0 z-50">
</div>
)
}

View File

@ -220,7 +220,7 @@ const UpdateDSLModal = ({
</button>
</div>
<div className="relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs">
<div className="absolute top-0 left-0 size-full bg-toast-warning-bg opacity-40" />
<div className="pointer-events-none absolute top-0 left-0 size-full bg-toast-warning-bg opacity-40" />
<div className="flex items-start justify-center p-1">
<RiAlertFill className="size-4 shrink-0 text-text-warning-secondary" />
</div>
@ -230,7 +230,7 @@ const UpdateDSLModal = ({
<Button
size="small"
variant="secondary"
className="z-1000"
className="relative"
onClick={onBackup}
>
<RiFileDownloadLine className="size-3.5 text-components-button-secondary-text" />

View File

@ -1,7 +1,7 @@
import type { IterationNodeType } from '../../nodes/iteration/types'
import type { LoopNodeType } from '../../nodes/loop/types'
import type { CommonNodeType, Node } from '../../types'
import { CUSTOM_NODE, ITERATION_CHILDREN_Z_INDEX, ITERATION_NODE_Z_INDEX, LOOP_CHILDREN_Z_INDEX, LOOP_NODE_Z_INDEX } from '../../constants'
import { CUSTOM_NODE, NESTED_ELEMENT_Z_INDEX } from '../../constants'
import { CUSTOM_ITERATION_START_NODE } from '../../nodes/iteration-start/constants'
import { CUSTOM_LOOP_START_NODE } from '../../nodes/loop-start/constants'
import { CUSTOM_SIMPLE_NODE } from '../../simple-node/constants'
@ -18,6 +18,13 @@ import {
hasRetryNode,
} from '../node'
describe('nested workflow node layering', () => {
it('should keep nested elements above React Flow selected nodes', () => {
expect(NESTED_ELEMENT_Z_INDEX).toBe(1001)
expect(NESTED_ELEMENT_Z_INDEX).toBeGreaterThan(1000)
})
})
describe('getNodeCatalogType', () => {
it('should use Agent V2 catalog type for graph agent nodes with the Agent V2 discriminator', () => {
expect(getNodeCatalogType({
@ -67,22 +74,22 @@ describe('generateNewNode', () => {
expect(newNode.id).toBe('custom-id')
})
it('should set ITERATION_NODE_Z_INDEX for iteration nodes', () => {
it('should use the default layer for iteration nodes', () => {
const { newNode } = generateNewNode({
data: { title: 'Iter', desc: '', type: BlockEnum.Iteration } as CommonNodeType,
position: { x: 0, y: 0 },
})
expect(newNode.zIndex).toBe(ITERATION_NODE_Z_INDEX)
expect(newNode.zIndex).toBe(0)
})
it('should set LOOP_NODE_Z_INDEX for loop nodes', () => {
it('should use the default layer for loop nodes', () => {
const { newNode } = generateNewNode({
data: { title: 'Loop', desc: '', type: BlockEnum.Loop } as CommonNodeType,
position: { x: 0, y: 0 },
})
expect(newNode.zIndex).toBe(LOOP_NODE_Z_INDEX)
expect(newNode.zIndex).toBe(0)
})
it('should create an iteration start node for iteration type', () => {
@ -139,7 +146,7 @@ describe('getIterationStartNode', () => {
expect(node.parentId).toBe('parent-iter')
expect(node.selectable).toBe(false)
expect(node.draggable).toBe(false)
expect(node.zIndex).toBe(ITERATION_CHILDREN_Z_INDEX)
expect(node.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(node.position).toEqual({ x: 24, y: 68 })
})
})
@ -155,7 +162,7 @@ describe('getLoopStartNode', () => {
expect(node.parentId).toBe('parent-loop')
expect(node.selectable).toBe(false)
expect(node.draggable).toBe(false)
expect(node.zIndex).toBe(LOOP_CHILDREN_Z_INDEX)
expect(node.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(node.position).toEqual({ x: 24, y: 68 })
})
})

View File

@ -9,7 +9,7 @@ import type {
Edge,
Node,
} from '@/app/components/workflow/types'
import { CUSTOM_NODE, DEFAULT_RETRY_INTERVAL, DEFAULT_RETRY_MAX } from '@/app/components/workflow/constants'
import { CUSTOM_NODE, DEFAULT_RETRY_INTERVAL, DEFAULT_RETRY_MAX, NESTED_ELEMENT_Z_INDEX } from '@/app/components/workflow/constants'
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
import { BlockEnum, ErrorHandleMode } from '@/app/components/workflow/types'
@ -186,6 +186,37 @@ describe('preprocessNodesAndEdges', () => {
})
describe('initialNodes', () => {
it('should normalize legacy container and nested node layers regardless of node order', () => {
const nodes = [
createNode({
id: 'child-1',
parentId: 'iter-1',
zIndex: 1002,
data: { type: BlockEnum.Code, title: '', desc: '' },
}),
createNode({
id: 'iter-1',
selected: true,
zIndex: 1,
data: { type: BlockEnum.Iteration, title: '', desc: '' },
}),
createNode({
id: 'root-1',
zIndex: 1002,
data: { type: BlockEnum.Code, title: '', desc: '' },
}),
]
const result = initialNodes(nodes, [])
const parentNode = result.find(node => node.id === 'iter-1')!
const childNode = result.find(node => node.id === 'child-1')!
expect(parentNode.zIndex).toBe(0)
expect(childNode.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(result.find(node => node.id === 'root-1')!.zIndex).toBe(0)
expect(parentNode.zIndex! + 1000).toBeLessThan(childNode.zIndex!)
})
it('should set positions when first node has no position', () => {
const nodes = [
createNode({ id: 'n1', data: { type: BlockEnum.Start, title: '', desc: '' } }),
@ -564,6 +595,44 @@ describe('initialNodes', () => {
})
describe('initialEdges', () => {
it('should normalize legacy nested and root edge layers', () => {
const nodes = [
createNode({ id: 'iter-1', data: { type: BlockEnum.Iteration, title: '', desc: '' } }),
createNode({ id: 'child-1', parentId: 'iter-1', data: { type: BlockEnum.Code, title: '', desc: '' } }),
createNode({ id: 'child-2', parentId: 'iter-1', data: { type: BlockEnum.Code, title: '', desc: '' } }),
createNode({ id: 'root-1', data: { type: BlockEnum.Code, title: '', desc: '' } }),
]
const edges = [
createEdge({ id: 'nested-edge', source: 'child-1', target: 'child-2', zIndex: 1002 }),
createEdge({ id: 'boundary-edge', source: 'child-2', target: 'root-1', zIndex: 1002 }),
createEdge({ id: 'root-edge', source: 'root-1', target: 'iter-1', zIndex: 1002 }),
]
const result = initialEdges(edges, nodes)
expect(result.find(edge => edge.id === 'nested-edge')!.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(result.find(edge => edge.id === 'boundary-edge')!.zIndex).toBe(NESTED_ELEMENT_Z_INDEX)
expect(result.find(edge => edge.id === 'root-edge')!.zIndex).toBe(0)
expect(result.find(edge => edge.id === 'nested-edge')!.data).toEqual(expect.objectContaining({
isInIteration: true,
iteration_id: 'iter-1',
isInLoop: false,
loop_id: undefined,
}))
expect(result.find(edge => edge.id === 'boundary-edge')!.data).toEqual(expect.objectContaining({
isInIteration: true,
iteration_id: 'iter-1',
isInLoop: false,
loop_id: undefined,
}))
expect(result.find(edge => edge.id === 'root-edge')!.data).toEqual(expect.objectContaining({
isInIteration: false,
iteration_id: undefined,
isInLoop: false,
loop_id: undefined,
}))
})
it('should set edge type to custom', () => {
const nodes = [
createNode({ id: 'a', data: { type: BlockEnum.Start, title: '', desc: '' } }),

View File

@ -10,10 +10,7 @@ import {
import { CUSTOM_SIMPLE_NODE } from '@/app/components/workflow/simple-node/constants'
import {
CUSTOM_NODE,
ITERATION_CHILDREN_Z_INDEX,
ITERATION_NODE_Z_INDEX,
LOOP_CHILDREN_Z_INDEX,
LOOP_NODE_Z_INDEX,
NESTED_ELEMENT_Z_INDEX,
} from '../constants'
import { isAgentV2NodeData } from '../nodes/agent-v2/types'
import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
@ -38,7 +35,7 @@ export function generateNewNode({ data, position, id, zIndex, type, ...rest }: O
position,
targetPosition: Position.Left,
sourcePosition: Position.Right,
zIndex: data.type === BlockEnum.Iteration ? ITERATION_NODE_Z_INDEX : (data.type === BlockEnum.Loop ? LOOP_NODE_Z_INDEX : zIndex),
zIndex: data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop ? 0 : zIndex,
...rest,
} as Node
@ -81,7 +78,7 @@ export function getIterationStartNode(iterationId: string): Node {
x: 24,
y: 68,
},
zIndex: ITERATION_CHILDREN_Z_INDEX,
zIndex: NESTED_ELEMENT_Z_INDEX,
parentId: iterationId,
selectable: false,
draggable: false,
@ -102,7 +99,7 @@ export function getLoopStartNode(loopId: string): Node {
x: 24,
y: 68,
},
zIndex: LOOP_CHILDREN_Z_INDEX,
zIndex: NESTED_ELEMENT_Z_INDEX,
parentId: loopId,
selectable: false,
draggable: false,

View File

@ -20,8 +20,7 @@ import {
CUSTOM_NODE,
DEFAULT_RETRY_INTERVAL,
DEFAULT_RETRY_MAX,
ITERATION_CHILDREN_Z_INDEX,
LOOP_CHILDREN_Z_INDEX,
NESTED_ELEMENT_Z_INDEX,
NODE_WIDTH_X_OFFSET,
START_INITIAL_POSITION,
} from '../constants'
@ -176,7 +175,7 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
loop_id: isInLoop ? startNode!.parentId : undefined,
_connectedNodeIsSelected: true,
},
zIndex: isIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX,
zIndex: NESTED_ELEMENT_Z_INDEX,
}
})
nodes.forEach((node) => {
@ -196,6 +195,7 @@ export const preprocessNodesAndEdges = (nodes: Node[], edges: Edge[]) => {
export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
const { nodes, edges } = preprocessNodesAndEdges(cloneDeep(originNodes), cloneDeep(originEdges))
const firstNode = nodes[0]
const nodesById = new Map(nodes.map(node => [node.id, node]))
if (!firstNode?.position) {
nodes.forEach((node, index) => {
@ -217,6 +217,11 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
}, {} as Record<string, { nodeId: string, nodeType: BlockEnum }[]>)
return nodes.map((node) => {
const parentNode = node.parentId ? nodesById.get(node.parentId) : undefined
const isNested = parentNode?.data.type === BlockEnum.Iteration || parentNode?.data.type === BlockEnum.Loop
node.zIndex = isNested ? NESTED_ELEMENT_Z_INDEX : 0
if (!node.type)
node.type = CUSTOM_NODE
@ -356,6 +361,24 @@ export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => {
} as any
}
const sourceNode = nodesMap[edge.source]
const targetNode = nodesMap[edge.target]
const sourceParentNode = sourceNode?.parentId ? nodesMap[sourceNode.parentId] : undefined
const targetParentNode = targetNode?.parentId ? nodesMap[targetNode.parentId] : undefined
const nestedParentNode = [sourceParentNode, targetParentNode].find(node => node?.data.type === BlockEnum.Iteration || node?.data.type === BlockEnum.Loop)
const isInIteration = nestedParentNode?.data.type === BlockEnum.Iteration
const isInLoop = nestedParentNode?.data.type === BlockEnum.Loop
edge.data = {
...edge.data,
isInIteration,
iteration_id: isInIteration ? nestedParentNode.id : undefined,
isInLoop,
loop_id: isInLoop ? nestedParentNode.id : undefined,
} as Edge['data']
edge.zIndex = nestedParentNode
? NESTED_ELEMENT_Z_INDEX
: 0
return edge
})
}

View File

@ -25,7 +25,6 @@ import ReactFlow, {
import {
CUSTOM_EDGE,
CUSTOM_NODE,
ITERATION_CHILDREN_Z_INDEX,
} from '@/app/components/workflow/constants'
import CustomConnectionLine from '@/app/components/workflow/custom-connection-line'
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
@ -113,7 +112,6 @@ const WorkflowPreview = ({
edges={edgesData}
onEdgesChange={onEdgesChange}
connectionLineComponent={CustomConnectionLine}
connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
defaultViewport={viewport}
multiSelectionKeyCode={null}
deleteKeyCode={null}