From 0f6082061fc85fc2de8df2969b79685a7e67d371 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Thu, 16 Apr 2026 20:25:03 +0200 Subject: [PATCH] feat: add folder navigation to file browser Co-Authored-By: Claude Opus 4.6 --- client/src/components/FileBrowser.svelte | 61 +++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/client/src/components/FileBrowser.svelte b/client/src/components/FileBrowser.svelte index c19bef0..ae22bf8 100644 --- a/client/src/components/FileBrowser.svelte +++ b/client/src/components/FileBrowser.svelte @@ -7,6 +7,7 @@ } from "$lib/stores"; let selectedRoot = $state(""); + let currentFolder = $state(""); onMount(async () => { $roots = await getRoots(); @@ -30,11 +31,44 @@ $hiddenFiles = new Set(hidden); } + // Derive subfolders and files at current folder level + let subfolders = $derived.by(() => { + const prefix = currentFolder ? currentFolder + "/" : ""; + const folderSet = new Set(); + for (const f of $visibleFiles) { + if (!f.path.startsWith(prefix)) continue; + const rest = f.path.slice(prefix.length); + const slashIdx = rest.indexOf("/"); + if (slashIdx !== -1) { + folderSet.add(rest.slice(0, slashIdx)); + } + } + return [...folderSet].sort(); + }); + + let currentFiles = $derived.by(() => { + const prefix = currentFolder ? currentFolder + "/" : ""; + return $visibleFiles.filter(f => { + if (!f.path.startsWith(prefix)) return false; + const rest = f.path.slice(prefix.length); + return !rest.includes("/"); // only direct children + }); + }); + async function selectFile(file: typeof $files[0]) { $currentFile = file; $markers = await getMarkers(file.name, $profile); } + function navigateToFolder(name: string) { + currentFolder = currentFolder ? currentFolder + "/" + name : name; + } + + function navigateUp() { + const idx = currentFolder.lastIndexOf("/"); + currentFolder = idx === -1 ? "" : currentFolder.slice(0, idx); + } + function formatSize(bytes: number): string { if (bytes > 1e9) return (bytes / 1e9).toFixed(1) + " GB"; if (bytes > 1e6) return (bytes / 1e6).toFixed(0) + " MB"; @@ -53,15 +87,24 @@
- { currentFolder = ""; loadFiles(); }}> {#each $roots as root} {/each}
+ {#if currentFolder} + + {/if}
    - {#each $visibleFiles as file} + {#each subfolders as folder} +
  • navigateToFolder(folder)}> + {folder}/ + dir +
  • + {/each} + {#each currentFiles as file}
  • selectFile(file)} @@ -94,6 +137,18 @@ border: 1px solid #444; padding: 2px; } + .breadcrumb { + padding: 3px 8px; + font-size: 11px; + color: #88aaff; + cursor: pointer; + background: #252525; + border-bottom: 1px solid #333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .breadcrumb:hover { background: #2a2a2a; } .file-list { list-style: none; padding: 0; @@ -110,5 +165,7 @@ } .file-list li:hover { background: #333; } .file-list li.selected { background: #0066cc; } + .file-list li.folder { color: #88aaff; } .size { color: #888; font-size: 11px; } + .badge { color: #666; font-size: 10px; }