segments with leaderboards, auto-matching, and popular-stretch suggestions
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
fmtDate,
|
||||
fmtDistance,
|
||||
fmtDuration,
|
||||
fmtElapsed,
|
||||
fmtElevation,
|
||||
fmtSpeed,
|
||||
} from "$lib/format";
|
||||
@@ -23,6 +24,14 @@
|
||||
let editing = $state(false);
|
||||
let saving = $state(false);
|
||||
|
||||
// create-segment panel: a distance slice of this track
|
||||
let segmenting = $state(false);
|
||||
let segStart = $state(0);
|
||||
let segEnd = $state(0);
|
||||
$effect(() => {
|
||||
segEnd = Math.round(a.distance_m);
|
||||
});
|
||||
|
||||
// chart hover -> marker on the map at the matching track position
|
||||
function pointAt(d: number): [number, number] {
|
||||
const trackD = data.trackD;
|
||||
@@ -192,6 +201,65 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if data.segmentEfforts.length > 0}
|
||||
<div class="card segments">
|
||||
<span class="seg-title">Segments on this activity:</span>
|
||||
{#each data.segmentEfforts as effort, i (effort.segment_id)}{i > 0
|
||||
? " · "
|
||||
: " "}<a href="/segments/{effort.segment_id}">{effort.name}</a>{#if effort.elapsed_s !== null} ({fmtElapsed(
|
||||
effort.elapsed_s,
|
||||
)}){/if}{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if data.canCreateSegment}
|
||||
<div class="segment-create">
|
||||
{#if segmenting}
|
||||
<form class="card seg-form" method="POST" action="?/segment" use:enhance>
|
||||
<div class="seg-sliders">
|
||||
<label>
|
||||
Start · {fmtDistance(Math.min(segStart, segEnd))}
|
||||
<input
|
||||
type="range"
|
||||
name="start_d"
|
||||
min="0"
|
||||
max={Math.round(a.distance_m)}
|
||||
step="10"
|
||||
bind:value={segStart}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
End · {fmtDistance(Math.max(segStart, segEnd))}
|
||||
<input
|
||||
type="range"
|
||||
name="end_d"
|
||||
min="0"
|
||||
max={Math.round(a.distance_m)}
|
||||
step="10"
|
||||
bind:value={segEnd}
|
||||
/>
|
||||
</label>
|
||||
<span class="seg-len"
|
||||
>Length: {fmtDistance(Math.abs(segEnd - segStart))}</span
|
||||
>
|
||||
</div>
|
||||
<div class="seg-actions">
|
||||
<input name="name" placeholder="Segment name" required maxlength="80" />
|
||||
<button class="btn" type="submit">Create segment</button>
|
||||
<button class="btn ghost" type="button" onclick={() => (segmenting = false)}
|
||||
>Cancel</button
|
||||
>
|
||||
</div>
|
||||
{#if form?.error}<p class="edit-error">{form.error}</p>{/if}
|
||||
</form>
|
||||
{:else}
|
||||
<button class="btn ghost" onclick={() => (segmenting = true)}
|
||||
>+ Create segment from this activity</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<MapSlot />
|
||||
|
||||
{#if data.profile.length > 1}
|
||||
@@ -293,6 +361,73 @@
|
||||
.type {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.segments {
|
||||
margin-top: 1rem;
|
||||
padding: 0.85rem 1.15rem;
|
||||
border-left: 3px solid var(--accent);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.seg-title {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.segments a {
|
||||
color: var(--accent-strong);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
.segments a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.segment-create {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.seg-form {
|
||||
padding: 1.15rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
.seg-sliders {
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.seg-sliders label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
flex: 1;
|
||||
min-width: 180px;
|
||||
}
|
||||
.seg-sliders input[type="range"] {
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.seg-len {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
padding-bottom: 0.2rem;
|
||||
}
|
||||
.seg-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.seg-actions input {
|
||||
flex: 1;
|
||||
min-width: 180px;
|
||||
padding: 0.5rem 0.7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--surface-1);
|
||||
color: var(--text-primary);
|
||||
font: inherit;
|
||||
}
|
||||
.who {
|
||||
font-weight: 600;
|
||||
color: var(--accent-strong);
|
||||
|
||||
Reference in New Issue
Block a user