diff --git a/client/src/App.tsx b/client/src/App.tsx index 1aee99c..60d2110 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -436,6 +436,12 @@ function App() { onBackToBrowser={handleBackToBrowser} onOpenGitDiff={openGitDiffPage} onOpenFile={openFilePage} + onNavigateToDirectory={(path) => { + window.history.pushState({ page: "list" }, "", "/"); + setRoute({ page: "list" }); + resetFileViewer(); + setCurrentDirectory(path); + }} /> ) : isGitDiffRoute ? ( diff --git a/client/src/components/GitStatus.tsx b/client/src/components/GitStatus.tsx index 848b899..707e3dc 100644 --- a/client/src/components/GitStatus.tsx +++ b/client/src/components/GitStatus.tsx @@ -6,18 +6,21 @@ import type { StageAllResponse, } from "@shared/git"; import type { ErrorResponse } from "@shared/http"; +import type { FileListItem } from "@shared/files"; import Toolbar from "@/components/Toolbar"; type GitStatusProps = { onBackToBrowser: () => void; onOpenGitDiff: () => void; onOpenFile: (path: string) => void; + onNavigateToDirectory: (path: string) => void; }; const GitStatus = ({ onBackToBrowser, onOpenGitDiff, onOpenFile, + onNavigateToDirectory, }: GitStatusProps) => { const [status, setStatus] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -157,9 +160,21 @@ const GitStatus = ({ } }; - const renderFileList = (files: string[], title: string, color: string) => { + const renderFileList = ( + files: FileListItem[], + title: string, + color: string, + ) => { if (files.length === 0) return null; + const handleItemClick = (item: FileListItem) => { + if (item.kind === "directory") { + onNavigateToDirectory(item.path); + } else { + onOpenFile(item.path); + } + }; + return (

    - {files.map((file, index) => ( + {files.map((item, index) => (
  • ))} @@ -265,16 +280,20 @@ const GitStatus = ({ Unstaged ({status.unstaged.length})

    - {status.unstaged.map((file, index) => ( + {status.unstaged.map((item, index) => (
  • ))} diff --git a/server/services/gitService.ts b/server/services/gitService.ts index fb2ff11..87612a8 100644 --- a/server/services/gitService.ts +++ b/server/services/gitService.ts @@ -1,10 +1,36 @@ import type { GitStatusResponse, GitDiffResponse } from "../../shared/git"; +import type { FileListItem } from "../../shared/files"; import { exec } from "child_process"; import { promisify } from "util"; import { resolveFromRoot } from "../utils/paths"; +import { stat } from "fs/promises"; +import { join } from "path"; const execAsync = promisify(exec); +/** + * Helper function to convert a file path to a FileListItem by checking if it's a file or directory + */ +const pathToFileListItem = async ( + filePath: string, + projectRoot: string, +): Promise => { + try { + const fullPath = join(projectRoot, filePath); + const stats = await stat(fullPath); + return { + path: filePath, + kind: stats.isDirectory() ? "directory" : "file", + }; + } catch { + // If we can't stat the file, assume it's a file + return { + path: filePath, + kind: "file", + }; + } +}; + /** * Gets the current git status including branch, ahead/behind, and file statuses */ @@ -39,9 +65,9 @@ export const getGitStatus = async (): Promise => { cwd: projectRoot, }); - const staged: string[] = []; - const unstaged: string[] = []; - const untracked: string[] = []; + const stagedPaths: string[] = []; + const unstagedPaths: string[] = []; + const untrackedPaths: string[] = []; const lines = statusOutput.split("\n").filter((line) => line.length > 0); for (const line of lines) { @@ -53,17 +79,28 @@ export const getGitStatus = async (): Promise => { const unstagedStatus = status[1]; if (stagedStatus === "?" && unstagedStatus === "?") { - untracked.push(filePath); + untrackedPaths.push(filePath); } else { if (stagedStatus !== " " && stagedStatus !== "?") { - staged.push(filePath); + stagedPaths.push(filePath); } if (unstagedStatus !== " " && unstagedStatus !== "?") { - unstaged.push(filePath); + unstagedPaths.push(filePath); } } } + // Convert paths to FileListItems with kind information + const staged = await Promise.all( + stagedPaths.map((path) => pathToFileListItem(path, projectRoot)), + ); + const unstaged = await Promise.all( + unstagedPaths.map((path) => pathToFileListItem(path, projectRoot)), + ); + const untracked = await Promise.all( + untrackedPaths.map((path) => pathToFileListItem(path, projectRoot)), + ); + return { branch, ahead, diff --git a/shared/git.ts b/shared/git.ts index 6b53116..6eb1ac0 100644 --- a/shared/git.ts +++ b/shared/git.ts @@ -1,12 +1,13 @@ import type { SuccessResponse, TextRequest } from "./http"; +import type { FileListItem } from "./files"; export interface GitStatusResponse { branch: string; ahead: number; behind: number; - staged: string[]; - unstaged: string[]; - untracked: string[]; + staged: FileListItem[]; + unstaged: FileListItem[]; + untracked: FileListItem[]; } export interface GitDiffResponse {