157 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { FC } from 'react'
import { memo, useCallback, useEffect, useRef } from 'react'
import type { VersionHistoryPanelProps } from '@/app/components/workflow/panel/version-history-panel'
import { useShallow } from 'zustand/react/shallow'
import { useStore as useReactflow } from 'reactflow'
import { Panel as NodePanel } from '../nodes'
import { useStore } from '../store'
import EnvPanel from './env-panel'
import cn from '@/utils/classnames'
import dynamic from 'next/dynamic'
const VersionHistoryPanel = dynamic(() => import('@/app/components/workflow/panel/version-history-panel'), {
ssr: false,
})
export type PanelProps = {
components?: {
left?: React.ReactNode
right?: React.ReactNode
}
versionHistoryPanelProps?: VersionHistoryPanelProps
}
/**
* Reference MDN standard implementationhttps://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserverEntry/borderBoxSize
*/
const getEntryWidth = (entry: ResizeObserverEntry, element: HTMLElement): number => {
if (entry.borderBoxSize?.length > 0)
return entry.borderBoxSize[0].inlineSize
if (entry.contentRect.width > 0)
return entry.contentRect.width
return element.getBoundingClientRect().width
}
const useResizeObserver = (
callback: (width: number) => void,
dependencies: React.DependencyList = [],
) => {
const elementRef = useRef<HTMLDivElement>(null)
const stableCallback = useCallback(callback, [callback])
useEffect(() => {
const element = elementRef.current
if (!element) return
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const width = getEntryWidth(entry, element)
stableCallback(width)
}
})
resizeObserver.observe(element)
const initialWidth = element.getBoundingClientRect().width
stableCallback(initialWidth)
return () => {
resizeObserver.disconnect()
}
}, [stableCallback, ...dependencies])
return elementRef
}
const Panel: FC<PanelProps> = ({
components,
versionHistoryPanelProps,
}) => {
const selectedNode = useReactflow(useShallow((s) => {
const nodes = s.getNodes()
const currentNode = nodes.find(node => node.data.selected)
if (currentNode) {
return {
id: currentNode.id,
type: currentNode.type,
data: currentNode.data,
}
}
}))
const showEnvPanel = useStore(s => s.showEnvPanel)
const isRestoring = useStore(s => s.isRestoring)
const showWorkflowVersionHistoryPanel = useStore(s => s.showWorkflowVersionHistoryPanel)
// widths used for adaptive layout
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
const previewPanelWidth = useStore(s => s.previewPanelWidth)
const setPreviewPanelWidth = useStore(s => s.setPreviewPanelWidth)
// When a node is selected and the NodePanel appears, if the current width
// of preview/otherPanel is too large, it may result in the total width of
// the two panels exceeding the workflowCanvasWidth, causing the NodePanel
// to be pushed out. Here we check and, if necessary, reduce the previewPanelWidth
// to "workflowCanvasWidth - 400 (minimum NodePanel width) - 400 (minimum canvas space)",
// while still ensuring that previewPanelWidth ≥ 400.
useEffect(() => {
if (!selectedNode || !workflowCanvasWidth)
return
const reservedCanvasWidth = 400 // Reserve the minimum visible width for the canvas
const minNodePanelWidth = 400
const maxAllowed = Math.max(workflowCanvasWidth - reservedCanvasWidth - minNodePanelWidth, 400)
if (previewPanelWidth > maxAllowed)
setPreviewPanelWidth(maxAllowed)
}, [selectedNode, workflowCanvasWidth, previewPanelWidth, setPreviewPanelWidth])
const setRightPanelWidth = useStore(s => s.setRightPanelWidth)
const setOtherPanelWidth = useStore(s => s.setOtherPanelWidth)
const rightPanelRef = useResizeObserver(
setRightPanelWidth,
[setRightPanelWidth, selectedNode, showEnvPanel, showWorkflowVersionHistoryPanel],
)
const otherPanelRef = useResizeObserver(
setOtherPanelWidth,
[setOtherPanelWidth, showEnvPanel, showWorkflowVersionHistoryPanel],
)
return (
<div
ref={rightPanelRef}
tabIndex={-1}
className={cn('absolute bottom-1 right-0 top-14 z-10 flex outline-none')}
key={`${isRestoring}`}
>
{components?.left}
{!!selectedNode && <NodePanel {...selectedNode} />}
<div
className="relative"
ref={otherPanelRef}
>
{
components?.right
}
{
showWorkflowVersionHistoryPanel && (
<VersionHistoryPanel {...versionHistoryPanelProps} />
)
}
{
showEnvPanel && (
<EnvPanel />
)
}
</div>
</div>
)
}
export default memo(Panel)