import * as Config from 'config' import * as Step from 'step' import * as Arc from 'arc' import * as Router from 'router' import * as Audio from 'audio' import h from 'h' let interval: number | undefined = undefined const endDuration: number = 4 export function clearInterval() { if (interval !== undefined) { window.clearInterval(interval) interval = undefined } } 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 state = { isPlaying: true, elapsed: 0 } // Elements 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 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(step) stepElt.textContent = Step.prettyPrint(step) Audio.playFromStep(config, step, state.elapsed) } const quit = () => { const formRoute: Router.Route = { name: 'form', config } history.pushState({}, '', Router.toString(formRoute)) clearInterval() showPage(formRoute) } const update = () => { if (state.isPlaying) { state.elapsed = state.elapsed + 1 state.elapsed >= duration + endDuration ? quit() : updateDom() } } // Start timer if (interval !== undefined) { window.clearInterval(interval) interval = undefined } interval = window.setInterval(update, 1000) // Play initial audio Audio.playFromStep(config, initStep, state.elapsed) section.append( h('div', { className: 'g-Timer__Dial' }, h('svg', { class: 'g-Timer__Arc', viewBox: '-100 -100 200 200' }, h('path', { class: 'g-Timer__ArcTotal', d: Arc.describe(0, 0, 90.0, 0, 359.999) } ), ...arcPaths(config), arcPathElt ), stepElt ), h('div', { className: 'g-Timer__Buttons' }, h('button', { className: 'g-Button', onclick: (e: MouseEvent) => { state.isPlaying = !state.isPlaying const elt = e.target as HTMLElement elt.textContent = state.isPlaying ? 'pause' : 'resume' elt.className = state.isPlaying ? 'g-Button' : 'g-Button g-Button--Active' } }, 'Pause' ), h('a', { className: 'g-Button', href: formUrl }, 'Quit' ) ) ) return section } function arcPaths(config: Config.Config): Element[] { const paths = [] let t = 0 const totalDuration = Config.getDuration(config) let arc = (kind: string, duration: number): Element => { const startAngle = 360 * t / totalDuration const endAngle = Math.min(360 * (t + duration) / totalDuration, 359.999) t += duration return h('path', { class: `g-Timer__Arc${kind}`, d: Arc.describe(0, 0, 90.0, startAngle, endAngle) } ) } if (config.warmup > 0) { paths.push(arc('WarmUp', config.warmup)) } 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)) paths.push(arc('Rest', config.rest)) } } return paths } 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' } }