diff --git a/src/lib/components/fitness/WorkoutFab.svelte b/src/lib/components/fitness/WorkoutFab.svelte index abfec7d..d7d6cd2 100644 --- a/src/lib/components/fitness/WorkoutFab.svelte +++ b/src/lib/components/fitness/WorkoutFab.svelte @@ -1,88 +1,151 @@ + +
goto(href)} onkeydown={(e) => { if (e.key === 'Enter') goto(href); }}> +
+ + {elapsed} + +
+ {#if restTotal > 0 && restSeconds > 0} +
e.stopPropagation()}> +
+
+ + + +
+
+ {:else} + Active Workout + {/if} +
+ - - {elapsed} - - diff --git a/src/lib/js/workout.svelte.ts b/src/lib/js/workout.svelte.ts index d1dcc63..d33151b 100644 --- a/src/lib/js/workout.svelte.ts +++ b/src/lib/js/workout.svelte.ts @@ -43,6 +43,8 @@ export interface StoredState { savedAt: number; // Date.now() at time of save restStartedAt: number | null; // Date.now() when rest timer started restTotal: number; // total rest duration in seconds + restExerciseIdx: number; // which exercise the rest timer belongs to + restSetIdx: number; // which set the rest timer belongs to } export interface RemoteState { @@ -54,6 +56,8 @@ export interface RemoteState { savedAt: number; restStartedAt: number | null; restTotal: number; + restExerciseIdx: number; + restSetIdx: number; } function createEmptySet(): WorkoutSet { @@ -95,6 +99,8 @@ export function createWorkout() { let _restTotal = $state(0); let _restActive = $state(false); let _restStartedAt: number | null = null; // absolute timestamp + let _restExerciseIdx = $state(-1); + let _restSetIdx = $state(-1); let _timerInterval: ReturnType | null = null; let _restInterval: ReturnType | null = null; @@ -115,7 +121,9 @@ export function createWorkout() { elapsed: _elapsed, savedAt: Date.now(), restStartedAt: _restActive ? _restStartedAt : null, - restTotal: _restTotal + restTotal: _restTotal, + restExerciseIdx: _restActive ? _restExerciseIdx : -1, + restSetIdx: _restActive ? _restSetIdx : -1 }); _onChangeCallback?.(); } @@ -163,6 +171,8 @@ export function createWorkout() { _restSeconds = 0; _restTotal = 0; _restStartedAt = null; + _restExerciseIdx = -1; + _restSetIdx = -1; } // Restore from localStorage on creation @@ -200,6 +210,8 @@ export function createWorkout() { _restTotal = stored.restTotal; _restSeconds = remaining; _restActive = true; + _restExerciseIdx = stored.restExerciseIdx ?? -1; + _restSetIdx = stored.restSetIdx ?? -1; _startRestInterval(); } } @@ -323,12 +335,14 @@ export function createWorkout() { } } - function startRestTimer(seconds: number) { + function startRestTimer(seconds: number, exerciseIdx: number = -1, setIdx: number = -1) { _stopRestTimer(); _restStartedAt = Date.now(); _restSeconds = seconds; _restTotal = seconds; _restActive = true; + _restExerciseIdx = exerciseIdx; + _restSetIdx = setIdx; _startRestInterval(); _persist(); } @@ -445,6 +459,8 @@ export function createWorkout() { _restTotal = remote.restTotal; _restSeconds = remaining; _restActive = true; + _restExerciseIdx = remote.restExerciseIdx ?? -1; + _restSetIdx = remote.restSetIdx ?? -1; _startRestInterval(); } } @@ -460,7 +476,9 @@ export function createWorkout() { elapsed: _elapsed, savedAt: Date.now(), restStartedAt: _restActive ? _restStartedAt : null, - restTotal: _restTotal + restTotal: _restTotal, + restExerciseIdx: _restActive ? _restExerciseIdx : -1, + restSetIdx: _restActive ? _restSetIdx : -1 }); } @@ -488,6 +506,8 @@ export function createWorkout() { get restTimerTotal() { return _restTotal; }, get restTimerActive() { return _restActive; }, get restStartedAt() { return _restStartedAt; }, + get restExerciseIdx() { return _restExerciseIdx; }, + get restSetIdx() { return _restSetIdx; }, restore, startFromTemplate, startEmpty, diff --git a/src/lib/js/workoutSync.svelte.ts b/src/lib/js/workoutSync.svelte.ts index b65fe74..5e49689 100644 --- a/src/lib/js/workoutSync.svelte.ts +++ b/src/lib/js/workoutSync.svelte.ts @@ -21,6 +21,8 @@ interface ServerWorkout { savedAt: number; restStartedAt: number | null; restTotal: number; + restExerciseIdx: number; + restSetIdx: number; } export function createWorkoutSync() { @@ -46,7 +48,9 @@ export function createWorkoutSync() { elapsed, savedAt: Date.now(), restStartedAt: workout.restStartedAt, - restTotal: workout.restTimerTotal + restTotal: workout.restTimerTotal, + restExerciseIdx: workout.restExerciseIdx, + restSetIdx: workout.restSetIdx }; } @@ -109,7 +113,9 @@ export function createWorkoutSync() { elapsed: doc.elapsed, savedAt: doc.savedAt, restStartedAt: doc.restStartedAt ?? null, - restTotal: doc.restTotal ?? 0 + restTotal: doc.restTotal ?? 0, + restExerciseIdx: doc.restExerciseIdx ?? -1, + restSetIdx: doc.restSetIdx ?? -1 }); status = 'synced'; @@ -225,7 +231,9 @@ export function createWorkoutSync() { elapsed: serverDoc.elapsed, savedAt: serverDoc.savedAt, restStartedAt: serverDoc.restStartedAt ?? null, - restTotal: serverDoc.restTotal ?? 0 + restTotal: serverDoc.restTotal ?? 0, + restExerciseIdx: serverDoc.restExerciseIdx ?? -1, + restSetIdx: serverDoc.restSetIdx ?? -1 }); } connectSSE(); diff --git a/src/routes/fitness/+layout.svelte b/src/routes/fitness/+layout.svelte index 32c21a8..31fa5f7 100644 --- a/src/routes/fitness/+layout.svelte +++ b/src/routes/fitness/+layout.svelte @@ -65,6 +65,12 @@ href="/fitness/workout/active" elapsed={formatElapsed(workout.elapsedSeconds)} paused={workout.paused} + syncStatus={sync.status} + onPauseToggle={() => workout.paused ? workout.resumeTimer() : workout.pauseTimer()} + restSeconds={workout.restTimerSeconds} + restTotal={workout.restTimerTotal} + onRestAdjust={(delta) => workout.adjustRestTimer(delta)} + onRestSkip={() => workout.cancelRestTimer()} /> {/if} diff --git a/src/routes/fitness/workout/active/+page.svelte b/src/routes/fitness/workout/active/+page.svelte index e58eece..e27600f 100644 --- a/src/routes/fitness/workout/active/+page.svelte +++ b/src/routes/fitness/workout/active/+page.svelte @@ -16,8 +16,6 @@ let nameEditing = $state(false); $effect(() => { if (!nameEditing) nameInput = workout.name; }); let showPicker = $state(false); - let restExerciseIdx = $state(-1); - let restSetIdx = $state(-1); /** @type {Record>>} */ let previousData = $state({}); @@ -314,8 +312,6 @@ function cancelRest() { workout.cancelRestTimer(); - restExerciseIdx = -1; - restSetIdx = -1; } // Fetch previous data for existing exercises on mount @@ -462,17 +458,6 @@ {:else if workout.active}
-
-
- - {formatElapsed(workout.elapsedSeconds)} - -
- -
- workout.adjustRestTimer(delta)} @@ -518,9 +503,7 @@ onToggleComplete={(setIdx) => { workout.toggleSetComplete(exIdx, setIdx); if (ex.sets[setIdx]?.completed && !workout.restTimerActive) { - restExerciseIdx = exIdx; - restSetIdx = setIdx; - workout.startRestTimer(ex.restTime); + workout.startRestTimer(ex.restTime, exIdx, setIdx); } }} onRemove={(setIdx) => workout.removeSet(exIdx, setIdx)} @@ -540,6 +523,17 @@ CANCEL WORKOUT
+ +
+
+ + {formatElapsed(workout.elapsedSeconds)} + +
+ +
{/if} @@ -768,16 +762,16 @@ flex-direction: column; gap: 1rem; } - .workout-topbar { + .workout-bottombar { display: flex; align-items: center; justify-content: space-between; position: sticky; - top: 3.5rem; + bottom: 0; background: var(--color-bg-primary); z-index: 10; - padding: 0.5rem 0; - border-bottom: 1px solid var(--color-border); + padding: 0.75rem 0; + border-top: 1px solid var(--color-border); } .topbar-left { display: flex;