[OCaml] High Intensity Training Online
feat make logging fields compact on mobile
Lay each movement's exercise name, load input, and reps input out in one compact grid row so a set fits on one line on mobile without scrolling. The name takes the remaining space; the two numeric inputs take fixed, tap-friendly columns. Replaces the two-column load/reps row.
ENHANCEMENTS.org
@@ -56,7 +56,7 @@
56
56
Animate page transitions between Home, Current Workout, and Logbook.
57
57
** DONE Confirm workout cancellation in a modal
58
58
Show a modal menu that confirms workout logging cancellation before cancelling.
59
Removed:
** TODO Make workout logging fields compact on mobile
59
Added:
** DONE Make workout logging fields compact on mobile
60
60
Use a grid layout so the exercise name, load input, and reps input appear on one row on mobile.
61
61
** TODO Allow spontaneous logbook feedback
62
62
Allow users to submit subjective feedback at any time from the Logbook. Suggest this feedback flow at the end of each workout, while keeping it available independently.
lib/web/assets/hito.css
@@ -308,7 +308,21 @@
308
308
.button-group form { margin: 0; }
309
309
.button-group input[type="submit"] { width: 100%; }
310
310
311
Removed:
.row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.8rem; }
311
Added:
/* A movement's fields as one compact grid row: exercise name, load, reps. On
312
Added:
mobile all three sit on one line — the name takes the remaining space and the
313
Added:
two numeric inputs take fixed, tap-friendly columns — so a set fits without
314
Added:
scrolling. The [.field] top margin is dropped here; the grid handles spacing.
315
Added:
The name column spans the full width above the inputs only when it would
316
Added:
otherwise be too cramped, at the narrowest widths. */
317
Added:
.exercise-fields {
318
Added:
display: grid;
319
Added:
grid-template-columns: minmax(0, 1fr) 5.5rem 5.5rem;
320
Added:
align-items: end;
321
Added:
gap: 0.6rem;
322
Added:
margin-top: 1rem;
323
Added:
}
324
Added:
.exercise-fields .exercise-name { margin: 0; align-self: center; }
325
Added:
.exercise-fields .field { margin-top: 0; }
312
326
313
327
/* The Home card shown while a workout is in progress. A full-width pressable
314
328
panel that returns the user to the current workout. It reads as a raised
@@ -533,7 +547,6 @@
533
547
}
534
548
535
549
@media (max-width: 26rem) {
536
Removed:
.row,
537
550
.button-group { grid-template-columns: 1fr; }
538
551
}
539
552
lib/web/pages.ml
@@ -485,15 +485,16 @@
485
485
| Some v -> [ Dream_html.string_attr "value" "%s" v ]);
486
486
]
487
487
488
Removed:
(* Load and reps sit side by side. The [.row] class is a two-column grid that
489
Removed:
collapses to one column on a narrow screen. Each movement of a pre-exhaust
490
Removed:
pair gets its own row, so both stay paired. The placeholders hint the field's
491
Removed:
unit, not a target: a set ends at failure, never at a number. *)
492
Removed:
let load_reps_row ~(errors : (string * string) list) ~load_id ~load_field
488
Added:
(* A movement's fields as one compact grid row: the exercise name, the load
489
Added:
input, and the reps input side by side. On mobile the [.exercise-fields] grid
490
Added:
keeps all three on one line so a set fits without scrolling; it widens on
491
Added:
larger screens. The name reads as a heading, the inputs as labelled fields. *)
492
Added:
let movement_row ~(errors : (string * string) list) ~name ~load_id ~load_field
493
493
~reps_id ~reps_field ?load_value ?reps_value ~reps_label () =
494
494
tag "div"
495
Removed:
[ class_ "row" ]
495
Added:
[ class_ "exercise-fields" ]
496
496
[
497
Added:
tag "p" [ class_ "exercise-name" ] [ txt "%s" name ];
497
498
input_row ?value:load_value ~placeholder:"kg"
498
499
~invalid:(List.mem_assoc load_field errors)
499
500
~input_id:load_id load_field "Load (kg)";
@@ -543,29 +544,26 @@
543
544
in
544
545
let fields =
545
546
match Prescription.Stimulus.delivery prescription with
546
Removed:
| Prescription.Stimulus.Single _ ->
547
Added:
| Prescription.Stimulus.Single exercise ->
547
548
[
548
Removed:
load_reps_row ~errors ~load_id:(field_id "load") ~load_field:"load"
549
Added:
movement_row ~errors ~name:(Exercise.name exercise)
550
Added:
~load_id:(field_id "load") ~load_field:"load"
549
551
~reps_id:(field_id "reps") ~reps_field:"reps"
550
552
?load_value:(load_of 0) ?reps_value:(reps_of 0)
551
553
~reps_label:"Reps to failure" ();
552
554
]
553
555
| Prescription.Stimulus.Pre_exhaust { isolation; compound } ->
554
556
[
555
Removed:
tag "p"
556
Removed:
[ class_ "exercise-name" ]
557
Removed:
[ txt "%s" (Exercise.name isolation) ];
558
Removed:
load_reps_row ~errors ~load_id:(field_id "iso_load")
559
Removed:
~load_field:"iso_load" ~reps_id:(field_id "iso_reps")
560
Removed:
~reps_field:"iso_reps" ?load_value:(load_of 0)
561
Removed:
?reps_value:(reps_of 0) ~reps_label:"Reps" ();
562
Removed:
tag "p"
563
Removed:
[ class_ "exercise-name" ]
564
Removed:
[ txt "%s" (Exercise.name compound) ];
565
Removed:
load_reps_row ~errors ~load_id:(field_id "comp_load")
566
Removed:
~load_field:"comp_load" ~reps_id:(field_id "comp_reps")
567
Removed:
~reps_field:"comp_reps" ?load_value:(load_of 1)
568
Removed:
?reps_value:(reps_of 1) ~reps_label:"Reps" ();
557
Added:
movement_row ~errors ~name:(Exercise.name isolation)
558
Added:
~load_id:(field_id "iso_load") ~load_field:"iso_load"
559
Added:
~reps_id:(field_id "iso_reps") ~reps_field:"iso_reps"
560
Added:
?load_value:(load_of 0) ?reps_value:(reps_of 0) ~reps_label:"Reps"
561
Added:
();
562
Added:
movement_row ~errors ~name:(Exercise.name compound)
563
Added:
~load_id:(field_id "comp_load") ~load_field:"comp_load"
564
Added:
~reps_id:(field_id "comp_reps") ~reps_field:"comp_reps"
565
Added:
?load_value:(load_of 1) ?reps_value:(reps_of 1) ~reps_label:"Reps"
566
Added:
();
569
567
]
570
568
in
571
569
tag "form"
test/test_web.ml
@@ -508,7 +508,7 @@
508
508
Alcotest.(check bool)
509
509
"shows bottom navigation" true
510
510
(contains ~substring:"bottom-nav" page) );
511
Removed:
( "effort forms lay load and reps out side by side",
511
Added:
( "effort forms lay name, load, and reps out in one compact row",
512
512
`Quick,
513
513
fun () ->
514
514
let c = client () in
@@ -521,8 +521,8 @@
521
521
in
522
522
let page = body (get c "/workout") in
523
523
Alcotest.(check bool)
524
Removed:
"uses the two-column row class" true
525
Removed:
(contains ~substring:"class=\"row\"" page);
524
Added:
"uses the compact exercise-fields grid" true
525
Added:
(contains ~substring:"class=\"exercise-fields\"" page);
526
526
Alcotest.(check bool)
527
527
"still labels load" true
528
528
(contains ~substring:"Load (kg)" page);