mirror of
https://github.com/langgenius/dify.git
synced 2026-08-30 20:43:55 +08:00
205 lines
7.2 KiB
TypeScript
205 lines
7.2 KiB
TypeScript
'use client'
|
|
|
|
import type { GetAppsData } from '@dify/contracts/api/console/apps/types.gen'
|
|
import type { AppListCreationDialog } from './app-list-creation-modals'
|
|
import type { AppListUrlQuery } from './query-params'
|
|
import type { App } from '@/models/explore'
|
|
import type { TryAppSelection } from '@/types/try-app'
|
|
import {
|
|
ScrollArea,
|
|
ScrollAreaContent,
|
|
ScrollAreaScrollbar,
|
|
ScrollAreaThumb,
|
|
ScrollAreaViewport,
|
|
} from '@langgenius/dify-ui/scroll-area'
|
|
import { useDebounce } from 'ahooks'
|
|
import { useAtomValue } from 'jotai'
|
|
import { useQueryStates } from 'nuqs'
|
|
import { useCallback, useId, useMemo, useRef, useState } from 'react'
|
|
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
|
import { CheckModal } from '@/hooks/use-pay'
|
|
import { hasPermission } from '@/utils/permission'
|
|
import { AppListCatalog } from './app-list-catalog'
|
|
import { AppListCreationModals } from './app-list-creation-modals'
|
|
import { AppListHeader } from './app-list-header'
|
|
import { AppListTagManagementModal } from './app-list-tag-management-modal'
|
|
import { APP_LIST_SEARCH_DEBOUNCE_MS } from './constants'
|
|
import { useDSLDragDrop } from './hooks/use-dsl-drag-drop'
|
|
import { appListQueryParsers } from './query-params'
|
|
|
|
type AppListQuery = NonNullable<GetAppsData['query']>
|
|
type AppListSortBy = NonNullable<AppListQuery['sort_by']>
|
|
type AppListCategory = AppListUrlQuery['category']
|
|
|
|
type Props = Readonly<{
|
|
onCreateLearnDify?: (app: App) => void
|
|
onTryLearnDify?: (params: TryAppSelection) => void
|
|
}>
|
|
|
|
export function List({ onCreateLearnDify, onTryLearnDify }: Props) {
|
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
|
|
|
const [urlQuery, setUrlQuery] = useQueryStates(appListQueryParsers)
|
|
const { category, keywords } = urlQuery
|
|
const [creatorIDs, setCreatorIDs] = useState<string[]>([])
|
|
const [tagIDs, setTagIDs] = useState<string[]>([])
|
|
const [sortBy, setSortBy] = useState<AppListSortBy>('last_modified')
|
|
const debouncedKeywords = useDebounce(keywords, { wait: APP_LIST_SEARCH_DEBOUNCE_MS })
|
|
const dropZoneRef = useRef<HTMLDivElement>(null)
|
|
const scrollViewportRef = useRef<HTMLDivElement>(null)
|
|
const titleId = useId()
|
|
const [showTagManagementModal, setShowTagManagementModal] = useState(false)
|
|
const [creationDialog, setCreationDialog] = useState<AppListCreationDialog>(null)
|
|
const canCreateApp = hasPermission(workspacePermissionKeys, 'app.create_and_management')
|
|
const hasActiveFilters =
|
|
category !== 'all' ||
|
|
tagIDs.length > 0 ||
|
|
keywords.trim().length > 0 ||
|
|
debouncedKeywords.trim().length > 0 ||
|
|
creatorIDs.length > 0
|
|
|
|
const handleDSLFileDropped = useCallback(
|
|
(file: File) => {
|
|
if (!canCreateApp) return
|
|
|
|
setCreationDialog({ type: 'dsl', droppedFile: file })
|
|
},
|
|
[canCreateApp],
|
|
)
|
|
|
|
const { dragging } = useDSLDragDrop({
|
|
onDSLFileDropped: handleDSLFileDropped,
|
|
dropZoneRef,
|
|
enabled: canCreateApp,
|
|
})
|
|
|
|
const appListQuery = useMemo<AppListQuery>(
|
|
() => ({
|
|
page: 1,
|
|
limit: 30,
|
|
name: debouncedKeywords,
|
|
sort_by: sortBy,
|
|
...(tagIDs.length ? { tag_ids: tagIDs } : {}),
|
|
...(creatorIDs.length ? { creator_ids: creatorIDs } : {}),
|
|
...(category !== 'all' ? { mode: category } : {}),
|
|
}),
|
|
[category, creatorIDs, debouncedKeywords, sortBy, tagIDs],
|
|
)
|
|
|
|
const resetCatalogScroll = () => {
|
|
scrollViewportRef.current?.scrollTo({ top: 0 })
|
|
}
|
|
const changeCategory = (nextCategory: AppListCategory) => {
|
|
resetCatalogScroll()
|
|
void setUrlQuery({ category: nextCategory })
|
|
}
|
|
const changeTagIDs = (nextTagIDs: string[]) => {
|
|
resetCatalogScroll()
|
|
setTagIDs(nextTagIDs)
|
|
}
|
|
const changeKeywords = (nextKeywords: string) => {
|
|
resetCatalogScroll()
|
|
void setUrlQuery({ keywords: nextKeywords })
|
|
}
|
|
const changeCreatorIDs = (nextCreatorIDs: string[]) => {
|
|
resetCatalogScroll()
|
|
setCreatorIDs(nextCreatorIDs)
|
|
}
|
|
const changeSortBy = (nextSortBy: AppListSortBy) => {
|
|
resetCatalogScroll()
|
|
setSortBy(nextSortBy)
|
|
}
|
|
|
|
const openCreateBlankModal = useCallback(() => {
|
|
if (canCreateApp) setCreationDialog({ type: 'blank' })
|
|
}, [canCreateApp])
|
|
const openCreateTemplateDialog = useCallback(() => {
|
|
if (canCreateApp) setCreationDialog({ type: 'template' })
|
|
}, [canCreateApp])
|
|
const openCreateFromDSLModal = useCallback(() => {
|
|
if (canCreateApp) setCreationDialog({ type: 'dsl' })
|
|
}, [canCreateApp])
|
|
const openTagManagement = useCallback(() => setShowTagManagementModal(true), [])
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
ref={dropZoneRef}
|
|
className="relative flex h-0 min-h-0 shrink-0 grow flex-col bg-background-body"
|
|
>
|
|
{dragging && (
|
|
<div className="absolute inset-0 z-50 m-0.5 rounded-2xl border-2 border-dashed border-components-dropzone-border-accent bg-[rgba(21,90,239,0.14)] p-2"></div>
|
|
)}
|
|
|
|
<AppListHeader
|
|
titleId={titleId}
|
|
category={category}
|
|
tagIDs={tagIDs}
|
|
keywords={keywords}
|
|
creatorIDs={creatorIDs}
|
|
sortBy={sortBy}
|
|
onCategoryChange={changeCategory}
|
|
onTagIDsChange={changeTagIDs}
|
|
onKeywordsChange={changeKeywords}
|
|
onCreatorIDsChange={changeCreatorIDs}
|
|
onSortByChange={changeSortBy}
|
|
onCreateBlank={openCreateBlankModal}
|
|
onCreateTemplate={openCreateTemplateDialog}
|
|
onImportDSL={openCreateFromDSLModal}
|
|
onOpenTagManagement={openTagManagement}
|
|
showCreateButton={canCreateApp}
|
|
/>
|
|
|
|
<div className="relative min-h-0 grow">
|
|
<ScrollArea className="size-full overflow-hidden">
|
|
<ScrollAreaViewport
|
|
ref={scrollViewportRef}
|
|
role="region"
|
|
aria-labelledby={titleId}
|
|
className="overscroll-contain"
|
|
style={{ overflowX: 'hidden' }}
|
|
>
|
|
<ScrollAreaContent
|
|
className="flex min-h-full w-full max-w-full min-w-0 flex-col"
|
|
style={{ minWidth: 0 }}
|
|
>
|
|
<AppListCatalog
|
|
appListQuery={appListQuery}
|
|
canCreateApp={canCreateApp}
|
|
dragging={dragging}
|
|
hasActiveFilters={hasActiveFilters}
|
|
onCreateBlank={openCreateBlankModal}
|
|
onCreateLearnDify={onCreateLearnDify}
|
|
onCreateTemplate={openCreateTemplateDialog}
|
|
onImportDSL={openCreateFromDSLModal}
|
|
onOpenTagManagement={openTagManagement}
|
|
onTryLearnDify={onTryLearnDify}
|
|
scrollViewportRef={scrollViewportRef}
|
|
/>
|
|
</ScrollAreaContent>
|
|
</ScrollAreaViewport>
|
|
<ScrollAreaScrollbar>
|
|
<ScrollAreaThumb />
|
|
</ScrollAreaScrollbar>
|
|
</ScrollArea>
|
|
</div>
|
|
|
|
<CheckModal />
|
|
<AppListTagManagementModal
|
|
show={showTagManagementModal}
|
|
onClose={() => setShowTagManagementModal(false)}
|
|
/>
|
|
</div>
|
|
|
|
<AppListCreationModals
|
|
canCreateApp={canCreateApp}
|
|
category={category}
|
|
dialog={creationDialog}
|
|
onClose={() => setCreationDialog(null)}
|
|
onOpenBlank={openCreateBlankModal}
|
|
onOpenTemplate={openCreateTemplateDialog}
|
|
/>
|
|
</>
|
|
)
|
|
}
|