Initial commit

This commit is contained in:
Mahesh Asolkar 2025-03-15 21:33:10 -07:00
parent 084df08932
commit 26fff26fef
6 changed files with 397 additions and 0 deletions

14
App.js Normal file
View File

@ -0,0 +1,14 @@
document.addEventListener('DOMContentLoaded', function() {
fetch('http://127.0.0.1:9898/routine.json')
.then(response => response.json())
.then(workout_json => {
// Use the JSON workout_json here
console.log(workout_json);
const wo = new Workout(document.getElementById("content-wrapper"),
workout_json);
wo.render();
})
.catch(error => {
console.error('Error fetching JSON:', error);
});
});

244
Workout.js Normal file
View File

@ -0,0 +1,244 @@
class Workout {
constructor(cont, plan) {
const wo_cont = document.createElement("div");
wo_cont.classList.add("workout-wrapper");
cont.appendChild(wo_cont);
this.cont = wo_cont;
this.plan = plan;
this.action = "";
this.set_idx = 0;
this.exercise_idx = 0;
this.workout_done = 0;
// this.show_debug_status = 1;
}
text_element(typ, txt) {
const ret_elem = document.createElement(typ);
ret_elem.textContent = txt;
return ret_elem;
}
render_workout_header() {
this.cont.appendChild(this.text_element("h1", this.plan.name));
}
render_debug_status() {
const dbg_div = document.createElement("div");
dbg_div.classList.add("app_debug");
const dt = document.createElement("p");
dt.setAttribute("id", "app_debug_div");
dbg_div.appendChild(dt);
this.cont.appendChild(dbg_div);
}
update_debug_info() {
if (this.show_debug_status) {
const dt = this.cont.querySelector("#app_debug_div");
const done_str = (this.workout_done === 1) ? "Done " : "";
dt.textContent = `${done_str}Action: ${this.action} Current: Set-${this.set_idx}/Exercise-${this.exercise_idx}`;
}
}
render_exercise(prnt, el, idx) {
const tblr = document.createElement("tr");
tblr.setAttribute("class", `exercise-row exercise-idx-${idx} exercise-pending`);
const sts_td = document.createElement("td");
const sts_cb = document.createElement("input");
sts_cb.setAttribute("type", "checkbox");
sts_td.appendChild(sts_cb);
sts_cb.setAttribute("disabled", true);
// Status
let col = sts_td;
col.classList.add("col-status");
tblr.appendChild(col);
// Name
col = this.text_element("td", el.name);
col.classList.add("col-name");
tblr.appendChild(col);
// Pattern/count
col = this.text_element("td", `${el.pattern} x ${el.count}`);
col.classList.add("col-pattern-count");
tblr.appendChild(col);
prnt.appendChild(tblr);
}
render_set(el, idx) {
const wo = this;
// Table with one set
const tbl = document.createElement("table");
tbl.setAttribute("class", `set-table set-table-idx-${idx}`);
tbl.appendChild(this.text_element("caption", el.name));
let show_header = 0;
if (show_header) {
// Header of the table
const tblh = document.createElement("thead");
const tblhr = document.createElement("tr");
let col = this.text_element("th", "Status");
col.classList.add("col-status");
tblhr.appendChild(col);
col = this.text_element("th", "Exercise");
col.classList.add("col-name");
tblhr.appendChild(col);
col = this.text_element("th", "Pattern/Count");
col.classList.add("col-pattern-count");
tblhr.appendChild(col);
tblh.appendChild(tblhr);
tbl.appendChild(tblh);
}
// Content of table - exercises in the set
const tblb = document.createElement("tbody");
el.exercise.forEach(function(el,idx) {
wo.render_exercise(tblb, el, idx);
});
tbl.appendChild(tblb);
this.cont.appendChild(tbl);
}
render_workout() {
const wo = this;
this.render_workout_header();
if (this.show_debug_status) {
this.render_debug_status();
}
this.plan.set.forEach(function(el,idx) {
wo.render_set(el, idx);
})
this.update_debug_info();
}
update_active_item() {
// Make current active exercise row inactive
const curr_active_tr = this.cont.querySelector(".exercise-row.active");
if (curr_active_tr != null) {
curr_active_tr.classList.remove("active");
}
// Set active exercise row
let qstr = `table.set-table-idx-${this.set_idx} tr.exercise-idx-${this.exercise_idx}`;
console.log(`Looking for ${qstr}`);
const next_active_tr = this.cont.querySelector(qstr);
console.log(next_active_tr);
if (next_active_tr != null) {
next_active_tr.classList.add("active");
}
// Mark workout done
qstr = `table tr.exercise-pending`;
let pending_trs = this.cont.querySelectorAll(qstr);
console.log(`Remaining to be done ${pending_trs.length}`);
if (pending_trs.length === 0) {
this.workout_done = 1;
}
}
handle_exercise_advance() {
const curr_exercise_max_idx = this.plan.set[this.set_idx].exercise.length-1;
const curr_set_max_idx = this.plan.set.length-1;
console.log(`workout done ${this.workout_done} curr_exercise_max_idx = ${curr_exercise_max_idx} curr_set_max_idx = ${curr_set_max_idx}`);
if (this.exercise_idx === curr_exercise_max_idx) {
if (this.set_idx === curr_set_max_idx) {
if (this.workout_done === 1) {
console.log("Exercise done");
}
} else {
this.set_idx += 1;
this.exercise_idx = 0;
}
} else {
this.exercise_idx += 1;
}
this.update_active_item();
}
handle_exercise_regress() {
const curr_set_max_idx = this.plan.set.length-1;
console.log(`workout done ${this.workout_done}`);
if (this.exercise_idx === 0) {
if (this.set_idx === 0) {
console.log("Exercise start");
} else {
this.set_idx -= 1;
this.exercise_idx = this.plan.set[this.set_idx].exercise.length-1;
}
} else {
this.exercise_idx -= 1;
}
this.update_active_item();
}
handle_exercise_done() {
// Set active exercise row
let qstr = `table.set-table-idx-${this.set_idx} tr.exercise-idx-${this.exercise_idx} input`;
console.log(`Looking for ${qstr}`);
const active_chkbox = this.cont.querySelector(qstr);
if (!active_chkbox.checked) {
active_chkbox.checked = true;
active_chkbox.disabled = true;
qstr = `table.set-table-idx-${this.set_idx} tr.exercise-idx-${this.exercise_idx}`;
const active_tr = this.cont.querySelector(qstr);
active_tr.classList.remove("exercise-pending");
}
this.update_active_item();
}
handle_key_press(app, ev) {
if (this.workout_done) {
return;
}
// Access the pressed key using ev.key
const key = ev.key;
// Advance exercise
if (key === 'n') {
app.action = `Key-${ev.key}`;
app.handle_exercise_advance();
} else if (key === 'N') {
app.action = `Key-${ev.key}`;
app.handle_exercise_regress();
} else if (key === ' ') {
app.action = `Key-${ev.key}`;
app.handle_exercise_done();
} else {
app.action = `UN Key-${ev.key}`;
}
app.update_debug_info();
}
render() {
const app = this;
this.render_workout();
this.update_active_item();
document.addEventListener('keydown', function(ev) {
app.handle_key_press(app, ev);
});
}
}

