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 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({ kind: Router.Kind.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 stepCountElt = document.createTextNode(stepCount(state)) const arcPathElt = h('path', { class: 'g-Timer__ArcProgress' }) const updateDom = () => { const angle = 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) Audio.playFromStep(config, state) } const quit = () => { const formRoute = { kind: Router.Kind.Form, config } history.pushState({}, '', Router.toString(formRoute)) showPage(formRoute) } const update = () => { if (isPlaying) { elapsed = elapsed + 1 state = State.getAt(config, elapsed) elapsed > duration ? quit() : updateDom() } } // Start timer if (interval !== undefined) { window.clearInterval(interval) interval = undefined } interval = window.setInterval(update, 1000) 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', {}, stepCountElt) ), 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) } ) } 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.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 { 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()}` } }