mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-31 17:20:44 +08:00
Virtualize the file tree's root entries
The file tree was the one list left un-virtualized, so wide directories mounted every entry and re-rendered all rows on selection. Render entries through VirtualList like the other lists.
This commit is contained in:
@@ -11,6 +11,10 @@
|
||||
currentPath: string;
|
||||
name: string;
|
||||
prefix: string;
|
||||
// Expansion state lives in Tree.svelte so it survives the virtualizer
|
||||
// unmounting off-screen rows.
|
||||
isExpanded: (prefix: string) => boolean;
|
||||
toggleExpanded: (prefix: string) => void;
|
||||
indent?: number;
|
||||
}
|
||||
|
||||
@@ -19,10 +23,12 @@
|
||||
fetchBlob,
|
||||
currentPath,
|
||||
prefix,
|
||||
isExpanded,
|
||||
toggleExpanded,
|
||||
fetchTree,
|
||||
indent = 0.5,
|
||||
}: Props = $props();
|
||||
let expanded = $derived(currentPath.indexOf(prefix) === 0);
|
||||
const expanded = $derived(isExpanded(prefix));
|
||||
|
||||
const treePromise = $derived(
|
||||
expanded ? fetchTree(prefix) : Promise.resolve(undefined),
|
||||
@@ -46,7 +52,7 @@
|
||||
class="folder"
|
||||
style:padding-left="{indent}rem"
|
||||
style:padding-right="0.5rem"
|
||||
onclick={() => (expanded = !expanded)}>
|
||||
onclick={() => toggleExpanded(prefix)}>
|
||||
<div class="global-flex txt-body-m-regular" style:padding="0.25rem 0">
|
||||
<div class:txt-missing={!expanded}>
|
||||
<Icon name={expanded ? "folder-open" : "folder"} />
|
||||
@@ -68,6 +74,8 @@
|
||||
{fetchBlob}
|
||||
name={entry.name}
|
||||
{currentPath}
|
||||
{isExpanded}
|
||||
{toggleExpanded}
|
||||
prefix={`${entry.path}/`}
|
||||
indent={indent + 1.5} />
|
||||
{:else if entry.kind === "blob"}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { Tree } from "@bindings/source/Tree";
|
||||
|
||||
import { SvelteMap } from "svelte/reactivity";
|
||||
|
||||
import FileTreeFile from "@app/components/FileTreeFile.svelte";
|
||||
import FileTreeFolder from "@app/components/FileTreeFolder.svelte";
|
||||
import VirtualList from "@app/components/VirtualList.svelte";
|
||||
|
||||
interface Props {
|
||||
tree: Tree;
|
||||
@@ -12,15 +15,40 @@
|
||||
}
|
||||
|
||||
const { currentPath, tree, fetchTree, fetchBlob }: Props = $props();
|
||||
|
||||
// Manual expand/collapse overrides, keyed by folder prefix. Held here
|
||||
// rather than in FileTreeFolder so the state survives the virtualizer
|
||||
// unmounting off-screen rows; without an override a folder is expanded
|
||||
// when it contains the open file.
|
||||
const expandedOverrides = new SvelteMap<string, boolean>();
|
||||
$effect(() => {
|
||||
// Navigating to another file resets every folder to its default, the
|
||||
// same behaviour the per-folder writable $derived had before the state
|
||||
// was hoisted.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
currentPath;
|
||||
expandedOverrides.clear();
|
||||
});
|
||||
function isExpanded(prefix: string): boolean {
|
||||
return expandedOverrides.get(prefix) ?? currentPath.indexOf(prefix) === 0;
|
||||
}
|
||||
function toggleExpanded(prefix: string) {
|
||||
expandedOverrides.set(prefix, !isExpanded(prefix));
|
||||
}
|
||||
</script>
|
||||
|
||||
<div style:display="flex" style:flex-direction="column" style:gap="0.25rem">
|
||||
{#each tree.entries as entry (entry.name)}
|
||||
<VirtualList
|
||||
items={tree.entries}
|
||||
getKey={entry => entry.path}
|
||||
estimatedItemSize={28}>
|
||||
{#snippet row(entry)}
|
||||
{#if entry.kind === "tree"}
|
||||
<FileTreeFolder
|
||||
name={entry.name}
|
||||
prefix={`${entry.path}/`}
|
||||
{currentPath}
|
||||
{isExpanded}
|
||||
{toggleExpanded}
|
||||
{fetchTree}
|
||||
{fetchBlob} />
|
||||
{:else}
|
||||
@@ -29,5 +57,5 @@
|
||||
fetchBlob={() => fetchBlob(entry.path)}
|
||||
active={entry.path === currentPath} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/snippet}
|
||||
</VirtualList>
|
||||
|
||||
Reference in New Issue
Block a user