Compare commits
15 Commits
084df08932
...
main
Author | SHA1 | Date | |
---|---|---|---|
f96143757a
|
|||
fb4acf962f
|
|||
6cfce18819
|
|||
c46b37756a
|
|||
115728fe58
|
|||
7d7c89a0c9
|
|||
793689e29c
|
|||
20dd0de6c0
|
|||
fc65a0d5e0 | |||
e2afa31f88 | |||
6389a3695f | |||
924752d772 | |||
e253fd57c3 | |||
114f92c3d0 | |||
26fff26fef |
19
App.js
Normal file
19
App.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
fetch('collection.json')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(collection_json => {
|
||||||
|
// Generate Exercise plan
|
||||||
|
const egen = new ExerciseGenerator(collection_json);
|
||||||
|
|
||||||
|
// 2 sets of 4 exercises each, 3 reps per set
|
||||||
|
let workout_json = egen.generate_build_and_burn(2, 4, 3);
|
||||||
|
console.log(workout_json);
|
||||||
|
|
||||||
|
const wo = new Workout(document.getElementById("content-wrapper"),
|
||||||
|
workout_json);
|
||||||
|
wo.render();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching JSON:', error);
|
||||||
|
});
|
||||||
|
});
|
132
ExerciseGenerator.js
Normal file
132
ExerciseGenerator.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
class ExerciseGenerator {
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
constructor(collection) {
|
||||||
|
this.collection = collection;
|
||||||
|
|
||||||
|
// console.log(this.collection);
|
||||||
|
this.used_exercise_ids = new Array;
|
||||||
|
|
||||||
|
this.ex_nxt;
|
||||||
|
this.ex_last = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
exercise_id_used(ex) {
|
||||||
|
return (this.used_exercise_ids.filter(function(v) { return (v == ex.id) }).length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
exercise_related_to(ex) {
|
||||||
|
let related = false;
|
||||||
|
let gen = this;
|
||||||
|
|
||||||
|
if (gen.ex_last == null) {
|
||||||
|
return related;
|
||||||
|
}
|
||||||
|
|
||||||
|
ex.focus.forEach(function(e) {
|
||||||
|
related |= gen.ex_last.focus.includes(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
return related;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
get_next_exercise() {
|
||||||
|
let scope = this.collection.exercises;
|
||||||
|
let gen = this;
|
||||||
|
|
||||||
|
scope = this.collection.exercises.filter(function(e) {
|
||||||
|
let pick = true;
|
||||||
|
|
||||||
|
// Pick if exercise is not already used
|
||||||
|
pick &= (gen.exercise_id_used(e) == false);
|
||||||
|
|
||||||
|
// Pick if nor related to the previous one
|
||||||
|
pick &= (gen.exercise_related_to(e) == false);
|
||||||
|
|
||||||
|
return pick
|
||||||
|
});
|
||||||
|
gen.shuffle_exercise_array(scope);
|
||||||
|
// gen.show_exercise_array(scope);
|
||||||
|
|
||||||
|
return scope[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
show_exercise_array(arr) {
|
||||||
|
let gen = this;
|
||||||
|
|
||||||
|
arr.map(function(it) {
|
||||||
|
gen.show_exercise(it);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
show_exercise(ex) {
|
||||||
|
console.log(`${ex.id} : ${ex.name} [` + ex.focus.join(", ") + ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
// From answer on https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
||||||
|
shuffle_exercise_array(arr) {
|
||||||
|
let currentIndex = arr.length;
|
||||||
|
|
||||||
|
// While there remain elements to shuffle...
|
||||||
|
while (currentIndex != 0) {
|
||||||
|
// Pick a remaining element...
|
||||||
|
let randomIndex = Math.floor(Math.random() * currentIndex);
|
||||||
|
currentIndex--;
|
||||||
|
|
||||||
|
// And swap it with the current element.
|
||||||
|
[arr[currentIndex], arr[randomIndex]] = [
|
||||||
|
arr[randomIndex], arr[currentIndex]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
generate_set(size) {
|
||||||
|
let set_exercises = Array();
|
||||||
|
let lim = size;
|
||||||
|
|
||||||
|
this.ex_last = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
this.ex_nxt = this.get_next_exercise();
|
||||||
|
if (this.ex_nxt == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.used_exercise_ids.push(this.ex_nxt.id);
|
||||||
|
set_exercises.push(this.ex_nxt);
|
||||||
|
this.ex_last = this.ex_nxt;
|
||||||
|
lim--;
|
||||||
|
} while ((this.ex_nxt != null) && (lim > 0));
|
||||||
|
|
||||||
|
this.show_exercise_array(set_exercises);
|
||||||
|
console.log(this.used_exercise_ids);
|
||||||
|
|
||||||
|
return set_exercises;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
generate_build_and_burn(sets, exercises, reps) {
|
||||||
|
let wo_plan = {
|
||||||
|
"name": "Daily Build and Burn",
|
||||||
|
"set": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= sets; i ++) {
|
||||||
|
let set = {
|
||||||
|
"name": `Set ${i}`,
|
||||||
|
"count": reps,
|
||||||
|
"exercise": this.generate_set(exercises)
|
||||||
|
}
|
||||||
|
wo_plan.set.push(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wo_plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
454
Workout.js
Normal file
454
Workout.js
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
class Workout {
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
constructor(cont, plan) {
|
||||||
|
const wo_cont = document.createElement("div");
|
||||||
|
wo_cont.classList.add("workout-wrapper");
|
||||||
|
cont.appendChild(wo_cont);
|
||||||
|
|
||||||
|
// Inputs to the class. Workout plan and container to display it in
|
||||||
|
this.arena = cont;
|
||||||
|
this.container = wo_cont;
|
||||||
|
this.plan = plan;
|
||||||
|
|
||||||
|
// Status tracking information
|
||||||
|
this.action = "";
|
||||||
|
this.set_idx = 0;
|
||||||
|
this.set_repeat_idx = 0;
|
||||||
|
this.exercise_idx = 0;
|
||||||
|
this.workout_done = 0;
|
||||||
|
this.show_header = 0;
|
||||||
|
this.show_debug_status = 0;
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
this.vwidth = window.innerWidth;
|
||||||
|
this.vheight = window.innerHeight;
|
||||||
|
|
||||||
|
let qstring = window.location.search;
|
||||||
|
let url_params = new URLSearchParams(qstring);
|
||||||
|
|
||||||
|
// Enable debug
|
||||||
|
this.show_debug_status = url_params.has("dbg");
|
||||||
|
// Enable table header
|
||||||
|
this.show_header = url_params.has("hdr");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
text_element(typ, txt) {
|
||||||
|
const el = document.createElement(typ);
|
||||||
|
el.textContent = txt;
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
progress_indicator(id) {
|
||||||
|
const indicator = this.text_element("div", " ");
|
||||||
|
indicator.setAttribute("id", id);
|
||||||
|
indicator.classList.add("progress-indicator");
|
||||||
|
indicator.classList.add("pending");
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
render_workout_header() {
|
||||||
|
const hdr = this.text_element("h1", this.plan.name);
|
||||||
|
this.container.appendChild(hdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
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.container.appendChild(dbg_div);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
update_debug_info() {
|
||||||
|
if (this.show_debug_status) {
|
||||||
|
const dt = this.container.querySelector("#app_debug_div");
|
||||||
|
const curr_set_repeat_max_idx = this.plan.set[this.set_idx].count-1;
|
||||||
|
const done_str = (this.workout_done === 1) ? "Done " : "";
|
||||||
|
dt.textContent = `${done_str}Action: ${this.action} Current: Set-${this.set_idx} [${this.set_repeat_idx}/${curr_set_repeat_max_idx}]/Exercise-${this.exercise_idx}`;
|
||||||
|
dt.textContent += `\nView ${this.vwidth} x ${this.vheight}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
render_exercise(prnt, el, idx) {
|
||||||
|
const tblr = document.createElement("tr");
|
||||||
|
tblr.setAttribute("class", `exercise-row exercise-idx-${idx} exercise-pending`);
|
||||||
|
|
||||||
|
// Status
|
||||||
|
const sts_td = document.createElement("td");
|
||||||
|
const sts_cb = document.createElement("input");
|
||||||
|
sts_cb.setAttribute("type", "checkbox");
|
||||||
|
sts_cb.setAttribute("disabled", true);
|
||||||
|
sts_td.appendChild(sts_cb);
|
||||||
|
tblr.appendChild(sts_td);
|
||||||
|
|
||||||
|
// Name
|
||||||
|
const name_td = document.createElement("td");
|
||||||
|
let ex_name = this.text_element("div", el.name);
|
||||||
|
ex_name.classList.add("col-name");
|
||||||
|
let ex_patt = this.text_element("div", `${el.pattern} x ${el.count}`);
|
||||||
|
ex_patt.classList.add("col-patt");
|
||||||
|
name_td.appendChild(ex_name);
|
||||||
|
name_td.appendChild(ex_patt);
|
||||||
|
tblr.appendChild(name_td);
|
||||||
|
el.done = 0;
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
|
||||||
|
const tcap = this.text_element("caption", el.name);
|
||||||
|
for (var i = 0; i < el.count; i++) {
|
||||||
|
const set_indicator = this.progress_indicator(`set-${idx}-${i}-indicator`);
|
||||||
|
tcap.appendChild(set_indicator);
|
||||||
|
}
|
||||||
|
tbl.appendChild(tcap);
|
||||||
|
|
||||||
|
if (this.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);
|
||||||
|
|
||||||
|
tblh.appendChild(tblhr);
|
||||||
|
tbl.appendChild(tblh);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content of table - exercises in the set
|
||||||
|
const tblb = document.createElement("tbody");
|
||||||
|
|
||||||
|
el.exercise.forEach(function(ex_el,idx) {
|
||||||
|
wo.render_exercise(tblb, ex_el, idx);
|
||||||
|
});
|
||||||
|
tbl.appendChild(tblb);
|
||||||
|
|
||||||
|
this.container.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.render_fab();
|
||||||
|
|
||||||
|
this.update_debug_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
update_active_item() {
|
||||||
|
let wo = this;
|
||||||
|
// Make current active exercise row inactive
|
||||||
|
const curr_active_tr = this.container.querySelector(".exercise-row.active");
|
||||||
|
let wo_set_idx = this.set_idx;
|
||||||
|
let wo_exercise_idx = this.exercise_idx;
|
||||||
|
let wo_set_repeat_idx = this.set_repeat_idx;
|
||||||
|
|
||||||
|
const max_set_idx = this.plan.set.length-1;
|
||||||
|
const max_set_repeat_idx = this.plan.set[max_set_idx].count-1;
|
||||||
|
const max_exercise_idx = this.plan.set[max_set_idx].exercise.length-1;
|
||||||
|
|
||||||
|
if (curr_active_tr != null) {
|
||||||
|
curr_active_tr.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set active exercise row
|
||||||
|
let qstr = `table.set-table-idx-${wo_set_idx} tr.exercise-idx-${wo_exercise_idx}`;
|
||||||
|
console.log(`update_active_item - Looking for ${qstr}`);
|
||||||
|
const next_active_tr = this.container.querySelector(qstr);
|
||||||
|
if (next_active_tr != null) {
|
||||||
|
next_active_tr.classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set set repeat indicators
|
||||||
|
let set_tables = this.container.querySelectorAll(".set-table");
|
||||||
|
set_tables.forEach(function(stbl, tidx) {
|
||||||
|
let indicators = stbl.querySelectorAll(".progress-indicator");
|
||||||
|
indicators.forEach(function(ids, iidx) {
|
||||||
|
let ind_selector = `#set-${tidx}-${iidx}-indicator`;
|
||||||
|
console.log(`update_active_item - Ind: ${ind_selector} for set_idx ${wo_set_idx} repeat_idx ${wo_set_repeat_idx}`);
|
||||||
|
|
||||||
|
// if (stbl.querySelector(ind_selector) == null) return;
|
||||||
|
|
||||||
|
console.log(`update_active_item - Ind: tidx ${tidx} is ?? set_idx ${wo_set_idx}`);
|
||||||
|
if (tidx < wo_set_idx) {
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("pending");
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("active");
|
||||||
|
stbl.querySelector(ind_selector).classList.add("done");
|
||||||
|
}
|
||||||
|
else if (tidx > wo_set_idx) {
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("done");
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("active");
|
||||||
|
stbl.querySelector(ind_selector).classList.add("pending");
|
||||||
|
}
|
||||||
|
else if (tidx == wo_set_idx) {
|
||||||
|
const ex_chkbox = stbl.querySelector(`tr.exercise-idx-${wo_exercise_idx} input`);
|
||||||
|
if ((iidx < wo_set_repeat_idx)
|
||||||
|
// || ((iidx == wo_set_repeat_idx) && ex_chkbox.checked)
|
||||||
|
|| ((iidx == wo_set_repeat_idx) && wo.plan.set[tidx].exercise[wo_exercise_idx].done)
|
||||||
|
){
|
||||||
|
console.log(`update_active_item - Ind: iidx ${iidx} is < repeat_idx ${wo_set_repeat_idx} DONE`);
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("pending");
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("active");
|
||||||
|
stbl.querySelector(ind_selector).classList.add("done");
|
||||||
|
}
|
||||||
|
else if (iidx > wo_set_repeat_idx) {
|
||||||
|
console.log(`update_active_item - Ind: iidx ${iidx} is > repeat_idx ${wo_set_repeat_idx} PENDING`);
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("done");
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("active");
|
||||||
|
stbl.querySelector(ind_selector).classList.add("pending");
|
||||||
|
} else {
|
||||||
|
console.log(`update_active_item - Ind: iidx ${iidx} is = repeat_idx ${wo_set_repeat_idx} ACTIVE`);
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("pending");
|
||||||
|
stbl.querySelector(ind_selector).classList.remove("done");
|
||||||
|
stbl.querySelector(ind_selector).classList.add("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.plan.set.forEach(function(el,idx) {
|
||||||
|
let sidx = idx;
|
||||||
|
el.exercise.forEach(function(eel, eidx) {
|
||||||
|
let qstr = `table.set-table-idx-${sidx} tr.exercise-idx-${eidx}`;
|
||||||
|
const active_tr = wo.container.querySelector(qstr);
|
||||||
|
active_tr.classList.remove("exercise-pending");
|
||||||
|
|
||||||
|
qstr = `table.set-table-idx-${sidx} tr.exercise-idx-${eidx} input`;
|
||||||
|
const active_chkbox = wo.container.querySelector(qstr);
|
||||||
|
active_chkbox.disabled = true;
|
||||||
|
active_chkbox.checked = (wo.plan.set[sidx].exercise[eidx].done == 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark workout done
|
||||||
|
if (this.workout_done == 1) {
|
||||||
|
const wo_h1 = this.container.querySelector(".workout-wrapper h1");
|
||||||
|
wo_h1.classList.add("done");
|
||||||
|
this.container.classList.add("done");
|
||||||
|
let buttons = this.container.querySelectorAll("button");
|
||||||
|
buttons.forEach(function(bttn, bidx) {
|
||||||
|
bttn.disabled = true;
|
||||||
|
});
|
||||||
|
const wo_done = this.text_element("div", "");
|
||||||
|
wo_done.setAttribute("id", "workout-done");
|
||||||
|
const wo_done_txt = this.text_element("div", "Workout done!");
|
||||||
|
wo_done_txt.classList.add("workout-done-txt");
|
||||||
|
wo_done.appendChild(wo_done_txt);
|
||||||
|
this.arena.appendChild(wo_done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_exercise_advance() {
|
||||||
|
const curr_exercise_max_idx = this.plan.set[this.set_idx].exercise.length-1;
|
||||||
|
const curr_set_repeat_max_idx = this.plan.set[this.set_idx].count-1;
|
||||||
|
const curr_set_max_idx = this.plan.set.length-1;
|
||||||
|
|
||||||
|
console.log(`handle_exercise_advance - Workout done ${this.workout_done} curr_exercise_max_idx = ${curr_exercise_max_idx} curr_set_max_idx = ${curr_set_max_idx}`);
|
||||||
|
|
||||||
|
this.handle_exercise_done();
|
||||||
|
|
||||||
|
if (this.exercise_idx === curr_exercise_max_idx) {
|
||||||
|
if (this.set_repeat_idx == curr_set_repeat_max_idx) {
|
||||||
|
if (this.set_idx === curr_set_max_idx) {
|
||||||
|
if (this.workout_done === 1) {
|
||||||
|
console.log("handle_exercise_advance - Exercise done");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.set_idx += 1;
|
||||||
|
this.set_repeat_idx = 0;
|
||||||
|
this.exercise_idx = 0;
|
||||||
|
this.handle_set_undone();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.set_repeat_idx += 1;
|
||||||
|
this.exercise_idx = 0;
|
||||||
|
this.handle_set_undone();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.exercise_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_exercise_regress() {
|
||||||
|
const curr_set_max_idx = this.plan.set.length-1;
|
||||||
|
const curr_set_repeat_max_idx = this.plan.set[this.set_idx].count-1;
|
||||||
|
|
||||||
|
if (this.exercise_idx === 0) {
|
||||||
|
if (this.set_repeat_idx === 0) {
|
||||||
|
if (this.set_idx === 0) {
|
||||||
|
console.log("handle_exercise_regress - Exercise start");
|
||||||
|
} else {
|
||||||
|
this.set_idx -= 1;
|
||||||
|
this.set_repeat_idx = this.plan.set[this.set_idx].count-1;
|
||||||
|
this.exercise_idx = this.plan.set[this.set_idx].exercise.length-1;
|
||||||
|
this.handle_set_done();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.set_repeat_idx -= 1;
|
||||||
|
this.exercise_idx = this.plan.set[this.set_idx].exercise.length-1;
|
||||||
|
this.handle_set_done();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.exercise_idx -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handle_exercise_undone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_exercise_done() {
|
||||||
|
if (this.plan.set[this.set_idx].exercise[this.exercise_idx].done == 0) {
|
||||||
|
this.plan.set[this.set_idx].exercise[this.exercise_idx].done = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(this.plan.set[this.set_idx]);
|
||||||
|
|
||||||
|
this.handle_workout_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_exercise_undone() {
|
||||||
|
if (this.plan.set[this.set_idx].exercise[this.exercise_idx].done == 1) {
|
||||||
|
this.plan.set[this.set_idx].exercise[this.exercise_idx].done = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_set_done() {
|
||||||
|
// Set active exercise row
|
||||||
|
let qstr = `table.set-table-idx-${this.set_idx} tr input`;
|
||||||
|
console.log(`handle_set_done - Looking for ${qstr}`);
|
||||||
|
|
||||||
|
this.plan.set[this.set_idx].exercise.forEach(function(ex, idx) {
|
||||||
|
ex.done = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_set_undone() {
|
||||||
|
// Set active exercise row
|
||||||
|
let qstr = `table.set-table-idx-${this.set_idx} tr input`;
|
||||||
|
console.log(`handle_set_undone - Looking for ${qstr}`);
|
||||||
|
|
||||||
|
this.plan.set[this.set_idx].exercise.forEach(function(ex, idx) {
|
||||||
|
ex.done = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_workout_done() {
|
||||||
|
let wo = this;
|
||||||
|
|
||||||
|
const max_set_idx = this.plan.set.length-1;
|
||||||
|
const max_set_repeat_idx = this.plan.set[max_set_idx].count-1;
|
||||||
|
const max_exercise_idx = this.plan.set[max_set_idx].exercise.length-1;
|
||||||
|
|
||||||
|
if ((this.set_idx === max_set_idx)
|
||||||
|
&& (this.set_repeat_idx === max_set_repeat_idx)
|
||||||
|
&& (this.exercise_idx === max_exercise_idx)
|
||||||
|
&& (this.plan.set[this.set_idx].exercise[this.exercise_idx].done == 1)) {
|
||||||
|
this.workout_done = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`handle_workout_done - ${this.workout_done}`);
|
||||||
|
console.log(` compare: ${this.set_idx} === ${max_set_idx}`);
|
||||||
|
console.log(` compare: ${this.set_repeat_idx} === ${max_set_idx}`);
|
||||||
|
console.log(` compare: ${this.exercise_idx} === ${max_exercise_idx}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
handle_action(act) {
|
||||||
|
if (this.workout_done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.action = act;
|
||||||
|
if ((act == "Button-Advance") || (act == "Key-n")) {
|
||||||
|
this.handle_exercise_advance();
|
||||||
|
} else if ((act == "Button-Regress") || (act == "Key-N")) {
|
||||||
|
this.handle_exercise_regress();
|
||||||
|
} else {
|
||||||
|
this.action = `Unsupported action - ${act}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update_debug_info();
|
||||||
|
this.update_active_item();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
render_fab() {
|
||||||
|
const fab_wrapper = document.createElement("div");
|
||||||
|
fab_wrapper.setAttribute("id", "fab-wrapper");
|
||||||
|
|
||||||
|
const fab_regress = this.text_element("button", " ");
|
||||||
|
fab_regress.classList.add("fab-button");
|
||||||
|
fab_regress.setAttribute("id", "fab-regress");
|
||||||
|
const fab_advance = this.text_element("button", " ");
|
||||||
|
fab_advance.classList.add("fab-button");
|
||||||
|
fab_advance.setAttribute("id", "fab-advance");
|
||||||
|
|
||||||
|
fab_wrapper.appendChild(fab_regress);
|
||||||
|
fab_wrapper.appendChild(fab_advance);
|
||||||
|
|
||||||
|
this.container.appendChild(fab_wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------
|
||||||
|
render() {
|
||||||
|
const app = this;
|
||||||
|
this.render_workout();
|
||||||
|
this.update_active_item();
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function(ev) {
|
||||||
|
app.handle_action(`Key-${ev.key}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fab_advance = this.container.querySelector("#fab-advance");
|
||||||
|
fab_advance.addEventListener('click', function() {
|
||||||
|
app.handle_action('Button-Advance');
|
||||||
|
});
|
||||||
|
const fab_regress = this.container.querySelector("#fab-regress");
|
||||||
|
fab_regress.addEventListener('click', function() {
|
||||||
|
app.handle_action('Button-Regress');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
// Dimensions
|
||||||
|
app.vwidth = window.innerWidth;
|
||||||
|
app.vheight = window.innerHeight;
|
||||||
|
app.update_debug_info();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
65
collection.json
Normal file
65
collection.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "Collection of exercises",
|
||||||
|
"exercises": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Leg triads with dumbell",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "each side",
|
||||||
|
"focus": ["legs"]},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Forward lunge with dumbell twist",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "each side",
|
||||||
|
"focus": ["legs"]},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "A and Y core on floor",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "each end",
|
||||||
|
"focus": ["core"]},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Bow and arrow pull with bands",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "each side",
|
||||||
|
"focus": ["arms"]},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Push ups",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "",
|
||||||
|
"focus": ["arms", "chest"]},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Bicep curls",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "each side",
|
||||||
|
"focus": ["arms"]},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "Russian twists",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "",
|
||||||
|
"focus": ["core"]},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "Chin up core",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "",
|
||||||
|
"focus": ["arms", "core"]},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Tricep curls on the floor",
|
||||||
|
"count": 15,
|
||||||
|
"pattern": "",
|
||||||
|
"focus": ["arms"]},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Leg curl on floor",
|
||||||
|
"count": 10,
|
||||||
|
"pattern": "each leg",
|
||||||
|
"focus": ["legs"]}
|
||||||
|
]
|
||||||
|
}
|
21
index.html
Normal file
21
index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>Workout</title>
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Boldonse&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
|
||||||
|
<script type="text/javascript" src="ExerciseGenerator.js"></script>
|
||||||
|
<script type="text/javascript" src="Workout.js"></script>
|
||||||
|
<script type="text/javascript" src="App.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content-wrapper"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
4
launch.sh
Executable file
4
launch.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
npx http-server /home/mahesh/dev/js/workout -p 9898 --cors='Access-Control-Allow-Origin'
|
||||||
|
|
236
style.css
Normal file
236
style.css
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
:root {
|
||||||
|
--table-border: #ccc;
|
||||||
|
--table-grade-top: #eee;
|
||||||
|
--table-grade-bottom: #ddd;
|
||||||
|
--table-shadow: #ccc;
|
||||||
|
|
||||||
|
--table-active: #04a;
|
||||||
|
--table-active-grade: #037;
|
||||||
|
|
||||||
|
--indicator-pending: #aaa;
|
||||||
|
--indicator-pending-grade: #888;
|
||||||
|
|
||||||
|
--indicator-active: #04a;
|
||||||
|
--indicator-active-grade: #03a;
|
||||||
|
|
||||||
|
--indicator-done: #5a5;
|
||||||
|
--indicator-done-grade: #282;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dimensions for mobile screen */
|
||||||
|
.workout-wrapper {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
.workout-wrapper.done {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-wrapper table {
|
||||||
|
width: 97%;
|
||||||
|
}
|
||||||
|
.workout-wrapper input[type=checkbox] {
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles for larger screens (e.g., tablets and desktops) */
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
body {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-wrapper {
|
||||||
|
width: 1200px;
|
||||||
|
}
|
||||||
|
.workout-wrapper table {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-wrapper {
|
||||||
|
margin: auto;
|
||||||
|
padding: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-wrapper h1 {
|
||||||
|
font-family: "Boldonse", system-ui;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: underline var(--indicator-pending) solid 0.2em;
|
||||||
|
}
|
||||||
|
.workout-wrapper h1.done {
|
||||||
|
text-decoration: underline var(--indicator-done) solid 0.2em;
|
||||||
|
}
|
||||||
|
.workout-wrapper table {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
border-spacing: 0px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
box-shadow: 1px 1px 7px var(--table-shadow);
|
||||||
|
}
|
||||||
|
.workout-wrapper table caption {
|
||||||
|
font-family: "Boldonse", system-ui;
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
font-size: 1.5em;
|
||||||
|
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 10px 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: 20%;
|
||||||
|
}
|
||||||
|
.workout-wrapper .set-table .col-name {
|
||||||
|
width: 80%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Workout indicator */
|
||||||
|
.workout-wrapper .progress-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
width: 1em;
|
||||||
|
line-height: 1em;
|
||||||
|
border: thin solid gray;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
font-size: 0.5em;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
.workout-wrapper .progress-indicator.active {
|
||||||
|
background: linear-gradient(var(--indicator-active), var(--indicator-active-grade));
|
||||||
|
border: 1px solid var(--indicator-active);
|
||||||
|
box-shadow: 1px 1px 7px var(--indicator-active-grade);
|
||||||
|
}
|
||||||
|
.workout-wrapper .progress-indicator.pending {
|
||||||
|
background: linear-gradient(var(--indicator-pending), var(--indicator-pending-grade));
|
||||||
|
border: 1px solid var(--indicator-pending);
|
||||||
|
box-shadow: 1px 1px 7px var(--indicator-pending-grade);
|
||||||
|
}
|
||||||
|
.workout-wrapper .progress-indicator.done {
|
||||||
|
background: linear-gradient(var(--indicator-done), var(--indicator-done-grade));
|
||||||
|
border: 1px solid var(--indicator-done);
|
||||||
|
box-shadow: 1px 1px 7px var(--indicator-done-grade);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global */
|
||||||
|
.workout-wrapper #app_debug_div {
|
||||||
|
border: thin dotted blue;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Workout done sheet that covers everything */
|
||||||
|
#workout-done {
|
||||||
|
position: fixed;
|
||||||
|
top: 0%;
|
||||||
|
left: 0%;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba(200,200,200,0.5);
|
||||||
|
z-index: 2000;
|
||||||
|
backdrop-filter: blur(0.5em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workout-done .workout-done-txt {
|
||||||
|
position: absolute;
|
||||||
|
top: 45%;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-family: "Boldonse", system-ui;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: xx-large;
|
||||||
|
color: white;
|
||||||
|
text-shadow: var(--indicator-done-grade) 0.5em 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating Action Buttons */
|
||||||
|
#fab-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fab-wrapper .fab-button {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fab-wrapper #fab-regress {
|
||||||
|
border: 0.4em solid gray;
|
||||||
|
width: 3em;
|
||||||
|
height: 3em;
|
||||||
|
}
|
||||||
|
#fab-wrapper #fab-regress:after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 0.8em;
|
||||||
|
height: 0.8em;
|
||||||
|
margin-top: 0.2em;
|
||||||
|
margin-left: 0.2em;
|
||||||
|
border-top: 0.4em solid gray;
|
||||||
|
border-right: 0.4em solid gray;
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#fab-wrapper #fab-advance {
|
||||||
|
border: 0.4em solid gray;
|
||||||
|
width: 4em;
|
||||||
|
height: 4em;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
#fab-wrapper #fab-advance:after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
margin-bottom: -0.2em;
|
||||||
|
margin-right: 0.6em;
|
||||||
|
border-top: 0.4em solid gray;
|
||||||
|
border-right: 0.4em solid gray;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
Reference in New Issue
Block a user