Skip to content
Source
Sign inSign up

thread-file-navigator-pane.tsx

99 lines3.8 KB

1import { resolveFilesystemReadAccess } from "@t3tools/client-runtime/state/filesystem";
2import { environmentSession } from "../../state/session";
3import type { EnvironmentId } from "@t3tools/contracts";
4import { AppBar } from "../../components/ui/AppBar";
5import { ScreenContent } from "../../components/ui/ScreenContent";
6import { useState } from "react";
7import { SheetPane } from "../../components/ui/SheetPane";
8​
9import { useEnvironmentQuery } from "../../state/query";
10import { useEnvironmentPresentation } from "../../state/presentation";
11import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
12import { FileTreeBrowser } from "./FileTreeBrowser";
13import { useFileTreeEntries } from "./useFileTreeEntries";
14import { preloadWorkspaceFileContents } from "./preload-workspace-file";
15​
16export function ThreadFileNavigatorPane(props: {
17 readonly cwd: string;
18 readonly environmentId: EnvironmentId;
19 readonly projectName: string;
20 readonly selectedPath: string | null;
21 readonly onSelectFile: (path: string) => void;
22}) {
23 const workspaceKey = JSON.stringify([props.environmentId, props.cwd]);
24 // The pane stays mounted across thread switches; a query only filters the workspace it was typed in.
25 const [search, setSearch] = useState({ workspaceKey, query: "" });
26 const searchQuery = search.workspaceKey === workspaceKey ? search.query : "";
27 const setSearchQuery = (query: string) => setSearch({ workspaceKey, query });
28 const { themeAppearance: highlightTheme } = useAppearancePreferences();
29 const fileAccessSession = useEnvironmentQuery(
30 environmentSession.sessionStateAtom(props.environmentId),
31 );
32 const fileEnvironment = useEnvironmentPresentation(props.environmentId);
33 const fileAccess = resolveFilesystemReadAccess({
34 isCatalogReady: fileEnvironment.isReady,
35 connection: fileEnvironment.presentation?.connection ?? null,
36 session: fileAccessSession.data,
37 sessionError: fileAccessSession.error,
38 });
39 const { canReadFiles } = fileAccess;
40 const entriesQuery = useFileTreeEntries({
41 environmentId: props.environmentId,
42 cwd: canReadFiles ? props.cwd : null,
43 searchQuery,
44 });
45 const handlePreviewFile = (relativePath: string) => {
46 preloadWorkspaceFileContents({
47 cwd: props.cwd,
48 environmentId: props.environmentId,
49 relativePath,
50 theme: highlightTheme,
51 });
52 };
53 const fileTree = (
54 <FileTreeBrowser
55 key={workspaceKey}
56 revealSelection={entriesQuery.revealSelection}
57 expandedPaths={entriesQuery.expandedPaths}
58 setExpandedPaths={entriesQuery.setExpandedPaths}
59 entries={entriesQuery.entries}
60 loadingDirectories={entriesQuery.loadingDirectories}
61 onLoadDirectory={entriesQuery.loadDirectory}
62 error={
63 canReadFiles
64 ? entriesQuery.error
65 : fileAccess.isPending
66 ? null
67 : (fileAccess.error ?? "This connection cannot read host files.")
68 }
69 isPending={fileAccess.isPending || entriesQuery.isPending}
70 isRefreshing={entriesQuery.isRefreshing}
71 searchQuery={searchQuery}
72 searchResultPaths={entriesQuery.searchResultPaths}
73 searchTruncated={entriesQuery.searchTruncated}
74 selectedPath={props.selectedPath ?? entriesQuery.selectedPath}
75 onPreviewFile={handlePreviewFile}
76 onRefresh={entriesQuery.refresh}
77 onSelectFile={(path) => {
78 entriesQuery.selectPath(path);
79 props.onSelectFile(path);
80 }}
81 />
82 );
83​
84 return (
85 <SheetPane>
86 <AppBar
87 title="Files"
88 subtitle={props.projectName}
89 search={{
90 value: searchQuery,
91 onChangeText: setSearchQuery,
92 placeholder: "Search files",
93 }}
94 refresh={{ accessibilityLabel: "Refresh files", onPress: entriesQuery.refresh }}
95 />
96 <ScreenContent insetHorizontal>{fileTree}</ScreenContent>
97 </SheetPane>
98 );