perf(codebase): implement in-memory cache for file tree to persist state across tab navigations

This commit is contained in:
2026-06-14 13:13:50 -07:00
parent 3774a1771b
commit 1895c8f947

View File

@@ -38,21 +38,50 @@ interface GiteaFileTreeProps {
selectedPath?: string;
}
// ── In-memory cache to persist tree state across tab navigations ──
const treeCache: Record<
string,
{
rootItems: TreeItem[];
childrenByPath: Record<string, TreeItem[]>;
expanded: Set<string>;
}
> = {};
export function GiteaFileTree({
projectId,
rootPath,
onSelectFile,
selectedPath,
}: GiteaFileTreeProps) {
const [rootItems, setRootItems] = useState<TreeItem[] | null>(null);
const [loading, setLoading] = useState(true);
const cacheKey = `${projectId}::${rootPath}`;
const [rootItems, setRootItems] = useState<TreeItem[] | null>(() => {
return treeCache[cacheKey]?.rootItems ?? null;
});
const [loading, setLoading] = useState(() => !treeCache[cacheKey]);
const [error, setError] = useState<string | null>(null);
const [expanded, setExpanded] = useState<Set<string>>(new Set());
const [expanded, setExpanded] = useState<Set<string>>(() => {
return treeCache[cacheKey]?.expanded ?? new Set();
});
const [childrenByPath, setChildrenByPath] = useState<
Record<string, TreeItem[]>
>({});
>(() => {
return treeCache[cacheKey]?.childrenByPath ?? {};
});
const [loadingPaths, setLoadingPaths] = useState<Set<string>>(new Set());
// Keep cache synced with state updates
useEffect(() => {
if (rootItems) {
treeCache[cacheKey] = {
rootItems,
childrenByPath,
expanded,
};
}
}, [cacheKey, rootItems, childrenByPath, expanded]);
const fetchPath = useCallback(
async (path: string): Promise<TreeItem[]> => {
const res = await fetch(
@@ -71,12 +100,12 @@ export function GiteaFileTree({
// Load root whenever projectId or rootPath changes
useEffect(() => {
// If we already loaded this from cache on mount, skip fetching again
if (treeCache[cacheKey]) return;
let cancelled = false;
setLoading(true);
setError(null);
setRootItems(null);
setExpanded(new Set());
setChildrenByPath({});
fetchPath(rootPath)
.then(async (items) => {
@@ -119,7 +148,7 @@ export function GiteaFileTree({
return () => {
cancelled = true;
};
}, [projectId, rootPath, fetchPath]);
}, [projectId, rootPath, fetchPath, cacheKey]);
const toggleDir = useCallback(
async (path: string) => {