import * as Config from 'config' import * as State from 'state' 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 } } 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) // Elements const section = h('section', { className: timerClass(state.step) }) const stepElt = document.createTextNode(State.prettyPrintStep(state.step)) const stepInfoElt = document.createTextNode(state.info) const arcPathElt = h('path', { class: 'g-Timer__ArcProgress' }) const updateDom = () => { 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) stepInfoElt.textContent = state.info Audio.playFromStep(config, state) } const quit = () => { const formRoute: Router.Route = { name: 'form', config } history.pushState({}, '', Router.toString(formRoute)) clearInterval() showPage(formRoute) } const update = () => { if (isPlaying) { elapsed = elapsed + 1 state = State.getAt(config, elapsed) 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, state) 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 ), h('div', { className: 'g-Timer__Step' }, stepElt), h('div', {}, stepInfoElt) ), h('div', { className: 'g-Timer__Buttons' }, h('button', { className: 'g-Timer__Button', onclick: (e: MouseEvent) => { isPlaying = !isPlaying const elt = e.target as HTMLElement elt.textContent = isPlaying ? 'pause' : 'resume' elt.className = isPlaying ? 'g-Timer__Button' : 'g-Timer__Button g-Timer__Button--Active' } }, 'pause' ), h('a', { className: 'g-Timer__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 = 360 * (t + duration) / totalDuration 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; 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: 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' } }