fix: enable folder navigation in workspace file browser
FilesTab in WorkspaceDetailPage was returning early for directories with no action, making folders unclickable. Changes: - use-workspace-files.ts: add currentPath state and navigateTo() function; refresh() now passes currentPath to listWorkspaceFiles API - WorkspaceDetailPage.tsx FilesTab: handleSelect now calls navigateTo() for directories; added navigateUp() button using '..' when not at root - Clear selected file/editor state when changing directories Quality gates: tsc --noEmit pass, npm run build pass, 82/82 tests pass
This commit is contained in:
@@ -11,11 +11,13 @@ import {
|
|||||||
export interface UseWorkspaceFilesResult {
|
export interface UseWorkspaceFilesResult {
|
||||||
entries: FileEntry[];
|
entries: FileEntry[];
|
||||||
content: string | null;
|
content: string | null;
|
||||||
|
currentPath: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
refresh: () => Promise<void>;
|
refresh: () => Promise<void>;
|
||||||
loadFile: (path: string) => Promise<void>;
|
loadFile: (path: string) => Promise<void>;
|
||||||
saveFile: (path: string, content: string, message?: string) => Promise<void>;
|
saveFile: (path: string, content: string, message?: string) => Promise<void>;
|
||||||
|
navigateTo: (path: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWorkspaceFiles(
|
export function useWorkspaceFiles(
|
||||||
@@ -23,6 +25,7 @@ export function useWorkspaceFiles(
|
|||||||
): UseWorkspaceFilesResult {
|
): UseWorkspaceFilesResult {
|
||||||
const [entries, setEntries] = useState<FileEntry[]>([]);
|
const [entries, setEntries] = useState<FileEntry[]>([]);
|
||||||
const [content, setContent] = useState<string | null>(null);
|
const [content, setContent] = useState<string | null>(null);
|
||||||
|
const [currentPath, setCurrentPath] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -30,14 +33,19 @@ export function useWorkspaceFiles(
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const data = await listWorkspaceFiles(workspaceId);
|
const data = await listWorkspaceFiles(workspaceId, currentPath);
|
||||||
setEntries(data);
|
setEntries(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to load files");
|
setError(err instanceof Error ? err.message : "Failed to load files");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [workspaceId]);
|
}, [workspaceId, currentPath]);
|
||||||
|
|
||||||
|
const navigateTo = useCallback((path: string) => {
|
||||||
|
setCurrentPath(path);
|
||||||
|
setContent(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadFile = useCallback(
|
const loadFile = useCallback(
|
||||||
async (path: string) => {
|
async (path: string) => {
|
||||||
@@ -64,5 +72,5 @@ export function useWorkspaceFiles(
|
|||||||
refresh();
|
refresh();
|
||||||
}, [refresh]);
|
}, [refresh]);
|
||||||
|
|
||||||
return { entries, content, loading, error, refresh, loadFile, saveFile };
|
return { entries, content, currentPath, loading, error, refresh, loadFile, saveFile, navigateTo };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ function MobileTabBar({
|
|||||||
/* ─── Files Tab ─── */
|
/* ─── Files Tab ─── */
|
||||||
|
|
||||||
function FilesTab({ workspaceId }: { workspaceId: string }) {
|
function FilesTab({ workspaceId }: { workspaceId: string }) {
|
||||||
const { entries, content, loadFile, saveFile, loading, error } =
|
const { entries, content, currentPath, loadFile, saveFile, loading, error, navigateTo } =
|
||||||
useWorkspaceFiles(workspaceId);
|
useWorkspaceFiles(workspaceId);
|
||||||
const { status, commit, push, pull, fetch } = useWorkspaceGit(workspaceId);
|
const { status, commit, push, pull, fetch } = useWorkspaceGit(workspaceId);
|
||||||
const [selectedPath, setSelectedPath] = useState<string | null>(null);
|
const [selectedPath, setSelectedPath] = useState<string | null>(null);
|
||||||
@@ -159,13 +159,28 @@ function FilesTab({ workspaceId }: { workspaceId: string }) {
|
|||||||
const [commitMessage, setCommitMessage] = useState("");
|
const [commitMessage, setCommitMessage] = useState("");
|
||||||
|
|
||||||
const handleSelect = (entry: FileEntry) => {
|
const handleSelect = (entry: FileEntry) => {
|
||||||
if (entry.type === "directory") return;
|
if (entry.type === "directory") {
|
||||||
|
setSelectedPath(null);
|
||||||
|
setIsEditing(false);
|
||||||
|
setEditContent(null);
|
||||||
|
navigateTo(entry.path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setSelectedPath(entry.path);
|
setSelectedPath(entry.path);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
setEditContent(null);
|
setEditContent(null);
|
||||||
loadFile(entry.path);
|
loadFile(entry.path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const navigateUp = () => {
|
||||||
|
if (!currentPath) return;
|
||||||
|
const parentPath = currentPath.split("/").slice(0, -1).join("/");
|
||||||
|
navigateTo(parentPath);
|
||||||
|
setSelectedPath(null);
|
||||||
|
setIsEditing(false);
|
||||||
|
setEditContent(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
if (content !== null) {
|
if (content !== null) {
|
||||||
setEditContent(content);
|
setEditContent(content);
|
||||||
@@ -224,6 +239,15 @@ function FilesTab({ workspaceId }: { workspaceId: string }) {
|
|||||||
)}
|
)}
|
||||||
<div className="files-split">
|
<div className="files-split">
|
||||||
<div className="file-tree">
|
<div className="file-tree">
|
||||||
|
{currentPath && (
|
||||||
|
<button
|
||||||
|
className="tree-entry tree-up"
|
||||||
|
onClick={navigateUp}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Icon name="folder" size="sm" /> ..
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{loading && <p className="muted">Loading...</p>}
|
{loading && <p className="muted">Loading...</p>}
|
||||||
{error && <p className="error-text">{error}</p>}
|
{error && <p className="error-text">{error}</p>}
|
||||||
{entries.map((entry) => (
|
{entries.map((entry) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user