From 5f288295fcd17cc95d632515c4275dc9b00d14f7 Mon Sep 17 00:00:00 2001 From: silberengel Date: Sun, 7 Dec 2025 16:22:03 +0100 Subject: [PATCH] revamp toc for background-loading of the entire publication map --- .../publications/Publication.svelte | 101 ++++++++++++++++++ .../publications/TableOfContents.svelte | 29 ++++- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/src/lib/components/publications/Publication.svelte b/src/lib/components/publications/Publication.svelte index 6f912cf..e257a76 100644 --- a/src/lib/components/publications/Publication.svelte +++ b/src/lib/components/publications/Publication.svelte @@ -300,6 +300,94 @@ } } + /** + * Background-loads all events in the publication tree in breadth-first order (level by level). + * This ensures the TOC is fully populated with all sections. + * + * Loads: root -> level 1 children -> level 2 children -> etc. + * Also resolves children for each entry to establish parent relationships in TOC. + * + * AI-NOTE: Throttled to avoid blocking main publication loading. Processes in small batches + * with delays to prevent overwhelming relays. + */ + async function backgroundLoadAllEvents() { + if (!publicationTree || !toc) { + console.warn("[Publication] publicationTree or toc is not available for background loading"); + return; + } + + console.log("[Publication] Starting background load of all events in level-layers (throttled)"); + + // Throttling configuration + const BATCH_SIZE = 10; // Process 3 addresses at a time + const BATCH_DELAY_MS = 200; // 200ms delay between batches + const LEVEL_DELAY_MS = 500; // 500ms delay between levels + + // Track which addresses we've processed to avoid duplicates + const processedAddresses = new Set(); + + // Start with root address + const queue: string[] = [rootAddress]; + processedAddresses.add(rootAddress); + + // Process level by level (breadth-first) + while (queue.length > 0) { + const currentLevelAddresses = [...queue]; + queue.length = 0; // Clear queue for next level + + // Process addresses in small batches to avoid overwhelming relays + for (let i = 0; i < currentLevelAddresses.length; i += BATCH_SIZE) { + const batch = currentLevelAddresses.slice(i, i + BATCH_SIZE); + + // Process batch in parallel + const batchPromises = batch.map(async (address) => { + try { + // Get child addresses for this node - this triggers node resolution + const childAddresses = await publicationTree.getChildAddresses(address); + + // Resolve children for this entry to establish parent relationships in TOC + const entry = toc.getEntry(address); + if (entry && !entry.childrenResolved) { + await entry.resolveChildren(); + } + + // Add valid children to queue for next level + for (const childAddress of childAddresses) { + if (childAddress && !processedAddresses.has(childAddress)) { + processedAddresses.add(childAddress); + queue.push(childAddress); + + // Resolve the child event to populate TOC (non-blocking) + publicationTree.getEvent(childAddress).catch((error: unknown) => { + console.debug(`[Publication] Error fetching child event ${childAddress}:`, error); + }); + } + } + } catch (error) { + console.error(`[Publication] Error loading children for ${address}:`, error); + } + }); + + // Wait for batch to complete + await Promise.all(batchPromises); + + // Small delay between batches to avoid blocking main loading + if (i + BATCH_SIZE < currentLevelAddresses.length) { + await new Promise(resolve => setTimeout(resolve, BATCH_DELAY_MS)); + } + } + + console.log(`[Publication] Completed level, processed ${currentLevelAddresses.length} addresses, queued ${queue.length} for next level`); + + // Delay between levels to give main loading priority + if (queue.length > 0) { + await new Promise(resolve => setTimeout(resolve, LEVEL_DELAY_MS)); + } + } + + console.log("[Publication] Background load complete, processed", processedAddresses.size, "addresses"); + } + // #endregion // AI-NOTE: Combined effect to handle publicationTree changes and initial loading @@ -336,6 +424,19 @@ console.log("[Publication] Loading initial content"); hasInitialized = true; loadMore(INITIAL_LOAD_COUNT); + + // Start background loading all events in level-layers for TOC + // This runs in the background and doesn't block the UI + // Wait a bit for toc to be initialized + setTimeout(() => { + if (toc && publicationTree) { + backgroundLoadAllEvents().catch((error) => { + console.error("[Publication] Error in background loading:", error); + }); + } else { + console.warn("[Publication] toc or publicationTree not available for background loading"); + } + }, 100); }); // #region Columns visibility diff --git a/src/lib/components/publications/TableOfContents.svelte b/src/lib/components/publications/TableOfContents.svelte index 97b3af3..e01acf8 100644 --- a/src/lib/components/publications/TableOfContents.svelte +++ b/src/lib/components/publications/TableOfContents.svelte @@ -12,7 +12,7 @@ import Self from "./TableOfContents.svelte"; import { onMount, onDestroy } from "svelte"; - let { depth, onSectionFocused, onLoadMore, onClose, toc } = $props<{ + let { rootAddress, depth, onSectionFocused, onLoadMore, onClose, toc } = $props<{ rootAddress: string; depth: number; toc: TableOfContents; @@ -23,11 +23,36 @@ let entries = $derived.by(() => { const newEntries = []; + const rootEntry = rootAddress === toc.getRootEntry()?.address + ? toc.getRootEntry() + : toc.getEntry(rootAddress); + + if (!rootEntry) { + return []; + } + + // Filter entries that are direct children of rootAddress at the correct depth for (const [_, entry] of toc.addressMap) { + // Must match the depth if (entry.depth !== depth) { continue; } - + + // Check if entry is a direct child of rootAddress + // Primary check: parent relationship (set when resolveChildren is called) + // Fallback: entry is in rootEntry's children array + // Final fallback: depth-based check for root's direct children only + const isDirectChild = + entry.parent?.address === rootAddress || + rootEntry.children.some((child: TocEntry) => child.address === entry.address) || + (entry.depth === rootEntry.depth + 1 && + rootAddress === toc.getRootEntry()?.address && + !entry.parent); // Only use depth check if parent not set (temporary state) + + if (!isDirectChild) { + continue; + } + newEntries.push(entry); }