Files
dify/web/app/components/workflow/skill/hooks/use-modify-operations.ts
yyh 9f444f1f6a refactor(skill): split file operations hook and extract TreeNodeIcon component
Split use-file-operations.ts (248 lines) into smaller focused hooks:
- use-create-operations.ts for file/folder creation and upload
- use-modify-operations.ts for rename and delete operations
- use-file-operations.ts now serves as orchestrator maintaining backward compatibility

Extract TreeNodeIcon component from tree-node.tsx for cleaner separation of concerns.

Add brief comments to drag hooks explaining their purpose and relationships.
2026-01-19 19:13:09 +08:00

108 lines
2.9 KiB
TypeScript

'use client'
// Handles file/folder rename and delete operations
import type { NodeApi, TreeApi } from 'react-arborist'
import type { StoreApi } from 'zustand'
import type { TreeNodeData } from '../type'
import type { SkillEditorSliceShape } from '@/app/components/workflow/store/workflow/skill-editor/types'
import type { AppAssetTreeResponse } from '@/types/app-asset'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Toast from '@/app/components/base/toast'
import { useDeleteAppAssetNode } from '@/service/use-app-asset'
import { getAllDescendantFileIds } from '../utils/tree-utils'
type UseModifyOperationsOptions = {
nodeId: string
node?: NodeApi<TreeNodeData>
treeRef?: React.RefObject<TreeApi<TreeNodeData> | null>
appId: string
storeApi: StoreApi<SkillEditorSliceShape>
treeData?: AppAssetTreeResponse
onClose: () => void
}
export function useModifyOperations({
nodeId,
node,
treeRef,
appId,
storeApi,
treeData,
onClose,
}: UseModifyOperationsOptions) {
const { t } = useTranslation('workflow')
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const deleteNode = useDeleteAppAssetNode()
const handleRename = useCallback(() => {
if (treeRef?.current) {
const targetNode = treeRef.current.get(nodeId)
targetNode?.edit()
}
else if (node) {
node.edit()
}
onClose()
}, [nodeId, node, onClose, treeRef])
const handleDeleteClick = useCallback(() => {
setShowDeleteConfirm(true)
}, [])
const handleDeleteConfirm = useCallback(async () => {
const isFolder = node?.data?.node_type === 'folder'
try {
const descendantFileIds = treeData?.children
? getAllDescendantFileIds(nodeId, treeData.children)
: []
await deleteNode.mutateAsync({ appId, nodeId })
descendantFileIds.forEach((fileId) => {
storeApi.getState().closeTab(fileId)
storeApi.getState().clearDraftContent(fileId)
})
// Also close and clear the node itself if it's a file
if (!isFolder) {
storeApi.getState().closeTab(nodeId)
storeApi.getState().clearDraftContent(nodeId)
}
Toast.notify({
type: 'success',
message: isFolder
? t('skillSidebar.menu.deleted')
: t('skillSidebar.menu.fileDeleted'),
})
}
catch {
Toast.notify({
type: 'error',
message: isFolder
? t('skillSidebar.menu.deleteError')
: t('skillSidebar.menu.fileDeleteError'),
})
}
finally {
setShowDeleteConfirm(false)
onClose()
}
}, [appId, nodeId, node?.data?.node_type, deleteNode, storeApi, treeData?.children, onClose, t])
const handleDeleteCancel = useCallback(() => {
setShowDeleteConfirm(false)
}, [])
return {
showDeleteConfirm,
isDeleting: deleteNode.isPending,
handleRename,
handleDeleteClick,
handleDeleteConfirm,
handleDeleteCancel,
}
}