"use client"; import { useCallback, useMemo, type Key } from "react"; import { Spin, Tree } from "antd"; import type { TreeProps } from "antd"; import { FolderOutlined } from "@ant-design/icons"; import { useI18n } from "@/i18n"; import { getUserFileFolderKey, type UserFileItem } from "@/utils/userFileLibrary"; interface AssetLibraryFolderTreeProps { folders: UserFileItem[]; childrenByFolderId: Record; loadingFolderIds: Set; loadedFolderIds: Set; expandedKeys: Key[]; selectedFolderId: number | null; disabledFolderIds?: Set; onExpandedKeysChange: (keys: Key[]) => void; onSelectFolder: (folderId: number) => void; onLoadFolderChildren: (folderId: number) => void | Promise; } type FolderTreeNode = { key: string; title: string; item?: UserFileItem; isLeaf: boolean; depth: number; isEmpty?: boolean; children?: FolderTreeNode[]; }; const folderTreeClassName = [ "!bg-transparent !text-[var(--text-secondary)]", "[&_.ant-tree-list-holder-inner]:gap-y-1", "[&_.ant-tree-treenode]:!w-full [&_.ant-tree-treenode]:!items-center [&_.ant-tree-treenode]:!p-0", "[&_.ant-tree-indent-unit]:!w-5", "[&_.ant-tree-switcher]:!hidden", "[&_.ant-tree-node-content-wrapper]:!m-0 [&_.ant-tree-node-content-wrapper]:!min-w-0 [&_.ant-tree-node-content-wrapper]:!flex-1 [&_.ant-tree-node-content-wrapper]:!bg-transparent [&_.ant-tree-node-content-wrapper]:!p-0 [&_.ant-tree-node-content-wrapper]:!text-inherit", "[&_.ant-tree-node-content-wrapper:hover]:!bg-transparent [&_.ant-tree-node-content-wrapper.ant-tree-node-selected]:!bg-transparent", "[&_.ant-tree-title]:!block [&_.ant-tree-title]:!w-full", ].join(" "); export function AssetLibraryFolderTree({ folders, childrenByFolderId, loadingFolderIds, loadedFolderIds, expandedKeys, selectedFolderId, disabledFolderIds, onExpandedKeysChange, onSelectFolder, onLoadFolderChildren, }: AssetLibraryFolderTreeProps) { const { t } = useI18n(); const treeData = useMemo(() => { const buildNodes = (items: UserFileItem[], depth: number): FolderTreeNode[] => ( items .filter((item) => item.nodeType === "folder") .map((folder) => { const folderKey = getUserFileFolderKey(folder.id); const rawChildren = childrenByFolderId[folder.id] || []; const childFolders = buildNodes(rawChildren, depth + 1); const isLoadedEmpty = loadedFolderIds.has(folder.id) && !loadingFolderIds.has(folder.id) && childFolders.length === 0; return { key: folderKey, title: folder.name, item: folder, isLeaf: false, depth, children: isLoadedEmpty ? [{ key: `${folderKey}-empty`, title: "", isLeaf: true, depth: depth + 1, isEmpty: true, }] : childFolders, }; }) ); return buildNodes(folders, 0); }, [childrenByFolderId, folders, loadedFolderIds, loadingFolderIds]); const handleExpand = useCallback["onExpand"]>>((keys, info) => { onExpandedKeysChange(keys); if (info.expanded && info.node.item) { void onLoadFolderChildren(info.node.item.id); } }, [onExpandedKeysChange, onLoadFolderChildren]); const titleRender = useCallback((node: FolderTreeNode) => { if (node.isEmpty) { return (
{t("assetLibrary.folderEmpty")}
); } const item = node.item; if (!item) return null; const isSelected = selectedFolderId === item.id; const isLoading = loadingFolderIds.has(item.id); const isExpanded = expandedKeys.includes(node.key); const isDisabled = disabledFolderIds?.has(item.id) ?? false; return ( ); }, [ expandedKeys, disabledFolderIds, loadingFolderIds, onExpandedKeysChange, onLoadFolderChildren, onSelectFolder, selectedFolderId, t, ]); return ( blockNode showIcon={false} showLine={false} className={folderTreeClassName} treeData={treeData} expandedKeys={expandedKeys} selectedKeys={selectedFolderId === null ? [] : [getUserFileFolderKey(selectedFolderId)]} onExpand={handleExpand} titleRender={titleRender} /> ); }