From 353e37cff5f4a13bf38368fc2058c03ac970377f Mon Sep 17 00:00:00 2001
From: Simon Brooke <simon@journeyman.cc>
Date: Sun, 18 Sep 2022 18:27:53 +0100
Subject: [PATCH] Some user interface improvements

Close control on popup, progress bar when recording.
---
 resources/public/index.html         | 12 ++++---
 resources/public/scripts/muharni.js | 55 ++++++++++++++++++++++++++---
 resources/public/style.css          | 44 ++++++++++++++++++++++-
 src/muharni/construct.clj           | 15 +++++---
 4 files changed, 112 insertions(+), 14 deletions(-)

diff --git a/resources/public/index.html b/resources/public/index.html
index a4cafe9..446375c 100644
--- a/resources/public/index.html
+++ b/resources/public/index.html
@@ -16,6 +16,7 @@ src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"
   <body id="body">
     <div id="popup"
     style="display: none; border: thin solid gray; width: 10%">
+      <div id="closebox" onclick="$('#popup').hide();">✖</div>
       <p id="character"
       style="text-align: center; margin: 0; font-size: 4em;">?</p>
       <table id="controls"
@@ -23,23 +24,26 @@ src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"
         <tr>
           <th>Tutor</th>
           <td>
-            <button id="play-tutor">►</button>
+            <span id="play-tutor">►</span>
           </td>
         </tr>
         <tr>
           <th>You</th>
           <td>
-            <button id="play-student">►</button>
+            <span id="play-student">►</span>
           </td>
           <td>
-            <button id="record-stop">⏺</button>
+            <span id="record-stop">⏺</span>
           </td>
         </tr>
+        <tr>
+          <td colspan="3" id="progress"></td>
+        </tr>
       </table>
     </div>
     <h1>Muharni table</h1>
     <button
-    onclick="var l = document.getElementById('long'); var s = document.getElementById('short'); if (l.style.display == 'none') { l.style.display = 'block'; s.style.display = 'none'; } else { l.style.display = 'none'; s.style.display = 'block'; }">Toggle
+    onclick="var l = document.getElementById('long'); var s = document.getElementById('short'); if (l.style.display == 'none') { l.style.display = 'block'; s.style.display = 'none'; } else { l.style.display = 'none'; s.style.display = 'block'; } $('#popup').hide();">Toggle
     short/long</button>
     <div id="long" style="display: block;">
       <h2>Long forms</h2>
diff --git a/resources/public/scripts/muharni.js b/resources/public/scripts/muharni.js
index d83502e..be6c5b4 100644
--- a/resources/public/scripts/muharni.js
+++ b/resources/public/scripts/muharni.js
@@ -16,13 +16,57 @@
  */
 const studentSounds = Array(80).fill(0).map(x => Array(13).fill(null));
 
