feat: marketplace list

This commit is contained in:
StyleZhang 2024-10-30 15:15:53 +08:00
parent 9c4e809799
commit e7fb92e169
7 changed files with 174 additions and 22 deletions

View File

@ -13,10 +13,15 @@ import { useDebounceFn } from 'ahooks'
import { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch'
import type { Plugin } from '../types'
import type {
CollectionsAndPluginsSearchParams,
MarketplaceCollection,
PluginsSearchParams,
PluginsSort,
} from './types'
import { getMarketplacePlugins } from './utils'
import {
getMarketplaceCollectionsAndPlugins,
getMarketplacePlugins,
} from './utils'
import { DEFAULT_SORT } from './constants'
export type MarketplaceContextValue = {
@ -29,9 +34,13 @@ export type MarketplaceContextValue = {
activePluginType: string
handleActivePluginTypeChange: (type: string) => void
plugins?: Plugin[]
setPlugins?: (plugins: Plugin[]) => void
setPlugins: (plugins: Plugin[]) => void
sort: PluginsSort
handleSortChange: (sort: PluginsSort) => void
marketplaceCollectionsFromClient?: MarketplaceCollection[]
setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void
marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]>
setMarketplaceCollectionPluginsMapFromClient: (map: Record<string, Plugin[]>) => void
}
export const MarketplaceContext = createContext<MarketplaceContextValue>({
@ -47,6 +56,10 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
setPlugins: () => {},
sort: DEFAULT_SORT,
handleSortChange: () => {},
marketplaceCollectionsFromClient: [],
setMarketplaceCollectionsFromClient: () => {},
marketplaceCollectionPluginsMapFromClient: {},
setMarketplaceCollectionPluginsMapFromClient: () => {},
})
type MarketplaceContextProviderProps = {
@ -66,11 +79,26 @@ export const MarketplaceContextProvider = ({
const [activePluginType, setActivePluginType] = useState(PLUGIN_TYPE_SEARCH_MAP.all)
const [plugins, setPlugins] = useState<Plugin[]>()
const [sort, setSort] = useState(DEFAULT_SORT)
const [marketplaceCollectionsFromClient, setMarketplaceCollectionsFromClient] = useState<MarketplaceCollection[] | undefined>(undefined)
const [marketplaceCollectionPluginsMapFromClient, setMarketplaceCollectionPluginsMapFromClient] = useState<Record<string, Plugin[]> | undefined>(undefined)
const handleUpdatePlugins = useCallback(async (query: PluginsSearchParams) => {
const { marketplacePlugins } = await getMarketplacePlugins(query)
setPlugins(marketplacePlugins)
setMarketplaceCollectionsFromClient(undefined)
setMarketplaceCollectionPluginsMapFromClient(undefined)
}, [])
const handleUpdateMarketplaceCollectionsAndPlugins = useCallback(async (query?: CollectionsAndPluginsSearchParams) => {
const {
marketplaceCollections,
marketplaceCollectionPluginsMap,
} = await getMarketplaceCollectionsAndPlugins(query)
setMarketplaceCollectionsFromClient(marketplaceCollections)
setMarketplaceCollectionPluginsMapFromClient(marketplaceCollectionPluginsMap)
setPlugins(undefined)
}, [])
const { run: handleUpdatePluginsWithDebounced } = useDebounceFn(handleUpdatePlugins, {
@ -80,20 +108,58 @@ export const MarketplaceContextProvider = ({
const handleSearchPluginTextChange = useCallback((text: string) => {
setSearchPluginText(text)
handleUpdatePluginsWithDebounced({ query: text })
}, [handleUpdatePluginsWithDebounced])
handleUpdatePluginsWithDebounced({
query: text,
category: activePluginType === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginType,
tags: filterPluginTags,
sortBy: sort.sortBy,
sortOrder: sort.sortOrder,
})
}, [handleUpdatePluginsWithDebounced, activePluginType, filterPluginTags, sort])
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
setFilterPluginTags(tags)
}, [])
handleUpdatePlugins({
query: searchPluginText,
category: activePluginType === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginType,
tags,
sortBy: sort.sortBy,
sortOrder: sort.sortOrder,
})
}, [handleUpdatePlugins, searchPluginText, activePluginType, sort])
const handleActivePluginTypeChange = useCallback((type: string) => {
setActivePluginType(type)
}, [])
if (!searchPluginText && !filterPluginTags.length) {
handleUpdateMarketplaceCollectionsAndPlugins({
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
})
return
}
handleUpdatePlugins({
query: searchPluginText,
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
tags: filterPluginTags,
sortBy: sort.sortBy,
sortOrder: sort.sortOrder,
})
}, [handleUpdatePlugins, searchPluginText, filterPluginTags, sort, handleUpdateMarketplaceCollectionsAndPlugins])
const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort)
}, [])
handleUpdatePlugins({
query: searchPluginText,
category: activePluginType === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginType,
tags: filterPluginTags,
sortBy: sort.sortBy,
sortOrder: sort.sortOrder,
})
}, [handleUpdatePlugins, searchPluginText, activePluginType, filterPluginTags])
return (
<MarketplaceContext.Provider
@ -110,6 +176,10 @@ export const MarketplaceContextProvider = ({
setPlugins,
sort,
handleSortChange,
marketplaceCollectionsFromClient,
setMarketplaceCollectionsFromClient,
marketplaceCollectionPluginsMapFromClient,
setMarketplaceCollectionPluginsMapFromClient,
}}
>
{children}

View File

@ -0,0 +1,40 @@
import { Group } from '@/app/components/base/icons/src/vender/other'
import Line from './line'
const Empty = () => {
return (
<div
className='relative grid grid-cols-4 grid-rows-4 gap-3 p-2'
>
{
Array.from({ length: 16 }).map((_, index) => (
<div
key={index}
className='h-[144px] rounded-xl bg-background-section-burn'
>
</div>
))
}
<div
className='absolute inset-0 z-[1]'
style={{
backgroundImage: 'linear-gradient(180deg, rgba(255,255,255,0.01), #FCFCFD)',
}}
></div>
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[2] flex flex-col items-center'>
<div className='relative flex items-center justify-center mb-3 w-14 h-14 rounded-xl border border-divider-subtle bg-components-card-bg shadow-lg'>
<Group className='w-5 h-5' />
<Line className='absolute -right-[1px] top-1/2 -translate-y-1/2' />
<Line className='absolute -left-[1px] top-1/2 -translate-y-1/2' />
<Line className='absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
<Line className='absolute top-full left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
</div>
<div className='text-center system-md-regular text-text-tertiary'>
No plugin found
</div>
</div>
</div>
)
}
export default Empty

View File

@ -0,0 +1,21 @@
type LineProps = {
className?: string
}
const Line = ({
className,
}: LineProps) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="2" height="241" viewBox="0 0 2 241" fill="none" className={className}>
<path d="M1 0.5L1 240.5" stroke="url(#paint0_linear_1989_74474)"/>
<defs>
<linearGradient id="paint0_linear_1989_74474" x1="-7.99584" y1="240.5" x2="-7.88094" y2="0.50004" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stopOpacity="0.01"/>
<stop offset="0.503965" stopColor="#101828" stopOpacity="0.08"/>
<stop offset="1" stopColor="white" stopOpacity="0.01"/>
</linearGradient>
</defs>
</svg>
)
}
export default Line

