No notable peaks in this view - try panning to the Alps or zooming in.
+ {#if toClimb.length === 0}
+ No peaks left to climb in this view - pan around or zoom in.
{:else}
+ Showing the {LIST_CAP} most notable of {toClimb.length} peaks on the map - zoom in for more.
+
+ {/if}
{/if}
{:else if ascents.length === 0}
@@ -163,6 +223,71 @@
{/if}
diff --git a/src/routes/upload/+page.server.ts b/src/routes/upload/+page.server.ts
index 31daea2..7dc3a47 100644
--- a/src/routes/upload/+page.server.ts
+++ b/src/routes/upload/+page.server.ts
@@ -3,6 +3,25 @@ import { db, peaksNearBounds, recordAscent } from '$lib/server/db';
import { matchPeaks, parseGpx } from '$lib/server/gpx';
import type { Actions } from './$types';
+/**
+ * Parse export-style filenames like
+ * "2018-01-11 141322 - Run - Afternoon Run.gpx" (Date - Type - Name).
+ */
+function parseFilename(
+ filename: string
+): { date: string; type: string; name: string } | null {
+ const base = filename.replace(/\.gpx$/i, '');
+ const parts = base.split(' - ');
+ if (parts.length < 3) return null;
+ const dateMatch = parts[0].match(/^(\d{4}-\d{2}-\d{2})(\s+\d{4,6})?$/);
+ if (!dateMatch) return null;
+ return {
+ date: dateMatch[1],
+ type: parts[1].trim().toLowerCase(),
+ name: parts.slice(2).join(' - ').trim()
+ };
+}
+
export const actions: Actions = {
default: async ({ request, locals }) => {
const userId = locals.user!.id;
@@ -25,11 +44,12 @@ export const actions: Actions = {
for (const file of files) {
try {
const gpx = parseGpx(await file.text());
+ const parsed = parseFilename(file.name);
const result = insert.run({
user_id: userId,
- name: gpx.name ?? file.name.replace(/\.gpx$/i, ''),
- type: gpx.type ?? 'outdoor',
- date: gpx.date,
+ name: parsed?.name ?? gpx.name ?? file.name.replace(/\.gpx$/i, ''),
+ type: parsed?.type ?? gpx.type ?? 'outdoor',
+ date: gpx.date ?? parsed?.date ?? null,
distance_m: gpx.distance_m,
duration_s: gpx.duration_s,
moving_s: gpx.moving_s,
@@ -49,7 +69,11 @@ export const actions: Actions = {
if (recordAscent(userId, peak.id, activityId, gpx.date)) newPeaks.push(peak.name);
}
- uploaded.push({ id: activityId, name: gpx.name ?? file.name, newPeaks });
+ uploaded.push({
+ id: activityId,
+ name: parsed?.name ?? gpx.name ?? file.name,
+ newPeaks
+ });
} catch (err) {
errors.push(`${file.name}: ${err instanceof Error ? err.message : 'could not parse'}`);
}
diff --git a/src/routes/users/[username]/+page.server.ts b/src/routes/users/[username]/+page.server.ts
new file mode 100644
index 0000000..825aa5a
--- /dev/null
+++ b/src/routes/users/[username]/+page.server.ts
@@ -0,0 +1,17 @@
+import { error } from '@sveltejs/kit';
+import { countAscents, getProfile, listActivities, listAscents, userStats } from '$lib/server/db';
+import type { PageServerLoad } from './$types';
+
+export const load: PageServerLoad = ({ params }) => {
+ const profile = getProfile(params.username);
+ if (!profile) error(404, 'No such user');
+
+ const ascents = listAscents(profile.id);
+ return {
+ profile,
+ stats: userStats(profile.id),
+ peaksReached: countAscents(profile.id),
+ highestAscent: ascents[0] ?? null,
+ activities: listActivities(profile.id).slice(0, 10)
+ };
+};
diff --git a/src/routes/users/[username]/+page.svelte b/src/routes/users/[username]/+page.svelte
new file mode 100644
index 0000000..fb331c1
--- /dev/null
+++ b/src/routes/users/[username]/+page.svelte
@@ -0,0 +1,99 @@
+
+
+
+ {p.username} · Streba
+
+
+
+
+
+
{p.username}
+
+ {#if p.location}{p.location} · {/if}member since {memberSince}
+
+ {#if p.bio}
{p.bio}
{/if}
+
+
+
+
+
+
+
+
+
+
+{#if data.peaksReached > 0}
+
+ ⛰ {data.peaksReached} peak{data.peaksReached === 1 ? '' : 's'} reached{#if data.highestAscent},
+ highest: {data.highestAscent.name}
+ ({Math.round(data.highestAscent.elevation_m ?? 0).toLocaleString('en-US')} m){/if}.
+
+{/if}
+
+
Recent activities
+{#if data.activities.length === 0}
+
No activities yet.
+{:else}
+
+ {#each data.activities as activity (activity.id)}
+
+ {/each}
+
+{/if}
+
+
diff --git a/static/logo.png b/static/logo.png
deleted file mode 100644
index cb4bbef..0000000
Binary files a/static/logo.png and /dev/null differ