fitness: add exercise reorder buttons in template editor and active workout
All checks were successful
CI / update (push) Successful in 2m6s
All checks were successful
CI / update (push) Successful in 2m6s
This commit is contained in:
@@ -276,6 +276,14 @@ export function createWorkout() {
|
|||||||
_persist();
|
_persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveExercise(index: number, direction: number) {
|
||||||
|
const newIndex = index + direction;
|
||||||
|
if (newIndex < 0 || newIndex >= exercises.length) return;
|
||||||
|
const [item] = exercises.splice(index, 1);
|
||||||
|
exercises.splice(newIndex, 0, item);
|
||||||
|
_persist();
|
||||||
|
}
|
||||||
|
|
||||||
function addSet(exerciseIndex: number) {
|
function addSet(exerciseIndex: number) {
|
||||||
const ex = exercises[exerciseIndex];
|
const ex = exercises[exerciseIndex];
|
||||||
if (ex) {
|
if (ex) {
|
||||||
@@ -487,6 +495,7 @@ export function createWorkout() {
|
|||||||
resumeTimer,
|
resumeTimer,
|
||||||
addExercise,
|
addExercise,
|
||||||
removeExercise,
|
removeExercise,
|
||||||
|
moveExercise,
|
||||||
addSet,
|
addSet,
|
||||||
removeSet,
|
removeSet,
|
||||||
updateSet,
|
updateSet,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { Plus, Trash2, Play, Pencil, X, Save, CalendarClock, ChevronUp, ChevronDown, ArrowRight } from 'lucide-svelte';
|
import { Plus, Trash2, Play, Pencil, X, Save, CalendarClock, ChevronUp, ChevronDown, ArrowRight, GripVertical } from 'lucide-svelte';
|
||||||
import { getWorkout } from '$lib/js/workout.svelte';
|
import { getWorkout } from '$lib/js/workout.svelte';
|
||||||
import { getWorkoutSync } from '$lib/js/workoutSync.svelte';
|
import { getWorkoutSync } from '$lib/js/workoutSync.svelte';
|
||||||
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
|
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
|
||||||
@@ -135,6 +135,15 @@
|
|||||||
editorExercises = editorExercises.filter((_, i) => i !== idx);
|
editorExercises = editorExercises.filter((_, i) => i !== idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {number} idx @param {number} dir */
|
||||||
|
function editorMoveExercise(idx, dir) {
|
||||||
|
const newIdx = idx + dir;
|
||||||
|
if (newIdx < 0 || newIdx >= editorExercises.length) return;
|
||||||
|
const copy = [...editorExercises];
|
||||||
|
[copy[idx], copy[newIdx]] = [copy[newIdx], copy[idx]];
|
||||||
|
editorExercises = copy;
|
||||||
|
}
|
||||||
|
|
||||||
/** @param {number} exIdx */
|
/** @param {number} exIdx */
|
||||||
function editorAddSet(exIdx) {
|
function editorAddSet(exIdx) {
|
||||||
const metrics = getExerciseMetrics(getExerciseById(editorExercises[exIdx].exerciseId));
|
const metrics = getExerciseMetrics(getExerciseById(editorExercises[exIdx].exerciseId));
|
||||||
@@ -404,9 +413,17 @@
|
|||||||
<div class="editor-exercise">
|
<div class="editor-exercise">
|
||||||
<div class="editor-ex-header">
|
<div class="editor-ex-header">
|
||||||
<span class="editor-ex-name">{exercise?.name ?? ex.exerciseId}</span>
|
<span class="editor-ex-name">{exercise?.name ?? ex.exerciseId}</span>
|
||||||
<button class="remove-exercise" onclick={() => editorRemoveExercise(exIdx)} aria-label="Remove">
|
<div class="editor-ex-actions">
|
||||||
<Trash2 size={14} />
|
<button class="move-exercise" disabled={exIdx === 0} onclick={() => editorMoveExercise(exIdx, -1)} aria-label="Move up">
|
||||||
</button>
|
<ChevronUp size={14} />
|
||||||
|
</button>
|
||||||
|
<button class="move-exercise" disabled={exIdx === editorExercises.length - 1} onclick={() => editorMoveExercise(exIdx, 1)} aria-label="Move down">
|
||||||
|
<ChevronDown size={14} />
|
||||||
|
</button>
|
||||||
|
<button class="remove-exercise" onclick={() => editorRemoveExercise(exIdx)} aria-label="Remove">
|
||||||
|
<Trash2 size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-sets">
|
<div class="editor-sets">
|
||||||
{#each ex.sets as set, setIdx (setIdx)}
|
{#each ex.sets as set, setIdx (setIdx)}
|
||||||
@@ -841,6 +858,27 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
.editor-ex-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
.move-exercise {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.move-exercise:hover:not(:disabled) {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.move-exercise:disabled {
|
||||||
|
opacity: 0.2;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
.remove-exercise {
|
.remove-exercise {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { Plus, Trash2, Play, Pause, Trophy, Clock, Dumbbell, Route, RefreshCw, Check } from 'lucide-svelte';
|
import { Plus, Trash2, Play, Pause, Trophy, Clock, Dumbbell, Route, RefreshCw, Check, ChevronUp, ChevronDown } from 'lucide-svelte';
|
||||||
import { getWorkout } from '$lib/js/workout.svelte';
|
import { getWorkout } from '$lib/js/workout.svelte';
|
||||||
import { getWorkoutSync } from '$lib/js/workoutSync.svelte';
|
import { getWorkoutSync } from '$lib/js/workoutSync.svelte';
|
||||||
import { getExerciseById, getExerciseMetrics } from '$lib/data/exercises';
|
import { getExerciseById, getExerciseMetrics } from '$lib/data/exercises';
|
||||||
@@ -487,13 +487,21 @@
|
|||||||
<div class="exercise-block">
|
<div class="exercise-block">
|
||||||
<div class="exercise-header">
|
<div class="exercise-header">
|
||||||
<ExerciseName exerciseId={ex.exerciseId} />
|
<ExerciseName exerciseId={ex.exerciseId} />
|
||||||
<button
|
<div class="exercise-header-actions">
|
||||||
class="remove-exercise"
|
<button class="move-exercise" disabled={exIdx === 0} onclick={() => workout.moveExercise(exIdx, -1)} aria-label="Move up">
|
||||||
onclick={() => workout.removeExercise(exIdx)}
|
<ChevronUp size={16} />
|
||||||
aria-label="Remove exercise"
|
</button>
|
||||||
>
|
<button class="move-exercise" disabled={exIdx === workout.exercises.length - 1} onclick={() => workout.moveExercise(exIdx, 1)} aria-label="Move down">
|
||||||
<Trash2 size={16} />
|
<ChevronDown size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="remove-exercise"
|
||||||
|
onclick={() => workout.removeExercise(exIdx)}
|
||||||
|
aria-label="Remove exercise"
|
||||||
|
>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SetTable
|
<SetTable
|
||||||
@@ -837,6 +845,27 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
.exercise-header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
.move-exercise {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.move-exercise:hover:not(:disabled) {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.move-exercise:disabled {
|
||||||
|
opacity: 0.2;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
.remove-exercise {
|
.remove-exercise {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user