aboutsummaryrefslogtreecommitdiff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/form.ts149
-rw-r--r--src/view/layout.ts13
-rw-r--r--src/view/options.ts22
-rw-r--r--src/view/play.ts89
4 files changed, 162 insertions, 111 deletions
diff --git a/src/view/form.ts b/src/view/form.ts
index 77a8cb7..5547e0c 100644
--- a/src/view/form.ts
+++ b/src/view/form.ts
@@ -1,66 +1,121 @@
-import h, { classNames } from 'lib/h'
-import * as dom from 'lib/dom'
-import * as play from 'view/play'
-import * as layout from 'view/layout'
-import * as chord from 'chord'
-import * as options from 'view/options'
+import { h, withVar, Html, Rx } from 'lib/rx'
+import * as Options from 'view/options'
-// View
-
-export function view(): Element[] {
- let opts = options.load()
+interface Params {
+ options: Options.Model
+ onSubmit: (options: Options.Model) => void
+}
- return layout.view(
+export function view({ options, onSubmit }: Params): Html {
+ return withVar(options, (opts, updateOptions) =>
h('form',
- {
- className: 'g-Form',
- onsubmit
+ { className: 'g-Form',
+ onsubmit: opts.map(o =>
+ (event: Event) => {
+ event.preventDefault()
+ onSubmit(o)
+ }
+ )
},
- chordCheckbox({ name: 'major', label: '', checked: opts.major }),
- chordCheckbox({ name: 'minor', label: '-', checked: opts.minor }),
- chordCheckbox({ name: 'seventh', label: '7', checked: opts.seventh }),
- chordCheckbox({ name: 'minorSeventh', label: '-7', checked: opts.minorSeventh }),
- chordCheckbox({ name: 'majorSeventh', label: '7', checked: opts.majorSeventh }),
- labelInput({ type: 'number', name: 'bpm', label: 'BPM', value: opts.bpm.toString() }),
- labelInput({ type: 'number', name: 'beatsPerChord', label: 'Beats per Chord', value: opts.beatsPerChord.toString() }),
+ chordCheckbox({
+ label: '',
+ checked: opts.map(o => o.major),
+ onCheck: (checked => updateOptions(o => {
+ o.major = checked
+ return o
+ }))
+ }),
+ chordCheckbox({
+ label: '-',
+ checked: opts.map(o => o.minor),
+ onCheck: (checked => updateOptions(o => {
+ o.minor = checked
+ return o
+ }))
+ }),
+ chordCheckbox({
+ label: '7',
+ checked: opts.map(o => o.seventh),
+ onCheck: (checked => updateOptions(o => {
+ o.seventh = checked
+ return o
+ }))
+ }),
+ chordCheckbox({
+ label: '-7',
+ checked: opts.map(o => o.minorSeventh),
+ onCheck: (checked => updateOptions(o => {
+ o.minorSeventh = checked
+ return o
+ }))
+ }),
+ chordCheckbox({
+ label: '7',
+ checked: opts.map(o => o.majorSeventh),
+ onCheck: (checked => updateOptions(o => {
+ o.majorSeventh = checked
+ return o
+ }))
+ }),
+ numberInput({
+ label: 'BPM',
+ value: opts.map(o => o.bpm.toString()),
+ onChange: (n => updateOptions(o => {
+ o.bpm = n
+ return o
+ }))
+ }),
+ numberInput({
+ label: 'Beats per Chord',
+ value: opts.map(o => o.beatsPerChord.toString()),
+ onChange: (n => updateOptions(o => {
+ o.beatsPerChord = n
+ return o
+ }))
+ }),
h('input', { type: 'submit', value: 'Play' })
)
)
}
-function chordCheckbox({ name, label, checked }: any): Element {
- return labelInput({ className: 'g-ChordLabel', type: 'checkbox', name, label, checked })
+interface ChordCheckboxParams {
+ label: string
+ checked: Rx<boolean>
+ onCheck: (checked: boolean) => void
}
-function onsubmit(event: Event): void {
- event.preventDefault()
- let input = (name: String) => document.querySelector(`input[name="${name}"]`) as HTMLInputElement
- let opts = {
- major: input('major').checked,
- minor: input('minor').checked,
- seventh: input('seventh').checked,
- minorSeventh: input('minorSeventh').checked,
- majorSeventh: input('majorSeventh').checked,
- bpm: parseInt(input('bpm').value),
- beatsPerChord: parseInt(input('beatsPerChord').value)
- }
- options.save(opts)
- dom.show(play.view(opts))
+function chordCheckbox({ label, checked, onCheck }: ChordCheckboxParams): Html {
+ return h('label',
+ { className: 'g-ChordLabel' },
+ h('input',
+ { type: 'checkbox',
+ checked,
+ onchange: (event: Event) => onCheck((event.target as HTMLInputElement).checked)
+ }
+ ),
+ label
+ )
}
-type LabelInputParams = {
- className?: string,
- type: string,
- name: string,
- label: string,
- checked?: boolean,
- value?: string
+interface NumberInputParams {
+ label: string
+ value: Rx<string>
+ onChange: (n: number) => void
}
-function labelInput({ className, type, name, label, checked, value }: LabelInputParams) {
+function numberInput({ label, value, onChange }: NumberInputParams): Html {
return h('label',
- className !== undefined ? { className } : {},
- h('input', { type, name, checked, value }),
+ h('input',
+ { type: 'number',
+ value,
+ onchange: (event: Event) => {
+ const n = parseInt((event.target as HTMLInputElement).value)
+ if (typeof n === 'number') {
+ onChange(n)
+ }
+ }
+ }
+ ),
label
)
}
diff --git a/src/view/layout.ts b/src/view/layout.ts
deleted file mode 100644
index ac62290..0000000
--- a/src/view/layout.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import h from 'lib/h'
-import * as dom from 'lib/dom'
-import * as form from 'view/form'
-
-export function view(main: Element): Element[] {
- return [
- h('header',
- { onclick: () => dom.show(form.view()) },
- 'Chords'
- ),
- main
- ]
-}
diff --git a/src/view/options.ts b/src/view/options.ts
index 4a57f97..31fd631 100644
--- a/src/view/options.ts
+++ b/src/view/options.ts
@@ -1,14 +1,14 @@
-export type Options = {
- major: boolean,
- minor: boolean,
- seventh: boolean,
- minorSeventh: boolean,
- majorSeventh: boolean,
- bpm: number,
+export interface Model {
+ major: boolean
+ minor: boolean
+ seventh: boolean
+ minorSeventh: boolean
+ majorSeventh: boolean
+ bpm: number
beatsPerChord: number
}
-let defaultOptions: Options = {
+let init: Model = {
major: true,
minor: false,
seventh: false,
@@ -20,11 +20,11 @@ let defaultOptions: Options = {
let key: string = 'options'
-export function load(): Options {
+export function load(): Model {
let str = localStorage[key]
- return str && JSON.parse(str) || defaultOptions
+ return str && JSON.parse(str) || init
}
-export function save(options: Options): void {
+export function save(options: Model): void {
localStorage[key] = JSON.stringify(options)
}
diff --git a/src/view/play.ts b/src/view/play.ts
index f0340f7..b85e505 100644
--- a/src/view/play.ts
+++ b/src/view/play.ts
@@ -1,49 +1,58 @@
-import h, { classNames } from 'lib/h'
-import * as dom from 'lib/dom'
-import { Options } from 'view/options'
-import * as chord from 'chord'
-import * as layout from 'view/layout'
+import { h, withVar, Html } from 'lib/rx'
+import * as Options from 'view/options'
+import * as Chord from 'chord'
-export function view(options: Options): Element[] {
- let chords = h('div',
- { className: 'g-Play' },
- chordNode(),
- chordNode(options),
- chordNode(options)
- )
-
- let chordBeat = 1
+interface ViewParams {
+ options: Options.Model
+}
- dom.triggerAnimation(chords.children[1] as HTMLElement, 'g-Chord--Beat')
- setInterval(() => {
- if (chordBeat == options.beatsPerChord) {
- shiftChords(chords, options)
- chords.children[0].classList.remove('g-Chord--Beat')
- dom.triggerAnimation(chords as HTMLElement, 'g-Play--Shift')
- dom.triggerAnimation(chords.children[1] as HTMLElement, 'g-Chord--Beat')
- chordBeat = 1
- } else {
- dom.triggerAnimation(chords.children[1] as HTMLElement, 'g-Chord--Beat')
- chordBeat += 1
- }
- }, 60 / options.bpm * 1000)
+export function view({ options }: ViewParams): Html {
+ const initChords: Array<[string, string]> = [['', ''], Chord.generate(options), Chord.generate(options)]
- return layout.view(chords)
-}
+ return withVar(initChords, (chords, updateChords) =>
+ withVar(undefined, (beat, updateBeat) => {
+ let chordBeat = 1
+ const interval = setInterval(() => {
+ if (chordBeat === options.beatsPerChord) {
+ updateChords(xs => {
+ xs.shift()
+ xs.push(Chord.generate(options))
+ return xs
+ })
+ chordBeat = 1
+ } else {
+ updateBeat(_ => undefined)
+ chordBeat += 1
+ }
+ }, 60 / options.bpm * 1000)
-/* Shift chords and generate a new random one.
- */
-function shiftChords(chords: Element, options: Options) {
- chords.removeChild(chords.children[0])
- chords.appendChild(chordNode(options))
+ return h('div',
+ { className: 'g-Play',
+ onunmount: () => clearInterval(interval)
+ },
+ chords.map(xs =>
+ h('div',
+ { className: 'g-Chords' },
+ xs.map((chord, i) => {
+ if (i == 0) {
+ return viewChord(chord, 'g-Chord--Disappear')
+ } else if (i == 1) {
+ return beat.map(_ => viewChord(chord))
+ } else {
+ return viewChord(chord)
+ }
+ })
+ )
+ )
+ )
+ })
+ )
}
-function chordNode(options?: Options): Element {
- let [base, alteration] = options ? chord.generate(options) : ['', '']
-
- return h('div',
- { className: 'g-Chord' },
+function viewChord([base, alteration]: [string, string], className: string = ''): Html {
+ return h('div',
+ { className: `g-Chord ${className}` },
base,
- h('sup', {}, alteration)
+ h('sup', alteration)
)
}