Skip to content

Commit

Permalink
feat: added warm-up calculator for exercises
Browse files Browse the repository at this point in the history
  • Loading branch information
WhyAsh5114 committed Dec 25, 2024
1 parent 1c9fc1d commit 6a20911
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import * as Popover from '$lib/components/ui/popover';
import { cn } from '$lib/utils';
import TimerIcon from 'virtual:icons/lucide/timer';
import MinusIcon from 'virtual:icons/lucide/minus';
import { workoutRunes } from '../../workoutRunes.svelte';
let lastCompletedSet = $derived(
Expand Down Expand Up @@ -42,6 +43,8 @@
<Popover.Content class="w-20 text-center font-semibold">
{#if lastCompletedSet}
{secondsToMS(secondsSinceLastSet)}
{:else}
<MinusIcon class="mx-auto" />
{/if}
</Popover.Content>
</Popover.Root>
123 changes: 123 additions & 0 deletions src/routes/workouts/manage/exercises/(components)/WarmUpDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<script lang="ts">
import ResponsiveDialog from '$lib/components/ResponsiveDialog.svelte';
import Button from '$lib/components/ui/button/button.svelte';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import * as Popover from '$lib/components/ui/popover';
import * as Table from '$lib/components/ui/table';
import { untrack } from 'svelte';
import CalculatorIcon from 'virtual:icons/lucide/calculator';
import { workoutRunes } from '../../workoutRunes.svelte';
type WarmUpSet = { reps: number; load: number; oneRMPercentage: number };
let exercise = $derived(workoutRunes.exerciseWarmUpDialogExercise);
let oneRepMax = $state<number>();
let totalWarmUpSets = $state(3);
let oneRepMaxCalculatorOpen = $state(false);
let weight = $state<number>();
let reps = $state<number>();
let warmUpSets = $state<WarmUpSet[]>();
$effect(() => {
if (workoutRunes.exerciseWarmUpDialogExercise) {
untrack(() => {
weight = workoutRunes.exerciseWarmUpDialogExercise?.sets[0].load ?? 0;
reps = workoutRunes.exerciseWarmUpDialogExercise?.sets[0].reps ?? 0;
warmUpSets = undefined;
calculateOneRepMax();
});
}
});
function calculateOneRepMax() {
if (weight === undefined || reps === undefined) return;
if (reps > 0) {
oneRepMax = Math.round(weight * (1 + reps / 30));
}
}
function submitOneRepMax(e: SubmitEvent) {
e.preventDefault();
calculateOneRepMax();
oneRepMaxCalculatorOpen = false;
}
function generateWarmUp(e: SubmitEvent) {
e.preventDefault();
if (oneRepMax === undefined || totalWarmUpSets === undefined) return;
warmUpSets = [];
for (let i = 0; i < totalWarmUpSets; i++) {
warmUpSets.push({
reps: Math.round(10 - (7 / (totalWarmUpSets - 1)) * i),
load: Math.round((oneRepMax * (0.4 + (0.4 / (totalWarmUpSets - 1)) * i)) / 2.5) * 2.5,
oneRMPercentage: 40 + (40 / (totalWarmUpSets - 1)) * i
});
}
}
</script>

<ResponsiveDialog title="Warm up" bind:open={workoutRunes.exerciseWarmUpDialogOpen}>
{#snippet description()}
{exercise?.name}
{/snippet}
{#if exercise}
<form class="grid grid-cols-2 gap-1" onsubmit={generateWarmUp}>
<div class="flex w-full max-w-sm flex-col gap-1.5">
<Label for="one-rep-max">One rep max</Label>
<Input required id="one-rep-max" type="number" step={0.25} placeholder="Type here" bind:value={oneRepMax} />
</div>
<div class="flex w-full max-w-sm flex-col gap-1.5">
<Label for="total-warm-up-sets">Total warm-up sets</Label>
<Input required id="total-warm-up-sets" type="number" placeholder="Type here" bind:value={totalWarmUpSets} />
</div>
<Popover.Root bind:open={oneRepMaxCalculatorOpen} onOpenChange={(v) => (oneRepMaxCalculatorOpen = v)}>
<Popover.Trigger asChild let:builder>
<Button variant="secondary" builders={[builder]} class="gap-2">
1-RM calculator
<CalculatorIcon />
</Button>
</Popover.Trigger>
<Popover.Content>
<form onsubmit={submitOneRepMax} class="grid grid-cols-2 gap-1">
<div class="flex w-full max-w-sm flex-col gap-1.5">
<Label for="weight">Weight</Label>
<Input required id="weight" type="number" step={0.25} placeholder="Type here" bind:value={weight} />
</div>
<div class="flex w-full max-w-sm flex-col gap-1.5">
<Label for="reps">Reps</Label>
<Input required id="reps" type="number" placeholder="Type here" bind:value={reps} />
</div>
<Button type="submit" class="col-span-full">Calculate</Button>
</form>
</Popover.Content>
</Popover.Root>
<Button type="submit">Generate warm up</Button>
</form>
{/if}
{#if warmUpSets}
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head></Table.Head>
<Table.Head>Reps</Table.Head>
<Table.Head>Load</Table.Head>
<Table.Head>%RM</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each warmUpSets as warmUpSet, idx}
<Table.Row>
<Table.Cell class="font-medium">{idx + 1}</Table.Cell>
<Table.Cell>{warmUpSet.reps}</Table.Cell>
<Table.Cell>{warmUpSet.load.toFixed(1)}</Table.Cell>
<Table.Cell>{warmUpSet.oneRMPercentage}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
{/if}
</ResponsiveDialog>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import SkipIcon from 'virtual:icons/lucide/skip-forward';
import DeleteIcon from 'virtual:icons/lucide/trash';
import HistoryIcon from 'virtual:icons/lucide/history';
import ChartIcon from 'virtual:icons/lucide/chart-no-axes-column-increasing';
import { workoutRunes } from '../../workoutRunes.svelte';
import CompareComponent from './CompareComponent.svelte';
import SetsComponent from './SetsComponent.svelte';
Expand Down Expand Up @@ -58,6 +59,9 @@
<DropdownMenu.Item class="gap-2" onclick={() => workoutRunes.setEditingExercise(exercise)}>
<EditIcon /> Edit
</DropdownMenu.Item>
<DropdownMenu.Item class="gap-2" onclick={() => workoutRunes.openExerciseWarmupDialog(exercise)}>
<ChartIcon /> Warm up
</DropdownMenu.Item>
<DropdownMenu.Item class="gap-2" onclick={skipSetsLeft}>
<SkipIcon /> Skip sets left
</DropdownMenu.Item>
Expand Down
2 changes: 2 additions & 0 deletions src/routes/workouts/manage/exercises/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import DndComponent from './(components)/DndComponent.svelte';
import ExerciseHistorySheet from './(components)/ExerciseHistorySheet.svelte';
import SetTimerComponent from './(components)/SetTimerComponent.svelte';
import WarmUpDialog from './(components)/WarmUpDialog.svelte';
let { data } = $props();
let reordering = $state(false);
Expand Down Expand Up @@ -169,3 +170,4 @@
</div>

<ExerciseHistorySheet />
<WarmUpDialog />
25 changes: 23 additions & 2 deletions src/routes/workouts/manage/workoutRunes.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { MesocycleExerciseTemplateWithoutIdsOrIndex } from '$lib/components/mesocycleAndExerciseSplit/commonTypes';
import type { Prisma } from '@prisma/client';
import type { FullWorkoutWithMesoData } from '../[workoutId]/+page.server';
import type { RouterOutputs } from '$lib/trpc/router';
import {
type WorkoutExerciseInProgress,
createWorkoutExerciseInProgressFromMesocycleExerciseTemplate
} from '$lib/utils/workoutUtils';
import type { Prisma } from '@prisma/client';
import type { FullWorkoutWithMesoData } from '../[workoutId]/+page.server';

export type PreviousWorkoutData =
RouterOutputs['workouts']['getWorkoutExercisesWithPreviousData']['previousWorkoutData'];
Expand All @@ -21,6 +21,8 @@ function createWorkoutRunes() {

let exerciseHistorySheetOpen = $state(false);
let exerciseHistorySheetName: string | undefined = $state();
let exerciseWarmUpDialogOpen = $state(false);
let exerciseWarmUpDialogExercise: WorkoutExerciseInProgress | undefined = $state();

if (globalThis.localStorage) {
const savedState = localStorage.getItem('workoutRunes');
Expand Down Expand Up @@ -88,6 +90,12 @@ function createWorkoutRunes() {
exerciseHistorySheetOpen = true;
}

function openExerciseWarmupDialog(exercise: WorkoutExerciseInProgress) {
if (workoutExercises === null) return;
exerciseWarmUpDialogOpen = true;
exerciseWarmUpDialogExercise = exercise;
}

function copyExerciseSetNumbersFromHistory(
exerciseFromHistory: Prisma.WorkoutExerciseGetPayload<{
include: { sets: { include: { miniSets: true } } };
Expand Down Expand Up @@ -185,6 +193,18 @@ function createWorkoutRunes() {
set exerciseHistorySheetOpen(value) {
exerciseHistorySheetOpen = value;
},
get exerciseWarmUpDialogOpen() {
return exerciseWarmUpDialogOpen;
},
set exerciseWarmUpDialogOpen(value) {
exerciseWarmUpDialogOpen = value;
},
get exerciseWarmUpDialogExercise() {
return exerciseWarmUpDialogExercise;
},
set exerciseWarmUpDialogExercise(value) {
exerciseWarmUpDialogExercise = value;
},
saveStoresToLocalStorage,
resetStores,
addExercise,
Expand All @@ -193,6 +213,7 @@ function createWorkoutRunes() {
deleteExercise,
loadWorkout,
openExerciseHistorySheet,
openExerciseWarmupDialog,
copyExerciseSetNumbersFromHistory
};
}
Expand Down

0 comments on commit 6a20911

Please sign in to comment.