perf: year-shard index.json to cut initial load from MBs to ~1 year
merge_all/_merged/index.json is now a shard manifest; activities are
split into index-{year}.json files. The feed loads only the most-recent
year on first paint (~200 activities instead of all of them). Older
years are fetched lazily when the user clicks "Load older activities".
Also strips best_efforts / best_climb_m / source from shard files —
these fields are aggregation inputs only, never read by the feed UI.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import type { ActivitySummary, BASIndex, Sport } from '../lib/types';
|
||||
import { formatDistance, formatDuration, formatElevation, formatDate, isUnlisted, sportIcon, sportColor, sportLabel } from '../lib/format';
|
||||
import { loadIndex } from '../lib/dataloader';
|
||||
import { loadIndexPaged, loadShardActivities } from '../lib/dataloader';
|
||||
|
||||
/** Render preview_coords as an SVG polyline path string. */
|
||||
function trackPath(coords: [number, number][] | null, w: number, h: number): string {
|
||||
@@ -41,8 +41,10 @@
|
||||
let sport: Sport | 'all' = 'all';
|
||||
let shown = PAGE_SIZE;
|
||||
let loading = true;
|
||||
let loadingMore = false;
|
||||
let error = '';
|
||||
let mounted = false;
|
||||
let pendingShards: string[] = [];
|
||||
/** Logged-in handle — resolved async via bincio:me event. */
|
||||
let me: string = '';
|
||||
|
||||
@@ -58,7 +60,33 @@
|
||||
});
|
||||
$: filtered = sport === 'all' ? withPrivacy : withPrivacy.filter(a => a.sport === sport);
|
||||
$: visible = filtered.slice(0, shown);
|
||||
$: hasMore = shown < filtered.length;
|
||||
$: canShowMore = shown < filtered.length;
|
||||
$: hasMore = canShowMore || pendingShards.length > 0;
|
||||
|
||||
async function loadMore() {
|
||||
if (canShowMore) {
|
||||
shown += PAGE_SIZE;
|
||||
return;
|
||||
}
|
||||
if (!pendingShards.length) return;
|
||||
loadingMore = true;
|
||||
const url = pendingShards[0];
|
||||
pendingShards = pendingShards.slice(1);
|
||||
try {
|
||||
const fresh = await loadShardActivities(url);
|
||||
// Merge avoiding duplicates (IDB activities may already be present)
|
||||
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 ?? ''),
|
||||
);
|
||||
shown += PAGE_SIZE;
|
||||
} catch {
|
||||
// shard load failed — don't block the user
|
||||
} finally {
|
||||
loadingMore = false;
|
||||
}
|
||||
}
|
||||
|
||||
$: if (sport) shown = PAGE_SIZE; // reset pagination on filter change
|
||||
|
||||
@@ -84,12 +112,9 @@
|
||||
const indexUrl = profileIndexUrl
|
||||
? `${base}data/${profileIndexUrl}`
|
||||
: `${base}data/index.json`;
|
||||
const index = await loadIndex(base, indexUrl);
|
||||
const { index, pendingShards: pending } = await loadIndexPaged(base, indexUrl);
|
||||
pendingShards = pending;
|
||||
let activities = index.activities;
|
||||
// filterHandle only applies when loading the root manifest (multi-user feed).
|
||||
// When profileIndexUrl is set we already loaded the right user's shard directly —
|
||||
// activities from a direct shard fetch have no handle tag, so the filter would
|
||||
// remove everything.
|
||||
if (filterHandle && !profileIndexUrl) {
|
||||
activities = activities.filter(a => (a as any).handle === filterHandle);
|
||||
}
|
||||
@@ -230,10 +255,17 @@
|
||||
{#if hasMore}
|
||||
<div class="text-center mt-8">
|
||||
<button
|
||||
class="px-6 py-2 rounded-full border border-zinc-700 text-zinc-300 hover:border-zinc-500 hover:text-white transition-colors text-sm"
|
||||
on:click={() => shown += PAGE_SIZE}
|
||||
class="px-6 py-2 rounded-full border border-zinc-700 text-zinc-300 hover:border-zinc-500 hover:text-white disabled:opacity-40 transition-colors text-sm"
|
||||
disabled={loadingMore}
|
||||
on:click={loadMore}
|
||||
>
|
||||
Load more ({filtered.length - shown} remaining)
|
||||
{#if loadingMore}
|
||||
Loading…
|
||||
{:else if canShowMore}
|
||||
Load more ({filtered.length - shown} remaining)
|
||||
{:else}
|
||||
Load older activities ({pendingShards.length} more {pendingShards.length === 1 ? 'year' : 'years'})
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user