feat: revamp tool list page (#22879)

This commit is contained in:
Nite Knite
2025-07-24 11:51:39 +08:00
committed by GitHub
parent a327d024e9
commit c6d7328e15
3 changed files with 116 additions and 89 deletions

View File

@@ -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}

View File

@@ -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>
</>
) )
} }

View File

@@ -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' && (