From ea3ec65de1954502e75d6b2ddcf4a0a1c063c8be Mon Sep 17 00:00:00 2001
From: jahugg <jan@huggenberg.ch>
Date: Wed, 1 Mar 2023 15:38:05 +0100
Subject: [PATCH] time delay selection

---
 src/main.js          | 74 +++++++++++++++++++++++++++++++++++---------
 src/styles/main.css  | 17 ++++++++++
 src/styles/main.less | 19 ++++++++++++
 3 files changed, 96 insertions(+), 14 deletions(-)

diff --git a/src/main.js b/src/main.js
index 70c3e69..e3eeb71 100644
--- a/src/main.js
+++ b/src/main.js
@@ -237,6 +237,13 @@ function buildPageContents(object) {
       linkEl.dataset.command = item.command;
       itemEl.appendChild(linkEl);
 
+      const progressEl = document.createElement("div");
+      progressEl.className = "progress";
+      linkEl.appendChild(progressEl);
+      progressEl.addEventListener("animationend", (event) => {
+        navigateToSelected();
+      });
+
       if (item.label.toLowerCase() !== item.command.toLowerCase()) {
         const commandEl = document.createElement("div");
         commandEl.className = "command";
@@ -282,14 +289,17 @@ function navigateToSelected() {
   if (currentItem) {
     const path = currentItem.getAttribute("href");
     navigateToPath(path);
-  } else printLogMsg("no item selected to open");
+  } else console.log("no item selected to open");
 }
 
 // focus specific navigation item
 function focusNavigationItem(index, broadcast = true) {
   // unselect all items
   const navigationList = document.querySelectorAll("#navigation li");
-  for (let item of navigationList) delete item.dataset.selected;
+  for (let item of navigationList) {
+    delete item.dataset.selected;
+    item.querySelector(".progress").style.animation = "";
+  }
 
   // select requested element
   const navigationItem = document.querySelector(
@@ -300,6 +310,8 @@ function focusNavigationItem(index, broadcast = true) {
   // broadcast focus event to other clients
   if (broadcast && socket.readyState === WebSocket.OPEN)
     socket.send(JSON.stringify({ type: "focus", value: index }));
+
+  return navigationItem;
 }
 
 // focus next navigation item
@@ -350,7 +362,7 @@ function focusClosestItem(targetY = window.innerHeight / 2) {
     }
     index++;
   }
-  focusNavigationItem(closestIndex);
+  return focusNavigationItem(closestIndex);
 }
 
 // moving up navigation tree
@@ -382,7 +394,7 @@ function addMouseControls() {
     if (listItem) {
       // get childnode index
       let index = Array.from(listItem.parentNode.children).indexOf(listItem);
-      focusNavigationItem(index + 1);
+      let itemEl = focusNavigationItem(index + 1);
     }
   });
 
@@ -392,7 +404,10 @@ function addMouseControls() {
     if (listItem) {
       // unselect all items
       const navigationList = document.querySelectorAll("#navigation li");
-      for (let item of navigationList) delete item.dataset.selected;
+      for (let item of navigationList) {
+        item.querySelector(".progress").style.animation = "";
+        delete item.dataset.selected;
+      }
     }
   });
 
