106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import React, { useEffect, useMemo } from 'react';
|
|
import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
|
|
|
|
type Identifier = string | number;
|
|
type FolderId = Identifier | 'root';
|
|
|
|
interface UseWorkspaceBreadcrumbsArgs {
|
|
selectedFolder: FolderId | null;
|
|
folderNodes: Map<FolderId, { id?: FolderId; name?: string | null; parentId?: FolderId | null; parent_id?: FolderId | null }>;
|
|
currentFolder: { id?: FolderId; name?: string | null; parentId?: FolderId | null; parent_id?: FolderId | null } | null;
|
|
breadcrumbFetchRef: React.MutableRefObject<Set<FolderId>>;
|
|
ensureFolderData: (folderId: FolderId, options?: Record<string, unknown>) => Promise<unknown>;
|
|
}
|
|
|
|
const useWorkspaceBreadcrumbs = ({
|
|
selectedFolder,
|
|
folderNodes,
|
|
currentFolder,
|
|
breadcrumbFetchRef,
|
|
ensureFolderData,
|
|
}: UseWorkspaceBreadcrumbsArgs) => {
|
|
const { breadcrumbs, missingBreadcrumbAncestors } = useMemo(() => {
|
|
const chain: Array<{ id: FolderId; name?: string | null }> = [];
|
|
const seen = new Set<FolderId>();
|
|
const pending = new Set<FolderId>();
|
|
let currentId: FolderId | null = (selectedFolder || 'root') as FolderId;
|
|
let guard = 0;
|
|
|
|
while (currentId && !seen.has(currentId) && guard < 32) {
|
|
guard += 1;
|
|
seen.add(currentId);
|
|
|
|
if (currentId === 'root') {
|
|
chain.push({ id: 'root', name: DEFAULT_FOLDER_NAME });
|
|
currentId = null;
|
|
break;
|
|
}
|
|
|
|
const node = folderNodes.get(currentId as FolderId);
|
|
if (node) {
|
|
chain.push({ id: currentId, name: node.name || 'Folder' });
|
|
currentId = (node.parentId ?? node.parent_id ?? 'root') as FolderId;
|
|
continue;
|
|
}
|
|
|
|
let fallbackName: string | null | undefined = '…';
|
|
let parentId: FolderId | null | undefined = null;
|
|
|
|
if (currentFolder && currentFolder.id === currentId) {
|
|
fallbackName = currentFolder.name;
|
|
parentId = (currentFolder.parent_id ?? currentFolder.parentId ?? 'root') as FolderId;
|
|
}
|
|
|
|
chain.push({ id: currentId, name: fallbackName });
|
|
pending.add(currentId);
|
|
currentId = parentId as FolderId | null;
|
|
}
|
|
|
|
if (!chain.some((crumb) => crumb.id === 'root')) {
|
|
chain.push({ id: 'root', name: DEFAULT_FOLDER_NAME });
|
|
}
|
|
|
|
const ordered: Array<{ id: FolderId; name?: string | null }> = [];
|
|
const seenOrdered = new Set<FolderId>();
|
|
chain
|
|
.slice()
|
|
.reverse()
|
|
.forEach((crumb) => {
|
|
if (!seenOrdered.has(crumb.id)) {
|
|
seenOrdered.add(crumb.id);
|
|
ordered.push(crumb);
|
|
}
|
|
});
|
|
|
|
return { breadcrumbs: ordered, missingBreadcrumbAncestors: Array.from(pending) };
|
|
}, [selectedFolder, folderNodes, currentFolder]);
|
|
|
|
useEffect(() => {
|
|
if (!missingBreadcrumbAncestors.length) {
|
|
return;
|
|
}
|
|
|
|
missingBreadcrumbAncestors.forEach((folderId) => {
|
|
if (!folderId || folderId === 'root') {
|
|
return;
|
|
}
|
|
if (breadcrumbFetchRef.current.has(folderId)) {
|
|
return;
|
|
}
|
|
|
|
breadcrumbFetchRef.current.add(folderId);
|
|
ensureFolderData(folderId, { force: false })
|
|
.catch((error) => {
|
|
console.warn('Failed to preload breadcrumb ancestor', folderId, error);
|
|
})
|
|
.finally(() => {
|
|
breadcrumbFetchRef.current.delete(folderId);
|
|
});
|
|
});
|
|
}, [missingBreadcrumbAncestors, ensureFolderData, breadcrumbFetchRef]);
|
|
|
|
return breadcrumbs;
|
|
};
|
|
|
|
export default useWorkspaceBreadcrumbs;
|