Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,43 @@ interface FileTreeState {
loadingDirectories: Set<string>;
}

interface LoadDirectoryOptions {
force?: boolean;
}

function applyDirectoryEntries(
current: FileTreeState,
absolutePath: string,
entries: FsEntry[],
): FileTreeState {
const nextEntries = new Map(current.entriesByPath);
const nextChildren = new Map(current.childPathsByDirectory);
const nextLoaded = new Set(current.loadedDirectories);
const nextInvalidated = new Set(current.invalidatedDirectories);
const nextLoading = new Set(current.loadingDirectories);
nextLoading.delete(absolutePath);
nextLoaded.add(absolutePath);
nextInvalidated.delete(absolutePath);

for (const entry of entries) {
nextEntries.set(entry.absolutePath, entry);
}

nextChildren.set(
absolutePath,
entries.map((entry) => entry.absolutePath),
);

return {
...current,
childPathsByDirectory: nextChildren,
entriesByPath: nextEntries,
invalidatedDirectories: nextInvalidated,
loadedDirectories: nextLoaded,
loadingDirectories: nextLoading,
};
}

function createInitialState(): FileTreeState {
return {
childPathsByDirectory: new Map<string, string[]>(),
Expand Down Expand Up @@ -187,16 +224,15 @@ export function useFileTree({
);

const loadDirectory = useCallback(
async (absolutePath: string, force = false): Promise<void> => {
if (!workspaceId || !absolutePath) {
return;
}
async (
absolutePath: string,
options: LoadDirectoryOptions = {},
): Promise<void> => {
const { force = false } = options;
if (!workspaceId || !absolutePath) return;

const currentState = stateRef.current;
if (currentState.loadingDirectories.has(absolutePath)) {
return;
}

if (currentState.loadingDirectories.has(absolutePath)) return;
if (
!force &&
currentState.loadedDirectories.has(absolutePath) &&
Expand All @@ -205,65 +241,38 @@ export function useFileTree({
return;
}

updateState((current) => {
const nextLoading = new Set(current.loadingDirectories);
nextLoading.add(absolutePath);
return {
...current,
loadingDirectories: nextLoading,
};
});
const input = { workspaceId, absolutePath };
const cachedResult = utils.filesystem.listDirectory.getData(input);
if (cachedResult) {
updateState((current) =>
applyDirectoryEntries(current, absolutePath, cachedResult.entries),
);
if (!force) return;
}

try {
const result = await utils.filesystem.listDirectory.fetch({
workspaceId,
updateState((current) => ({
...current,
loadingDirectories: new Set(current.loadingDirectories).add(
absolutePath,
});

updateState((current) => {
const nextEntries = new Map(current.entriesByPath);
const nextChildren = new Map(current.childPathsByDirectory);
const nextLoaded = new Set(current.loadedDirectories);
const nextInvalidated = new Set(current.invalidatedDirectories);
const nextLoading = new Set(current.loadingDirectories);
nextLoading.delete(absolutePath);
nextLoaded.add(absolutePath);
nextInvalidated.delete(absolutePath);

for (const entry of result.entries) {
nextEntries.set(entry.absolutePath, entry);
}

nextChildren.set(
absolutePath,
result.entries.map((entry) => entry.absolutePath),
);
),
}));

return {
...current,
childPathsByDirectory: nextChildren,
entriesByPath: nextEntries,
invalidatedDirectories: nextInvalidated,
loadedDirectories: nextLoaded,
loadingDirectories: nextLoading,
};
});
try {
// Server-side timeout + React Query's TIMEOUT-aware retry handle
// hung host-service IPC; we just await the fetch and apply results.
const result = await utils.filesystem.listDirectory.fetch(input);
updateState((current) =>
applyDirectoryEntries(current, absolutePath, result.entries),
);
} catch (error) {
console.error(
"[workspace-client/useFileTree] Failed to load directory:",
{
absolutePath,
error,
},
{ absolutePath, error },
);

updateState((current) => {
const nextLoading = new Set(current.loadingDirectories);
nextLoading.delete(absolutePath);
return {
...current,
loadingDirectories: nextLoading,
};
return { ...current, loadingDirectories: nextLoading };
});
}
},
Expand All @@ -272,7 +281,7 @@ export function useFileTree({

const refreshPath = useCallback(
async (absolutePath: string): Promise<void> => {
await loadDirectory(absolutePath, true);
await loadDirectory(absolutePath, { force: true });
},
[loadDirectory],
);
Expand All @@ -288,10 +297,10 @@ export function useFileTree({
(left, right) => left.split(/[/\\]/).length - right.split(/[/\\]/).length,
);

await loadDirectory(rootPath, true);
await loadDirectory(rootPath, { force: true });
for (const absolutePath of expandedDirectories) {
if (absolutePath !== rootPath) {
await loadDirectory(absolutePath, true);
await loadDirectory(absolutePath, { force: true });
}
}
}, [loadDirectory, rootPath]);
Expand Down Expand Up @@ -347,11 +356,8 @@ export function useFileTree({

useEffect(() => {
updateState(() => createInitialState());
if (!rootPath) {
return;
}

void loadDirectory(rootPath, true);
if (!rootPath) return;
void loadDirectory(rootPath, { force: true });
}, [loadDirectory, rootPath, updateState]);

useWorkspaceEvent(
Expand Down Expand Up @@ -404,16 +410,16 @@ export function useFileTree({
});

if (stateRef.current.loadedDirectories.has(oldParentPath)) {
void loadDirectory(oldParentPath, true);
void loadDirectory(oldParentPath, { force: true });
}
if (stateRef.current.loadedDirectories.has(newParentPath)) {
void loadDirectory(newParentPath, true);
void loadDirectory(newParentPath, { force: true });
}
if (
event.isDirectory &&
stateRef.current.expandedDirectories.has(event.absolutePath)
) {
void loadDirectory(event.absolutePath, true);
void loadDirectory(event.absolutePath, { force: true });
}
return;
}
Expand All @@ -438,7 +444,7 @@ export function useFileTree({
});

if (stateRef.current.loadedDirectories.has(parentPath)) {
void loadDirectory(parentPath, true);
void loadDirectory(parentPath, { force: true });
}
},
Boolean(workspaceId && rootPath),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ interface WorkspaceSidebarProps {
selectedFilePath?: string;
pendingReveal?: PendingReveal | null;
workspaceId: string;
workspaceName?: string;
}

function IconButton({
Expand Down Expand Up @@ -80,7 +79,6 @@ export function WorkspaceSidebar({
selectedFilePath,
pendingReveal,
workspaceId,
workspaceName,
}: WorkspaceSidebarProps) {
const collections = useCollections();
const localState = collections.v2WorkspaceLocalState.get(workspaceId);
Expand Down Expand Up @@ -142,7 +140,6 @@ export function WorkspaceSidebar({
selectedFilePath={selectedFilePath}
pendingReveal={pendingReveal}
workspaceId={workspaceId}
workspaceName={workspaceName}
gitStatus={gitStatus.data}
/>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { toast } from "@superset/ui/sonner";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { workspaceTrpc } from "@superset/workspace-client";
import type { inferRouterOutputs } from "@trpc/server";
import { FilePlus, FolderPlus, FoldVertical, RefreshCw } from "lucide-react";
import {
FilePlus,
FolderPlus,
FoldVertical,
Loader2,
RefreshCw,
} from "lucide-react";
import { Fragment, useCallback, useEffect, useRef, useState } from "react";
import {
type FileTreeNode,
Expand Down Expand Up @@ -39,7 +45,6 @@ interface FilesTabProps {
isDirectory: boolean;
} | null;
workspaceId: string;
workspaceName?: string;
gitStatus: GitStatusData | undefined;
}

Expand Down Expand Up @@ -209,7 +214,6 @@ export function FilesTab({
selectedFilePath,
pendingReveal,
workspaceId,
workspaceName,
gitStatus,
}: FilesTabProps) {
const [_isRefreshing, setIsRefreshing] = useState(false);
Expand Down Expand Up @@ -462,10 +466,17 @@ export function FilesTab({
[workspaceId, deletePath],
);

if (!workspaceQuery.data?.worktreePath) {
if (!rootPath) {
return (
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
Workspace worktree not available
<div className="flex h-full items-center justify-center gap-2 text-sm text-muted-foreground">
{workspaceQuery.isLoading ? (
<>
<Loader2 className="size-3.5 animate-spin" />
<span>Loading files...</span>
</>
) : (
"Workspace worktree not available"
)}
</div>
);
}
Expand Down Expand Up @@ -503,7 +514,7 @@ export function FilesTab({
zIndex: 20,
}}
>
<span className="truncate">{workspaceName ?? "Explorer"}</span>
<span className="truncate">Explorer</span>
<div className="flex items-center gap-0.5">
<Tooltip>
<TooltipTrigger asChild>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ function V2WorkspacePage() {
<WorkspaceContent
projectId={workspace.projectId}
workspaceId={workspace.id}
workspaceName={workspace.name}
terminalId={terminalId}
chatSessionId={chatSessionId}
focusRequestId={focusRequestId}
Expand All @@ -116,7 +115,6 @@ function V2WorkspacePage() {
function WorkspaceContent({
projectId,
workspaceId,
workspaceName,
terminalId,
chatSessionId,
focusRequestId,
Expand All @@ -126,7 +124,6 @@ function WorkspaceContent({
}: {
projectId: string;
workspaceId: string;
workspaceName: string;
terminalId?: string;
chatSessionId?: string;
focusRequestId?: string;
Expand Down Expand Up @@ -295,7 +292,6 @@ function WorkspaceContent({
>
<WorkspaceSidebar
workspaceId={workspaceId}
workspaceName={workspaceName}
onSelectFile={openFilePane}
onSelectDiffFile={openDiffPane}
onOpenComment={openCommentPane}
Expand Down
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading