diff --git a/site/src/components/ActivityFeed.svelte b/site/src/components/ActivityFeed.svelte index c30bf30..3dc0233 100644 --- a/site/src/components/ActivityFeed.svelte +++ b/site/src/components/ActivityFeed.svelte @@ -3,6 +3,7 @@ import type { ActivitySummary, BASIndex, Sport } from '../lib/types'; import { formatDistance, formatDuration, formatElevation, formatDate, isUnlisted, sportIcon, sportColor, sportLabel } from '../lib/format'; import { loadIndexPaged, loadShardActivities, loadCombinedFeed } from '../lib/dataloader'; + import FeedMapView from './FeedMapView.svelte'; /** Render preview_coords as an SVG polyline path string. */ function trackPath(coords: [number, number][] | null, w: number, h: number): string { @@ -50,6 +51,7 @@ let loading = true; let loadingMore = false; let error = ''; + let viewMode: 'list' | 'map' = 'list'; let mounted = false; let pendingShards: string[] = []; /** Grand total from feed.json — shows instance-wide count even before all pages are loaded. */ @@ -247,7 +249,7 @@ bind:value={query} class="flex-1 px-4 py-2 rounded-lg bg-zinc-900 border border-zinc-700 text-white placeholder-zinc-500 text-sm focus:outline-none focus:border-zinc-500 transition-colors" /> -
+
From
+
+ + +
@@ -324,6 +342,25 @@ {:else if error}

Could not load activities: {error}

+{:else if viewMode === 'map'} + + {#if hasMore} +
+ +
+ {/if} {:else if withSearch.length === 0}

{#if loadingAllShards}Loading…{:else if query.trim()}No activities match "{query.trim()}".{:else}No activities found.{/if} diff --git a/site/src/components/FeedMapView.svelte b/site/src/components/FeedMapView.svelte new file mode 100644 index 0000000..32de697 --- /dev/null +++ b/site/src/components/FeedMapView.svelte @@ -0,0 +1,190 @@ + + +

+ +
+
+
+ + +
+ {#if activities.length === 0} +

No activities to display.

+ {:else} +
+ {#each activities as a (a.id)} +
selectActivity(a.id)} + role="button" + tabindex="0" + on:keydown={e => e.key === 'Enter' && selectActivity(a.id)} + > + +
+
+ {sportIcon(a.sport)} +
+ {a.title} +

+ {formatDate(a.started_at)}{#if a.handle} · @{a.handle}{/if} +

+
+
+
+ {#if a.distance_m != null} + {formatDistance(a.distance_m)} + {/if} + {#if a.elevation_gain_m != null} + ↑{formatElevation(a.elevation_gain_m)} + {/if} +
+
+ + + {#if selectedId === a.id} +
+ {#if a.moving_time_s != null} +
{formatDuration(a.moving_time_s)} time
+ {/if} + {#if a.avg_speed_kmh != null} +
{a.avg_speed_kmh.toFixed(1)} km/h avg speed
+ {/if} + {#if a.avg_hr_bpm != null} +
{a.avg_hr_bpm} bpm avg HR
+ {/if} + {#if a.avg_power_w != null} +
{a.avg_power_w} W avg power
+ {/if} +
+ {/if} +
+ {/each} +
+ {/if} +
+