"use client"; import { useState, useCallback } from "react"; import { FolderItem } from "../../lib/api"; import { useTranslation } from "../../lib/i18n/context"; interface TreeNode extends FolderItem { children?: TreeNode[]; isLoading?: boolean; } interface FolderBrowserProps { initialFolders: FolderItem[]; selectedPath: string; onSelect: (path: string) => void; } export function FolderBrowser({ initialFolders, selectedPath, onSelect }: FolderBrowserProps) { const { t } = useTranslation(); // Convert initial folders to tree structure const [tree, setTree] = useState( initialFolders.map(f => ({ ...f, children: f.has_children ? [] : undefined })) ); const [expandedPaths, setExpandedPaths] = useState>(new Set()); const loadChildren = useCallback(async (parentPath: string): Promise => { try { const response = await fetch(`/api/folders?path=${encodeURIComponent(parentPath)}`); if (response.ok) { return await response.json(); } } catch (error) { console.error("Failed to load folders:", error); } return []; }, []); const findAndUpdateNode = ( nodes: TreeNode[], targetPath: string, updateFn: (node: TreeNode) => TreeNode ): TreeNode[] => { return nodes.map(node => { if (node.path === targetPath) { return updateFn(node); } if (node.children) { return { ...node, children: findAndUpdateNode(node.children, targetPath, updateFn) }; } return node; }); }; const toggleExpand = useCallback(async (node: TreeNode) => { if (!node.has_children) { onSelect(node.path); return; } const isExpanded = expandedPaths.has(node.path); if (isExpanded) { // Collapse setExpandedPaths(prev => { const next = new Set(prev); next.delete(node.path); return next; }); } else { // Expand setExpandedPaths(prev => new Set(prev).add(node.path)); // Load children if not already loaded if (!node.children || node.children.length === 0) { setTree(prev => findAndUpdateNode(prev, node.path, n => ({ ...n, isLoading: true }))); const children = await loadChildren(node.path); const childNodes = children.map(f => ({ ...f, children: f.has_children ? [] : undefined })); setTree(prev => findAndUpdateNode(prev, node.path, n => ({ ...n, children: childNodes, isLoading: false }))); } } }, [expandedPaths, loadChildren, onSelect]); const renderNode = (node: TreeNode, level: number = 0) => { const isExpanded = expandedPaths.has(node.path); const isSelected = selectedPath === node.path; const hasChildren = node.has_children; return (
onSelect(node.path)} > {/* Expand/Collapse button */} {hasChildren ? ( ) : ( )} {/* Folder icon */} {/* Folder name */} {node.name} {/* Selected indicator */} {isSelected && ( )}
{/* Render children if expanded */} {isExpanded && node.children && node.children.length > 0 && (
{node.children.map(child => renderNode(child, level + 1))}
)}
); }; return (
{/* Folder tree */}
{tree.length === 0 ? (
{t("folder.noFolders")}
) : ( tree.map(node => renderNode(node)) )}
); }