diff --git a/ui/client-app/src/components/FileSystemNavigator.js b/ui/client-app/src/components/FileSystemNavigator.js index c01bf20..8677aa0 100644 --- a/ui/client-app/src/components/FileSystemNavigator.js +++ b/ui/client-app/src/components/FileSystemNavigator.js @@ -59,18 +59,23 @@ const toggleFolder = async (path) => { const isExpanded = expanded[path]; if (!isExpanded) { - // Loading child depth... - try { - const children = await fetchLevel(path); - setExpanded(prev => ({ ...prev, [path]: true })); - // Merge children into tree (prevent duplicates) - setTree(prev => { - const existingPaths = new Set(prev.map(f => f.path)); - const newOnes = children.filter(c => !existingPaths.has(c.path)); - return [...prev, ...newOnes]; - }); - } catch (err) { - setError(`Failed to open folder: ${err.message}`); + setExpanded(prev => ({ ...prev, [path]: true })); + // Only fetch if we don't already have children for this path in our flat tree + // A child would have a path like "path/filename" or "/path/filename" + const prefix = path === "/" ? "/" : (path.endsWith("/") ? path : path + "/"); + const hasChildren = tree.some(node => node.path.startsWith(prefix) && node.path !== path); + + if (!hasChildren) { + try { + const children = await fetchLevel(path === "/" ? "." : path); + setTree(prev => { + const existingPaths = new Set(prev.map(f => f.path)); + const newOnes = children.filter(c => !existingPaths.has(c.path)); + return [...prev, ...newOnes]; + }); + } catch (err) { + setError(`Failed to open folder: ${err.message}`); + } } } else { setExpanded(prev => ({ ...prev, [path]: false })); @@ -148,20 +153,22 @@ // Helper to render tree recursively // Helper to render tree recursively const renderSubTree = (currentPath, depth = 0) => { - // Find children of this path + // Normalize currentPath for filtering const children = tree.filter(node => { - if (currentPath === initialPath || currentPath === "." || currentPath === "") { - const prefix = initialPath === "." || initialPath === "" ? "" : (initialPath.endsWith("/") ? initialPath : initialPath + "/"); - if (!node.path.startsWith(prefix)) return false; - if (node.path === initialPath) return false; - const sub = node.path.slice(prefix.length); - return !sub.includes("/"); - } else { - const prefix = currentPath.endsWith("/") ? currentPath : currentPath + "/"; - if (!node.path.startsWith(prefix)) return false; - const sub = node.path.slice(prefix.length); - return !sub.includes("/"); + // Special handling for root "/" + if (currentPath === "/" || currentPath === initialPath) { + // If it's a top-level node, it shouldn't contain more slashes than the root path + const rel = node.path.startsWith("/") ? node.path.slice(1) : node.path; + return !rel.includes("/"); } + + // For nested folders, a child is anything that starts with "currentPath/" and has no further slashes + const prefix = currentPath.endsWith("/") ? currentPath : currentPath + "/"; + if (!node.path.startsWith(prefix)) return false; + if (node.path === currentPath) return false; + + const sub = node.path.slice(prefix.length); + return !sub.includes("/"); }); // Sort: Folders first, then Alphabetical