diff options
Diffstat (limited to 'src/View')
-rw-r--r-- | src/View/configView.ml | 83 | ||||
-rw-r--r-- | src/View/timerView.ml | 123 |
2 files changed, 206 insertions, 0 deletions
diff --git a/src/View/configView.ml b/src/View/configView.ml new file mode 100644 index 0000000..5db6ea5 --- /dev/null +++ b/src/View/configView.ml @@ -0,0 +1,83 @@ +open CreateElement +open Config + +let labelledInput labelValue minValue inputValue update writeDuration = + label + ~attributes:[| className "g-Form__Label" |] + ~eventListeners: + [| + onInput (fun e -> + match + EventTarget.value (Event.target e) + |> Option.flatMap Belt.Int.fromString + with + | Some n -> + let () = update n in + writeDuration () + | None -> ()); + |] + ~children: + [| + text labelValue; + input_ + ~attributes: + [| + className "g-Form__Input"; + type_ "number"; + min_ (Js.Int.toString minValue); + value (Js.Int.toString inputValue); + |] + (); + |] + () + +let render initialConfig onStart = + let config = ref initialConfig in + let duration = text (Duration.prettyPrint (getDuration !config)) in + let wd () = + Element.setTextContent duration (Duration.prettyPrint (getDuration !config)) + in + div + ~children: + [| + header + ~attributes:[| className "g-Layout__Header" |] + ~children:[| text "Tabata timer" |] + (); + form + ~attributes:[| className "g-Form" |] + ~eventListeners: + [| + onSubmit (fun e -> + let () = Event.preventDefault e in + onStart !config); + |] + ~children: + [| + labelledInput "prepare" 0 !config.prepare + (fun n -> config := { !config with prepare = n }) + wd; + labelledInput "tabatas" 1 !config.tabatas + (fun n -> config := { !config with tabatas = n }) + wd; + labelledInput "cycles" 1 !config.cycles + (fun n -> config := { !config with cycles = n }) + wd; + labelledInput "work" 5 !config.work + (fun n -> config := { !config with work = n }) + wd; + labelledInput "rest" 5 !config.rest + (fun n -> config := { !config with rest = n }) + wd; + div + ~attributes:[| className "g-Form__Duration" |] + ~children:[| text "duration"; div ~children:[| duration |] () |] + (); + button + ~attributes:[| className "g-Form__Start" |] + ~children:[| text "start" |] + (); + |] + (); + |] + () diff --git a/src/View/timerView.ml b/src/View/timerView.ml new file mode 100644 index 0000000..2384f85 --- /dev/null +++ b/src/View/timerView.ml @@ -0,0 +1,123 @@ +open CreateElement + +let render (config : Config.config) onStop = + let duration = Config.getDuration config in + (* State *) + let interval = ref None in + let elapsed = ref 0 in + let step = ref (Step.getAt config !elapsed) in + let isPlaying = ref true in + (* Elements *) + let stepElt = text (Step.prettyPrint !step.step) in + let durationElt = text (Duration.prettyPrint !step.remaining) in + let arcPathElt = path ~attributes:[| className "g-Timer__ArcProgress" |] () in + let tabataCurrentElt = text (Js.Int.toString !step.tabata) in + let cycleCurrentElt = text (Js.Int.toString !step.cycle) in + (* Update *) + let stop () = + let () = Belt.Option.forEach !interval Js.Global.clearInterval in + onStop config + in + let updateDom () = + let angle = Js.Int.toFloat !elapsed /. Js.Int.toFloat duration *. 360.0 in + let () = + Element.setAttribute arcPathElt "d" (Arc.describe 0.0 0.0 95.0 0.0 angle) + in + let step = Step.getAt config !elapsed in + let () = Element.setTextContent stepElt (Step.prettyPrint step.step) in + let () = + Element.setTextContent durationElt (Duration.prettyPrint step.remaining) + in + let () = + Element.setTextContent tabataCurrentElt (Js.Int.toString step.tabata) + in + let () = + Element.setTextContent cycleCurrentElt (Js.Int.toString step.cycle) + in + Audio.playFromStep config step + in + let update () = + if !isPlaying then + let () = elapsed := !elapsed + 1 in + let () = step := Step.getAt config !elapsed in + if !elapsed > duration then stop () else updateDom () + else () + in + (* Start timer *) + let () = interval := Some (Js.Global.setInterval update 1000) in + (* View *) + section + ~attributes:[| className "g-Timer" |] + ~children: + [| + button + ~attributes:[| className "g-Timer__Dial" |] + ~eventListeners:[| onClick (fun _ -> isPlaying := not !isPlaying) |] + ~children: + [| + svg + ~attributes: + [| className "g-Timer__Arc"; viewBox "-100 -100 200 200" |] + ~children: + [| + path + ~attributes: + [| + className "g-Timer__ArcTotal"; + d (Arc.describe 0.0 0.0 95.0 0.0 359.999); + |] + (); + arcPathElt; + |] + (); + div + ~attributes:[| className "g-Timer__Step" |] + ~children:[| stepElt |] (); + div + ~attributes:[| className "g-Timer__Duration" |] + ~children:[| durationElt |] (); + |] + (); + div + ~attributes:[| className "g-Timer__TabataAndCycle" |] + ~children: + [| + div + ~attributes:[| className "g-Timer__Tabata" |] + ~children: + [| + div ~children:[| text "Tabata" |] (); + span + ~attributes:[| className "g-Timer__TabataCurrent" |] + ~children:[| tabataCurrentElt |] (); + text "/"; + span + ~attributes:[| className "g-Timer__TabataTotal" |] + ~children:[| text (Js.Int.toString config.tabatas) |] + (); + |] + (); + div + ~attributes:[| className "g-Timer__Cycle" |] + ~children: + [| + div ~children:[| text "Cycle" |] (); + span + ~attributes:[| className "g-Timer__CycleCurrent" |] + ~children:[| cycleCurrentElt |] (); + text "/"; + span + ~attributes:[| className "g-Timer__CycleTotal" |] + ~children:[| text (Js.Int.toString config.cycles) |] + (); + |] + (); + |] + (); + div + ~attributes:[| className "g-Timer__Stop" |] + ~children:[| text "stop" |] + ~eventListeners:[| onClick (fun _ -> stop ()) |] + (); + |] + () |