Add map view toggle to activity feed
Adds a List/Map toggle to the feed and @user profile pages. The map view plots all filtered activities as sport-coloured tracks on a MapLibre map with no extra requests (uses preview_coords already in memory). Clicking a track or list row selects it: pans the map to fit, expands the list item with key stats, and scrolls it into view.
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-zinc-900 border border-zinc-700 focus-within:border-zinc-500 transition-colors">
|
||||
<span class="text-xs text-zinc-500 whitespace-nowrap select-none">From</span>
|
||||
<input
|
||||
@@ -269,6 +271,22 @@
|
||||
class="bg-transparent text-white text-sm focus:outline-none [color-scheme:dark]"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex rounded-lg border border-zinc-700 overflow-hidden text-sm shrink-0">
|
||||
<button
|
||||
class="px-3 py-2 transition-colors"
|
||||
class:bg-zinc-800={viewMode === 'list'}
|
||||
class:text-white={viewMode === 'list'}
|
||||
class:text-zinc-400={viewMode !== 'list'}
|
||||
on:click={() => viewMode = 'list'}
|
||||
>List</button>
|
||||
<button
|
||||
class="px-3 py-2 border-l border-zinc-700 transition-colors"
|
||||
class:bg-zinc-800={viewMode === 'map'}
|
||||
class:text-white={viewMode === 'map'}
|
||||
class:text-zinc-400={viewMode !== 'map'}
|
||||
on:click={() => viewMode = 'map'}
|
||||
>Map</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -324,6 +342,25 @@
|
||||
</div>
|
||||
{:else if error}
|
||||
<p class="text-red-400 text-center py-12">Could not load activities: {error}</p>
|
||||
{:else if viewMode === 'map'}
|
||||
<FeedMapView activities={withSearch} base={base} />
|
||||
{#if hasMore}
|
||||
<div class="text-center mt-4">
|
||||
<button
|
||||
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}
|
||||
>
|
||||
{#if loadingMore}
|
||||
Loading…
|
||||
{:else if canShowMore}
|
||||
Load more ({filtered.length - shown} remaining)
|
||||
{:else}
|
||||
Load older activities ({pendingShards.length} more {pendingShards.length === 1 ? 'period' : 'periods'})
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if withSearch.length === 0}
|
||||
<p class="text-zinc-500 text-center py-12">
|
||||
{#if loadingAllShards}Loading…{:else if query.trim()}No activities match "{query.trim()}".{:else}No activities found.{/if}
|
||||
|
||||
Reference in New Issue
Block a user