mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
feat: introduce creation type management in marketplace components for improved data handling
This commit is contained in:
parent
9f8289b185
commit
5c6da34539
@ -1,10 +1,10 @@
|
||||
import type { SearchTab } from './search-params'
|
||||
import type { PluginsSort, SearchParamsFromCollection } from './types'
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useQueryState } from 'nuqs'
|
||||
import { useCallback } from 'react'
|
||||
import { DEFAULT_SORT, getValidatedPluginCategory, getValidatedTemplateCategory, PLUGIN_CATEGORY_WITH_COLLECTIONS } from './constants'
|
||||
import type { SearchTab } from './search-params'
|
||||
import { marketplaceSearchParamsParsers } from './search-params'
|
||||
import { CREATION_TYPE, marketplaceSearchParamsParsers } from './search-params'
|
||||
|
||||
const marketplaceSortAtom = atom<PluginsSort>(DEFAULT_SORT)
|
||||
export function useMarketplaceSort() {
|
||||
@ -37,6 +37,10 @@ export function useSearchTab() {
|
||||
return useQueryState('searchTab', marketplaceSearchParamsParsers.searchTab)
|
||||
}
|
||||
|
||||
export function useCreationType() {
|
||||
return useQueryState('creationType', marketplaceSearchParamsParsers.creationType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Not all categories have collections, so we need to
|
||||
* force the search mode for those categories.
|
||||
@ -44,14 +48,17 @@ export function useSearchTab() {
|
||||
export const searchModeAtom = atom<true | null>(null)
|
||||
|
||||
export function useMarketplaceSearchMode() {
|
||||
// const [searchText] = useSearchText()
|
||||
const [creationType] = useCreationType()
|
||||
const [searchText] = useSearchText()
|
||||
const [searchTab] = useSearchTab()
|
||||
// const [filterPluginTags] = useFilterPluginTags()
|
||||
const [filterPluginTags] = useFilterPluginTags()
|
||||
const [activePluginCategory] = useActivePluginCategory()
|
||||
const isPluginsView = creationType === CREATION_TYPE.plugins
|
||||
|
||||
const searchMode = useAtomValue(searchModeAtom)
|
||||
const isSearchMode = searchTab
|
||||
|| (searchMode ?? (!PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginCategory)))
|
||||
const isSearchMode = searchTab || searchText
|
||||
|| (isPluginsView && filterPluginTags.length > 0)
|
||||
|| (searchMode ?? (isPluginsView && !PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginCategory)))
|
||||
return isSearchMode
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,9 @@ import { useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import marketPlaceBg from '@/public/marketplace/hero-bg.jpg'
|
||||
import marketplaceGradientNoise from '@/public/marketplace/hero-gradient-noise.svg'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useCreationType } from '../atoms'
|
||||
import { PluginCategorySwitch, TemplateCategorySwitch } from '../category-switch/index'
|
||||
import { useMarketplaceData } from '../state'
|
||||
import { CREATION_TYPE } from '../search-params'
|
||||
|
||||
type DescriptionProps = {
|
||||
className?: string
|
||||
@ -29,8 +30,8 @@ export const Description = ({
|
||||
marketplaceNav,
|
||||
}: DescriptionProps) => {
|
||||
const { t } = useTranslation('plugin')
|
||||
const { creationType } = useMarketplaceData()
|
||||
const isTemplatesView = creationType === 'templates'
|
||||
const [creationType] = useCreationType()
|
||||
const isTemplatesView = creationType === CREATION_TYPE.templates
|
||||
const heroTitleKey = isTemplatesView ? 'marketplace.templatesHeroTitle' : 'marketplace.pluginsHeroTitle'
|
||||
const heroSubtitleKey = isTemplatesView ? 'marketplace.templatesHeroSubtitle' : 'marketplace.pluginsHeroSubtitle'
|
||||
const rafRef = useRef<number | null>(null)
|
||||
|
||||
@ -4,7 +4,7 @@ import { createLoader } from 'nuqs/server'
|
||||
import { getQueryClientServer } from '@/context/query-client-server'
|
||||
import { marketplaceQuery } from '@/service/client'
|
||||
import { getValidatedPluginCategory, PLUGIN_CATEGORY_WITH_COLLECTIONS } from './constants'
|
||||
import { marketplaceSearchParamsParsers } from './search-params'
|
||||
import { CREATION_TYPE, marketplaceSearchParamsParsers } from './search-params'
|
||||
import { getCollectionsParams, getMarketplaceCollectionsAndPlugins, getMarketplaceTemplateCollectionsAndTemplates } from './utils'
|
||||
|
||||
// The server side logic should move to marketplace's codebase so that we can get rid of Next.js
|
||||
@ -17,7 +17,7 @@ async function getDehydratedState(searchParams?: Promise<SearchParams>) {
|
||||
const params = await loadSearchParams(searchParams)
|
||||
const queryClient = getQueryClientServer()
|
||||
|
||||
if (params.creationType === 'templates') {
|
||||
if (params.creationType === CREATION_TYPE.templates) {
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: marketplaceQuery.templateCollections.list.queryKey({ input: { query: undefined } }),
|
||||
queryFn: () => getMarketplaceTemplateCollectionsAndTemplates(),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useMarketplaceData } from '../state'
|
||||
import { isPluginsData, useMarketplaceData } from '../state'
|
||||
import FlatList from './flat-list'
|
||||
import ListWithCollection from './list-with-collection'
|
||||
|
||||
@ -11,50 +11,33 @@ type ListWrapperProps = {
|
||||
|
||||
const ListWrapper = ({ showInstallButton }: ListWrapperProps) => {
|
||||
const marketplaceData = useMarketplaceData()
|
||||
const { creationType, isLoading, page, isFetchingNextPage } = marketplaceData
|
||||
|
||||
const isPluginView = creationType === 'plugins'
|
||||
const { isLoading, page, isFetchingNextPage } = marketplaceData
|
||||
|
||||
const renderContent = () => {
|
||||
if (!isPluginView) {
|
||||
const { templateCollections, templateCollectionTemplatesMap, templates } = marketplaceData
|
||||
if (templates !== undefined) {
|
||||
return (
|
||||
<FlatList
|
||||
if (isPluginsData(marketplaceData)) {
|
||||
const { pluginCollections, pluginCollectionPluginsMap, plugins } = marketplaceData
|
||||
return plugins !== undefined
|
||||
? <FlatList variant="plugins" items={plugins} showInstallButton={showInstallButton} />
|
||||
: (
|
||||
<ListWithCollection
|
||||
variant="plugins"
|
||||
collections={pluginCollections || []}
|
||||
collectionItemsMap={pluginCollectionPluginsMap || {}}
|
||||
showInstallButton={showInstallButton}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const { templateCollections, templateCollectionTemplatesMap, templates } = marketplaceData
|
||||
return templates !== undefined
|
||||
? <FlatList variant="templates" items={templates} />
|
||||
: (
|
||||
<ListWithCollection
|
||||
variant="templates"
|
||||
items={templates}
|
||||
collections={templateCollections || []}
|
||||
collectionItemsMap={templateCollectionTemplatesMap || {}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ListWithCollection
|
||||
variant="templates"
|
||||
collections={templateCollections || []}
|
||||
collectionItemsMap={templateCollectionTemplatesMap || {}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const { pluginCollections, pluginCollectionPluginsMap, plugins } = marketplaceData
|
||||
if (plugins !== undefined) {
|
||||
return (
|
||||
<FlatList
|
||||
variant="plugins"
|
||||
items={plugins}
|
||||
showInstallButton={showInstallButton}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ListWithCollection
|
||||
variant="plugins"
|
||||
collections={pluginCollections || []}
|
||||
collectionItemsMap={pluginCollectionPluginsMap || {}}
|
||||
showInstallButton={showInstallButton}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
import { parseAsArrayOf, parseAsString, parseAsStringEnum } from 'nuqs/server'
|
||||
|
||||
export type CreationType = 'plugins' | 'templates'
|
||||
export const CREATION_TYPE = {
|
||||
plugins: 'plugins',
|
||||
templates: 'templates',
|
||||
} as const
|
||||
|
||||
export type CreationType = typeof CREATION_TYPE[keyof typeof CREATION_TYPE]
|
||||
|
||||
export const marketplaceSearchParamsParsers = {
|
||||
category: parseAsString.withDefault('all').withOptions({ history: 'replace', clearOnDefault: false }),
|
||||
q: parseAsString.withDefault('').withOptions({ history: 'replace' }),
|
||||
tags: parseAsArrayOf(parseAsString).withDefault([]).withOptions({ history: 'replace' }),
|
||||
creationType: parseAsStringEnum<CreationType>(['plugins', 'templates']).withDefault('plugins').withOptions({ history: 'replace' }),
|
||||
creationType: parseAsStringEnum<CreationType>([CREATION_TYPE.plugins, CREATION_TYPE.templates]).withDefault(CREATION_TYPE.plugins).withOptions({ history: 'replace' }),
|
||||
searchTab: parseAsStringEnum<SearchTab>(['all', 'plugins', 'templates', 'creators']).withDefault('').withOptions({ history: 'replace' }),
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import type { PluginsSearchParams, TemplateSearchParams } from './types'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useActivePluginCategory, useActiveTemplateCategory, useFilterPluginTags, useMarketplaceSearchMode, useMarketplaceSortValue, useSearchText } from './atoms'
|
||||
import { useActivePluginCategory, useActiveTemplateCategory, useCreationType, useFilterPluginTags, useMarketplaceSearchMode, useMarketplaceSortValue, useSearchText } from './atoms'
|
||||
import { CATEGORY_ALL } from './constants'
|
||||
import { useMarketplaceContainerScroll } from './hooks'
|
||||
import { useMarketplaceCollectionsAndPlugins, useMarketplacePlugins, useMarketplaceTemplateCollectionsAndTemplates, useMarketplaceTemplates } from './query'
|
||||
import { CREATION_TYPE } from './search-params'
|
||||
import { getCollectionsParams, getPluginFilterType, mapTemplateDetailToTemplate } from './utils'
|
||||
|
||||
const getCategory = (category: string) => {
|
||||
@ -121,31 +121,22 @@ export function useTemplatesMarketplaceData(enabled = true) {
|
||||
}
|
||||
}
|
||||
|
||||
type PluginsMarketplaceData = ReturnType<typeof usePluginsMarketplaceData>
|
||||
type TemplatesMarketplaceData = ReturnType<typeof useTemplatesMarketplaceData>
|
||||
type MarketplaceData
|
||||
= ({ creationType: 'plugins' } & PluginsMarketplaceData)
|
||||
| ({ creationType: 'templates' } & TemplatesMarketplaceData)
|
||||
export type PluginsMarketplaceData = ReturnType<typeof usePluginsMarketplaceData>
|
||||
export type TemplatesMarketplaceData = ReturnType<typeof useTemplatesMarketplaceData>
|
||||
export type MarketplaceData = PluginsMarketplaceData | TemplatesMarketplaceData
|
||||
|
||||
export function isPluginsData(data: MarketplaceData): data is PluginsMarketplaceData {
|
||||
return 'pluginCollections' in data
|
||||
}
|
||||
|
||||
/**
|
||||
* Main hook that routes to appropriate data based on creationType
|
||||
* Returns either plugins or templates data based on URL parameter
|
||||
*/
|
||||
export function useMarketplaceData(): MarketplaceData {
|
||||
const searchParams = useSearchParams()
|
||||
const creationType = (searchParams.get('creationType') || 'plugins') as 'plugins' | 'templates'
|
||||
const [creationType] = useCreationType()
|
||||
|
||||
const pluginsData = usePluginsMarketplaceData(creationType === 'plugins')
|
||||
const templatesData = useTemplatesMarketplaceData(creationType === 'templates')
|
||||
if (creationType === 'templates') {
|
||||
return {
|
||||
creationType,
|
||||
...templatesData,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
creationType,
|
||||
...pluginsData,
|
||||
}
|
||||
const pluginsData = usePluginsMarketplaceData(creationType === CREATION_TYPE.plugins)
|
||||
const templatesData = useTemplatesMarketplaceData(creationType === CREATION_TYPE.templates)
|
||||
return creationType === CREATION_TYPE.templates ? templatesData : pluginsData
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import { RiAddLine, RiBookOpenLine, RiGithubLine } from '@remixicon/react'
|
||||
import Link from 'next/link'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
@ -12,8 +11,10 @@ import {
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { CREATION_TYPE } from '@/app/components/plugins/marketplace/search-params'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useCreationType } from '../marketplace/atoms'
|
||||
|
||||
type DropdownItemProps = {
|
||||
href: string
|
||||
@ -80,23 +81,18 @@ export const SubmitRequestDropdown = () => {
|
||||
)
|
||||
}
|
||||
|
||||
type CreationTypeTabsProps = {
|
||||
creationType?: string
|
||||
}
|
||||
|
||||
export const CreationTypeTabs = ({ creationType: creationTypeProp }: CreationTypeTabsProps = {}) => {
|
||||
export const CreationTypeTabs = () => {
|
||||
const { t } = useTranslation()
|
||||
const searchParams = useSearchParams()
|
||||
const creationType = creationTypeProp || searchParams.get('creationType') || 'plugins'
|
||||
const [creationType] = useCreationType()
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Link
|
||||
href="/?creationType=plugins"
|
||||
href={`/?creationType=${CREATION_TYPE.plugins}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'flex items-center gap-1 px-3 py-2 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
|
||||
creationType === 'plugins' && 'bg-state-base-hover text-text-secondary',
|
||||
creationType === CREATION_TYPE.plugins && 'bg-state-base-hover text-text-secondary',
|
||||
)}
|
||||
>
|
||||
<Plugin className="h-4 w-4 shrink-0" />
|
||||
@ -105,11 +101,11 @@ export const CreationTypeTabs = ({ creationType: creationTypeProp }: CreationTyp
|
||||
</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/?creationType=templates"
|
||||
href={`/?creationType=${CREATION_TYPE.templates}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'flex items-center gap-1 px-3 py-2 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
|
||||
creationType === 'templates' && 'bg-state-base-hover text-text-secondary',
|
||||
creationType === CREATION_TYPE.templates && 'bg-state-base-hover text-text-secondary',
|
||||
)}
|
||||
>
|
||||
<Playground className="h-4 w-4 shrink-0" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user