"use client"; import { FormEvent, useCallback, useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { useI18n } from "@/hooks/useI18n"; interface DirectoryEntry { name: string; path: string; } interface BrowseResponse { path?: string; parentPath?: string | null; directories?: DirectoryEntry[]; drives?: DirectoryEntry[]; error?: string; } async function loadDirectories(directory?: string): Promise { const query = directory ? `?path=${encodeURIComponent(directory)}` : ""; const response = await fetch(`/api/cwd/browse${query}`); const data = await response.json() as BrowseResponse; if (!response.ok || data.error) throw new Error(data.error ?? `HTTP ${response.status}`); return data; } function FolderIcon() { return ( ); } function DriveIcon() { return ( ); } function isWindowsDriveRoot(directory: string): boolean { return /^[a-zA-Z]:[\\/]?$/.test(directory); } interface Props { onCancel: () => void; onSelect: (path: string) => void; busy?: boolean; error?: string | null; } export function DirectoryPicker({ onCancel, onSelect, busy = false, error }: Props) { const { t } = useI18n(); const [portalTarget, setPortalTarget] = useState(null); const [currentPath, setCurrentPath] = useState(""); const [parentDirectory, setParentDirectory] = useState(null); const [pathInput, setPathInput] = useState(""); const [directories, setDirectories] = useState([]); const [drives, setDrives] = useState(null); const [loadError, setLoadError] = useState(null); const [loading, setLoading] = useState(true); const navigateTo = useCallback(async (directory?: string) => { setLoading(true); setLoadError(null); try { const data = await loadDirectories(directory); const nextPath = data.path ?? directory ?? "/"; setCurrentPath(nextPath); setParentDirectory(data.parentPath ?? null); setPathInput(nextPath); setDirectories(data.directories ?? []); setDrives(data.drives ?? null); } catch (cause) { setLoadError(cause instanceof Error ? cause.message : String(cause)); } finally { setLoading(false); } }, []); useEffect(() => { setPortalTarget(document.body); void navigateTo(); }, [navigateTo]); const handlePathSubmit = (event: FormEvent) => { event.preventDefault(); const candidate = pathInput.trim(); if (candidate) void navigateTo(candidate); }; const hasUncommittedPath = pathInput.trim() !== currentPath; const canSelect = Boolean(currentPath) && !hasUncommittedPath && !busy; const canNavigateUp = Boolean(parentDirectory) || isWindowsDriveRoot(currentPath); if (!portalTarget) return null; return createPortal(
{ if (event.target === event.currentTarget && !busy) onCancel(); }} onKeyDown={(event) => { if (event.key === "Escape" && !busy) onCancel(); }} style={{ position: "fixed", inset: 0, zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.35)" }} >
{t("directoryPicker.selectDirectory")}
{ setPathInput(event.target.value); setLoadError(null); }} style={{ minWidth: 0, flex: 1, height: 36, padding: "0 10px", border: "1px solid var(--border)", borderRadius: 6, outline: "none", background: "var(--bg-panel)", color: "var(--text)", fontFamily: "var(--font-mono)", fontSize: 12 }} />
{loading ? (
{t("directoryPicker.loadingDirectories")}
) : drives !== null ? ( <> {drives.length > 0 ? ( drives.map((drive) => ( )) ) : (
{t("directoryPicker.noDrives")}
)} ) : directories.length > 0 ? ( directories.map((entry) => ( )) ) : (
{t("directoryPicker.noSubdirectories")}
)} {(loadError || error) &&
{loadError ?? error}
}
, portalTarget, ); }