feat: revamp tool list page (#22879)
This commit is contained in:
@@ -56,7 +56,7 @@ const List = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CardWrapper
|
<CardWrapper
|
||||||
key={plugin.name}
|
key={`${plugin.org}/${plugin.name}`}
|
||||||
plugin={plugin}
|
plugin={plugin}
|
||||||
showInstallButton={showInstallButton}
|
showInstallButton={showInstallButton}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
import {
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
} from 'react'
|
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import {
|
import {
|
||||||
RiArrowRightUpLine,
|
RiArrowRightUpLine,
|
||||||
RiArrowUpDoubleLine,
|
RiArrowUpDoubleLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useMarketplace } from './hooks'
|
import type { useMarketplace } from './hooks'
|
||||||
import List from '@/app/components/plugins/marketplace/list'
|
import List from '@/app/components/plugins/marketplace/list'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { getLocaleOnClient } from '@/i18n'
|
import { getLocaleOnClient } from '@/i18n'
|
||||||
@@ -17,12 +13,16 @@ import { getMarketplaceUrl } from '@/utils/var'
|
|||||||
type MarketplaceProps = {
|
type MarketplaceProps = {
|
||||||
searchPluginText: string
|
searchPluginText: string
|
||||||
filterPluginTags: string[]
|
filterPluginTags: string[]
|
||||||
onMarketplaceScroll: () => void
|
isMarketplaceArrowVisible: boolean
|
||||||
|
showMarketplacePanel: () => void
|
||||||
|
marketplaceContext: ReturnType<typeof useMarketplace>
|
||||||
}
|
}
|
||||||
const Marketplace = ({
|
const Marketplace = ({
|
||||||
searchPluginText,
|
searchPluginText,
|
||||||
filterPluginTags,
|
filterPluginTags,
|
||||||
onMarketplaceScroll,
|
isMarketplaceArrowVisible,
|
||||||
|
showMarketplacePanel,
|
||||||
|
marketplaceContext,
|
||||||
}: MarketplaceProps) => {
|
}: MarketplaceProps) => {
|
||||||
const locale = getLocaleOnClient()
|
const locale = getLocaleOnClient()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -32,32 +32,19 @@ const Marketplace = ({
|
|||||||
marketplaceCollections,
|
marketplaceCollections,
|
||||||
marketplaceCollectionPluginsMap,
|
marketplaceCollectionPluginsMap,
|
||||||
plugins,
|
plugins,
|
||||||
handleScroll,
|
|
||||||
page,
|
page,
|
||||||
} = useMarketplace(searchPluginText, filterPluginTags)
|
} = marketplaceContext
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const container = containerRef.current
|
|
||||||
if (container)
|
|
||||||
container.addEventListener('scroll', handleScroll)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (container)
|
|
||||||
container.removeEventListener('scroll', handleScroll)
|
|
||||||
}
|
|
||||||
}, [handleScroll])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
ref={containerRef}
|
<div className='sticky bottom-0 flex shrink-0 flex-col bg-background-default-subtle px-12 pb-[14px] pt-2'>
|
||||||
className='sticky bottom-[-442px] flex h-[530px] shrink-0 grow flex-col overflow-y-auto bg-background-default-subtle px-12 py-2 pt-0'
|
{isMarketplaceArrowVisible && (
|
||||||
>
|
|
||||||
<RiArrowUpDoubleLine
|
<RiArrowUpDoubleLine
|
||||||
className='absolute left-1/2 top-2 h-4 w-4 -translate-x-1/2 cursor-pointer text-text-quaternary'
|
className='absolute left-1/2 top-2 z-10 h-4 w-4 -translate-x-1/2 cursor-pointer text-text-quaternary'
|
||||||
onClick={() => onMarketplaceScroll()}
|
onClick={showMarketplacePanel}
|
||||||
/>
|
/>
|
||||||
<div className='sticky top-0 z-10 bg-background-default-subtle pb-3 pt-5'>
|
)}
|
||||||
|
<div className='pb-3 pt-4'>
|
||||||
<div className='title-2xl-semi-bold bg-gradient-to-r from-[rgba(11,165,236,0.95)] to-[rgba(21,90,239,0.95)] bg-clip-text text-transparent'>
|
<div className='title-2xl-semi-bold bg-gradient-to-r from-[rgba(11,165,236,0.95)] to-[rgba(21,90,239,0.95)] bg-clip-text text-transparent'>
|
||||||
{t('plugin.marketplace.moreFrom')}
|
{t('plugin.marketplace.moreFrom')}
|
||||||
</div>
|
</div>
|
||||||
@@ -93,6 +80,8 @@ const Marketplace = ({
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-[-14px] shrink-0 grow bg-background-default-subtle px-12 pb-2'>
|
||||||
{
|
{
|
||||||
isLoading && page === 1 && (
|
isLoading && page === 1 && (
|
||||||
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
|
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||||
@@ -112,6 +101,7 @@ const Marketplace = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { Collection } from './types'
|
import type { Collection } from './types'
|
||||||
import Marketplace from './marketplace'
|
import Marketplace from './marketplace'
|
||||||
@@ -20,6 +20,7 @@ import { useAllToolProviders } from '@/service/use-tools'
|
|||||||
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import { ToolTypeEnum } from '../workflow/block-selector/types'
|
import { ToolTypeEnum } from '../workflow/block-selector/types'
|
||||||
|
import { useMarketplace } from './marketplace/hooks'
|
||||||
|
|
||||||
const getToolType = (type: string) => {
|
const getToolType = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -83,6 +84,41 @@ const ProviderList = () => {
|
|||||||
return detail
|
return detail
|
||||||
}, [currentProvider?.plugin_id, pluginList?.plugins])
|
}, [currentProvider?.plugin_id, pluginList?.plugins])
|
||||||
|
|
||||||
|
const toolListTailRef = useRef<HTMLDivElement>(null)
|
||||||
|
const showMarketplacePanel = useCallback(() => {
|
||||||
|
containerRef.current?.scrollTo({
|
||||||
|
top: toolListTailRef.current
|
||||||
|
? toolListTailRef.current?.offsetTop - 80
|
||||||
|
: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
}, [toolListTailRef])
|
||||||
|
|
||||||
|
const marketplaceContext = useMarketplace(keywords, tagFilterValue)
|
||||||
|
const {
|
||||||
|
handleScroll,
|
||||||
|
} = marketplaceContext
|
||||||
|
|
||||||
|
const [isMarketplaceArrowVisible, setIsMarketplaceArrowVisible] = useState(true)
|
||||||
|
const onContainerScroll = useMemo(() => {
|
||||||
|
return (e: Event) => {
|
||||||
|
handleScroll(e)
|
||||||
|
if (containerRef.current && toolListTailRef.current)
|
||||||
|
setIsMarketplaceArrowVisible(containerRef.current.scrollTop < (toolListTailRef.current?.offsetTop - 80))
|
||||||
|
}
|
||||||
|
}, [handleScroll, containerRef, toolListTailRef, setIsMarketplaceArrowVisible])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current
|
||||||
|
if (container)
|
||||||
|
container.addEventListener('scroll', onContainerScroll)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (container)
|
||||||
|
container.removeEventListener('scroll', onContainerScroll)
|
||||||
|
}
|
||||||
|
}, [onContainerScroll])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='relative flex h-0 shrink-0 grow overflow-hidden'>
|
<div className='relative flex h-0 shrink-0 grow overflow-hidden'>
|
||||||
@@ -152,15 +188,16 @@ const ProviderList = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!filteredCollectionList.length && activeTab === 'builtin' && (
|
{!filteredCollectionList.length && activeTab === 'builtin' && (
|
||||||
<Empty lightCard text={t('tools.noTools')} className='h-[224px] px-12' />
|
<Empty lightCard text={t('tools.noTools')} className='h-[224px] shrink-0 px-12' />
|
||||||
)}
|
)}
|
||||||
|
<div ref={toolListTailRef} />
|
||||||
{enable_marketplace && activeTab === 'builtin' && (
|
{enable_marketplace && activeTab === 'builtin' && (
|
||||||
<Marketplace
|
<Marketplace
|
||||||
onMarketplaceScroll={() => {
|
|
||||||
containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' })
|
|
||||||
}}
|
|
||||||
searchPluginText={keywords}
|
searchPluginText={keywords}
|
||||||
filterPluginTags={tagFilterValue}
|
filterPluginTags={tagFilterValue}
|
||||||
|
isMarketplaceArrowVisible={isMarketplaceArrowVisible}
|
||||||
|
showMarketplacePanel={showMarketplacePanel}
|
||||||
|
marketplaceContext={marketplaceContext}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'mcp' && (
|
{activeTab === 'mcp' && (
|
||||||
|
Reference in New Issue
Block a user