import { h, withState, Html, Rx, pure, RxAble } from 'lib/rx' import * as Options from 'view/options' import * as Chord from 'chord' interface Params { options: Options.Model onSubmit: (options: Options.Model) => void } export function view({ options, onSubmit }: Params): Html { return withState(options, opts => h('form', { className: 'g-Form', onsubmit: opts.map(o => (event: Event) => { event.preventDefault() onSubmit(o) } ) }, h('table', { className: 'g-ChordsTable' }, chordsGroup({ title: 'Major', chords: opts.map(o => o.major), updateChords: (chords: Chord.Chords) => { opts.update(o => { o.major = chords return o }) } }), chordsGroup({ title: 'Minor', chords: opts.map(o => o.minor), updateChords: (chords: Chord.Chords) => { opts.update(o => { o.minor = chords return o }) } }), chordsGroup({ title: '7th', chords: opts.map(o => o.seventh), updateChords: (chords: Chord.Chords) => { opts.update(o => { o.seventh = chords return o }) } }), chordsGroup({ title: 'Minor 7th', chords: opts.map(o => o.minorSeventh), updateChords: (chords: Chord.Chords) => { opts.update(o => { o.minorSeventh = chords return o }) } }), chordsGroup({ title: 'Major 7th', chords: opts.map(o => o.majorSeventh), updateChords: (chords: Chord.Chords) => { opts.update(o => { o.majorSeventh = chords return o }) } }) ), numberInput({ label: 'BPM', value: opts.map(o => o.bpm.toString()), onChange: (n => opts.update(o => { o.bpm = n return o })) }), numberInput({ label: 'Beats per Chord', value: opts.map(o => o.beatsPerChord.toString()), onChange: (n => opts.update(o => { o.beatsPerChord = n return o })) }), h('input', { type: 'submit', value: 'Play' }) ) ) } interface ChordsGroupParams { title: string, chords: Rx, updateChords: (chords: Chord.Chords) => void } function chordsGroup({ title, chords, updateChords }: ChordsGroupParams): Html { const line = (lineChords: Array, label: (chord: Chord.Chord) => string) => h('div', { className: 'g-ChordsLine' }, chordCheckbox({ checked: chords.map(cs => lineChords.every(c => cs.has(c))), onCheck: chords.map(cs => (checked: boolean) => { if (checked) { lineChords.forEach(c => cs.add(c)) } else { lineChords.forEach(c => cs.delete(c)) } updateChords(cs) } ) }), lineChords.map(chord => chordCheckbox({ label: label(chord), checked: chords.map(cs => cs.has(chord)), onCheck: chords.map(cs => (checked: boolean) => { if (checked) { cs.add(chord) } else { cs.delete(chord) } updateChords(cs) } ) }) ) ) return h('tr', h('td', chordCheckbox({ label: title, checked: chords.map(cs => Chord.sharps.every(c => cs.has(c)) && Chord.plains.every(c => cs.has(c)) && Chord.bemols.every(c => cs.has(c)) ), onCheck: chords.map(cs => (checked: boolean) => { if (checked) { Chord.sharps.forEach(c => cs.add(c)) Chord.plains.forEach(c => cs.add(c)) Chord.bemols.forEach(c => cs.add(c)) } else { Chord.sharps.forEach(c => cs.delete(c)) Chord.plains.forEach(c => cs.delete(c)) Chord.bemols.forEach(c => cs.delete(c)) } updateChords(cs) } ) }) ), h('td', line(Chord.sharps, () => '♯'), line(Chord.plains, chord => chord), line(Chord.bemols, () => '♭') ) ) } interface ChordCheckboxParams { label?: string checked: Rx onCheck: Rx<(checked: boolean) => void> } function chordCheckbox({ label, checked, onCheck }: ChordCheckboxParams): Html { return h('label', { className: 'g-ChordLabel' }, h('input', { type: 'checkbox', checked, onchange: onCheck.map(f => (event: Event) => f((event.target as HTMLInputElement).checked)) } ), label ) } interface NumberInputParams { label: string value: Rx onChange: (n: number) => void } function numberInput({ label, value, onChange }: NumberInputParams): Html { return h('label', h('input', { type: 'number', value, onchange: (event: Event) => { const n = parseInt((event.target as HTMLInputElement).value) if (typeof n === 'number') { onChange(n) } } } ), label ) }