diff --git a/web/app/components/workflow/nodes/_base/hooks/use-resize-panel.ts b/web/app/components/workflow/nodes/_base/hooks/use-resize-panel.ts new file mode 100644 index 0000000000..fbb7185874 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/hooks/use-resize-panel.ts @@ -0,0 +1,116 @@ +import { + useCallback, + useEffect, + useRef, + useState, +} from 'react' + +export type UseResizePanelPrarams = { + direction?: 'horizontal' | 'vertical' | 'both' + triggerDirection?: 'top' | 'right' | 'bottom' | 'left' | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' + minWidth?: number + maxWidth?: number + minHeight?: number + maxHeight?: number + onResized?: (width: number, height: number) => void +} +export const useResizePanel = (params?: UseResizePanelPrarams) => { + const { + direction = 'both', + triggerDirection = 'bottom-right', + minWidth = -Infinity, + maxWidth = Infinity, + minHeight = -Infinity, + maxHeight = Infinity, + onResized, + } = params || {} + const triggerRef = useRef(null) + const containerRef = useRef(null) + const initXRef = useRef(0) + const initYRef = useRef(0) + const initContainerWidthRef = useRef(0) + const initContainerHeightRef = useRef(0) + const isResizingRef = useRef(false) + const [prevUserSelectStyle, setPrevUserSelectStyle] = useState(getComputedStyle(document.body).userSelect) + + const handleStartResize = useCallback((e: MouseEvent) => { + initXRef.current = e.clientX + initYRef.current = e.clientY + initContainerWidthRef.current = containerRef.current?.offsetWidth || minWidth + initContainerHeightRef.current = containerRef.current?.offsetHeight || minHeight + isResizingRef.current = true + setPrevUserSelectStyle(getComputedStyle(document.body).userSelect) + document.body.style.userSelect = 'none' + }, [minWidth, minHeight]) + + const handleResize = useCallback((e: MouseEvent) => { + if (!isResizingRef.current) + return + + if (!containerRef.current) + return + + if (direction === 'horizontal' || direction === 'both') { + const offsetX = e.clientX - initXRef.current + let width = 0 + if (triggerDirection === 'left' || triggerDirection === 'top-left' || triggerDirection === 'bottom-left') + width = initContainerWidthRef.current - offsetX + else if (triggerDirection === 'right' || triggerDirection === 'top-right' || triggerDirection === 'bottom-right') + width = initContainerWidthRef.current + offsetX + + if (width < minWidth) + width = minWidth + if (width > maxWidth) + width = maxWidth + containerRef.current.style.width = `${width}px` + } + + if (direction === 'vertical' || direction === 'both') { + const offsetY = e.clientY - initYRef.current + let height = 0 + if (triggerDirection === 'top' || triggerDirection === 'top-left' || triggerDirection === 'top-right') + height = initContainerHeightRef.current - offsetY + else if (triggerDirection === 'bottom' || triggerDirection === 'bottom-left' || triggerDirection === 'bottom-right') + height = initContainerHeightRef.current + offsetY + + if (height < minHeight) + height = minHeight + if (height > maxHeight) + height = maxHeight + + containerRef.current.style.height = `${height}px` + } + }, [ + direction, + triggerDirection, + minWidth, + maxWidth, + minHeight, + maxHeight, + ]) + + const handleStopResize = useCallback(() => { + isResizingRef.current = false + document.body.style.userSelect = prevUserSelectStyle + + if (onResized && containerRef.current) + onResized(containerRef.current.offsetWidth, containerRef.current.offsetHeight) + }, [prevUserSelectStyle, onResized]) + + useEffect(() => { + const element = triggerRef.current + element?.addEventListener('mousedown', handleStartResize) + document.addEventListener('mousemove', handleResize) + document.addEventListener('mouseup', handleStopResize) + return () => { + if (element) + element.removeEventListener('mousedown', handleStartResize) + document.removeEventListener('mousemove', handleResize) + } + }, [handleStartResize, handleResize, handleStopResize]) + + return { + triggerRef, + containerRef, + } +} diff --git a/web/app/components/workflow/nodes/_base/panel.tsx b/web/app/components/workflow/nodes/_base/panel.tsx index 1f8f1ff1bd..edf3fa0222 100644 --- a/web/app/components/workflow/nodes/_base/panel.tsx +++ b/web/app/components/workflow/nodes/_base/panel.tsx @@ -14,6 +14,7 @@ import { DescriptionInput, TitleInput, } from './components/title-description-input' +import { useResizePanel } from './hooks/use-resize-panel' import { XClose, } from '@/app/components/base/icons/src/vender/line/general' @@ -42,6 +43,7 @@ const BasePanel: FC = ({ children, }) => { const { t } = useTranslation() + const initPanelWidth = localStorage.getItem('workflow-node-panel-width') || 420 const { handleNodeSelect } = useNodesInteractions() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { nodesReadOnly } = useNodesReadOnly() @@ -49,6 +51,21 @@ const BasePanel: FC = ({ const availableNextNodes = nodesExtraData[data.type].availableNextNodes const toolIcon = useToolIcon(data) + const handleResized = useCallback((width: number) => { + localStorage.setItem('workflow-node-panel-width', `${width}`) + }, []) + + const { + triggerRef, + containerRef, + } = useResizePanel({ + direction: 'horizontal', + triggerDirection: 'left', + minWidth: 420, + maxWidth: 720, + onResized: handleResized, + }) + const { handleNodeDataUpdate, handleNodeDataUpdateWithSyncDraft, @@ -62,71 +79,82 @@ const BasePanel: FC = ({ }, [handleNodeDataUpdateWithSyncDraft, id]) return ( -
-
-
- - -
- { - canRunBySingle(data.type) && !nodesReadOnly && ( - -
{ - handleNodeDataUpdate({ id, data: { _isSingleRun: true } }) - handleSyncWorkflowDraft(true) - }} +
+
+
+
+
+ + +
+ { + canRunBySingle(data.type) && !nodesReadOnly && ( + - -
- - ) - } - -
-
handleNodeSelect(id, true)} - > - +
{ + handleNodeDataUpdate({ id, data: { _isSingleRun: true } }) + handleSyncWorkflowDraft(true) + }} + > + +
+ + ) + } + +
+
handleNodeSelect(id, true)} + > + +
-
-
- -
-
-
- {cloneElement(children, { id, data })} -
- { - !!availableNextNodes.length && ( -
-
- - {t('workflow.panel.nextStep').toLocaleUpperCase()} -
-
- {t('workflow.panel.addNextStep')} -
- +
+
- ) - } +
+
+ {cloneElement(children, { id, data })} +
+ { + !!availableNextNodes.length && ( +
+
+ + {t('workflow.panel.nextStep').toLocaleUpperCase()} +
+
+ {t('workflow.panel.addNextStep')} +
+ +
+ ) + } +
) } diff --git a/web/app/components/workflow/operator/index.tsx b/web/app/components/workflow/operator/index.tsx index d3156ea1f5..72fc5d2975 100644 --- a/web/app/components/workflow/operator/index.tsx +++ b/web/app/components/workflow/operator/index.tsx @@ -25,7 +25,7 @@ const Operator = () => { return (