+/** 
+ *  Creates a progressbar. Adapted from
+ *  https://stackoverflow.com/questions/31109581/javascript-timer-progress-bar
+ *
+ *  @param id the id of the div we want to transform in a progressbar
+ *  @param duration the duration of the timer example: '10s'
+ *  @param callback, optional function which is called when the progressbar reaches 0.
+ */
+function createProgressbar(id, duration) {
+    // We select the div that we want to turn into a progressbar
+    try {
+        const progressbar = document.getElementById(id);
+        progressbar.className = 'progressbar';
+
+        // We create the div that changes width to show progress
+        let progressbarinner = progressbar.querySelector('.inner');
+
+        if (progressbarinner == null) {
+            progressbarinner = document.createElement('div');
+            progressbarinner.className = 'inner';
+
+            // Now we set the animation parameters
+            progressbarinner.style.animationDuration = duration;
+
+            // Append the progressbar to the main progressbardiv
+            progressbar.appendChild(progressbarinner);
+        }
+        progressbarinner.addEventListener('animationend', () => {
+            while (progressbar.hasChildNodes()) {
+                progressbar.removeChild(progressbar.lastChild);
+            }
+        });
+
+        // When everything is set up we start the animation
+        progressbarinner.style.animationPlayState = 'running';
+    } catch (e) {
+        console.warn("Failed to create progress bar because " +
+            e.message +
+            ". This does not, cosmically speaking, matter.");
+    }
+}
+
 function recordStudentSound(r, c) {
     console.info("Entered recordStudentSound for row " + r + ", column " + c);
 
     if (Number.isInteger(r) && Number.isInteger(c)) {
-        $('#record-student').css('color', 'green');
+        $('#record-stop').css('color', 'green');
 
         try {
+            createProgressbar('progress', '5s');
+
             navigator.mediaDevices.getUserMedia({ audio: true })
                 .then(stream => {
                     const mediaRecorder = new MediaRecorder(stream);
@@ -42,18 +86,19 @@ function recordStudentSound(r, c) {
 
                         if (audioChunks.length > 0) {
                             studentSounds[r][c] = new Blob(audioChunks);
-                            
+
                             $('#play-student').prop('disabled', false);
+                            $('#play-student').css('color', 'black');
                             $("#play-student").on("click", function (e) {
                                 console.log("Playing student sound for row " + r + ", column " + c);
                                 new Audio(URL.createObjectURL(studentSounds[r][c])).play();
-                            });            
+                            });
                             console.log("Successfully recorded student sound for row " + r + ", column " + c);
                         } else {
                             console.warn("Failed to record student sound for row " + r + ", column " + c);
                             window.alert("No sound detected. Check your microphone?");
                         }
-                        $('#record-student').css('color', 'red');
+                        $('#record-stop').css('color', 'red');
                     };
 
                     setTimeout(() => {
@@ -115,7 +160,9 @@ $(document).ready(function () {
             });
 
             $("#play-student").off("click");
+            $('#play-student').css('color', 'gray');
             if (studentAudio != null) {
+                $('#play-student').css('color', 'black');
                 $("#play-student").on("click", function (e) {
                     console.log("Playing student sound for row " + row + ", column " + col);
                     new Audio(URL.createObjectURL(studentAudio)).play();
diff --git a/resources/public/style.css b/resources/public/style.css
index 33c7a14..2d83e6a 100644
--- a/resources/public/style.css
+++ b/resources/public/style.css
@@ -29,11 +29,49 @@ th {
     font-size: 3em;
 }
 
+.progressbar {
+    width: 80%;
+    margin: 25px auto;
+    border: solid 1px #000;
+}
+
+.progressbar .inner {
+    height: 15px;
+    animation: progressbar-countdown;
+    /* Placeholder, this will be updated using javascript */
+    animation-duration: 40s;
+    /* We stop in the end */
+    animation-iteration-count: 1;
+    /* Stay on pause when the animation is finished finished */
+    animation-fill-mode: forwards;
+    /* We start paused, we start the animation using javascript */
+    animation-play-state: paused;
+    /* We want a linear animation, ease-out is standard */
+    animation-timing-function: linear;
+}
+
+@keyframes progressbar-countdown {
+    0% {
+        width: 100%;
+        background: #0F0;
+    }
+
+    100% {
+        width: 0%;
+        background: #F00;
+    }
+}
+
 #bug {
     width: 1em;
     height: 1em;
 }
 
+#closebox {
+    color: red;
+    float: right;
+}
+
 #footer {
     margin-top: 2em;
     border-top: thin solid gray;
@@ -51,4 +89,8 @@ th {
     display: none;
     background-color: whitesmoke;
     z-index: 10;
-}
\ No newline at end of file
+}
+
+#record-stop {
+    color: red;
+}
diff --git a/src/muharni/construct.clj b/src/muharni/construct.clj
index 59b7784..2b4f533 100644
--- a/src/muharni/construct.clj
+++ b/src/muharni/construct.clj
@@ -4,7 +4,7 @@
   (:require [hiccup.core :refer [html]]
             [clojure.java.io :refer [input-stream]]
             [clojure.string :as s])
-  (:import [java.io StringWriter PrintWriter]
+  (:import [java.io StringWriter]
            [java.util Properties]
            [org.w3c.tidy Tidy]))
 
@@ -192,16 +192,20 @@
    [:body {:id "body"}
     [:div {:id "popup"
            :style "display: none; border: thin solid gray; width: 10%"}
+     [:div {:id "closebox"
+            :onclick "$('#popup').hide();"} "&#10006;"]
      [:p {:id "character" :style "text-align: center; margin: 0; font-size: 4em;"} "?"]
      [:table {:id "controls" :summary "Controls for audio playback and recording"}
       [:tr
        [:th "Tutor"]
-       [:td  [:button {:id "play-tutor"}
+       [:td  [:span {:id "play-tutor"}
               "&#9658;"]]]
       [:tr
        [:th "You"]
-       [:td  [:button {:id "play-student"} "&#9658;"]]
-       [:td  [:button {:id "record-stop"} "&#9210;"]]]]]
+       [:td  [:span {:id "play-student"} "&#9658;"]]
+       [:td  [:span {:id "record-stop"} "&#9210;"]]]
+      [:tr
+       [:td {:colspan 3 :id "progress"}]]]]
     [:h1 (str title)]
     [:button {:onclick "var l = document.getElementById('long');
                         var s = document.getElementById('short');
@@ -211,7 +215,8 @@
                         } else {
                         l.style.display = 'none';
                         s.style.display = 'block';
-                        }"} "Toggle short/long"]
+                        }
+                        $('#popup').hide();"} "Toggle short/long"]
     [:div {:id "long"
            :style "display: block;"}
      [:h2 "Long forms"]