diff --git a/apps/client/app/components/file-tree.tsx b/apps/client/app/components/file-tree.tsx index 8e5df05..05f760f 100644 --- a/apps/client/app/components/file-tree.tsx +++ b/apps/client/app/components/file-tree.tsx @@ -27,6 +27,7 @@ interface Props { selectedFile?: string; onFileSelect?: (filePath: string) => void; onFolderExpand?: (folderPath: string) => void; + onFolderHover?: (folderPath: string) => void; expandedFolders?: Set; loadingFolders?: Set; className?: string; @@ -38,6 +39,7 @@ export const FileTree = memo((props: Props) => { onFileSelect, selectedFile, onFolderExpand, + onFolderHover, expandedFolders = new Set(), loadingFolders = new Set(), className, @@ -137,6 +139,7 @@ export const FileTree = memo((props: Props) => { collapsed={collapsedFolders.has(fileOrFolder.fullPath)} loading={loadingFolders.has(fileOrFolder.fullPath)} onToggle={toggleCollapseState} + onHover={onFolderHover} /> ); } @@ -154,9 +157,10 @@ interface FolderProps { collapsed: boolean; loading?: boolean; onToggle: (fullPath: string) => void; + onHover?: (fullPath: string) => void; } -const Folder = memo(({ folder, collapsed, loading, onToggle }: FolderProps) => { +const Folder = memo(({ folder, collapsed, loading, onToggle, onHover }: FolderProps) => { const { depth, name, fullPath } = folder; const FolderIconComponent = collapsed ? FolderIcon : FolderOpen; @@ -164,6 +168,12 @@ const Folder = memo(({ folder, collapsed, loading, onToggle }: FolderProps) => { onToggle(fullPath); }, [onToggle, fullPath]); + const handleMouseEnter = useCallback(() => { + if (collapsed) { + onHover?.(fullPath); + } + }, [onHover, fullPath, collapsed]); + return ( { ) } onClick={handleClick} + onMouseEnter={handleMouseEnter} > {name} @@ -219,9 +230,10 @@ interface ButtonProps { children: ReactNode; className?: string; onClick?: () => void; + onMouseEnter?: () => void; } -const NodeButton = memo(({ depth, icon, onClick, className, children }: ButtonProps) => { +const NodeButton = memo(({ depth, icon, onClick, onMouseEnter, className, children }: ButtonProps) => { const paddingLeft = useMemo(() => `${8 + depth * NODE_PADDING_LEFT}px`, [depth]); return ( @@ -230,6 +242,7 @@ const NodeButton = memo(({ depth, icon, onClick, className, children }: ButtonPr className={cn("flex items-center gap-2 w-full pr-2 text-sm py-1.5 text-left", className)} style={{ paddingLeft }} onClick={onClick} + onMouseEnter={onMouseEnter} > {icon}
{children}
diff --git a/apps/client/app/modules/details/tabs/files.tsx b/apps/client/app/modules/details/tabs/files.tsx index 559fab4..256f1ee 100644 --- a/apps/client/app/modules/details/tabs/files.tsx +++ b/apps/client/app/modules/details/tabs/files.tsx @@ -1,11 +1,10 @@ -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; import { FolderOpen } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; import { listFilesOptions } from "~/api-client/@tanstack/react-query.gen"; import { listFiles } from "~/api-client/sdk.gen"; import { FileTree } from "~/components/file-tree"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; -import { parseError } from "~/lib/errors"; import type { Volume } from "~/lib/types"; type Props = { @@ -21,6 +20,7 @@ interface FileEntry { } export const FilesTabContent = ({ volume }: Props) => { + const queryClient = useQueryClient(); const [expandedFolders, setExpandedFolders] = useState>(new Set()); const [fetchedFolders, setFetchedFolders] = useState>(new Set(["/"])); const [loadingFolders, setLoadingFolders] = useState>(new Set()); @@ -88,6 +88,21 @@ export const FilesTabContent = ({ volume }: Props) => { [volume.name, fetchedFolders], ); + // Prefetch folder contents on hover + const handleFolderHover = useCallback( + (folderPath: string) => { + if (!fetchedFolders.has(folderPath) && !loadingFolders.has(folderPath)) { + queryClient.prefetchQuery( + listFilesOptions({ + path: { name: volume.name }, + query: { path: folderPath }, + }), + ); + } + }, + [volume.name, fetchedFolders, loadingFolders, queryClient], + ); + const fileArray = useMemo(() => Array.from(allFiles.values()), [allFiles]); if (volume.status !== "mounted") { @@ -133,6 +148,7 @@ export const FilesTabContent = ({ volume }: Props) => {