aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2021-05-15 12:47:04 +0200
committerJoris2021-05-15 12:47:04 +0200
commitd1ce8774ec3291374c222c8f64c085e3a99f6147 (patch)
tree3ed888c65600cfea0d56494ae35940744eba1d14
parent6a9f9a9f3cf547da80df973950489e343143289d (diff)
downloadtabata-d1ce8774ec3291374c222c8f64c085e3a99f6147.tar.gz
tabata-d1ce8774ec3291374c222c8f64c085e3a99f6147.tar.bz2
tabata-d1ce8774ec3291374c222c8f64c085e3a99f6147.zip
Add warm up
-rw-r--r--public/main.css17
-rw-r--r--src/audio.ts8
-rw-r--r--src/config.ts6
-rw-r--r--src/router.ts10
-rw-r--r--src/state.ts80
-rw-r--r--src/view/form.ts13
-rw-r--r--src/view/timer.ts34
7 files changed, 110 insertions, 58 deletions
diff --git a/public/main.css b/public/main.css
index 5df74af..deec437 100644
--- a/public/main.css
+++ b/public/main.css
@@ -16,7 +16,8 @@ html {
--color-action-darker: #222222;
--color-prepare: #3B6EDC;
--color-pause: #888888;
- --color-work: #71b571;
+ --color-warm-up: #C679D9;
+ --color-work: #71B571;
--color-rest: #B15B5B;
--color-timer-arc-total: #222222;
--color-timer-hover: #DDEEDD;
@@ -123,19 +124,27 @@ body {
display: grid;
grid-template-columns: 10% auto 10%;
grid-template-rows: 10% auto 10% 10% 10%;
+ color: black;
+}
+
+.g-Timer--WarmUp {
+ background-color: var(--color-warm-up);
color: white;
}
.g-Timer--Work {
background-color: var(--color-work);
+ color: white;
}
.g-Timer--Rest {
background-color: var(--color-rest);
+ color: white;
}
.g-Timer--Prepare {
background-color: var(--color-prepare);
+ color: white;
}
.g-Timer__Pause {
@@ -180,6 +189,12 @@ body {
stroke-width: 18;
}
+.g-Timer__ArcWarmUp {
+ stroke: var(--color-warm-up);
+ fill: none;
+ stroke-width: 18;
+}
+
.g-Timer__ArcWork {
stroke: var(--color-work);
fill: none;
diff --git a/src/audio.ts b/src/audio.ts
index bdf64eb..4147164 100644
--- a/src/audio.ts
+++ b/src/audio.ts
@@ -7,13 +7,15 @@ const endTabata = new Audio('sound/end-tabata.mp3')
const endTraining = new Audio('sound/end-training.mp3')
export function playFromStep(config: Config.Config, state: State.State) {
- if (state.step === State.Step.Work && state.remaining === config.work) {
+ if (state.step === State.Step.WarmUp && state.remaining === config.warmup) {
+ start.play()
+ } else if (state.step === State.Step.Work && state.remaining === config.work) {
start.play()
} else if (state.step === State.Step.Rest && state.remaining === config.rest) {
stop.play()
- } else if (state.step === State.Step.Prepare && state.remaining === config.prepare) {
+ } else if (state.step === State.Step.Prepare && state.remaining === config.prepare && state.elapsed > 0) {
endTabata.play()
- } else if (state.step === State.Step.End) {
+ } else if (state.step === State.Step.End && state.elapsed === Config.getDuration(config)) {
endTraining.play()
}
}
diff --git a/src/config.ts b/src/config.ts
index c20bff2..d1d369e 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,4 +1,5 @@
export interface Config {
+ warmup: number;
prepare : number;
tabatas : number;
cycles : number;
@@ -8,8 +9,9 @@ export interface Config {
export function init(): Config {
return {
- prepare: 10,
+ warmup: 120,
tabatas: 4,
+ prepare: 10,
cycles: 8,
work: 20,
rest: 10
@@ -17,5 +19,5 @@ export function init(): Config {
}
export function getDuration(c: Config): number {
- return c.tabatas * (c.prepare + (c.cycles * (c.work + c.rest)))
+ return c.warmup + c.tabatas * (c.prepare + (c.cycles * (c.work + c.rest)))
}
diff --git a/src/router.ts b/src/router.ts
index abbdc65..ff6c724 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -24,8 +24,9 @@ export function from(location: Location): Route {
if (xs.length === 2) {
const key = xs[0]
const value = parseInt(xs[1])
- if (key == 'prepare') config.prepare = value
- else if (key == 'tabatas') config.tabatas = value
+ if (key == 'warmup') config.warmup = value
+ else if (key == 'tabatas') config.tabatas = value
+ else if (key == 'prepare') config.prepare = value
else if (key == 'cycles') config.cycles = value
else if (key == 'work') config.work = value
else if (key == 'rest') config.rest = value
@@ -41,10 +42,11 @@ export function toString(route: Route): string {
const path = route.kind === Kind.Form ? '/' : '/timer'
let query = ''
if (route.config !== undefined) {
- const { prepare, tabatas, cycles, work, rest } = route.config
+ const { warmup, tabatas, prepare, cycles, work, rest } = route.config
const params = [
- `prepare=${prepare}`,
+ `warmup=${warmup}`,
`tabatas=${tabatas}`,
+ `prepare=${prepare}`,
`cycles=${cycles}`,
`work=${work}`,
`rest=${rest}`,
diff --git a/src/state.ts b/src/state.ts
index 3b390c5..a0348f0 100644
--- a/src/state.ts
+++ b/src/state.ts
@@ -1,6 +1,7 @@
import * as Config from 'config'
export enum Step {
+ WarmUp,
Prepare,
Work,
Rest,
@@ -8,56 +9,79 @@ export enum Step {
}
export function prettyPrintStep(step: Step): string {
+ if (step === Step.WarmUp)
+ return 'Warm Up'
if (step === Step.Prepare)
- return "Prepare"
+ return 'Prepare'
else if (step === Step.Work)
- return "Work"
+ return 'Work'
else if (step === Step.Rest)
- return "Rest"
+ return 'Rest'
else
- return "End"
+ return 'End'
}
export interface State {
step: Step,
remaining: number,
- tabata: number,
- cycle: number,
+ info: string,
+ elapsed: number,
}
export function getAt(config: Config.Config, elapsed: number): State {
+ if (elapsed < config.warmup) {
+ return {
+ step: Step.WarmUp,
+ remaining: config.warmup - elapsed,
+ info: '',
+ elapsed
+ }
+ }
+
+ const tabataElapsed = elapsed - config.warmup
+
const cycleDuration = config.work + config.rest
const tabataDuration = config.prepare + (config.cycles * cycleDuration)
- if (elapsed >= tabataDuration * config.tabatas) {
+
+ if (tabataElapsed >= tabataDuration * config.tabatas) {
return {
step: Step.End,
remaining: 0,
- tabata: config.tabatas,
- cycle: config.cycles,
+ info: '',
+ elapsed
}
+ }
+
+ const currentTabataElapsed = tabataElapsed % tabataDuration
+ let step, remaining
+ if (currentTabataElapsed < config.prepare) {
+ step = Step.Prepare
+ remaining = config.prepare - currentTabataElapsed
} else {
- const currentTabataElapsed = elapsed % tabataDuration
- let step, remaining
- if (currentTabataElapsed < config.prepare) {
- step = Step.Prepare
- remaining = config.prepare - currentTabataElapsed
+ const currentCycleElapsed = (currentTabataElapsed - config.prepare) % cycleDuration
+ if (currentCycleElapsed < config.work) {
+ step = Step.Work
+ remaining = config.work - currentCycleElapsed
} else {
- const currentCycleElapsed = (currentTabataElapsed - config.prepare) % cycleDuration
- if (currentCycleElapsed < config.work) {
- step = Step.Work
- remaining = config.work - currentCycleElapsed
- } else {
- step = Step.Rest
- remaining = config.work + config.rest - currentCycleElapsed
- }
+ step = Step.Rest
+ remaining = config.work + config.rest - currentCycleElapsed
}
+ }
- const tabata = Math.floor(elapsed / tabataDuration) + 1
- const cycle =
- currentTabataElapsed < config.prepare
- ? 1
- : Math.floor((currentTabataElapsed - config.prepare) / cycleDuration) + 1
+ const tabata = Math.floor(tabataElapsed / tabataDuration) + 1
+ const cycle =
+ currentTabataElapsed < config.prepare
+ ? 1
+ : Math.floor((currentTabataElapsed - config.prepare) / cycleDuration) + 1
+ const info = stepCount(step, tabata, cycle)
- return { step, remaining, tabata, cycle }
+ return { step, remaining, info, elapsed }
+}
+
+function stepCount(step: Step, tabata: number, cycle: number): string {
+ if (step === Step.Work || step === Step.Rest) {
+ return `#${tabata.toString()}.${cycle.toString()}`
+ } else {
+ return `#${tabata.toString()}`
}
}
diff --git a/src/view/form.ts b/src/view/form.ts
index 60e5f08..0b04cfb 100644
--- a/src/view/form.ts
+++ b/src/view/form.ts
@@ -36,12 +36,13 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
history.pushState({}, '', Router.toString(timerRoute))
showPage(timerRoute)
}},
- labelledInput('prepare', 0, config.prepare, n => { config.prepare = n; wd()}),
- labelledInput('tabatas', 1, config.tabatas, n => { config.tabatas = n; wd()}),
- labelledInput('cycles', 1, config.cycles, n => { config.cycles = n; wd()}),
- labelledInput('work', 5, config.work, n => { config.work = n; wd()}),
- labelledInput('rest', 5, config.rest, n => { config.rest = n; wd()}),
- h('div', { className: 'g-Form__Duration' }, 'duration', h('div', {}, duration)),
+ labelledInput('Warm Up', 0, config.warmup, n => { config.warmup = n; wd()}),
+ labelledInput('Tabatas', 1, config.tabatas, n => { config.tabatas = n; wd()}),
+ labelledInput('Prepare', 0, config.prepare, n => { config.prepare = n; wd()}),
+ labelledInput('Cycles', 1, config.cycles, n => { config.cycles = n; wd()}),
+ labelledInput('Work', 5, config.work, n => { config.work = n; wd()}),
+ labelledInput('Rest', 5, config.rest, n => { config.rest = n; wd()}),
+ h('div', { className: 'g-Form__Duration' }, 'Duration:', h('div', {}, duration)),
h('button', { className: 'g-Form__Start' }, 'start'))
)
}
diff --git a/src/view/timer.ts b/src/view/timer.ts
index ddcea71..061f5d4 100644
--- a/src/view/timer.ts
+++ b/src/view/timer.ts
@@ -7,6 +7,8 @@ import h from 'h'
let interval: number | undefined = undefined
+const endDuration: number = 4
+
export function clearInterval() {
if (interval !== undefined) {
window.clearInterval(interval)
@@ -27,21 +29,22 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
// Elements
const section = h('section', { className: timerClass(state.step) })
const stepElt = document.createTextNode(State.prettyPrintStep(state.step))
- const stepCountElt = document.createTextNode(stepCount(state))
+ const stepInfoElt = document.createTextNode(state.info)
const arcPathElt = h('path', { class: 'g-Timer__ArcProgress' })
const updateDom = () => {
- const angle = elapsed / duration * 360
+ const angle = Math.min(359.999, elapsed / duration * 360)
arcPathElt.setAttribute("d", Arc.describe(0, 0, 90, 0, angle))
section.className = timerClass(state.step)
stepElt.textContent = State.prettyPrintStep(state.step)
- stepCountElt.textContent = stepCount(state)
+ stepInfoElt.textContent = state.info
Audio.playFromStep(config, state)
}
const quit = () => {
const formRoute = { kind: Router.Kind.Form, config }
history.pushState({}, '', Router.toString(formRoute))
+ clearInterval()
showPage(formRoute)
}
@@ -49,7 +52,7 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
if (isPlaying) {
elapsed = elapsed + 1
state = State.getAt(config, elapsed)
- elapsed > duration
+ elapsed >= duration + endDuration
? quit()
: updateDom()
}
@@ -62,6 +65,9 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
}
interval = window.setInterval(update, 1000)
+ // Play initial audio
+ Audio.playFromStep(config, state)
+
section.append(
h('div',
{ className: 'g-Timer__Dial' },
@@ -78,7 +84,7 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
arcPathElt
),
h('div', { className: 'g-Timer__Step' }, stepElt),
- h('div', {}, stepCountElt)
+ h('div', {}, stepInfoElt)
),
h('div',
{ className: 'g-Timer__Buttons' },
@@ -128,6 +134,10 @@ function arcPaths(config: Config.Config): Element[] {
)
}
+ if (config.warmup > 0) {
+ paths.push(arc('WarmUp', config.warmup))
+ }
+
for (let tabata = 0; tabata < config.tabatas; tabata++) {
paths.push(arc('Prepare', config.prepare))
for (let cycle = 0; cycle < config.cycles; cycle++) {
@@ -140,19 +150,15 @@ function arcPaths(config: Config.Config): Element[] {
}
function timerClass(step: State.Step): string {
- if (step === State.Step.Work) {
+ if (step === State.Step.WarmUp) {
+ return 'g-Layout__Page g-Timer g-Timer--WarmUp'
+ } else if (step === State.Step.Work) {
return 'g-Layout__Page g-Timer g-Timer--Work'
} else if (step === State.Step.Rest) {
return 'g-Layout__Page g-Timer g-Timer--Rest'
- } else {
+ } else if (step === State.Step.Prepare) {
return 'g-Layout__Page g-Timer g-Timer--Prepare'
- }
-}
-
-function stepCount(state: State.State): string {
- if (state.step === State.Step.Work || state.step === State.Step.Rest) {
- return `#${state.tabata.toString()}.${state.cycle.toString()}`
} else {
- return `#${state.tabata.toString()}`
+ return 'g-Layout__Page g-Timer'
}
}