feat: marketplace list
This commit is contained in:
parent
9c4e809799
commit
e7fb92e169
@ -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}
|
||||
|
40
web/app/components/plugins/marketplace/empty/index.tsx
Normal file
40
web/app/components/plugins/marketplace/empty/index.tsx
Normal 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
|
21
web/app/components/plugins/marketplace/empty/line.tsx
Normal file
21
web/app/components/plugins/marketplace/empty/line.tsx
Normal 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
|
@ -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 />
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user