integrate edit button into astro site
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { marked } from 'marked';
|
||||
import type { ActivitySummary, ActivityDetail } from '../lib/types';
|
||||
import { formatDistance, formatDuration, formatElevation, formatSpeed, formatDate, formatTime, sportIcon, sportLabel, sportColor } from '../lib/format';
|
||||
import ActivityMap from './ActivityMap.svelte';
|
||||
import ActivityCharts from './ActivityCharts.svelte';
|
||||
import EditDrawer from './EditDrawer.svelte';
|
||||
|
||||
export let activity: ActivitySummary;
|
||||
export let base: string = '/';
|
||||
|
||||
const editUrl = import.meta.env.PUBLIC_EDIT_URL;
|
||||
|
||||
let detail: ActivityDetail | null = null;
|
||||
let error = '';
|
||||
// Linked hover index shared between map and charts
|
||||
let hoveredIdx: number | null = null;
|
||||
let editOpen = false;
|
||||
|
||||
// Local overrides applied immediately after a save (no re-fetch needed)
|
||||
let localTitle = '';
|
||||
let localDescription = '';
|
||||
$: displayTitle = localTitle || activity.title;
|
||||
|
||||
onMount(async () => {
|
||||
if (!activity.detail_url) return;
|
||||
@@ -24,22 +33,48 @@
|
||||
}
|
||||
});
|
||||
|
||||
function onSaved(e: CustomEvent<{ title: string; description: string }>) {
|
||||
editOpen = false;
|
||||
localTitle = e.detail.title;
|
||||
localDescription = e.detail.description;
|
||||
}
|
||||
|
||||
$: trackUrl = activity.track_url ? `${base}data/${activity.track_url}` : null;
|
||||
$: color = sportColor(activity.sport);
|
||||
|
||||
const stat = (label: string, value: string) => ({ label, value });
|
||||
$: rawDescription = localDescription || detail?.description || '';
|
||||
$: descriptionHtml = (() => {
|
||||
if (!rawDescription) return '';
|
||||
const imageBase = `${base}data/activities/images/${activity.id}/`;
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.image = ({ href, title, text }) => {
|
||||
const src = href && !href.startsWith('http') && !href.startsWith('/') && !href.startsWith('data:')
|
||||
? imageBase + href
|
||||
: href ?? '';
|
||||
const titleAttr = title ? ` title="${title}"` : '';
|
||||
return `<img src="${src}" alt="${text}"${titleAttr} class="rounded-lg max-w-full my-2">`;
|
||||
};
|
||||
return marked(rawDescription, { renderer }) as string;
|
||||
})();
|
||||
|
||||
const stat = (label: string, value: string, key?: string) => ({ label, value, key });
|
||||
$: hiddenStats = new Set<string>((detail?.custom as any)?.hide_stats ?? []);
|
||||
$: stats = [
|
||||
stat('Distance', formatDistance(activity.distance_m)),
|
||||
stat('Moving time', formatDuration(activity.moving_time_s ?? activity.duration_s)),
|
||||
stat('Elevation ↑', formatElevation(activity.elevation_gain_m)),
|
||||
stat('Avg speed', formatSpeed(activity.avg_speed_kmh)),
|
||||
stat('Max speed', formatSpeed(activity.max_speed_kmh)),
|
||||
stat('Avg HR', activity.avg_hr_bpm ? `${activity.avg_hr_bpm} bpm` : '—'),
|
||||
stat('Max HR', activity.max_hr_bpm ? `${activity.max_hr_bpm} bpm` : '—'),
|
||||
stat('Cadence', activity.avg_cadence_rpm ? `${activity.avg_cadence_rpm} rpm` : '—'),
|
||||
];
|
||||
stat('Elevation ↑', formatElevation(activity.elevation_gain_m), 'elevation'),
|
||||
stat('Avg speed', formatSpeed(activity.avg_speed_kmh), 'speed'),
|
||||
stat('Max speed', formatSpeed(activity.max_speed_kmh), 'speed'),
|
||||
stat('Avg HR', activity.avg_hr_bpm ? `${activity.avg_hr_bpm} bpm` : '—', 'heart_rate'),
|
||||
stat('Max HR', activity.max_hr_bpm ? `${activity.max_hr_bpm} bpm` : '—', 'heart_rate'),
|
||||
stat('Cadence', activity.avg_cadence_rpm ? `${activity.avg_cadence_rpm} rpm` : '—', 'cadence'),
|
||||
].filter(s => !s.key || !hiddenStats.has(s.key));
|
||||
</script>
|
||||
|
||||
{#if editOpen && editUrl}
|
||||
<EditDrawer activityId={activity.id} {editUrl} on:saved={onSaved} />
|
||||
{/if}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-start gap-4 mb-6">
|
||||
<a href={`${base}`} class="text-zinc-500 hover:text-white transition-colors mt-1 shrink-0">
|
||||
@@ -57,9 +92,21 @@
|
||||
{formatDate(activity.started_at)} · {formatTime(activity.started_at)}
|
||||
</span>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-white">{activity.title}</h1>
|
||||
{#if detail?.description}
|
||||
<p class="text-zinc-400 mt-1 text-sm">{detail.description}</p>
|
||||
<div class="flex items-center gap-3">
|
||||
<h1 class="text-2xl font-bold text-white">{displayTitle}</h1>
|
||||
{#if editUrl}
|
||||
<button
|
||||
class="text-xs px-2 py-0.5 rounded border border-zinc-700 text-zinc-400 hover:border-zinc-500 hover:text-white transition-colors shrink-0"
|
||||
on:click={() => editOpen = true}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if descriptionHtml}
|
||||
<div class="text-zinc-400 mt-2 text-sm leading-relaxed [&_img]:rounded-lg [&_img]:my-2 [&_p]:my-1 [&_a]:text-blue-400">
|
||||
{@html descriptionHtml}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user