import { useState, useEffect, useRef, useCallback } from "react"; import { open } from "@tauri-apps/plugin-dialog"; import { useProjects } from "../../hooks/useProjects"; import type { ProjectPath } from "../../lib/types"; interface Props { onClose: () => void; } interface PathEntry { host_path: string; mount_name: string; } function basenameFromPath(p: string): string { return p.replace(/[/\\]$/, "").split(/[/\\]/).pop() || ""; } export default function AddProjectDialog({ onClose }: Props) { const { add } = useProjects(); const [name, setName] = useState(""); const [pathEntries, setPathEntries] = useState([ { host_path: "", mount_name: "" }, ]); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const nameInputRef = useRef(null); const overlayRef = useRef(null); useEffect(() => { nameInputRef.current?.focus(); }, []); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [onClose]); const handleOverlayClick = useCallback( (e: React.MouseEvent) => { if (e.target === overlayRef.current) onClose(); }, [onClose], ); const handleBrowse = async (index: number) => { const selected = await open({ directory: true, multiple: false }); if (typeof selected === "string") { const basename = basenameFromPath(selected); const entries = [...pathEntries]; entries[index] = { host_path: selected, mount_name: entries[index].mount_name || basename, }; setPathEntries(entries); // Auto-fill project name from first folder if (!name && index === 0) { setName(basename); } } }; const updateEntry = ( index: number, field: keyof PathEntry, value: string, ) => { const entries = [...pathEntries]; entries[index] = { ...entries[index], [field]: value }; setPathEntries(entries); }; const removeEntry = (index: number) => { setPathEntries(pathEntries.filter((_, i) => i !== index)); }; const addEntry = () => { setPathEntries([...pathEntries, { host_path: "", mount_name: "" }]); }; const handleSubmit = async (e?: React.FormEvent) => { if (e) e.preventDefault(); if (!name.trim()) { setError("Project name is required"); return; } const validPaths: ProjectPath[] = pathEntries .filter((p) => p.host_path.trim()) .map((p) => ({ host_path: p.host_path.trim(), mount_name: p.mount_name.trim() || basenameFromPath(p.host_path), })); if (validPaths.length === 0) { setError("At least one folder path is required"); return; } const mountNames = validPaths.map((p) => p.mount_name); if (new Set(mountNames).size !== mountNames.length) { setError("Mount names must be unique"); return; } setLoading(true); setError(null); try { await add(name.trim(), validPaths); onClose(); } catch (err) { setError(String(err)); } finally { setLoading(false); } }; return (

Add Project

setName(e.target.value)} placeholder="my-project" className="w-full px-3 py-2 mb-3 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-sm text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)]" />
{pathEntries.map((entry, i) => (
updateEntry(i, "host_path", e.target.value)} placeholder="/path/to/folder" className="flex-1 px-2 py-1.5 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)]" /> {pathEntries.length > 1 && ( )}
/workspace/ updateEntry(i, "mount_name", e.target.value)} placeholder="mount-name" className="flex-1 px-2 py-1 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)] font-mono" />
))}
{error && (
{error}
)}
); }