perf(codebase): implement in-memory cache for file tree to persist state across tab navigations
This commit is contained in:
@@ -38,21 +38,50 @@ interface GiteaFileTreeProps {
|
|||||||
selectedPath?: string;
|
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({
|
export function GiteaFileTree({
|
||||||
projectId,
|
projectId,
|
||||||
rootPath,
|
rootPath,
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
selectedPath,
|
selectedPath,
|
||||||
}: GiteaFileTreeProps) {
|
}: GiteaFileTreeProps) {
|
||||||
const [rootItems, setRootItems] = useState<TreeItem[] | null>(null);
|
const cacheKey = `${projectId}::${rootPath}`;
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
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 [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<
|
const [childrenByPath, setChildrenByPath] = useState<
|
||||||
Record<string, TreeItem[]>
|
Record<string, TreeItem[]>
|
||||||
>({});
|
>(() => {
|
||||||
|
return treeCache[cacheKey]?.childrenByPath ?? {};
|
||||||
|
});
|
||||||
const [loadingPaths, setLoadingPaths] = useState<Set<string>>(new Set());
|
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(
|
const fetchPath = useCallback(
|
||||||
async (path: string): Promise<TreeItem[]> => {
|
async (path: string): Promise<TreeItem[]> => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
@@ -71,12 +100,12 @@ export function GiteaFileTree({
|
|||||||
|
|
||||||
// Load root whenever projectId or rootPath changes
|
// Load root whenever projectId or rootPath changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// If we already loaded this from cache on mount, skip fetching again
|
||||||
|
if (treeCache[cacheKey]) return;
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setRootItems(null);
|
|
||||||
setExpanded(new Set());
|
|
||||||
setChildrenByPath({});
|
|
||||||
|
|
||||||
fetchPath(rootPath)
|
fetchPath(rootPath)
|
||||||
.then(async (items) => {
|
.then(async (items) => {
|
||||||
@@ -119,7 +148,7 @@ export function GiteaFileTree({
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [projectId, rootPath, fetchPath]);
|
}, [projectId, rootPath, fetchPath, cacheKey]);
|
||||||
|
|
||||||
const toggleDir = useCallback(
|
const toggleDir = useCallback(
|
||||||
async (path: string) => {
|
async (path: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user