(* Audio *) let c3 = Audio.create "sounds/c3.mp3" let c4 = Audio.create "sounds/c4.mp3" let c5 = Audio.create "sounds/c5.mp3" let playAudio (step : Step.state) = match step.step with | Step.Prepare when step.remaining == !Config.config.prepare -> Audio.playOrReplay c3 | Step.Work when step.remaining == !Config.config.work -> Audio.playOrReplay c5 | Step.Rest when step.remaining == !Config.config.rest -> Audio.playOrReplay c3 | Step.End -> Audio.playOrReplay c3 | _ -> if step.remaining <= 3 then Audio.playOrReplay c4 else () (* Elements *) let timerElt = Document.querySelectorUnsafe "#g-Timer" let dialElt = Document.querySelectorUnsafe "#g-Timer__Dial" let arcPathElt = Document.querySelectorUnsafe "#g-Timer__ArcProgress" let stepElt = Document.querySelectorUnsafe "#g-Timer__Step" let durationElt = Document.querySelectorUnsafe "#g-Timer__Duration" let tabataCurrentElt = Document.querySelectorUnsafe "#g-Timer__TabataCurrent" let tabataTotalElt = Document.querySelectorUnsafe "#g-Timer__TabataTotal" let cycleCurrentElt = Document.querySelectorUnsafe "#g-Timer__CycleCurrent" let cycleTotalElt = Document.querySelectorUnsafe "#g-Timer__CycleTotal" let stopElt = Document.querySelectorUnsafe "#g-Timer__Stop" (* State *) let interval = ref None let duration = ref 0 let elapsedTime = ref 0 let onStop : (unit -> unit) ref = ref (fun () -> ()) let isPlaying = ref false (* Actions *) let playPause _ = isPlaying := not !isPlaying let stop _ = let () = Belt.Option.forEach !interval Js.Global.clearInterval in !onStop () (* View *) let updateDom () = let angle = Js.Int.toFloat !elapsedTime /. 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.config !elapsedTime in let () = Element.setInnerText stepElt (Step.prettyPrint step.step) in let () = Element.setInnerText durationElt (Duration.prettyPrint step.remaining) in let () = Element.setInnerText tabataCurrentElt (Js.Int.toString step.tabata) in let () = playAudio step in Element.setInnerText cycleCurrentElt (Js.Int.toString step.cycle) (* Update *) let update () = if !isPlaying then let () = elapsedTime := !elapsedTime + 1 in if !elapsedTime > !duration then stop () else updateDom () else () (* Init *) let init () = let () = duration := Config.getDuration () in let () = elapsedTime := 0 in let () = Element.setInnerText tabataTotalElt (Js.Int.toString !Config.config.tabatas) in Element.setInnerText cycleTotalElt (Js.Int.toString !Config.config.cycles) (* Setup and start *) let setup onTimerStop = onStop := onTimerStop let show () = let () = updateDom () in Element.setStyle timerElt "display: flex" let hide () = Element.setStyle timerElt "display: none" let start () = let () = interval := Some (Js.Global.setInterval update 1000) in isPlaying := true let () = let () = Element.addEventListener stopElt "click" stop in Element.addEventListener dialElt "click" playPause