aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2021-05-20 09:43:02 +0200
committerJoris2021-05-20 09:43:02 +0200
commitf9e7e819a0a673befb11b24404efeb9d6644bceb (patch)
tree07fdcc252964382568236647e74709980dc479d4
parentcde24cbf3fbc418af3c98d82e47dcd5df71e5b26 (diff)
downloadtabata-f9e7e819a0a673befb11b24404efeb9d6644bceb.tar.gz
tabata-f9e7e819a0a673befb11b24404efeb9d6644bceb.tar.bz2
tabata-f9e7e819a0a673befb11b24404efeb9d6644bceb.zip
Provide named exercices
-rw-r--r--public/main.css137
-rw-r--r--src/audio.ts14
-rw-r--r--src/config.ts8
-rw-r--r--src/router.ts29
-rw-r--r--src/state.ts87
-rw-r--r--src/step.ts74
-rw-r--r--src/view/form.ts178
-rw-r--r--src/view/timer.ts87
8 files changed, 401 insertions, 213 deletions
diff --git a/public/main.css b/public/main.css
index deec437..1461fef 100644
--- a/public/main.css
+++ b/public/main.css
@@ -11,9 +11,13 @@ html {
:root {
--color-active: #F3E87F;
- --color-header: #333333;
+ --color-active-hover: #FBEF81;
+ --color-header: brown;
+ --color-header-darker: #822929;
--color-action: #333333;
- --color-action-darker: #222222;
+ --color-action-darker: #111111;
+ --color-action-hover: #555555;
+ --color-label: #333333;
--color-prepare: #3B6EDC;
--color-pause: #888888;
--color-warm-up: #C679D9;
@@ -21,6 +25,9 @@ html {
--color-rest: #B15B5B;
--color-timer-arc-total: #222222;
--color-timer-hover: #DDEEDD;
+ --color-focus: orange;
+ --color-title: brown;
+ --color-input-border: #CCCCCC;
--base-font-size: 18px;
}
@@ -51,6 +58,7 @@ body {
color: white;
padding: 1rem 2rem;
font-size: 2rem;
+ border-bottom: 0.1rem solid var(--color-header-darker);
}
/* Animation */
@@ -80,42 +88,59 @@ body {
display: flex;
flex-direction: column;
align-items: center;
- padding-top: 5rem;
+ padding-top: 2rem;
background-color: white;
}
-.g-Form__Label {
+.g-Form__Section {
display: flex;
flex-direction: column;
align-items: center;
- margin-bottom: 1rem;
- text-align: center;
- font-size: 1.3rem;
+ padding: 1rem 0;
}
-.g-Form__Input {
- display: block;
+.g-Form__Tabatas {
+ display: flex;
+ flex-direction: column;
+ width: 17rem;
+}
+
+.g-Form__Line {
+ display: flex;
+ align-items: flex-end;
+ margin-bottom: 2rem;
+}
+
+.g-Form__Title {
text-align: center;
- margin-top: 0.5rem;
- font-size: 1.3rem;
- width: 10rem;
}
-.g-Form__Duration {
+.g-Form__Label {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ margin-bottom: 1rem;
text-align: center;
- font-size: 1.5rem;
- margin-top: 1rem;
+ margin: 0 1rem;
+ color: var(--color-label);
+ line-height: 2rem;
+}
+
+.g-Form__Tabata {
+ display: grid;
+ grid-template-columns: auto auto;
+ grid-gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.g-Form__Operator {
+ padding-bottom: 0.3rem;
}
-.g-Form__Start {
+.g-Form__Duration {
+ text-align: center;
font-size: 1.5rem;
- background-color: var(--color-action);
- border: 3px solid var(--color-action-darker);
- color: white;
- padding: 0.5rem 0.8rem;
- width: 10rem;
- margin-top: 2rem;
- cursor: pointer;
+ margin-bottom: 1rem;
}
/* Timer */
@@ -156,6 +181,9 @@ body {
align-items: center;
justify-content: center;
flex-direction: column;
+ white-space: pre-wrap;
+ text-align: center;
+ line-height: 6rem;
width: 100%;
height: 100%;
@@ -213,10 +241,6 @@ body {
stroke-width: 18;
}
-.g-Timer__Step {
- margin-bottom: 2rem;
-}
-
.g-Timer__Buttons {
display: flex;
justify-content: space-around;
@@ -226,26 +250,65 @@ body {
height: 100%;
}
-.g-Timer__Button {
- display: flex;
+/* Titles */
+
+h1 {
+ font-weight: normal;
+ color: var(--color-title);
+ margin-top: 0;
+ text-decoration: underline;
+}
+
+/* Input */
+
+.g-Input {
+ display: block;
+ font-size: inherit;
+ width: 10rem;
+ padding: 0.3rem;
+ border: 0.1rem solid var(--color-input-border);
+}
+
+.g-Input:focus {
+ border-color: var(--color-focus);
+}
+
+/* Button */
+
+.g-Button {
+ display: inline-flex;
justify-content: center;
align-items: center;
- font-size: 1.5rem;
+ text-decoration: none;
+ font-size: inherit;
background-color: var(--color-action);
- border: 3px solid var(--color-action-darker);
+ border: 0.1rem solid var(--color-action);
color: white;
padding: 0.5rem 0.8rem;
- width: 10rem;
cursor: pointer;
- text-align: center;
- text-decoration: none;
}
-.g-Timer__Button:not(:last-child) {
- margin-right: 2rem;
+.g-Button:hover {
+ background-color: var(--color-action-hover);
+}
+
+.g-Button:focus {
+ border-color: var(--color-focus);
}
-.g-Timer__Button--Active {
+.g-Button--Active {
background-color: var(--color-active);
color: black;
}
+
+.g-Button--Active:hover {
+ background-color: var(--color-active-hover);
+ color: black;
+}
+
+/* List */
+
+.g-List {
+ margin: 0;
+ padding: 0;
+}
diff --git a/src/audio.ts b/src/audio.ts
index 4147164..350093a 100644
--- a/src/audio.ts
+++ b/src/audio.ts
@@ -1,21 +1,21 @@
import * as Config from 'config'
-import * as State from 'state'
+import * as Step from 'step'
const start = new Audio('sound/start.mp3')
const stop = new Audio('sound/stop.mp3')
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.WarmUp && state.remaining === config.warmup) {
+export function playFromStep(config: Config.Config, step: Step.Step, elapsed: number) {
+ if (step.name === 'warmup' && step.remaining === config.warmup) {
start.play()
- } else if (state.step === State.Step.Work && state.remaining === config.work) {
+ } else if (step.name === 'work' && step.remaining === config.work) {
start.play()
- } else if (state.step === State.Step.Rest && state.remaining === config.rest) {
+ } else if (step.name === 'rest' && step.remaining === config.rest) {
stop.play()
- } else if (state.step === State.Step.Prepare && state.remaining === config.prepare && state.elapsed > 0) {
+ } else if (step.name === 'prepare' && step.remaining === config.prepare && elapsed > 0) {
endTabata.play()
- } else if (state.step === State.Step.End && state.elapsed === Config.getDuration(config)) {
+ } else if (step.name === 'end' && elapsed === Config.getDuration(config)) {
endTraining.play()
}
}
diff --git a/src/config.ts b/src/config.ts
index d1d369e..5cd23e6 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,7 +1,7 @@
export interface Config {
warmup: number;
prepare : number;
- tabatas : number;
+ tabatas : string[];
cycles : number;
work : number;
rest : number;
@@ -9,8 +9,8 @@ export interface Config {
export function init(): Config {
return {
- warmup: 120,
- tabatas: 4,
+ warmup: 180,
+ tabatas: [],
prepare: 10,
cycles: 8,
work: 20,
@@ -19,5 +19,5 @@ export function init(): Config {
}
export function getDuration(c: Config): number {
- return c.warmup + c.tabatas * (c.prepare + (c.cycles * (c.work + c.rest)))
+ return c.warmup + c.tabatas.length * (c.prepare + (c.cycles * (c.work + c.rest)))
}
diff --git a/src/router.ts b/src/router.ts
index 2d229b0..bcdf8eb 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -17,13 +17,12 @@ export function from(location: Location): Route {
const xs = entry.split('=')
if (xs.length === 2) {
const key = xs[0]
- const value = parseInt(xs[1])
- 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
+ if (key == 'warmup') config.warmup = parseInt(xs[1])
+ else if (key == 'tabatas') config.tabatas = decodeTabatas(xs[1])
+ else if (key == 'prepare') config.prepare = parseInt(xs[1])
+ else if (key == 'cycles') config.cycles = parseInt(xs[1])
+ else if (key == 'work') config.work = parseInt(xs[1])
+ else if (key == 'rest') config.rest = parseInt(xs[1])
}
})
const params = search.split('&')
@@ -39,13 +38,23 @@ export function toString(route: Route): string {
const { warmup, tabatas, prepare, cycles, work, rest } = route.config
const params = [
`warmup=${warmup}`,
- `tabatas=${tabatas}`,
`prepare=${prepare}`,
`cycles=${cycles}`,
`work=${work}`,
`rest=${rest}`,
- ].join('&')
- query = `?${params}`
+ ]
+ if(tabatas.length > 0) {
+ params.push(`tabatas=${encodeTabatas(tabatas)}`)
+ }
+ query = `?${params.join('&')}`
}
return `#${path}${query}`
}
+
+function encodeTabatas(xs: string[]): string {
+ return encodeURIComponent(xs.join(','))
+}
+
+function decodeTabatas(str: string): string[] {
+ return decodeURIComponent(str).split(',').map(t => t.trim())
+}
diff --git a/src/state.ts b/src/state.ts
deleted file mode 100644
index a0348f0..0000000
--- a/src/state.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import * as Config from 'config'
-
-export enum Step {
- WarmUp,
- Prepare,
- Work,
- Rest,
- End,
-}
-
-export function prettyPrintStep(step: Step): string {
- if (step === Step.WarmUp)
- return 'Warm Up'
- if (step === Step.Prepare)
- return 'Prepare'
- else if (step === Step.Work)
- return 'Work'
- else if (step === Step.Rest)
- return 'Rest'
- else
- return 'End'
-}
-
-export interface State {
- step: Step,
- remaining: 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 (tabataElapsed >= tabataDuration * config.tabatas) {
- return {
- step: Step.End,
- remaining: 0,
- info: '',
- elapsed
- }
- }
-
- const currentTabataElapsed = tabataElapsed % tabataDuration
- let step, remaining
- if (currentTabataElapsed < config.prepare) {
- step = Step.Prepare
- remaining = config.prepare - currentTabataElapsed
- } 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
- }
- }
-
- 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, 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/step.ts b/src/step.ts
new file mode 100644
index 0000000..13ca183
--- /dev/null
+++ b/src/step.ts
@@ -0,0 +1,74 @@
+import * as Config from 'config'
+
+export type Step
+ = { name: 'warmup', remaining: number }
+ | { name: 'prepare', tabata: string, remaining: number }
+ | { name: 'work', tabata: string, cycle: number, remaining: number }
+ | { name: 'rest', tabata: string, cycle: number, remaining: number }
+ | { name: 'end' }
+
+export function prettyPrint(step: Step): string {
+ switch (step.name) {
+ case 'warmup':
+ return 'Warm Up'
+ case 'prepare':
+ return `${step.tabata}\nPreparation`
+ case 'work':
+ return `${step.tabata}\nWork ${step.cycle}`
+ case 'rest':
+ return `${step.tabata}\nRest ${step.cycle}`
+ case 'end':
+ return 'End!'
+ }
+}
+
+export function getAt(config: Config.Config, elapsed: number): Step {
+ if (elapsed < config.warmup) {
+ return {
+ name: 'warmup',
+ remaining: config.warmup - elapsed
+ }
+ }
+
+ const tabataElapsed = elapsed - config.warmup
+
+ const cycleDuration = config.work + config.rest
+ const tabataDuration = config.prepare + (config.cycles * cycleDuration)
+
+ if (tabataElapsed >= tabataDuration * config.tabatas.length) {
+ return { name: 'end' }
+ }
+
+ const tabata = config.tabatas[Math.floor(tabataElapsed / tabataDuration)]
+ const currentTabataElapsed = tabataElapsed % tabataDuration
+
+ if (currentTabataElapsed < config.prepare) {
+ return {
+ name: 'prepare',
+ tabata,
+ remaining: config.prepare - currentTabataElapsed
+ }
+ } else {
+ const currentCycleElapsed = (currentTabataElapsed - config.prepare) % cycleDuration
+ const cycle =
+ currentTabataElapsed < config.prepare
+ ? 1
+ : Math.floor((currentTabataElapsed - config.prepare) / cycleDuration) + 1
+
+ if (currentCycleElapsed < config.work) {
+ return {
+ name: 'work',
+ tabata,
+ cycle,
+ remaining: config.work - currentCycleElapsed
+ }
+ } else {
+ return {
+ name: 'rest',
+ tabata,
+ cycle,
+ remaining: config.work + config.rest - currentCycleElapsed
+ }
+ }
+ }
+}
diff --git a/src/view/form.ts b/src/view/form.ts
index b0d5827..a5e7253 100644
--- a/src/view/form.ts
+++ b/src/view/form.ts
@@ -3,25 +3,6 @@ import h from 'h'
import * as Router from 'router'
import * as Duration from 'duration'
-function labelledInput(
- labelValue: string,
- min: number,
- value: number,
- update: (n: number) => void
-): Element {
- return h('label',
- { className: 'g-Form__Label',
- oninput: (e: Event) => {
- if (e.target !== null) {
- const target = e.target as HTMLInputElement
- update(parseInt(target.value) || 0)
- }
- }
- },
- labelValue,
- h('input', { className: 'g-Form__Input', type: 'number', min, value }))
-}
-
export function view(config: Config.Config, showPage: (route: Router.Route) => void) {
const duration = document.createTextNode(Duration.prettyPrint(Config.getDuration(config)))
const wd = () => duration.textContent = Duration.prettyPrint(Config.getDuration(config))
@@ -36,13 +17,156 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
history.pushState({}, '', Router.toString(timerRoute))
showPage(timerRoute)
}},
- 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'))
+ h('section',
+ { className: 'g-Form__Section' },
+ numberInput('Warm Up', 0, config.warmup, n => { config.warmup = n; wd()})
+ ),
+ h('section',
+ { className: 'g-Form__Section' },
+ h('h1', { className: 'g-Form__Title' }, 'Tabatas'),
+ h('div',
+ { className: 'g-Form__Line' },
+ numberInput('Preparation', 0, config.prepare, n => { config.prepare = n; wd()}),
+ operator('+'),
+ numberInput('Cycles', 1, config.cycles, n => { config.cycles = n; wd()}),
+ operator('× ('),
+ numberInput('Work', 5, config.work, n => { config.work = n; wd()}),
+ operator('+'),
+ numberInput('Rest', 5, config.rest, n => { config.rest = n; wd()}),
+ operator(')')
+ ),
+ tabatasSection(
+ config.tabatas,
+ (str: string) => { config.tabatas.push(str); wd() },
+ (index: number, str: string) => { config.tabatas[index] = str; wd() },
+ (index: number) => { config.tabatas.splice(index, 1); wd() }
+ )
+ ),
+ h('section',
+ { className: 'g-Form__Section' },
+ h('h1', { className: 'g-Title' }, 'Duration'),
+ h('div', { className: 'g-Form__Duration' }, duration)
+ ),
+ h('button', { className: 'g-Button' }, 'Start')
+ )
+ )
+}
+
+function operator(str: string): Element {
+ return h('span', { className: 'g-Form__Operator' }, str)
+}
+
+function tabatasSection(
+ init: string[],
+ onAdd: (str: string) => void,
+ onUpdate: (index: number, str: string) => void,
+ onRemove: (inedx: number) => void
+) {
+ const tabatas = h('ol', { className: 'g-List' })
+ let counter = init.length
+ let removedIndexes: number[] = []
+ let adjustIndex = (index: number) => index - removedIndexes.filter(i => i < index).length
+
+ init.forEach((initStr, index) => {
+ const { tabata } = tabataInput(
+ initStr,
+ (str: string) => onUpdate(adjustIndex(index), str),
+ () => {
+ onRemove(adjustIndex(index))
+ tabatas.removeChild(tabata)
+ removedIndexes.push(index)
+ }
+ )
+ tabatas.appendChild(tabata)
+ })
+
+ return h('div',
+ { className: 'g-Form__Tabatas' },
+ tabatas,
+ h('input',
+ { type: 'button',
+ value: 'Add',
+ className: 'g-Button',
+ onclick: (e: Event) => {
+ let index = counter++
+ const txt = `Exercise ${adjustIndex(index) + 1}`
+ onAdd(txt)
+ let { tabata, textInput } = tabataInput(
+ txt,
+ (str: string) => onUpdate(adjustIndex(index), str),
+ () => {
+ onRemove(adjustIndex(index))
+ tabatas.removeChild(tabata)
+ removedIndexes.push(index)
+ }
+ )
+ tabatas.appendChild(tabata)
+ textInput.select()
+ }
+ }
+ )
+ )
+}
+
+interface TabataInput {
+ tabata: Element,
+ textInput: HTMLInputElement,
+}
+
+function tabataInput(
+ init: string,
+ onUpdate: (str: string) => void,
+ onRemove: () => void
+): TabataInput {
+ const textInput = h('input',
+ { value: init,
+ className: 'g-Input',
+ required: 'required',
+ oninput: (e: Event) => {
+ if (e.target !== null) {
+ const target = e.target as HTMLInputElement
+ onUpdate(target.value.trim())
+ }
+ }
+ }
+ ) as HTMLInputElement
+
+ const tabata = h('li',
+ { className: 'g-Form__Tabata'},
+ textInput,
+ h('input',
+ { type: 'button',
+ value: 'Remove',
+ className: 'g-Button',
+ onclick: (e: Event) => onRemove()
+ }
+ )
)
+
+ return { tabata, textInput }
+}
+
+function numberInput(
+ labelValue: string,
+ min: number,
+ value: number,
+ update: (n: number) => void
+): Element {
+ return h('label',
+ { className: 'g-Form__Label',
+ oninput: (e: Event) => {
+ if (e.target !== null) {
+ const target = e.target as HTMLInputElement
+ update(parseInt(target.value) || 0)
+ }
+ }
+ },
+ labelValue,
+ h('input',
+ { className: 'g-Input',
+ type: 'number',
+ required: 'required',
+ min,
+ value
+ }))
}
diff --git a/src/view/timer.ts b/src/view/timer.ts
index 07b5db3..3cdf0d7 100644
--- a/src/view/timer.ts
+++ b/src/view/timer.ts
@@ -1,5 +1,5 @@
import * as Config from 'config'
-import * as State from 'state'
+import * as Step from 'step'
import * as Arc from 'arc'
import * as Router from 'router'
import * as Audio from 'audio'
@@ -16,29 +16,35 @@ export function clearInterval() {
}
}
+interface State {
+ isPlaying: boolean,
+ elapsed: number
+}
+
export function view(config: Config.Config, showPage: (route: Router.Route) => void) {
const formUrl = `${Router.toString({ name: 'form', config })}`
const duration = Config.getDuration(config)
// State
- let isPlaying = true
- let elapsed = 0
- let state = State.getAt(config, elapsed)
+ let state = {
+ isPlaying: true,
+ elapsed: 0
+ }
// Elements
- const section = h('section', { className: timerClass(state.step) })
- const stepElt = document.createTextNode(State.prettyPrintStep(state.step))
- const stepInfoElt = document.createTextNode(state.info)
+ const initStep = Step.getAt(config, state.elapsed)
+ const section = h('section', { className: timerClass(initStep) })
+ const stepElt = document.createTextNode(Step.prettyPrint(initStep))
const arcPathElt = h('path', { class: 'g-Timer__ArcProgress' })
const updateDom = () => {
- const angle = Math.min(359.999, elapsed / duration * 360)
+ const step = Step.getAt(config, state.elapsed)
+ const angle = Math.min(359.999, state.elapsed / duration * 360)
arcPathElt.setAttribute("d", Arc.describe(0, 0, 90, 0, angle))
- section.className = timerClass(state.step)
- stepElt.textContent = State.prettyPrintStep(state.step)
- stepInfoElt.textContent = state.info
- Audio.playFromStep(config, state)
+ section.className = timerClass(step)
+ stepElt.textContent = Step.prettyPrint(step)
+ Audio.playFromStep(config, step, state.elapsed)
}
const quit = () => {
@@ -49,10 +55,9 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
}
const update = () => {
- if (isPlaying) {
- elapsed = elapsed + 1
- state = State.getAt(config, elapsed)
- elapsed >= duration + endDuration
+ if (state.isPlaying) {
+ state.elapsed = state.elapsed + 1
+ state.elapsed >= duration + endDuration
? quit()
: updateDom()
}
@@ -66,7 +71,7 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
interval = window.setInterval(update, 1000)
// Play initial audio
- Audio.playFromStep(config, state)
+ Audio.playFromStep(config, initStep, state.elapsed)
section.append(
h('div',
@@ -83,31 +88,30 @@ export function view(config: Config.Config, showPage: (route: Router.Route) => v
...arcPaths(config),
arcPathElt
),
- h('div', { className: 'g-Timer__Step' }, stepElt),
- h('div', {}, stepInfoElt)
+ stepElt
),
h('div',
{ className: 'g-Timer__Buttons' },
h('button',
- { className: 'g-Timer__Button',
+ { className: 'g-Button',
onclick: (e: MouseEvent) => {
- isPlaying = !isPlaying
+ state.isPlaying = !state.isPlaying
const elt = e.target as HTMLElement
- elt.textContent = isPlaying
+ elt.textContent = state.isPlaying
? 'pause'
: 'resume'
- elt.className = isPlaying
- ? 'g-Timer__Button'
- : 'g-Timer__Button g-Timer__Button--Active'
+ elt.className = state.isPlaying
+ ? 'g-Button'
+ : 'g-Button g-Button--Active'
}
},
- 'pause'
+ 'Pause'
),
h('a',
- { className: 'g-Timer__Button',
+ { className: 'g-Button',
href: formUrl
},
- 'quit'
+ 'Quit'
)
)
)
@@ -123,7 +127,7 @@ function arcPaths(config: Config.Config): Element[] {
let arc = (kind: string, duration: number): Element => {
const startAngle = 360 * t / totalDuration
- const endAngle = 360 * (t + duration) / totalDuration
+ const endAngle = Math.min(360 * (t + duration) / totalDuration, 359.999)
t += duration
@@ -138,7 +142,7 @@ function arcPaths(config: Config.Config): Element[] {
paths.push(arc('WarmUp', config.warmup))
}
- for (let tabata = 0; tabata < config.tabatas; tabata++) {
+ for (let tabata = 0; tabata < config.tabatas.length; tabata++) {
paths.push(arc('Prepare', config.prepare))
for (let cycle = 0; cycle < config.cycles; cycle++) {
paths.push(arc('Work', config.work))
@@ -149,16 +153,17 @@ function arcPaths(config: Config.Config): Element[] {
return paths
}
-function timerClass(step: State.Step): string {
- 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 if (step === State.Step.Prepare) {
- return 'g-Layout__Page g-Timer g-Timer--Prepare'
- } else {
- return 'g-Layout__Page g-Timer'
+function timerClass(step: Step.Step): string {
+ switch (step.name) {
+ case 'warmup':
+ return 'g-Layout__Page g-Timer g-Timer--WarmUp'
+ case 'prepare':
+ return 'g-Layout__Page g-Timer g-Timer--Prepare'
+ case 'work':
+ return 'g-Layout__Page g-Timer g-Timer--Work'
+ case 'rest':
+ return 'g-Layout__Page g-Timer g-Timer--Rest'
+ case 'end':
+ return 'g-Layout__Page g-Timer'
}
}