From 43d7a538dc1606bbeb5dd97f50c92d66d18eb8d7 Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Tue, 19 Nov 2024 15:08:39 +0800 Subject: [PATCH] feat: marketplace list url support search & tags --- web/app/components/plugins/constants.ts | 25 +++++ web/app/components/plugins/hooks.ts | 100 +++--------------- .../plugins/marketplace/context.tsx | 33 +++++- .../components/plugins/marketplace/index.tsx | 5 +- .../components/plugins/marketplace/types.ts | 7 ++ web/app/components/plugins/utils.ts | 13 +++ .../components/tools/marketplace/index.tsx | 2 +- 7 files changed, 96 insertions(+), 89 deletions(-) create mode 100644 web/app/components/plugins/constants.ts create mode 100644 web/app/components/plugins/utils.ts diff --git a/web/app/components/plugins/constants.ts b/web/app/components/plugins/constants.ts new file mode 100644 index 0000000000..d4f683eb9b --- /dev/null +++ b/web/app/components/plugins/constants.ts @@ -0,0 +1,25 @@ +export const tagKeys = [ + 'search', + 'image', + 'videos', + 'weather', + 'finance', + 'design', + 'travel', + 'social', + 'news', + 'medical', + 'productivity', + 'education', + 'business', + 'entertainment', + 'utilities', + 'other', +] + +export const categoryKeys = [ + 'model', + 'tool', + 'extension', + 'bundle', +] diff --git a/web/app/components/plugins/hooks.ts b/web/app/components/plugins/hooks.ts index 81488fed30..9b2bf61f10 100644 --- a/web/app/components/plugins/hooks.ts +++ b/web/app/components/plugins/hooks.ts @@ -1,5 +1,9 @@ import { useTranslation } from 'react-i18next' import type { TFunction } from 'i18next' +import { + categoryKeys, + tagKeys, +} from './constants' type Tag = { name: string @@ -10,72 +14,12 @@ export const useTags = (translateFromOut?: TFunction) => { const { t: translation } = useTranslation() const t = translateFromOut || translation - const tags = [ - { - name: 'search', - label: t('pluginTags.tags.search'), - }, - { - name: 'image', - label: t('pluginTags.tags.image'), - }, - { - name: 'videos', - label: t('pluginTags.tags.videos'), - }, - { - name: 'weather', - label: t('pluginTags.tags.weather'), - }, - { - name: 'finance', - label: t('pluginTags.tags.finance'), - }, - { - name: 'design', - label: t('pluginTags.tags.design'), - }, - { - name: 'travel', - label: t('pluginTags.tags.travel'), - }, - { - name: 'social', - label: t('pluginTags.tags.social'), - }, - { - name: 'news', - label: t('pluginTags.tags.news'), - }, - { - name: 'medical', - label: t('pluginTags.tags.medical'), - }, - { - name: 'productivity', - label: t('pluginTags.tags.productivity'), - }, - { - name: 'education', - label: t('pluginTags.tags.education'), - }, - { - name: 'business', - label: t('pluginTags.tags.business'), - }, - { - name: 'entertainment', - label: t('pluginTags.tags.entertainment'), - }, - { - name: 'utilities', - label: t('pluginTags.tags.utilities'), - }, - { - name: 'other', - label: t('pluginTags.tags.other'), - }, - ] + const tags = tagKeys.map((tag) => { + return { + name: tag, + label: t(`pluginTags.tags.${tag}`), + } + }) const tagsMap = tags.reduce((acc, tag) => { acc[tag.name] = tag @@ -97,24 +41,12 @@ export const useCategories = (translateFromOut?: TFunction) => { const { t: translation } = useTranslation() const t = translateFromOut || translation - const categories = [ - { - name: 'model', - label: t('plugin.category.models'), - }, - { - name: 'tool', - label: t('plugin.category.tools'), - }, - { - name: 'extension', - label: t('plugin.category.extensions'), - }, - { - name: 'bundle', - label: t('plugin.category.bundles'), - }, - ] + const categories = categoryKeys.map((category) => { + return { + name: category, + label: t(`plugin.category.${category}s`), + } + }) const categoriesMap = categories.reduce((acc, category) => { acc[category.name] = category diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx index 6dabfdf736..31fe2ced5e 100644 --- a/web/app/components/plugins/marketplace/context.tsx +++ b/web/app/components/plugins/marketplace/context.tsx @@ -5,6 +5,7 @@ import type { } from 'react' import { useCallback, + useEffect, useRef, useState, } from 'react' @@ -14,9 +15,14 @@ import { } from 'use-context-selector' import { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch' import type { Plugin } from '../types' +import { + getValidCategoryKeys, + getValidTagKeys, +} from '../utils' import type { MarketplaceCollection, PluginsSort, + SearchParams, } from './types' import { DEFAULT_SORT } from './constants' import { @@ -66,6 +72,7 @@ export const MarketplaceContext = createContext({ type MarketplaceContextProviderProps = { children: ReactNode + searchParams?: SearchParams } export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) { @@ -74,13 +81,19 @@ export function useMarketplaceContext(selector: (value: MarketplaceContextValue) export const MarketplaceContextProvider = ({ children, + searchParams, }: MarketplaceContextProviderProps) => { + const queryFromSearchParams = searchParams?.q || '' + const tagsFromSearchParams = searchParams?.tags ? getValidTagKeys(searchParams.tags.split(',')) : [] + const hasValidTags = !!tagsFromSearchParams.length + const hasValidCategory = getValidCategoryKeys(searchParams?.category) + const categoryFromSearchParams = hasValidCategory || PLUGIN_TYPE_SEARCH_MAP.all const [intersected, setIntersected] = useState(true) - const [searchPluginText, setSearchPluginText] = useState('') + const [searchPluginText, setSearchPluginText] = useState(queryFromSearchParams) const searchPluginTextRef = useRef(searchPluginText) - const [filterPluginTags, setFilterPluginTags] = useState([]) + const [filterPluginTags, setFilterPluginTags] = useState(tagsFromSearchParams) const filterPluginTagsRef = useRef(filterPluginTags) - const [activePluginType, setActivePluginType] = useState(PLUGIN_TYPE_SEARCH_MAP.all) + const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams) const activePluginTypeRef = useRef(activePluginType) const [sort, setSort] = useState(DEFAULT_SORT) const sortRef = useRef(sort) @@ -100,6 +113,20 @@ export const MarketplaceContextProvider = ({ isLoading: isPluginsLoading, } = useMarketplacePlugins() + useEffect(() => { + if (queryFromSearchParams || hasValidTags || hasValidCategory) { + queryPlugins({ + query: queryFromSearchParams, + category: hasValidCategory, + tags: hasValidTags ? tagsFromSearchParams : [], + sortBy: sortRef.current.sortBy, + sortOrder: sortRef.current.sortOrder, + }) + history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [queryPlugins]) + const handleSearchPluginTextChange = useCallback((text: string) => { setSearchPluginText(text) searchPluginTextRef.current = text diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 10e623710e..0ffe94a048 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -4,22 +4,25 @@ import IntersectionLine from './intersection-line' import SearchBoxWrapper from './search-box/search-box-wrapper' import PluginTypeSwitch from './plugin-type-switch' import ListWrapper from './list/list-wrapper' +import type { SearchParams } from './types' import { getMarketplaceCollectionsAndPlugins } from './utils' import { TanstackQueryIniter } from '@/context/query-client' type MarketplaceProps = { locale?: string showInstallButton?: boolean + searchParams?: SearchParams } const Marketplace = async ({ locale, showInstallButton = true, + searchParams, }: MarketplaceProps) => { const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins() return ( - + diff --git a/web/app/components/plugins/marketplace/types.ts b/web/app/components/plugins/marketplace/types.ts index 1fe8d5aa37..e2c4315f3d 100644 --- a/web/app/components/plugins/marketplace/types.ts +++ b/web/app/components/plugins/marketplace/types.ts @@ -36,3 +36,10 @@ export type PluginsSort = { export type CollectionsAndPluginsSearchParams = { category?: string } + +export type SearchParams = { + language?: string + q?: string + tags?: string + category?: string +} diff --git a/web/app/components/plugins/utils.ts b/web/app/components/plugins/utils.ts new file mode 100644 index 0000000000..a87ee021eb --- /dev/null +++ b/web/app/components/plugins/utils.ts @@ -0,0 +1,13 @@ +import { + categoryKeys, + tagKeys, +} from './constants' + +export const getValidTagKeys = (tags: string[]) => { + return tags.filter(tag => tagKeys.includes(tag)) +} + +export const getValidCategoryKeys = (category?: string) => { + const currentCategory = categoryKeys.find(key => key === category) + return currentCategory ? `${currentCategory}s` : '' +} diff --git a/web/app/components/tools/marketplace/index.tsx b/web/app/components/tools/marketplace/index.tsx index c487a912d2..71cac1f781 100644 --- a/web/app/components/tools/marketplace/index.tsx +++ b/web/app/components/tools/marketplace/index.tsx @@ -57,7 +57,7 @@ const Marketplace = ({ {t('common.operation.in')}