diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 2f75f90fc3..a9a316b272 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -1,5 +1,6 @@ import { useMemo, + useRef, useState, } from 'react' import type { @@ -51,6 +52,10 @@ const AllTools = ({ }) }) }, [activeTab, buildInTools, customTools, workflowTools, searchText, language]) + + const pluginRef = useRef(null) + const wrapElemRef = useRef(null) + return (
@@ -73,13 +78,19 @@ const AllTools = ({
- - +
+ + +
) } diff --git a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx index 33c00081c6..906f31657c 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx @@ -1,26 +1,50 @@ 'use client' -import type { FC } from 'react' -import React from 'react' +import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' +import useStickyScroll, { ScrollPosition } from '../use-sticky-scroll' import Item from './item' import type { Plugin } from '@/app/components/plugins/types.ts' +import cn from '@/utils/classnames' type Props = { + wrapElemRef: React.RefObject list: Plugin[] - // onInstall: () => } -const List: FC = ({ +const List = ({ + wrapElemRef, list, -}) => { +}: Props, ref: any) => { const { t } = useTranslation() + const nextToStickyELemRef = useRef(null) + + const { handleScroll, scrollPosition } = useStickyScroll({ + wrapElemRef, + nextToStickyELemRef, + }) + + const stickyClassName = useMemo(() => { + switch (scrollPosition) { + case ScrollPosition.aboveTheWrap: + return 'top-0 shadow-md bg-white' + case ScrollPosition.showing: + return 'bottom-0' + case ScrollPosition.belowTheWrap: + return 'bottom-0 border-t border-gray-500 bg-white text-blue-500' + } + }, [scrollPosition]) + + useImperativeHandle(ref, () => ({ + handleScroll, + })) return ( -
-
+ <> +
{t('plugin.fromMarketplace')}
-
+
{list.map((item, index) => ( = ({ /> ))}
-
+ ) } -export default React.memo(List) +export default forwardRef(List) diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index 43f44bf882..326ad1fde6 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -75,7 +75,7 @@ const Blocks = ({ } return ( -
+
{ !tools.length && !showWorkflowEmpty && (
{t('workflow.tabs.noResult')}
diff --git a/web/app/components/workflow/block-selector/use-sticky-scroll.ts b/web/app/components/workflow/block-selector/use-sticky-scroll.ts new file mode 100644 index 0000000000..405ecdba7e --- /dev/null +++ b/web/app/components/workflow/block-selector/use-sticky-scroll.ts @@ -0,0 +1,45 @@ +import React from 'react' +import { useThrottleFn } from 'ahooks' + +export enum ScrollPosition { + belowTheWrap = 'belowTheWrap', + showing = 'showing', + aboveTheWrap = 'aboveTheWrap', +} + +type Params = { + wrapElemRef: React.RefObject + nextToStickyELemRef: React.RefObject +} +const useStickyScroll = ({ + wrapElemRef, + nextToStickyELemRef, +}: Params) => { + const [scrollPosition, setScrollPosition] = React.useState(ScrollPosition.belowTheWrap) + const { run: handleScroll } = useThrottleFn(() => { + const wrapDom = wrapElemRef.current + const stickyDOM = nextToStickyELemRef.current + if (!wrapDom || !stickyDOM) + return + const { height: wrapHeight, top: wrapTop } = wrapDom.getBoundingClientRect() + const { top: nextToStickyTop } = stickyDOM.getBoundingClientRect() + let scrollPositionNew = ScrollPosition.belowTheWrap + + if (nextToStickyTop - wrapTop >= wrapHeight) + scrollPositionNew = ScrollPosition.belowTheWrap + else if (nextToStickyTop <= wrapTop) + scrollPositionNew = ScrollPosition.aboveTheWrap + else + scrollPositionNew = ScrollPosition.showing + + if (scrollPosition !== scrollPositionNew) + setScrollPosition(scrollPositionNew) + }, { wait: 100 }) + + return { + handleScroll, + scrollPosition, + } +} + +export default useStickyScroll