web(sidebar): auto-expand folder tree ancestors of the active folder
When the timeline navigates to a nested folder (RightSidebar's open-folder icon, URL hydration, back/forward), the LeftSidebar already highlighted the matching row via filters.folderPath — but if the parent folder was collapsed in the persisted openSet, the highlighted row wasn't visible at all. Each FolderTree instance now runs an effect that adds every ancestor of the active path to its openSet on filter change. The root instance expands the top-level ancestor first, which mounts the next-depth FolderTree instance — and the same effect runs there, cascading down to the leaf. Persisted to localStorage so the expansion sticks across reloads. Skipped in `readonly` mode (heap-convert picker has its own selectedPath and shouldn't drive the sidebar state). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { filters } from '$lib/stores/filters.svelte';
|
import { filters } from '$lib/stores/filters.svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import { untrack } from 'svelte';
|
||||||
import { FolderPlus, Pencil, Trash2 } from 'lucide-svelte';
|
import { FolderPlus, Pencil, Trash2 } from 'lucide-svelte';
|
||||||
import Self from './FolderTree.svelte';
|
import Self from './FolderTree.svelte';
|
||||||
import KebabMenu, { Item, Separator } from './KebabMenu.svelte';
|
import KebabMenu, { Item, Separator } from './KebabMenu.svelte';
|
||||||
@@ -105,6 +106,37 @@
|
|||||||
if (selectedPath !== undefined) return selectedPath === path;
|
if (selectedPath !== undefined) return selectedPath === path;
|
||||||
return filters.folderPath === path;
|
return filters.folderPath === path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-expand the ancestor chain of the active folder so the
|
||||||
|
// highlighted row is actually visible after a deep-link navigation
|
||||||
|
// (RightSidebar's open-folder icon, URL hydration, etc.). Each
|
||||||
|
// FolderTree instance only owns the openSet entries for the nodes
|
||||||
|
// rendered at its depth, but since the root instance expands the
|
||||||
|
// top-level ancestor first, the child instance for that subtree is
|
||||||
|
// then mounted and runs the same effect — the cascade naturally
|
||||||
|
// reaches the leaf. Skipped in `readonly` mode (the heap-convert
|
||||||
|
// picker has its own selectedPath and shouldn't drive the sidebar
|
||||||
|
// state). Skipped for top-level paths (nothing to expand).
|
||||||
|
$effect(() => {
|
||||||
|
if (readonly || !browser) return;
|
||||||
|
const fp = selectedPath ?? filters.folderPath;
|
||||||
|
if (!fp || fp === '/' || !fp.includes('/')) return;
|
||||||
|
untrack(() => {
|
||||||
|
const parts = fp.split('/');
|
||||||
|
let changed = false;
|
||||||
|
for (let i = 1; i < parts.length; i++) {
|
||||||
|
const ancestor = parts.slice(0, i).join('/');
|
||||||
|
if (ancestor && !openSet.has(ancestor)) {
|
||||||
|
openSet.add(ancestor);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
openSet = new Set(openSet);
|
||||||
|
persist();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user