Feed: eager-load only the year shards needed for the active date filter

This commit is contained in:
Davide Scaini
2026-05-15 09:32:12 +02:00
parent 8a06227243
commit d3bce49445
+73 -47
View File
@@ -132,57 +132,83 @@
$: if (sport || datePre || query || customFrom || customTo) shown = PAGE_SIZE; // reset pagination on filter change
// When a search query or any date filter is active, eagerly load all
// remaining shards so results aren't limited to the initially-loaded year.
// Use only primary let-variables here — Svelte 5 doesn't reliably track
// derived $: variables as dependencies of side-effect $: blocks.
let loadingAllShards = false;
// Eager-load shards / feed-pages when a filter needs data not yet in memory.
let loadingAllShards = false;
let loadingAllFeedPages = false;
$: if ((query.trim() || customFrom || customTo || datePre !== 'all') && pendingShards.length > 0 && !loadingAllShards) {
loadingAllShards = true;
(async () => {
while (pendingShards.length > 0) {
const url = pendingShards[0];
pendingShards = pendingShards.slice(1);
try {
const fresh = await loadShardActivities(url);
const existing = new Map(all.map(a => [a.id, a]));
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) =>
(b.started_at ?? '').localeCompare(a.started_at ?? ''),
);
} catch { /* ignore — partial results still useful */ }
}
loadingAllShards = false;
})();
function _yearFromShard(url: string): number | null {
const m = url.match(/index-(\d{4})\.json$/);
return m ? parseInt(m[1], 10) : null;
}
$: if ((query.trim() || customFrom || customTo || datePre !== 'all') && feedNextPage > 0 && !loadingAllFeedPages) {
loadingAllFeedPages = true;
(async () => {
// Snapshot at loop start — customFrom may change while we're fetching.
const fromFilter = customFrom;
while (feedNextPage > 0) {
const page = feedNextPage;
feedNextPage = page < feedTotalPages ? page + 1 : 0;
try {
const fresh = await loadCombinedFeedPage(base, page);
const existing = new Map(all.map(a => [a.id, a]));
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) =>
(b.started_at ?? '').localeCompare(a.started_at ?? ''),
);
// Feed is sorted newest-first. Once the oldest activity in this page
// predates our from-filter, everything needed is already loaded.
if (fromFilter && fresh.length > 0) {
const oldest = fresh.reduce((m, a) => (a.started_at ?? '') < (m.started_at ?? '') ? a : m);
if ((oldest.started_at ?? '') < fromFilter) { feedNextPage = 0; break; }
}
} catch { /* ignore — partial results still useful */ }
}
loadingAllFeedPages = false;
})();
// Returns [minYear, maxYear] for year-specific filters (year preset or custom
// date range). Returns null for open-ended filters (all, 7d, 30d, 6mo).
function _neededYearRange(pre: string, from: string, to: string): [number, number] | null {
if (from || to) {
const fy = from ? parseInt(from.slice(0, 4), 10) : 0;
const ty = to ? parseInt(to.slice(0, 4), 10) : 9999;
return [fy, ty];
}
if (/^\d{4}$/.test(pre)) { const y = parseInt(pre, 10); return [y, y]; }
return null;
}
$: {
const yr = _neededYearRange(datePre, customFrom, customTo);
const needEager = !!query.trim() || yr !== null;
if (needEager && pendingShards.length > 0 && !loadingAllShards) {
loadingAllShards = true;
// When year-specific filter (no search), load only shards that cover
// the needed range; unneeded shards stay in pendingShards for "Load more".
// When search is active, load everything so full-text search works.
const toLoad = (yr && !query.trim())
? pendingShards.filter(url => { const y = _yearFromShard(url); return y !== null && y >= yr[0] && y <= yr[1]; })
: [...pendingShards];
(async () => {
for (const url of toLoad) {
pendingShards = pendingShards.filter(u => u !== url);
try {
const fresh = await loadShardActivities(url);
const existing = new Map(all.map(a => [a.id, a]));
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) =>
(b.started_at ?? '').localeCompare(a.started_at ?? ''),
);
} catch { /* ignore — partial results still useful */ }
}
loadingAllShards = false;
})();
}
}
$: {
const yr = _neededYearRange(datePre, customFrom, customTo);
if ((!!query.trim() || yr !== null) && feedNextPage > 0 && !loadingAllFeedPages) {
loadingAllFeedPages = true;
// Capture at loop start — dateFrom is reactive and may change mid-fetch.
const effectiveFrom = dateFrom;
(async () => {
while (feedNextPage > 0) {
const page = feedNextPage;
feedNextPage = page < feedTotalPages ? page + 1 : 0;
try {
const fresh = await loadCombinedFeedPage(base, page);
const existing = new Map(all.map(a => [a.id, a]));
for (const a of fresh) if (!existing.has(a.id)) existing.set(a.id, a);
all = [...existing.values()].sort((a, b) =>
(b.started_at ?? '').localeCompare(a.started_at ?? ''),
);
// Feed is sorted newest-first. Once the oldest activity in this page
// predates our from-filter, everything needed is already loaded.
if (effectiveFrom && fresh.length > 0) {
const oldest = fresh.reduce((m, a) => (a.started_at ?? '') < (m.started_at ?? '') ? a : m);
if ((oldest.started_at ?? '') < effectiveFrom) { feedNextPage = 0; break; }
}
} catch { /* ignore — partial results still useful */ }
}
loadingAllFeedPages = false;
})();
}
}
$: if (mounted) {