17
index.html Normal file
View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Workout</title>
<link rel="stylesheet" href="style.css" />
<script type="text/javascript" src="http://127.0.0.1:9898/Workout.js"></script>
<script type="text/javascript" src="http://127.0.0.1:9898/App.js"></script>
</head>
<body>
<div id="content-wrapper"></div>
</body>
</html>

4
launch.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
npx http-server /home/mahesh/dev/js/workout -p 9898 --cors='Access-Control-Allow-Origin'

37
routine.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "Daily Build and Burn",
"set": [
{ "name": "Build",
"exercise": [
{ "name": "Forward lunge with dumbell twist",
"count": 10,
"pattern": "each side"},
{ "name": "A and Y core on floor",
"count": 10,
"pattern": "each end"},
{ "name": "Bow and arrow pull with bands",
"count": 10,
"pattern": "each side"},
{ "name": "Push ups",
"count": 10,
"pattern": ""}
]
},
{ "name": "Burn",
"exercise": [
{ "name": "Bicep curls",
"count": 10,
"pattern": "each side"},
{ "name": "Russian twists",
"count": 10,
"pattern": ""},
{ "name": "Chin up core",
"count": 10,
"pattern": ""},
{ "name": "Tricep curls on the floor",
"count": 15,
"pattern": ""}
]
}
]
}

81
style.css Normal file
View File

@ -0,0 +1,81 @@
:root {
--table-border: #ccc;
--table-grade-top: #eee;
--table-grade-bottom: #ddd;
--table-shadow: #ccc;
--table-active: #363;
--table-active-grade: #474;
}
html {
font-family: "Iosevka", monospace;
}
.workout-wrapper {
width: 60em;
margin: auto;
padding: 1.5em;
}
.workout-wrapper h1 {
text-align: center;
}
.workout-wrapper table {
margin-left: auto;
margin-right: auto;
margin-top: 2em;
margin-bottom: 1em;
width: 95%;
border-spacing: 0px;
border-collapse: collapse;
box-shadow: 1px 1px 7px var(--table-shadow);
}
.workout-wrapper table caption {
font-size: large;
text-align: left;
padding: 7px 7px 7px 10px;
font-weight: bold;
border: thin solid var(--table-border);
margin: 0;
border-radius: 0.3em 0.3em 0 0;
background: linear-gradient(var(--table-grade-top), var(--table-grade-bottom));
box-shadow: 1px 1px 7px var(--table-shadow);
}
.workout-wrapper .set-table thead tr {
border: thin solid var(--table-border);
}
.workout-wrapper .set-table tbody tr {
border-bottom: thin solid var(--table-border-top);
}
.workout-wrapper .set-table tbody tr.active {
background: linear-gradient(var(--table-active), var(--table-active-grade));
color: white;
border: 1px solid var(--table-active);
box-shadow: 1px 1px 15px var(--table-active);
}
.workout-wrapper .set-table td,
.workout-wrapper .set-table th {
text-align: left;
padding: 5px;
}
.workout-wrapper .set-table th {
background-color: var(--table-border);
}
.workout-wrapper .set-table .col-status {
width: 10%;
}
.workout-wrapper .set-table .col-name {
width: 60%;
}
.workout-wrapper .set-table .col-pattern-count {
width: 30%;
}
/* Global */
.workout-wrapper #app_debug_div {
border: thin dotted blue;
color: grey;
font-size: x-small;
}