mirror of
https://github.com/langgenius/dify.git
synced 2026-04-15 09:57:03 +08:00
fix(workflow): correct maximized editor panel layout in execution logs (#34909)
This commit is contained in:
parent
2dc015b360
commit
6612ba69b1
@ -84,7 +84,7 @@ const Base: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<Wrap className={cn(wrapClassName)} style={wrapStyle} isInNode={isInNode} isExpand={isExpand}>
|
||||
<div ref={ref} className={cn(className, isExpand && 'h-full', 'rounded-lg border', !isFocus ? 'border-transparent bg-components-input-bg-normal' : 'overflow-hidden border-components-input-border-hover bg-components-input-bg-hover')}>
|
||||
<div ref={ref} className={cn(className, isExpand ? 'h-full border-0' : 'rounded-lg border', !isFocus ? 'border-transparent bg-components-input-bg-normal' : 'overflow-hidden border-components-input-border-hover bg-components-input-bg-hover')}>
|
||||
<div className="flex h-7 items-center justify-between pl-3 pr-2 pt-1">
|
||||
<div className="system-xs-semibold-uppercase text-text-secondary">{title}</div>
|
||||
<div
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { useRef } from 'react'
|
||||
import useToggleExpend from '../use-toggle-expend'
|
||||
|
||||
type HookProps = {
|
||||
hasFooter?: boolean
|
||||
isInNode?: boolean
|
||||
clientHeight?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper that provides a real ref whose `.current.clientHeight` is stubbed
|
||||
* so we can verify the height math without a real DOM layout pass.
|
||||
*/
|
||||
function useHarness({ hasFooter, isInNode, clientHeight = 400 }: HookProps) {
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
// Stub a ref-like object so measurements are deterministic.
|
||||
if (!ref.current) {
|
||||
Object.defineProperty(ref, 'current', {
|
||||
value: { clientHeight } as HTMLDivElement,
|
||||
writable: true,
|
||||
})
|
||||
}
|
||||
|
||||
return useToggleExpend({ ref, hasFooter, isInNode })
|
||||
}
|
||||
|
||||
describe('useToggleExpend', () => {
|
||||
describe('collapsed state', () => {
|
||||
it('returns empty wrapClassName and zero expand height when collapsed', () => {
|
||||
const { result } = renderHook(() => useHarness({ clientHeight: 400 }))
|
||||
|
||||
expect(result.current.isExpand).toBe(false)
|
||||
expect(result.current.wrapClassName).toBe('')
|
||||
expect(result.current.editorExpandHeight).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('expanded state (node context)', () => {
|
||||
it('uses fixed positioning inside a workflow node panel', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useHarness({ isInNode: true, clientHeight: 400 }),
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setIsExpand(true)
|
||||
})
|
||||
|
||||
expect(result.current.isExpand).toBe(true)
|
||||
expect(result.current.wrapClassName).toContain('fixed')
|
||||
expect(result.current.wrapClassName).toContain('bg-components-panel-bg')
|
||||
expect(result.current.wrapStyle).toEqual(
|
||||
expect.objectContaining({ boxShadow: expect.any(String) }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('expanded state (execution-log / webapp context)', () => {
|
||||
it('fills its positioned ancestor edge-to-edge without hardcoded offsets', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useHarness({ isInNode: false, clientHeight: 400 }),
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setIsExpand(true)
|
||||
})
|
||||
|
||||
// The expanded panel must fill the nearest positioned ancestor entirely
|
||||
// (absolute + inset-0). Previously it used hardcoded `top-[52px]` which
|
||||
// assumed a 52px header that does not exist in the conversation-log
|
||||
// layout, causing the expanded panel to overlap the status bar above
|
||||
// the editor (#34887).
|
||||
expect(result.current.wrapClassName).toContain('absolute')
|
||||
expect(result.current.wrapClassName).toContain('inset-0')
|
||||
expect(result.current.wrapClassName).not.toMatch(/top-\[\d+px\]/)
|
||||
expect(result.current.wrapClassName).not.toMatch(/left-\d+/)
|
||||
expect(result.current.wrapClassName).not.toMatch(/right-\d+/)
|
||||
expect(result.current.wrapClassName).toContain('bg-components-panel-bg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('expanded state height math', () => {
|
||||
it('subtracts the 29px chrome when hasFooter is false', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useHarness({ hasFooter: false, clientHeight: 400 }),
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setIsExpand(true)
|
||||
})
|
||||
|
||||
// 400 (clientHeight) - 29 (title bar) = 371
|
||||
expect(result.current.editorExpandHeight).toBe(371)
|
||||
})
|
||||
|
||||
it('subtracts the 56px chrome when hasFooter is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useHarness({ hasFooter: true, clientHeight: 400 }),
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setIsExpand(true)
|
||||
})
|
||||
|
||||
// 400 (clientHeight) - 56 (title bar + footer) = 344
|
||||
expect(result.current.editorExpandHeight).toBe(344)
|
||||
})
|
||||
|
||||
it('never returns a negative height even if chrome exceeds wrap', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useHarness({ hasFooter: true, clientHeight: 20 }),
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.setIsExpand(true)
|
||||
})
|
||||
|
||||
// 20 - 56 would be -36; clamped to 0.
|
||||
expect(result.current.editorExpandHeight).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLayoutEffect, useState } from 'react'
|
||||
|
||||
type Params = {
|
||||
ref?: React.RefObject<HTMLDivElement | null>
|
||||
@ -6,30 +6,62 @@ type Params = {
|
||||
isInNode?: boolean
|
||||
}
|
||||
|
||||
// Chrome (title bar + optional footer) heights subtracted from the wrap so
|
||||
// the editor body never paints underneath its own controls.
|
||||
const CHROME_HEIGHT_WITH_FOOTER = 56
|
||||
const CHROME_HEIGHT_WITHOUT_FOOTER = 29
|
||||
|
||||
/**
|
||||
* Controls the expand/collapse behavior of the code editor wrapper used across
|
||||
* workflow nodes and execution-log panels.
|
||||
*
|
||||
* Returns:
|
||||
* - `wrapClassName` / `wrapStyle` — positioning + shadow applied to the outer
|
||||
* wrapper when the editor is expanded.
|
||||
* - `editorExpandHeight` — height for the editor body (wrap minus chrome).
|
||||
* - `isExpand` / `setIsExpand` — state + setter for the consumer.
|
||||
*
|
||||
* Height is measured via `useLayoutEffect` so the first expanded render
|
||||
* already has the correct value — the previous `useEffect` implementation
|
||||
* left the editor at the collapsed height for one paint on first expand.
|
||||
*/
|
||||
const useToggleExpend = ({ ref, hasFooter = true, isInNode }: Params) => {
|
||||
const [isExpand, setIsExpand] = useState(false)
|
||||
const [wrapHeight, setWrapHeight] = useState(ref?.current?.clientHeight)
|
||||
const editorExpandHeight = isExpand ? wrapHeight! - (hasFooter ? 56 : 29) : 0
|
||||
useEffect(() => {
|
||||
const [wrapHeight, setWrapHeight] = useState<number | undefined>(undefined)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!ref?.current)
|
||||
return
|
||||
setWrapHeight(ref.current?.clientHeight)
|
||||
}, [isExpand])
|
||||
setWrapHeight(ref.current.clientHeight)
|
||||
}, [isExpand, ref])
|
||||
|
||||
const chromeHeight = hasFooter ? CHROME_HEIGHT_WITH_FOOTER : CHROME_HEIGHT_WITHOUT_FOOTER
|
||||
const editorExpandHeight = isExpand && wrapHeight !== undefined
|
||||
? Math.max(0, wrapHeight - chromeHeight)
|
||||
: 0
|
||||
|
||||
const wrapClassName = (() => {
|
||||
if (!isExpand)
|
||||
return ''
|
||||
|
||||
if (isInNode)
|
||||
return 'fixed z-10 right-[9px] top-[166px] bottom-[8px] p-4 bg-components-panel-bg rounded-xl'
|
||||
return 'fixed z-10 right-[9px] top-[166px] bottom-[8px] p-4 bg-components-panel-bg rounded-xl'
|
||||
|
||||
return 'absolute z-10 left-4 right-6 top-[52px] bottom-0 pb-4 bg-components-panel-bg'
|
||||
// Fill the nearest positioned ancestor entirely. Previously hardcoded
|
||||
// `top-[52px] left-4 right-6` offsets assumed a 52px header above the
|
||||
// scroll container — that assumption no longer holds in the conversation
|
||||
// log (result-panel) layout, where the status bar above the editor is
|
||||
// taller than 52px, causing the expanded panel to partially overlap the
|
||||
// status bar (issue #34887).
|
||||
return 'absolute z-10 inset-0 pb-4 bg-components-panel-bg'
|
||||
})()
|
||||
|
||||
const wrapStyle = isExpand
|
||||
? {
|
||||
boxShadow: '0px 0px 12px -4px rgba(16, 24, 40, 0.05), 0px -3px 6px -2px rgba(16, 24, 40, 0.03)',
|
||||
}
|
||||
: {}
|
||||
|
||||
return {
|
||||
wrapClassName,
|
||||
wrapStyle,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user