feat(web): add file tree manual refresh + 5s polling + focus refetch

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tao.chen
2026-07-03 16:57:20 +08:00
co-authored by Claude
parent 53fe249dd6
commit ac87454711
2 changed files with 32 additions and 7 deletions
@@ -1,4 +1,5 @@
import { ChevronRight, FileCode2, Folder } from "lucide-react"; import { useQueryClient, useIsFetching } from "@tanstack/react-query";
import { ChevronRight, FileCode2, Folder, RefreshCw } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
@@ -131,6 +132,9 @@ function EmptyState({ children }: { children: ReactNode }) {
export function FileExplorerPanel({ workspaceId }: FileExplorerPanelProps) { export function FileExplorerPanel({ workspaceId }: FileExplorerPanelProps) {
const setActiveFilePath = useWorkspaceUiStore((s) => s.setActiveFilePath); const setActiveFilePath = useWorkspaceUiStore((s) => s.setActiveFilePath);
const setSelectedPath = useFileTreeStore((s) => s.setSelectedPath); const setSelectedPath = useFileTreeStore((s) => s.setSelectedPath);
const queryClient = useQueryClient();
const filesQueryKey = ["workspaces", workspaceId ?? "", "files"] as const;
const isFetchingFiles = useIsFetching({ queryKey: filesQueryKey }) > 0;
const { data, isLoading, error } = useFileList(workspaceId, "."); const { data, isLoading, error } = useFileList(workspaceId, ".");
const onSelectFile = (path: string) => { const onSelectFile = (path: string) => {
@@ -138,12 +142,33 @@ export function FileExplorerPanel({ workspaceId }: FileExplorerPanelProps) {
setActiveFilePath(path); setActiveFilePath(path);
}; };
const refreshAll = () => {
queryClient.invalidateQueries({ queryKey: filesQueryKey });
};
const header = (
<div className="flex h-9 shrink-0 items-center justify-between border-b border-sidebar-border px-3 text-[11px] font-semibold uppercase text-muted-foreground">
<span>Explorer</span>
{workspaceId && (
<button
type="button"
onClick={refreshAll}
aria-label="refresh file tree"
className="inline-flex h-5 w-5 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
>
<RefreshCw
className={cn("size-3", isFetchingFiles && "animate-spin")}
aria-hidden="true"
/>
</button>
)}
</div>
);
if (!workspaceId) { if (!workspaceId) {
return ( return (
<aside className="flex h-full min-h-0 flex-col bg-sidebar text-sidebar-foreground"> <aside className="flex h-full min-h-0 flex-col bg-sidebar text-sidebar-foreground">
<div className="flex h-9 shrink-0 items-center border-b border-sidebar-border px-3 text-[11px] font-semibold uppercase text-muted-foreground"> {header}
Explorer
</div>
<EmptyState> <EmptyState>
<p>No workspace selected</p> <p>No workspace selected</p>
</EmptyState> </EmptyState>
@@ -153,9 +178,7 @@ export function FileExplorerPanel({ workspaceId }: FileExplorerPanelProps) {
return ( return (
<aside className="flex h-full min-h-0 flex-col bg-sidebar text-sidebar-foreground"> <aside className="flex h-full min-h-0 flex-col bg-sidebar text-sidebar-foreground">
<div className="flex h-9 shrink-0 items-center border-b border-sidebar-border px-3 text-[11px] font-semibold uppercase text-muted-foreground"> {header}
Explorer
</div>
<ScrollArea className="min-h-0 flex-1"> <ScrollArea className="min-h-0 flex-1">
<nav className="py-2" aria-label="Workspace files"> <nav className="py-2" aria-label="Workspace files">
<ul> <ul>
+2
View File
@@ -100,6 +100,8 @@ export function useFileList(
queryKey: fileListQueryKey(workspaceId ?? "", queryPath), queryKey: fileListQueryKey(workspaceId ?? "", queryPath),
queryFn: () => fetchFileList(workspaceId!, queryPath), queryFn: () => fetchFileList(workspaceId!, queryPath),
enabled: !!workspaceId && enabled, enabled: !!workspaceId && enabled,
refetchInterval: 5_000,
refetchOnWindowFocus: true,
}); });
} }