@@ -502,24 +517,27 @@ function addVoiceControls() {
   recognition.maxAlternatives = 1;
 
   recognition.start();
-  printLogMsg("Voice > listening");
+  console.log("Voice > listening");
 
   recognition.onresult = function (event) {
     let command = event.results[0][0].transcript.toLowerCase();
     let confidence = event.results[0][0].confidence;
     command = command.toLowerCase();
 
-    printLogMsg(`Voice > Received "${command}" (${confidence.toFixed(2)})`);
+    console.log(`Voice > Received "${command}" (${confidence.toFixed(2)})`);
 
     if (commands.includes(command)) {
       let index = commands.indexOf(command); // get index of command in array
       let path = availableCommands[index].path; // get corresponding path in availableCommands array
       navigateToPath(path);
-    } else if (command === "select" || command === "okay") navigateToSelected();
-    else if (command === "open") moveDownNavigationLevel();
-    else if (command === "emergency call")
+    } else if (command.includes("select") || command.includes("okay"))
+      navigateToSelected();
+    else if (command.includes("open")) moveDownNavigationLevel();
+    else if (command.includes("emergency call"))
       navigateToPath("/menu/call/emergency");
-    else if (command === "exit") navigateToPath("/");
+    else if (command.includes("exit") || command.includes("home"))
+      navigateToPath("/");
+    else if (command.includes("back")) moveUpNavigationLevel();
   };
 
   recognition.onspeechend = function () {
@@ -527,7 +545,6 @@ function addVoiceControls() {
 
     setTimeout(() => {
       recognition.start();
-      printLogMsg("Voice > listening");
     }, "1000");
   };
 
@@ -557,8 +574,10 @@ function addVoiceControls() {
 // ===================
 function addMotionControls() {
   let referenceY;
-  let sensitivity = 5000;
+  let sensitivity = 4000;
   let screenCenter = new Point(window.innerWidth / 2, window.innerHeight / 2);
+  let idleTimeout;
+  let idleDelay = 3000;
 
   // motion buffer stuff
   let motionBuffer = [];
@@ -613,12 +632,39 @@ function addMotionControls() {
       bufferIterations++;
       averageYDiff = Math.abs(lastAverageY - averageY);
       if (averageYDiff > motionThreshold && referenceY !== undefined) {
+        // reseting reference point after a delay of holding still
+        clearTimeout(idleTimeout);
+        idleTimeout = setTimeout(() => {
+          referenceY = averageY;
+
+          // start approaching new reference point
+          // somehow this currently only executes once...
+          let incrementValue = 0.001;
+          const intervalId = setInterval(() => {
+            if (referenceY < averageY) {
+              referenceY += incrementValue;
+              if (referenceY >= averageY) {
+                clearInterval(intervalId);
+                referenceY = averageY; // Ensure exact match with target
+              }
+            } else if (referenceY > averageY) {
+              referenceY -= incrementValue;
+              if (referenceY <= averageY) {
+                clearInterval(intervalId);
+                referenceY = averageY; // Ensure exact match with target
+              }
+            } else clearInterval(intervalId);
+          }, 100);
+        }, idleDelay);
+
         lastAverageY = averageY;
         let targetY = averageY;
         targetY = (targetY - referenceY) * -sensitivity;
 
         drawMotion(targetY);
-        focusClosestItem(targetY);
+        let itemEl = focusClosestItem(targetY);
+        let progressEl = itemEl.querySelector(".progress");
+        progressEl.style.animation = "progress 3s ease-out forwards";
       }
     }
   }
diff --git a/src/styles/main.css b/src/styles/main.css
index 7648f41..1c87e4d 100644
--- a/src/styles/main.css
+++ b/src/styles/main.css
@@ -24,6 +24,14 @@
     transform: translateX(0);
   }
 }
+@keyframes progress {
+  from {
+    width: 0%;
+  }
+  to {
+    width: 100%;
+  }
+}
 .link {
   position: relative;
   opacity: 0;
@@ -33,6 +41,7 @@
   margin-bottom: 0.4em;
 }
 .link a {
+  position: relative;
   color: inherit;
   text-decoration: none;
   border-bottom: solid 0.1em transparent;
@@ -83,6 +92,14 @@
   opacity: 1;
   background-position: center center;
 }
+.link .progress {
+  height: 100%;
+  width: 0%;
+  position: absolute;
+  top: 0;
+  z-index: -100;
+  background: var(--color-grey-01);
+}
 #canvas {
   display: block;
   width: 100%;
diff --git a/src/styles/main.less b/src/styles/main.less
index beddea4..f9a3b50 100644
--- a/src/styles/main.less
+++ b/src/styles/main.less
@@ -27,6 +27,15 @@
   }
 }
 
+@keyframes progress {
+  from {
+    width: 0%;
+  }
+  to {
+    width: 100%;
+  }
+}
+
 .link {
   position: relative;
   opacity: 0;
@@ -37,6 +46,7 @@
   }
 
   a {
+    position: relative;
     color: inherit;
     text-decoration: none;
     border-bottom: solid 0.1em transparent;
@@ -95,6 +105,15 @@
       background-position: center center;
     }
   }
+
+  .progress {
+    height: 100%;
+    width: 0%;
+    position: absolute;
+    top: 0;
+    z-index: -100;
+    background: var(--color-grey-01);
+  }
 }
 
 #canvas {
-- 
GitLab