From a88f92e786eb800f7d2909de49c015e716f4d4f8 Mon Sep 17 00:00:00 2001 From: Daniel Arroyo Date: Mon, 6 Jul 2026 09:42:50 -0400 Subject: [PATCH] feat: add direct path input bar to Files page and FileBrowserModal --- web/src/components/FileBrowserModal.tsx | 52 ++++++++++++++++++------- web/src/pages/Files.tsx | 51 ++++++++++++++++++------ 2 files changed, 78 insertions(+), 25 deletions(-) diff --git a/web/src/components/FileBrowserModal.tsx b/web/src/components/FileBrowserModal.tsx index 1b9771d..fae7cd1 100644 --- a/web/src/components/FileBrowserModal.tsx +++ b/web/src/components/FileBrowserModal.tsx @@ -41,6 +41,8 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro const [mkdirName, setMkdirName] = useState(""); const [renameState, setRenameState] = useState<{ path: string; name: string } | null>(null); const [selectedPath, setSelectedPath] = useState(null); + const [pathInput, setPathInput] = useState(currentPath); + const [editingPath, setEditingPath] = useState(false); const fileInputRef = useRef(null); const LIMIT = 200; @@ -58,6 +60,10 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro loadDir(currentPath, 1); }, [currentPath]); + useEffect(() => { + if (!editingPath) setPathInput(currentPath); + }, [currentPath, editingPath]); + async function loadDir(path: string, pageNum: number) { setLoading(true); setError(null); @@ -88,6 +94,21 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro setSelectedPath(null); } + async function handleNavigateToPath(e: React.FormEvent) { + e.preventDefault(); + const path = pathInput.trim() || "/"; + try { + const info = await api.fileInfo(path); + if (!info.is_dir) { + setError("La ruta no es un directorio"); + return; + } + navigateTo(path); + } catch (err) { + setError(err instanceof Error ? err.message : "Ruta no accesible"); + } + } + function handleDoubleClick(entry: FileEntry) { if (entry.is_dir) { navigateTo(entry.path); @@ -153,11 +174,6 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro return "/" + parts.join("/"); })(); - const breadcrumbs = currentPath.split("/").filter(Boolean).map((part, i, arr) => { - const path = "/" + arr.slice(0, i + 1).join("/"); - return { part, path }; - }); - return (
@@ -177,14 +193,24 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro ))} )} -
- - {breadcrumbs.map((b, i) => ( - - - {i < breadcrumbs.length - 1 && /} - - ))} +
+ +
+ setEditingPath(true)} + onBlur={() => { setEditingPath(false); setPathInput(currentPath); }} + onChange={e => setPathInput(e.target.value)} + onKeyDown={e => { + if (e.key === "Escape") { + setPathInput(currentPath); + (e.target as HTMLInputElement).blur(); + } + }} + /> +
diff --git a/web/src/pages/Files.tsx b/web/src/pages/Files.tsx index 50bf550..7eff375 100644 --- a/web/src/pages/Files.tsx +++ b/web/src/pages/Files.tsx @@ -44,6 +44,9 @@ export default function Files() { const [searchResults, setSearchResults] = useState([]); const [searching, setSearching] = useState(false); + const [pathInput, setPathInput] = useState(currentPath); + const [editingPath, setEditingPath] = useState(false); + const [showMkdir, setShowMkdir] = useState(false); const [mkdirName, setMkdirName] = useState(""); @@ -86,6 +89,10 @@ export default function Files() { setSearchQuery(""); }, [currentPath]); + useEffect(() => { + if (!editingPath) setPathInput(currentPath); + }, [currentPath, editingPath]); + async function loadDir(path: string, pageNum: number) { setLoading(true); setError(null); @@ -118,6 +125,21 @@ export default function Files() { setCurrentPath(path); } + async function handleNavigateToPath(e: React.FormEvent) { + e.preventDefault(); + const path = pathInput.trim() || "/"; + try { + const info = await api.fileInfo(path); + if (!info.is_dir) { + setError("La ruta no es un directorio"); + return; + } + navigateTo(path); + } catch (err) { + setError(err instanceof Error ? err.message : "Ruta no accesible"); + } + } + function handleDoubleClick(entry: FileEntry) { if (entry.is_dir) { navigateTo(entry.path); @@ -148,11 +170,6 @@ export default function Files() { return parent === "/" ? "/" : parent; } - const breadcrumbs = currentPath.split("/").filter(Boolean).map((part, i, arr) => { - const path = "/" + arr.slice(0, i + 1).join("/"); - return { part, path }; - }); - async function handleMkdir() { if (!mkdirName.trim()) return; try { @@ -305,18 +322,28 @@ export default function Files() { Sin restricción — acceso a todo el FS )} -
+
- {breadcrumbs.map((b, i) => ( - - - {i < breadcrumbs.length - 1 && /} - - ))} +
+ setEditingPath(true)} + onBlur={() => { setEditingPath(false); setPathInput(currentPath); }} + onChange={e => setPathInput(e.target.value)} + onKeyDown={e => { + if (e.key === "Escape") { + setPathInput(currentPath); + (e.target as HTMLInputElement).blur(); + } + }} + /> +