fix(fitness): hoist rest timer above set table, persist across exercise switches
Rest timer was inlined as a row inside SetTable, tied to a specific set. Switching to another exercise hid it from view. Now lives below the exercise focus card, renders whenever a rest is active regardless of focused exercise, and labels which set/exercise it belongs to when looking at a different one.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.69.2",
|
"version": "1.69.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<script>
|
||||||
|
import RestTimer from './RestTimer.svelte';
|
||||||
|
import ExerciseName from './ExerciseName.svelte';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { detectFitnessLang } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {{
|
||||||
|
* active: boolean,
|
||||||
|
* seconds: number,
|
||||||
|
* total: number,
|
||||||
|
* exerciseId?: string | null,
|
||||||
|
* setIdx: number,
|
||||||
|
* activeExerciseIdx: number,
|
||||||
|
* restExerciseIdx: number,
|
||||||
|
* onAdjust?: ((delta: number) => void) | null,
|
||||||
|
* onSkip?: (() => void) | null
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
let {
|
||||||
|
active,
|
||||||
|
seconds,
|
||||||
|
total,
|
||||||
|
exerciseId = null,
|
||||||
|
setIdx,
|
||||||
|
activeExerciseIdx,
|
||||||
|
restExerciseIdx,
|
||||||
|
onAdjust = null,
|
||||||
|
onSkip = null
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
|
const isEn = $derived(lang === 'en');
|
||||||
|
const isOtherExercise = $derived(restExerciseIdx >= 0 && restExerciseIdx !== activeExerciseIdx);
|
||||||
|
const setLabel = $derived(setIdx >= 0
|
||||||
|
? (isEn ? `Rest · Set ${setIdx + 1}` : `Pause · Satz ${setIdx + 1}`)
|
||||||
|
: (isEn ? 'Rest' : 'Pause'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if active && total > 0}
|
||||||
|
<section class="active-rest" aria-live="polite">
|
||||||
|
<header class="rest-context">
|
||||||
|
<span class="rest-label">{setLabel}</span>
|
||||||
|
{#if isOtherExercise && exerciseId}
|
||||||
|
<span class="rest-sep" aria-hidden="true">·</span>
|
||||||
|
<span class="rest-exercise"><ExerciseName {exerciseId} plain /></span>
|
||||||
|
{/if}
|
||||||
|
</header>
|
||||||
|
<RestTimer
|
||||||
|
{seconds}
|
||||||
|
{total}
|
||||||
|
onComplete={onSkip}
|
||||||
|
{onAdjust}
|
||||||
|
{onSkip}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.active-rest {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
.rest-context {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.4rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
}
|
||||||
|
.rest-label {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
.rest-sep {
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
}
|
||||||
|
.rest-exercise {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
import Play from '@lucide/svelte/icons/play';
|
import Play from '@lucide/svelte/icons/play';
|
||||||
import Square from '@lucide/svelte/icons/square';
|
import Square from '@lucide/svelte/icons/square';
|
||||||
import { METRIC_LABELS } from '$lib/data/exercises';
|
import { METRIC_LABELS } from '$lib/data/exercises';
|
||||||
import RestTimer from './RestTimer.svelte';
|
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { detectFitnessLang, m } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, m } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
@@ -17,14 +16,9 @@
|
|||||||
* previousSets?: Array<Record<string, any>> | null,
|
* previousSets?: Array<Record<string, any>> | null,
|
||||||
* metrics?: Array<'weight' | 'reps' | 'rpe' | 'distance' | 'duration'>,
|
* metrics?: Array<'weight' | 'reps' | 'rpe' | 'distance' | 'duration'>,
|
||||||
* editable?: boolean,
|
* editable?: boolean,
|
||||||
* restAfterSet?: number,
|
|
||||||
* restSeconds?: number,
|
|
||||||
* restTotal?: number,
|
|
||||||
* holdAfterSet?: number,
|
* holdAfterSet?: number,
|
||||||
* holdSeconds?: number,
|
* holdSeconds?: number,
|
||||||
* holdTotal?: number,
|
* holdTotal?: number,
|
||||||
* onRestAdjust?: ((delta: number) => void) | null,
|
|
||||||
* onRestSkip?: (() => void) | null,
|
|
||||||
* timedHold?: boolean,
|
* timedHold?: boolean,
|
||||||
* onHoldSkip?: (() => void) | null,
|
* onHoldSkip?: (() => void) | null,
|
||||||
* onUpdate?: ((setIndex: number, data: Record<string, number | null>) => void) | null,
|
* onUpdate?: ((setIndex: number, data: Record<string, number | null>) => void) | null,
|
||||||
@@ -37,15 +31,10 @@
|
|||||||
previousSets = null,
|
previousSets = null,
|
||||||
metrics = ['weight', 'reps', 'rpe'],
|
metrics = ['weight', 'reps', 'rpe'],
|
||||||
editable = false,
|
editable = false,
|
||||||
restAfterSet = -1,
|
|
||||||
restSeconds = 0,
|
|
||||||
restTotal = 0,
|
|
||||||
timedHold = false,
|
timedHold = false,
|
||||||
holdAfterSet = -1,
|
holdAfterSet = -1,
|
||||||
holdSeconds = 0,
|
holdSeconds = 0,
|
||||||
holdTotal = 0,
|
holdTotal = 0,
|
||||||
onRestAdjust = null,
|
|
||||||
onRestSkip = null,
|
|
||||||
onHoldSkip = null,
|
onHoldSkip = null,
|
||||||
onUpdate = null,
|
onUpdate = null,
|
||||||
onToggleComplete = null,
|
onToggleComplete = null,
|
||||||
@@ -215,19 +204,6 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
{#if restAfterSet === i && restTotal > 0}
|
|
||||||
<tr class="rest-row">
|
|
||||||
<td colspan={totalCols} class="rest-cell">
|
|
||||||
<RestTimer
|
|
||||||
seconds={restSeconds}
|
|
||||||
total={restTotal}
|
|
||||||
onComplete={onRestSkip}
|
|
||||||
onAdjust={onRestAdjust}
|
|
||||||
onSkip={onRestSkip}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
import ExercisePicker from '$lib/components/fitness/ExercisePicker.svelte';
|
import ExercisePicker from '$lib/components/fitness/ExercisePicker.svelte';
|
||||||
import WorkoutRail from '$lib/components/fitness/WorkoutRail.svelte';
|
import WorkoutRail from '$lib/components/fitness/WorkoutRail.svelte';
|
||||||
import WorkoutFocusCard from '$lib/components/fitness/WorkoutFocusCard.svelte';
|
import WorkoutFocusCard from '$lib/components/fitness/WorkoutFocusCard.svelte';
|
||||||
|
import ActiveRestTimer from '$lib/components/fitness/ActiveRestTimer.svelte';
|
||||||
import Toggle from '$lib/components/Toggle.svelte';
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
@@ -1681,6 +1682,20 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ActiveRestTimer
|
||||||
|
active={workout.restTimerActive}
|
||||||
|
seconds={workout.restTimerSeconds}
|
||||||
|
total={workout.restTimerTotal}
|
||||||
|
exerciseId={workout.restExerciseIdx >= 0 && workout.exercises[workout.restExerciseIdx]
|
||||||
|
? workout.exercises[workout.restExerciseIdx].exerciseId
|
||||||
|
: null}
|
||||||
|
setIdx={workout.restSetIdx}
|
||||||
|
activeExerciseIdx={activeIdx}
|
||||||
|
restExerciseIdx={workout.restExerciseIdx}
|
||||||
|
onAdjust={(delta) => workout.adjustRestTimer(delta)}
|
||||||
|
onSkip={cancelRest}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="exercise-block focused">
|
<div class="exercise-block focused">
|
||||||
<SetTable
|
<SetTable
|
||||||
sets={activeExercise.sets}
|
sets={activeExercise.sets}
|
||||||
@@ -1688,14 +1703,9 @@
|
|||||||
metrics={exMetrics}
|
metrics={exMetrics}
|
||||||
editable={true}
|
editable={true}
|
||||||
timedHold={isDurationOnly}
|
timedHold={isDurationOnly}
|
||||||
restAfterSet={workout.restTimerActive && workout.restExerciseIdx === activeIdx ? workout.restSetIdx : -1}
|
|
||||||
restSeconds={workout.restTimerSeconds}
|
|
||||||
restTotal={workout.restTimerTotal}
|
|
||||||
holdAfterSet={workout.holdTimerActive && workout.holdExerciseIdx === activeIdx ? workout.holdSetIdx : -1}
|
holdAfterSet={workout.holdTimerActive && workout.holdExerciseIdx === activeIdx ? workout.holdSetIdx : -1}
|
||||||
holdSeconds={workout.holdTimerSeconds}
|
holdSeconds={workout.holdTimerSeconds}
|
||||||
holdTotal={workout.holdTimerTotal}
|
holdTotal={workout.holdTimerTotal}
|
||||||
onRestAdjust={(delta) => workout.adjustRestTimer(delta)}
|
|
||||||
onRestSkip={cancelRest}
|
|
||||||
onHoldSkip={() => workout.cancelHoldTimer()}
|
onHoldSkip={() => workout.cancelHoldTimer()}
|
||||||
onUpdate={(setIdx, d) => workout.updateSet(activeIdx, setIdx, d)}
|
onUpdate={(setIdx, d) => workout.updateSet(activeIdx, setIdx, d)}
|
||||||
onToggleComplete={(setIdx) => {
|
onToggleComplete={(setIdx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user