aboutsummaryrefslogtreecommitdiff
path: root/src/View
diff options
context:
space:
mode:
Diffstat (limited to 'src/View')
-rw-r--r--src/View/configView.ml83
-rw-r--r--src/View/timerView.ml123
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 ()) |]
+ ();
+ |]
+ ()