feat(KB-175): move file browser to header with workspace support
- Add workspace file APIs and hooks for session-scoped file browsing - Create WorkspaceSelector component for switching between workspaces - Update FileBrowserModal with workspace mode and improved UX - Add files button to header with workspace-aware file browsing - Add session files indicator to task cards for quick file access - Include comprehensive tests for all new components and hooks
This commit is contained in:
92
packages/dashboard/app/hooks/useWorkspaceFileBrowser.ts
Normal file
92
packages/dashboard/app/hooks/useWorkspaceFileBrowser.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import type { FileNode, FileListResponse } from "../api";
|
||||
import { fetchWorkspaceFileList } from "../api";
|
||||
|
||||
interface UseWorkspaceFileBrowserReturn {
|
||||
entries: FileNode[];
|
||||
currentPath: string;
|
||||
setPath: (path: string) => void;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for browsing files in a selected workspace.
|
||||
*
|
||||
* @param workspace - The workspace identifier ("project" or task ID)
|
||||
* @param enabled - Whether fetching is enabled
|
||||
*/
|
||||
export function useWorkspaceFileBrowser(
|
||||
workspace: string,
|
||||
enabled: boolean,
|
||||
): UseWorkspaceFileBrowserReturn {
|
||||
const [entries, setEntries] = useState<FileNode[]>([]);
|
||||
const [currentPath, setCurrentPath] = useState<string>(".");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
setRefreshKey((key) => key + 1);
|
||||
}, []);
|
||||
|
||||
const setPath = useCallback((path: string) => {
|
||||
setCurrentPath(path);
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPath(".");
|
||||
setError(null);
|
||||
setEntries([]);
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
async function loadFiles() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response: FileListResponse = await fetchWorkspaceFileList(
|
||||
workspace,
|
||||
currentPath === "." ? undefined : currentPath,
|
||||
);
|
||||
|
||||
if (!cancelled) {
|
||||
setEntries(response.entries);
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (!cancelled) {
|
||||
setError(err.message || "Failed to load files");
|
||||
setEntries([]);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadFiles();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [workspace, currentPath, enabled, refreshKey]);
|
||||
|
||||
return {
|
||||
entries,
|
||||
currentPath,
|
||||
setPath,
|
||||
loading,
|
||||
error,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user