feat: revamp tool list page (#22879)
This commit is contained in:
@@ -56,7 +56,7 @@ const List = ({
|
||||
|
||||
return (
|
||||
<CardWrapper
|
||||
key={plugin.name}
|
||||
key={`${plugin.org}/${plugin.name}`}
|
||||
plugin={plugin}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
|
@@ -1,14 +1,10 @@
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import {
|
||||
RiArrowRightUpLine,
|
||||
RiArrowUpDoubleLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMarketplace } from './hooks'
|
||||
import type { useMarketplace } from './hooks'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { getLocaleOnClient } from '@/i18n'
|
||||
@@ -17,12 +13,16 @@ import { getMarketplaceUrl } from '@/utils/var'
|
||||
type MarketplaceProps = {
|
||||
searchPluginText: string
|
||||
filterPluginTags: string[]
|
||||
onMarketplaceScroll: () => void
|
||||
isMarketplaceArrowVisible: boolean
|
||||
showMarketplacePanel: () => void
|
||||
marketplaceContext: ReturnType<typeof useMarketplace>
|
||||
}
|
||||
const Marketplace = ({
|
||||
searchPluginText,
|
||||
filterPluginTags,
|
||||
onMarketplaceScroll,
|
||||
isMarketplaceArrowVisible,
|
||||
showMarketplacePanel,
|
||||
marketplaceContext,
|
||||
}: MarketplaceProps) => {
|
||||
const locale = getLocaleOnClient()
|
||||
const { t } = useTranslation()
|
||||
@@ -32,86 +32,76 @@ const Marketplace = ({
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
plugins,
|
||||
handleScroll,
|
||||
page,
|
||||
} = useMarketplace(searchPluginText, filterPluginTags)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
if (container)
|
||||
container.addEventListener('scroll', handleScroll)
|
||||
|
||||
return () => {
|
||||
if (container)
|
||||
container.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [handleScroll])
|
||||
} = marketplaceContext
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
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'
|
||||
>
|
||||
<RiArrowUpDoubleLine
|
||||
className='absolute left-1/2 top-2 h-4 w-4 -translate-x-1/2 cursor-pointer text-text-quaternary'
|
||||
onClick={() => onMarketplaceScroll()}
|
||||
/>
|
||||
<div className='sticky top-0 z-10 bg-background-default-subtle pb-3 pt-5'>
|
||||
<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')}
|
||||
</div>
|
||||
<div className='body-md-regular flex items-center text-center text-text-tertiary'>
|
||||
{t('plugin.marketplace.discover')}
|
||||
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.models')}
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.tools')}
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.agents')}
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative ml-1 mr-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.extensions')}
|
||||
</span>
|
||||
{t('plugin.marketplace.and')}
|
||||
<span className="body-md-medium relative ml-1 mr-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.bundles')}
|
||||
</span>
|
||||
{t('common.operation.in')}
|
||||
<a
|
||||
href={getMarketplaceUrl('', { language: locale, q: searchPluginText, tags: filterPluginTags.join(','), theme })}
|
||||
className='system-sm-medium ml-1 flex items-center text-text-accent'
|
||||
target='_blank'
|
||||
>
|
||||
{t('plugin.marketplace.difyMarketplace')}
|
||||
<RiArrowRightUpLine className='h-4 w-4' />
|
||||
</a>
|
||||
<>
|
||||
<div className='sticky bottom-0 flex shrink-0 flex-col bg-background-default-subtle px-12 pb-[14px] pt-2'>
|
||||
{isMarketplaceArrowVisible && (
|
||||
<RiArrowUpDoubleLine
|
||||
className='absolute left-1/2 top-2 z-10 h-4 w-4 -translate-x-1/2 cursor-pointer text-text-quaternary'
|
||||
onClick={showMarketplacePanel}
|
||||
/>
|
||||
)}
|
||||
<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'>
|
||||
{t('plugin.marketplace.moreFrom')}
|
||||
</div>
|
||||
<div className='body-md-regular flex items-center text-center text-text-tertiary'>
|
||||
{t('plugin.marketplace.discover')}
|
||||
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.models')}
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.tools')}
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.agents')}
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative ml-1 mr-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.extensions')}
|
||||
</span>
|
||||
{t('plugin.marketplace.and')}
|
||||
<span className="body-md-medium relative ml-1 mr-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
{t('plugin.category.bundles')}
|
||||
</span>
|
||||
{t('common.operation.in')}
|
||||
<a
|
||||
href={getMarketplaceUrl('', { language: locale, q: searchPluginText, tags: filterPluginTags.join(','), theme })}
|
||||
className='system-sm-medium ml-1 flex items-center text-text-accent'
|
||||
target='_blank'
|
||||
>
|
||||
{t('plugin.marketplace.difyMarketplace')}
|
||||
<RiArrowRightUpLine className='h-4 w-4' />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
isLoading && page === 1 && (
|
||||
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
(!isLoading || page > 1) && (
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollections || []}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
|
||||
plugins={plugins}
|
||||
showInstallButton
|
||||
locale={locale}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='mt-[-14px] shrink-0 grow bg-background-default-subtle px-12 pb-2'>
|
||||
{
|
||||
isLoading && page === 1 && (
|
||||
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
(!isLoading || page > 1) && (
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollections || []}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
|
||||
plugins={plugins}
|
||||
showInstallButton
|
||||
locale={locale}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Collection } from './types'
|
||||
import Marketplace from './marketplace'
|
||||
@@ -20,6 +20,7 @@ import { useAllToolProviders } from '@/service/use-tools'
|
||||
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { ToolTypeEnum } from '../workflow/block-selector/types'
|
||||
import { useMarketplace } from './marketplace/hooks'
|
||||
|
||||
const getToolType = (type: string) => {
|
||||
switch (type) {
|
||||
@@ -37,7 +38,7 @@ const getToolType = (type: string) => {
|
||||
}
|
||||
const ProviderList = () => {
|
||||
// const searchParams = useSearchParams()
|
||||
// searchParams.get('category') === 'workflow'
|
||||
// searchParams.get('category') === 'workflow'
|
||||
const { t } = useTranslation()
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
@@ -83,6 +84,41 @@ const ProviderList = () => {
|
||||
return detail
|
||||
}, [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 (
|
||||
<>
|
||||
<div className='relative flex h-0 shrink-0 grow overflow-hidden'>
|
||||
@@ -152,15 +188,16 @@ const ProviderList = () => {
|
||||
</div>
|
||||
)}
|
||||
{!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' && (
|
||||
<Marketplace
|
||||
onMarketplaceScroll={() => {
|
||||
containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' })
|
||||
}}
|
||||
searchPluginText={keywords}
|
||||
filterPluginTags={tagFilterValue}
|
||||
isMarketplaceArrowVisible={isMarketplaceArrowVisible}
|
||||
showMarketplacePanel={showMarketplacePanel}
|
||||
marketplaceContext={marketplaceContext}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'mcp' && (
|
||||
|
Reference in New Issue
Block a user