mirror of
https://github.com/langgenius/dify.git
synced 2026-03-29 01:49:57 +08:00
refactor(skill): centralize asset tree data fetching with custom hooks
Extract repeated appId retrieval and tree data fetching patterns into dedicated hooks (useSkillAssetTreeData, useSkillAssetNodeMap) to reduce code duplication across 6 components and leverage TanStack Query's select option for efficient nodeMap computation.
This commit is contained in:
@ -1,32 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { AppAssetTreeView } from '@/types/app-asset'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useGetAppAssetTree } from '@/service/use-app-asset'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import EditorTabItem from './editor-tab-item'
|
||||
import { useSkillAssetNodeMap } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from './store'
|
||||
import { buildNodeMap } from './utils/tree-utils'
|
||||
|
||||
const EditorTabs: FC = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appId = appDetail?.id || ''
|
||||
|
||||
const { data: treeData } = useGetAppAssetTree(appId)
|
||||
|
||||
const openTabIds = useSkillEditorStore(s => s.openTabIds)
|
||||
const activeTabId = useSkillEditorStore(s => s.activeTabId)
|
||||
const dirtyContents = useSkillEditorStore(s => s.dirtyContents)
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
|
||||
const nodeMap = useMemo(() => {
|
||||
if (!treeData?.children)
|
||||
return new Map<string, AppAssetTreeView>()
|
||||
return buildNodeMap(treeData.children)
|
||||
}, [treeData?.children])
|
||||
const { data: nodeMap } = useSkillAssetNodeMap()
|
||||
|
||||
const handleTabClick = (fileId: string) => {
|
||||
storeApi.getState().activateTab(fileId)
|
||||
@ -47,7 +33,7 @@ const EditorTabs: FC = () => {
|
||||
)}
|
||||
>
|
||||
{openTabIds.map((fileId) => {
|
||||
const node = nodeMap.get(fileId)
|
||||
const node = nodeMap?.get(fileId)
|
||||
const name = node?.name ?? fileId
|
||||
const isActive = activeTabId === fileId
|
||||
const isDirty = dirtyContents.has(fileId)
|
||||
|
||||
@ -13,8 +13,9 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useGetAppAssetTree, useRenameAppAssetNode } from '@/service/use-app-asset'
|
||||
import { useRenameAppAssetNode } from '@/service/use-app-asset'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useSkillAssetTreeData } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from './store'
|
||||
import TreeContextMenu from './tree-context-menu'
|
||||
import TreeNode from './tree-node'
|
||||
@ -45,7 +46,7 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appId = appDetail?.id || ''
|
||||
|
||||
const { data: treeData, isLoading, error } = useGetAppAssetTree(appId)
|
||||
const { data: treeData, isLoading, error } = useSkillAssetTreeData()
|
||||
const isMutating = useIsMutating() > 0
|
||||
|
||||
const expandedFolderIds = useSkillEditorStore(s => s.expandedFolderIds)
|
||||
|
||||
@ -10,10 +10,10 @@ import {
|
||||
useCreateAppAssetFile,
|
||||
useCreateAppAssetFolder,
|
||||
useDeleteAppAssetNode,
|
||||
useGetAppAssetTree,
|
||||
} from '@/service/use-app-asset'
|
||||
import { useSkillEditorStoreApi } from '../store'
|
||||
import { getAllDescendantFileIds } from '../utils/tree-utils'
|
||||
import { useSkillAssetTreeData } from './use-skill-asset-tree'
|
||||
|
||||
type UseFileOperationsOptions = {
|
||||
nodeId: string
|
||||
@ -40,7 +40,7 @@ export function useFileOperations({
|
||||
const createFolder = useCreateAppAssetFolder()
|
||||
const createFile = useCreateAppAssetFile()
|
||||
const deleteNode = useDeleteAppAssetNode()
|
||||
const { data: treeData } = useGetAppAssetTree(appId)
|
||||
const { data: treeData } = useSkillAssetTreeData()
|
||||
|
||||
const parentId = nodeId === 'root' ? null : nodeId
|
||||
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import type { AppAssetTreeResponse, AppAssetTreeView } from '@/types/app-asset'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useGetAppAssetTree } from '@/service/use-app-asset'
|
||||
import { buildNodeMap } from '../utils/tree-utils'
|
||||
|
||||
/**
|
||||
* Get the current app ID from the app store.
|
||||
* Used internally by skill asset tree hooks.
|
||||
*/
|
||||
function useSkillAppId(): string {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
return appDetail?.id || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get the asset tree data for the current skill app.
|
||||
* Returns the raw tree data along with loading and error states.
|
||||
*/
|
||||
export function useSkillAssetTreeData() {
|
||||
const appId = useSkillAppId()
|
||||
return useGetAppAssetTree(appId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get the node map (id -> node) for the current skill app.
|
||||
* Uses TanStack Query's select option to compute and cache the map.
|
||||
*/
|
||||
export function useSkillAssetNodeMap() {
|
||||
const appId = useSkillAppId()
|
||||
return useGetAppAssetTree(appId, {
|
||||
select: (data: AppAssetTreeResponse): Map<string, AppAssetTreeView> => {
|
||||
if (!data?.children)
|
||||
return new Map()
|
||||
return buildNodeMap(data.children)
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -11,7 +11,6 @@ import {
|
||||
import * as React from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
@ -19,9 +18,9 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import SearchInput from '@/app/components/base/search-input'
|
||||
import { useGetAppAssetTree } from '@/service/use-app-asset'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useFileOperations } from './hooks/use-file-operations'
|
||||
import { useSkillAssetTreeData } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore } from './store'
|
||||
import { getTargetFolderIdFromSelection } from './utils/tree-utils'
|
||||
|
||||
@ -55,9 +54,7 @@ const SidebarSearchAdd: FC = () => {
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appId = appDetail?.id || ''
|
||||
const { data: treeData } = useGetAppAssetTree(appId)
|
||||
const { data: treeData } = useSkillAssetTreeData()
|
||||
const activeTabId = useSkillEditorStore(s => s.activeTabId)
|
||||
|
||||
const targetFolderId = useMemo(() => {
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import type { OnMount } from '@monaco-editor/react'
|
||||
import type { FC } from 'react'
|
||||
import type { AppAssetTreeView } from '@/types/app-asset'
|
||||
import { loader } from '@monaco-editor/react'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
@ -11,7 +10,7 @@ import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { useGetAppAssetFileContent, useGetAppAssetTree, useUpdateAppAssetFileContent } from '@/service/use-app-asset'
|
||||
import { useGetAppAssetFileContent, useUpdateAppAssetFileContent } from '@/service/use-app-asset'
|
||||
import { Theme } from '@/types/app'
|
||||
import { basePath } from '@/utils/var'
|
||||
import CodeFileEditor from './editor/code-file-editor'
|
||||
@ -19,9 +18,9 @@ import MarkdownFileEditor from './editor/markdown-file-editor'
|
||||
import MediaFilePreview from './editor/media-file-preview'
|
||||
import OfficeFilePlaceholder from './editor/office-file-placeholder'
|
||||
import UnsupportedFileDownload from './editor/unsupported-file-download'
|
||||
import { useSkillAssetNodeMap } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from './store'
|
||||
import { getFileExtension, getFileLanguage, isCodeOrTextFile, isImageFile, isMarkdownFile, isOfficeFile, isVideoFile } from './utils/file-utils'
|
||||
import { buildNodeMap } from './utils/tree-utils'
|
||||
|
||||
if (typeof window !== 'undefined')
|
||||
loader.config({ paths: { vs: `${window.location.origin}${basePath}/vs` } })
|
||||
@ -38,16 +37,9 @@ const SkillDocEditor: FC = () => {
|
||||
const activeTabId = useSkillEditorStore(s => s.activeTabId)
|
||||
const dirtyContents = useSkillEditorStore(s => s.dirtyContents)
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
const { data: nodeMap } = useSkillAssetNodeMap()
|
||||
|
||||
const { data: treeData } = useGetAppAssetTree(appId)
|
||||
|
||||
const nodeMap = useMemo(() => {
|
||||
if (!treeData?.children)
|
||||
return new Map<string, AppAssetTreeView>()
|
||||
return buildNodeMap(treeData.children)
|
||||
}, [treeData?.children])
|
||||
|
||||
const currentFileNode = activeTabId ? nodeMap.get(activeTabId) : undefined
|
||||
const currentFileNode = activeTabId ? nodeMap?.get(activeTabId) : undefined
|
||||
const fileExtension = getFileExtension(currentFileNode?.name, currentFileNode?.extension)
|
||||
const isMarkdown = isMarkdownFile(fileExtension)
|
||||
const isCodeOrText = isCodeOrTextFile(fileExtension)
|
||||
|
||||
@ -6,10 +6,9 @@ import type { TreeNodeData } from './type'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useGetAppAssetTree } from '@/service/use-app-asset'
|
||||
import FileNodeMenu from './file-node-menu'
|
||||
import FolderNodeMenu from './folder-node-menu'
|
||||
import { useSkillAssetTreeData } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from './store'
|
||||
import { findNodeById } from './utils/tree-utils'
|
||||
|
||||
@ -21,10 +20,7 @@ const TreeContextMenu: FC<TreeContextMenuProps> = ({ treeRef }) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const contextMenu = useSkillEditorStore(s => s.contextMenu)
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appId = appDetail?.id || ''
|
||||
const { data: treeData } = useGetAppAssetTree(appId)
|
||||
const { data: treeData } = useSkillAssetTreeData()
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
storeApi.getState().setContextMenu(null)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type {
|
||||
AppAssetNode,
|
||||
AppAssetTreeResponse,
|
||||
CreateFolderPayload,
|
||||
MoveNodePayload,
|
||||
RenameNodePayload,
|
||||
@ -14,11 +15,19 @@ import {
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { upload } from './base'
|
||||
|
||||
export const useGetAppAssetTree = (appId: string) => {
|
||||
type UseGetAppAssetTreeOptions<TData = AppAssetTreeResponse> = {
|
||||
select?: (data: AppAssetTreeResponse) => TData
|
||||
}
|
||||
|
||||
export function useGetAppAssetTree<TData = AppAssetTreeResponse>(
|
||||
appId: string,
|
||||
options?: UseGetAppAssetTreeOptions<TData>,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId } } }),
|
||||
queryFn: () => consoleClient.appAsset.tree({ params: { appId } }),
|
||||
enabled: !!appId,
|
||||
select: options?.select,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user