View File

@ -3,6 +3,7 @@ import type { Plugin } from '../../types'
import type { MarketplaceCollection } from '../types'
import ListWithCollection from './list-with-collection'
import CardWrapper from './card-wrapper'
import Empty from '../empty'
type ListProps = {
marketplaceCollections: MarketplaceCollection[]
@ -28,7 +29,7 @@ const List = ({
)
}
{
plugins && (
plugins && !!plugins.length && (
<div className='grid grid-cols-4 gap-3'>
{
plugins.map(plugin => (
@ -42,6 +43,11 @@ const List = ({
</div>
)
}
{
plugins && !plugins.length && (
<Empty />
)
}
</>
)
}

View File

@ -15,18 +15,24 @@ const ListWrapper = ({
marketplaceCollectionPluginsMap,
showInstallButton,
}: ListWrapperProps) => {
const plugins = useMarketplaceContext(s => s.plugins)
const plugins = useMarketplaceContext(v => v.plugins)
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient)
return (
<div className='px-12 py-2 bg-background-default-subtle'>
<div className='flex items-center'>
<div className='title-xl-semi-bold text-text-primary'>134 results</div>
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
<SortDropdown />
</div>
{
plugins && (
<div className='flex items-center mb-4 pt-3'>
<div className='title-xl-semi-bold text-text-primary'>{plugins.length} results</div>
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
<SortDropdown />
</div>
)
}
<List
marketplaceCollections={marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
marketplaceCollections={marketplaceCollectionsFromClient || marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap}
plugins={plugins}
showInstallButton={showInstallButton}
/>

View File

@ -25,10 +25,14 @@ export type PluginsSearchParams = {
sortBy?: string
sortOrder?: string
category?: string
tag?: string
tags?: string[]
}
export type PluginsSort = {
sortBy: string
sortOrder: string
}
export type CollectionsAndPluginsSearchParams = {
category?: string
}

View File

@ -1,11 +1,16 @@
import type { Plugin } from '@/app/components/plugins/types'
import type {
CollectionsAndPluginsSearchParams,
MarketplaceCollection,
PluginsSearchParams,
} from '@/app/components/plugins/marketplace/types'
import { MARKETPLACE_API_PREFIX } from '@/config'
export const getMarketplaceCollectionsAndPlugins = async () => {
export const getPluginIconInMarketplace = (plugin: Plugin) => {
return `${MARKETPLACE_API_PREFIX}/plugins/${plugin.org}/${plugin.name}/icon`
}
export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAndPluginsSearchParams) => {
let marketplaceCollections = [] as MarketplaceCollection[]
let marketplaceCollectionPluginsMap = {} as Record<string, Plugin[]>
try {
@ -13,12 +18,12 @@ export const getMarketplaceCollectionsAndPlugins = async () => {
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
marketplaceCollections = marketplaceCollectionsDataJson.data.collections
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
const marketplaceCollectionPluginsData = await globalThis.fetch(`${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins`)
const marketplaceCollectionPluginsData = await globalThis.fetch(`${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins?category=${query?.category}`)
const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json()
const plugins = marketplaceCollectionPluginsDataJson.data.plugins.map((plugin: Plugin) => {
return {
...plugin,
icon: `${MARKETPLACE_API_PREFIX}/plugins/${plugin.org}/${plugin.name}/icon`,
icon: getPluginIconInMarketplace(plugin),
}
})
@ -54,7 +59,7 @@ export const getMarketplacePlugins = async (query: PluginsSearchParams) => {
sort_by: query.sortBy,
sort_order: query.sortOrder,
category: query.category,
tag: query.tag,
tags: query.tags,
}),
},
)
@ -62,7 +67,7 @@ export const getMarketplacePlugins = async (query: PluginsSearchParams) => {
marketplacePlugins = marketplacePluginsDataJson.data.plugins.map((plugin: Plugin) => {
return {
...plugin,
icon: `${MARKETPLACE_API_PREFIX}/plugins/${plugin.org}/${plugin.name}/icon`,
icon: getPluginIconInMarketplace(plugin),
}
})
}