import { useQuery, useQueryClient } from "@tanstack/react-query"; import { FolderOpen } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; import { FileTree } from "~/client/components/file-tree"; import { listFilesOptions } from "../api-client/@tanstack/react-query.gen"; interface FileEntry { name: string; path: string; type: "file" | "directory"; size?: number; modifiedAt?: number; } type VolumeFileBrowserProps = { volumeName: string; enabled?: boolean; withCheckboxes?: boolean; selectedPaths?: Set; onSelectionChange?: (paths: Set) => void; foldersOnly?: boolean; className?: string; emptyMessage?: string; emptyDescription?: string; }; export const VolumeFileBrowser = ({ volumeName, enabled = true, withCheckboxes = false, selectedPaths, onSelectionChange, foldersOnly = false, className, emptyMessage = "This volume appears to be empty.", emptyDescription, }: VolumeFileBrowserProps) => { const queryClient = useQueryClient(); const [expandedFolders, setExpandedFolders] = useState>(new Set()); const [fetchedFolders, setFetchedFolders] = useState>(new Set(["/"])); const [loadingFolders, setLoadingFolders] = useState>(new Set()); const [allFiles, setAllFiles] = useState>(new Map()); const { data, isLoading, error } = useQuery({ ...listFilesOptions({ path: { name: volumeName } }), enabled, }); useMemo(() => { if (data?.files) { setAllFiles((prev) => { const next = new Map(prev); for (const file of data.files) { next.set(file.path, file); } return next; }); } }, [data]); const fileArray = useMemo(() => Array.from(allFiles.values()), [allFiles]); const handleFolderExpand = useCallback( async (folderPath: string) => { setExpandedFolders((prev) => { const next = new Set(prev); next.add(folderPath); return next; }); if (!fetchedFolders.has(folderPath)) { setLoadingFolders((prev) => new Set(prev).add(folderPath)); try { const result = await queryClient.ensureQueryData( listFilesOptions({ path: { name: volumeName }, query: { path: folderPath }, }), ); if (result.files) { setAllFiles((prev) => { const next = new Map(prev); for (const file of result.files) { next.set(file.path, file); } return next; }); setFetchedFolders((prev) => new Set(prev).add(folderPath)); } } catch (error) { console.error("Failed to fetch folder contents:", error); } finally { setLoadingFolders((prev) => { const next = new Set(prev); next.delete(folderPath); return next; }); } } }, [volumeName, fetchedFolders, queryClient.ensureQueryData], ); const handleFolderHover = useCallback( (folderPath: string) => { if (!fetchedFolders.has(folderPath) && !loadingFolders.has(folderPath)) { queryClient.prefetchQuery( listFilesOptions({ path: { name: volumeName }, query: { path: folderPath }, }), ); } }, [volumeName, fetchedFolders, loadingFolders, queryClient], ); if (isLoading && fileArray.length === 0) { return (

Loading files...

); } if (error) { return (

Failed to load files: {(error as Error).message}

); } if (fileArray.length === 0) { return (

{emptyMessage}

{emptyDescription &&

{emptyDescription}

}
); } return (
); };