fix: marketplace list

This commit is contained in:
StyleZhang 2024-12-05 14:54:04 +08:00
parent 0e419a7a16
commit 0e70e72594
7 changed files with 131 additions and 146 deletions

View File

@ -28,6 +28,7 @@ import type {
import { DEFAULT_SORT } from './constants'
import {
useMarketplaceCollectionsAndPlugins,
useMarketplaceContainerScroll,
useMarketplacePlugins,
} from './hooks'
import {
@ -51,7 +52,7 @@ export type MarketplaceContextValue = {
resetPlugins: () => void
sort: PluginsSort
handleSortChange: (sort: PluginsSort) => void
handleQueryPluginsWhenNoCollection: () => void
handleQueryPlugins: () => void
marketplaceCollectionsFromClient?: MarketplaceCollection[]
setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void
marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]>
@ -75,7 +76,7 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
resetPlugins: () => {},
sort: DEFAULT_SORT,
handleSortChange: () => {},
handleQueryPluginsWhenNoCollection: () => {},
handleQueryPlugins: () => {},
marketplaceCollectionsFromClient: [],
setMarketplaceCollectionsFromClient: () => {},
marketplaceCollectionPluginsMapFromClient: {},
@ -88,6 +89,7 @@ type MarketplaceContextProviderProps = {
children: ReactNode
searchParams?: SearchParams
shouldExclude?: boolean
scrollContainerId?: string
}
export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) {
@ -98,6 +100,7 @@ export const MarketplaceContextProvider = ({
children,
searchParams,
shouldExclude,
scrollContainerId,
}: MarketplaceContextProviderProps) => {
const { data, isSuccess } = useInstalledPluginList(!shouldExclude)
const exclude = useMemo(() => {
@ -131,6 +134,7 @@ export const MarketplaceContextProvider = ({
} = useMarketplaceCollectionsAndPlugins()
const {
plugins,
total: pluginsTotal,
resetPlugins,
queryPlugins,
queryPluginsWithDebounced,
@ -161,34 +165,60 @@ export const MarketplaceContextProvider = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryPlugins, queryMarketplaceCollectionsAndPlugins, isSuccess, exclude])
const handleQueryMarketplaceCollectionsAndPlugins = useCallback(() => {
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
condition: getMarketplaceListCondition(activePluginTypeRef.current),
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
})
resetPlugins()
}, [exclude, queryMarketplaceCollectionsAndPlugins, resetPlugins])
const handleQueryPlugins = useCallback((debounced?: boolean) => {
if (debounced) {
queryPluginsWithDebounced({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}
else {
queryPlugins({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}
}, [exclude, queryPluginsWithDebounced, queryPlugins])
const handleQuery = useCallback((debounced?: boolean) => {
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
handleQueryMarketplaceCollectionsAndPlugins()
return
}
handleQueryPlugins(debounced)
}, [handleQueryMarketplaceCollectionsAndPlugins, handleQueryPlugins])
const handleSearchPluginTextChange = useCallback((text: string) => {
setSearchPluginText(text)
searchPluginTextRef.current = text
setPage(1)
pageRef.current = 1
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
condition: getMarketplaceListCondition(activePluginTypeRef.current),
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
})
resetPlugins()
return
}
queryPluginsWithDebounced({
query: text,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
page: pageRef.current,
})
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins, exclude])
handleQuery(true)
}, [handleQuery])
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
setFilterPluginTags(tags)
@ -196,29 +226,8 @@ export const MarketplaceContextProvider = ({
setPage(1)
pageRef.current = 1
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
condition: getMarketplaceListCondition(activePluginTypeRef.current),
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
})
resetPlugins()
return
}
queryPlugins({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude])
handleQuery()
}, [handleQuery])
const handleActivePluginTypeChange = useCallback((type: string) => {
setActivePluginType(type)
@ -226,57 +235,8 @@ export const MarketplaceContextProvider = ({
setPage(1)
pageRef.current = 1
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
condition: getMarketplaceListCondition(type),
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
})
resetPlugins()
return
}
queryPlugins({
query: searchPluginTextRef.current,
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude])
const handlePageChange = useCallback(() => {
setPage(pageRef.current + 1)
pageRef.current++
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
condition: getMarketplaceListCondition(activePluginTypeRef.current),
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
})
resetPlugins()
return
}
queryPlugins({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}, [exclude, queryPlugins, queryMarketplaceCollectionsAndPlugins, resetPlugins])
handleQuery()
}, [handleQuery])
const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort)
@ -284,32 +244,19 @@ export const MarketplaceContextProvider = ({
setPage(1)
pageRef.current = 1
queryPlugins({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}, [queryPlugins, exclude])
handleQueryPlugins()
}, [handleQueryPlugins])
const handleQueryPluginsWhenNoCollection = useCallback(() => {
queryPlugins({
query: searchPluginTextRef.current,
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
})
}, [exclude, queryPlugins])
const handlePageChange = useCallback(() => {
if (pluginsTotal && plugins && pluginsTotal > plugins.length && (!!searchPluginTextRef.current || !!filterPluginTagsRef.current.length)) {
setPage(pageRef.current + 1)
pageRef.current++
// useMarketplaceContainerScroll(handlePageChange)
handleQueryPlugins()
}
}, [handleQueryPlugins, plugins, pluginsTotal])
useMarketplaceContainerScroll(handlePageChange, scrollContainerId)
return (
<MarketplaceContext.Provider
@ -328,7 +275,7 @@ export const MarketplaceContextProvider = ({
resetPlugins,
sort,
handleSortChange,
handleQueryPluginsWhenNoCollection,
handleQueryPlugins,
marketplaceCollectionsFromClient,
setMarketplaceCollectionsFromClient,
marketplaceCollectionPluginsMapFromClient,

View File

@ -59,27 +59,46 @@ export const useMarketplaceCollectionsAndPlugins = () => {
export const useMarketplacePlugins = () => {
const {
data,
mutate,
mutateAsync,
reset,
isPending,
} = useMutationPluginsFromMarketplace()
const [prevPlugins, setPrevPlugins] = useState<Plugin[] | undefined>()
const resetPlugins = useCallback(() => {
reset()
setPrevPlugins(undefined)
}, [reset])
const handleUpdatePlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => {
mutateAsync(pluginsSearchParams).then((res) => {
const currentPage = pluginsSearchParams.page || 1
const resPlugins = res.data.plugins
if (currentPage > 1) {
setPrevPlugins(prevPlugins => [...(prevPlugins || []), ...resPlugins.map((plugin) => {
return getFormattedPlugin(plugin)
})])
}
else {
setPrevPlugins(resPlugins.map((plugin) => {
return getFormattedPlugin(plugin)
}))
}
})
}, [mutateAsync])
const queryPlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => {
mutate(pluginsSearchParams)
}, [mutate])
handleUpdatePlugins(pluginsSearchParams)
}, [handleUpdatePlugins])
const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => {
mutate(pluginsSearchParams)
handleUpdatePlugins(pluginsSearchParams)
}, {
wait: 500,
})
return {
plugins: data?.data?.plugins.map((plugin) => {
return getFormattedPlugin(plugin)
}),
plugins: prevPlugins,
total: data?.data?.total,
resetPlugins: reset,
resetPlugins,
queryPlugins,
queryPluginsWithDebounced,
isLoading: isPending,
@ -97,8 +116,11 @@ export const useMixedTranslation = (localeFromOuter?: string) => {
}
}
export const useMarketplaceContainerScroll = (callback: () => void) => {
const container = document.getElementById('marketplace-container')
export const useMarketplaceContainerScroll = (
callback: () => void,
scrollContainerId = 'marketplace-container',
) => {
const container = document.getElementById(scrollContainerId)
const handleScroll = useCallback((e: Event) => {
const target = e.target as HTMLDivElement

View File

@ -14,6 +14,8 @@ type MarketplaceProps = {
shouldExclude?: boolean
searchParams?: SearchParams
pluginTypeSwitchClassName?: string
intersectionContainerId?: string
scrollContainerId?: string
}
const Marketplace = async ({
locale,
@ -21,6 +23,8 @@ const Marketplace = async ({
shouldExclude,
searchParams,
pluginTypeSwitchClassName,
intersectionContainerId,
scrollContainerId,
}: MarketplaceProps) => {
let marketplaceCollections: any = []
let marketplaceCollectionPluginsMap = {}
@ -32,9 +36,13 @@ const Marketplace = async ({
return (
<TanstackQueryIniter>
<MarketplaceContextProvider searchParams={searchParams} shouldExclude={shouldExclude}>
<MarketplaceContextProvider
searchParams={searchParams}
shouldExclude={shouldExclude}
scrollContainerId={scrollContainerId}
>
<Description locale={locale} />
<IntersectionLine />
<IntersectionLine intersectionContainerId={intersectionContainerId} />
<SearchBoxWrapper locale={locale} />
<PluginTypeSwitch
locale={locale}

View File

@ -3,12 +3,13 @@ import { useMarketplaceContext } from '@/app/components/plugins/marketplace/cont
export const useScrollIntersection = (
anchorRef: React.RefObject<HTMLDivElement>,
intersectionContainerId = 'marketplace-container',
) => {
const intersected = useMarketplaceContext(v => v.intersected)
const setIntersected = useMarketplaceContext(v => v.setIntersected)
useEffect(() => {
const container = document.getElementById('marketplace-container')
const container = document.getElementById(intersectionContainerId)
let observer: IntersectionObserver | undefined
if (container && anchorRef.current) {
observer = new IntersectionObserver((entries) => {
@ -25,5 +26,5 @@ export const useScrollIntersection = (
observer.observe(anchorRef.current)
}
return () => observer?.disconnect()
}, [anchorRef, intersected, setIntersected])
}, [anchorRef, intersected, setIntersected, intersectionContainerId])
}

View File

@ -3,10 +3,15 @@
import { useRef } from 'react'
import { useScrollIntersection } from './hooks'
const IntersectionLine = () => {
type IntersectionLineProps = {
intersectionContainerId?: string
}
const IntersectionLine = ({
intersectionContainerId,
}: IntersectionLineProps) => {
const ref = useRef<HTMLDivElement>(null)
useScrollIntersection(ref)
useScrollIntersection(ref, intersectionContainerId)
return (
<div ref={ref} className='mb-4 h-[1px] bg-transparent'></div>

View File

@ -26,18 +26,19 @@ const ListWrapper = ({
const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient)
const isLoading = useMarketplaceContext(v => v.isLoading)
const isSuccessCollections = useMarketplaceContext(v => v.isSuccessCollections)
const handleQueryPluginsWhenNoCollection = useMarketplaceContext(v => v.handleQueryPluginsWhenNoCollection)
const handleQueryPlugins = useMarketplaceContext(v => v.handleQueryPlugins)
const page = useMarketplaceContext(v => v.page)
useEffect(() => {
if (!marketplaceCollectionsFromClient?.length && isSuccessCollections)
handleQueryPluginsWhenNoCollection()
}, [handleQueryPluginsWhenNoCollection, marketplaceCollections, marketplaceCollectionsFromClient, isSuccessCollections])
handleQueryPlugins()
}, [handleQueryPlugins, marketplaceCollections, marketplaceCollectionsFromClient, isSuccessCollections])
return (
<div className='relative flex flex-col grow px-12 py-2 bg-background-default-subtle'>
{
plugins && (
<div className='flex items-center mb-4 pt-3'>
<div className='top-5 flex items-center mb-4 pt-3'>
<div className='title-xl-semi-bold text-text-primary'>{t('plugin.marketplace.pluginsResult', { num: plugins.length })}</div>
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
<SortDropdown />
@ -45,14 +46,14 @@ const ListWrapper = ({
)
}
{
isLoading && (
isLoading && page === 1 && (
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<Loading />
</div>
)
}
{
!isLoading && (
(!isLoading || page > 1) && (
<List
marketplaceCollections={marketplaceCollectionsFromClient || marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap}

View File

@ -298,11 +298,12 @@ export const useMutationPluginsFromMarketplace = () => {
exclude,
type,
page = 1,
pageSize = 20,
} = pluginsSearchParams
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', {
body: {
page,
page_size: 100,
page_size: pageSize,
query,
sort_by: sortBy,
sort_order: sortOrder,