Browse Source

feat: hide cursor after inactivity

Sv443 1 year ago
parent
commit
5c11d6440a

+ 30 - 16
assets/translations/README.md

@@ -20,15 +20,15 @@ To submit or edit a translation, please follow [this guide](../../contributing.m
 ### Translation progress:
 |   | Locale | Translated keys | Based on |
 | :----: | ------ | --------------- | :------: |
-| ─ | [`en_US`](./en_US.json) | 201 (default locale) |  |
-| ⚠ | [`de_DE`](./de_DE.json) | `198/201` (98.5%) | ─ |
-| ─ | [`en_UK`](./en_UK.json) | `201/201` (100%) | `en_US` |
-| ⚠ | [`es_ES`](./es_ES.json) | `198/201` (98.5%) | ─ |
-| ⚠ | [`fr_FR`](./fr_FR.json) | `198/201` (98.5%) | ─ |
-| ⚠ | [`hi_IN`](./hi_IN.json) | `198/201` (98.5%) | ─ |
-| ⚠ | [`ja_JA`](./ja_JA.json) | `198/201` (98.5%) | ─ |
-| ⚠ | [`pt_BR`](./pt_BR.json) | `198/201` (98.5%) | ─ |
-| ⚠ | [`zh_CN`](./zh_CN.json) | `198/201` (98.5%) | ─ |
+| ─ | [`en_US`](./en_US.json) | 203 (default locale) |  |
+| ⚠ | [`de_DE`](./de_DE.json) | `198/203` (97.5%) | ─ |
+| ─ | [`en_UK`](./en_UK.json) | `203/203` (100%) | `en_US` |
+| ⚠ | [`es_ES`](./es_ES.json) | `198/203` (97.5%) | ─ |
+| ⚠ | [`fr_FR`](./fr_FR.json) | `198/203` (97.5%) | ─ |
+| ⚠ | [`hi_IN`](./hi_IN.json) | `198/203` (97.5%) | ─ |
+| ⚠ | [`ja_JA`](./ja_JA.json) | `198/203` (97.5%) | ─ |
+| ⚠ | [`pt_BR`](./pt_BR.json) | `198/203` (97.5%) | ─ |
+| ⚠ | [`zh_CN`](./zh_CN.json) | `198/203` (97.5%) | ─ |
 
 <sub>
 ✅ - Fully translated
@@ -49,71 +49,85 @@ This means to figure out which keys are untranslated, you will need to manually
 
 ### Missing keys:
 
-<details><summary><code>de_DE</code> - 3 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>de_DE</code> - 5 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
 | `trigger_btn_action_running` | `Running...` |
+| `feature_desc_hideCursorOnIdle` | `Hide the cursor after a few seconds of inactivity over the video` |
+| `feature_desc_hideCursorOnIdleDelay` | `How many seconds of inactivity before the cursor should be hidden?` |
 | `feature_btn_clearLyricsCache_running` | `Clearing...` |
 | `feature_btn_checkVersionNow_running` | `Checking...` |
 
 <br></details>
 
-<details><summary><code>es_ES</code> - 3 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>es_ES</code> - 5 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
 | `trigger_btn_action_running` | `Running...` |
+| `feature_desc_hideCursorOnIdle` | `Hide the cursor after a few seconds of inactivity over the video` |
+| `feature_desc_hideCursorOnIdleDelay` | `How many seconds of inactivity before the cursor should be hidden?` |
 | `feature_btn_clearLyricsCache_running` | `Clearing...` |
 | `feature_btn_checkVersionNow_running` | `Checking...` |
 
 <br></details>
 
-<details><summary><code>fr_FR</code> - 3 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>fr_FR</code> - 5 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
 | `trigger_btn_action_running` | `Running...` |
+| `feature_desc_hideCursorOnIdle` | `Hide the cursor after a few seconds of inactivity over the video` |
+| `feature_desc_hideCursorOnIdleDelay` | `How many seconds of inactivity before the cursor should be hidden?` |
 | `feature_btn_clearLyricsCache_running` | `Clearing...` |
 | `feature_btn_checkVersionNow_running` | `Checking...` |
 
 <br></details>
 
-<details><summary><code>hi_IN</code> - 3 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>hi_IN</code> - 5 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
 | `trigger_btn_action_running` | `Running...` |
+| `feature_desc_hideCursorOnIdle` | `Hide the cursor after a few seconds of inactivity over the video` |
+| `feature_desc_hideCursorOnIdleDelay` | `How many seconds of inactivity before the cursor should be hidden?` |
 | `feature_btn_clearLyricsCache_running` | `Clearing...` |
 | `feature_btn_checkVersionNow_running` | `Checking...` |
 
 <br></details>
 
-<details><summary><code>ja_JA</code> - 3 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>ja_JA</code> - 5 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
 | `trigger_btn_action_running` | `Running...` |
+| `feature_desc_hideCursorOnIdle` | `Hide the cursor after a few seconds of inactivity over the video` |
+| `feature_desc_hideCursorOnIdleDelay` | `How many seconds of inactivity before the cursor should be hidden?` |
 | `feature_btn_clearLyricsCache_running` | `Clearing...` |
 | `feature_btn_checkVersionNow_running` | `Checking...` |
 
 <br></details>
 
-<details><summary><code>pt_BR</code> - 3 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>pt_BR</code> - 5 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
 | `trigger_btn_action_running` | `Running...` |
+| `feature_desc_hideCursorOnIdle` | `Hide the cursor after a few seconds of inactivity over the video` |
+| `feature_desc_hideCursorOnIdleDelay` | `How many seconds of inactivity before the cursor should be hidden?` |
 | `feature_btn_clearLyricsCache_running` | `Clearing...` |
 | `feature_btn_checkVersionNow_running` | `Checking...` |
 
 <br></details>
 
-<details><summary><code>zh_CN</code> - 3 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>zh_CN</code> - 5 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
 | `trigger_btn_action_running` | `Running...` |
+| `feature_desc_hideCursorOnIdle` | `Hide the cursor after a few seconds of inactivity over the video` |
+| `feature_desc_hideCursorOnIdleDelay` | `How many seconds of inactivity before the cursor should be hidden?` |
 | `feature_btn_clearLyricsCache_running` | `Clearing...` |
 | `feature_btn_checkVersionNow_running` | `Checking...` |
 

+ 2 - 0
assets/translations/en_US.json

@@ -162,6 +162,8 @@
     "feature_helptext_thumbnailOverlayToggleBtnShown": "This button will allow you to manually toggle the thumbnail on and off. This is not affected if the overlay is set to \"never shown\".\nOnce a new video or song starts playing, the default state will be restored.\nHold shift while clicking or press the middle mouse button to open the thumbnail of the highest quality in a new tab.",
     "feature_desc_thumbnailOverlayShowIndicator": "Show an indicator in the bottom right corner of the thumbnail while it's active?",
     "feature_desc_thumbnailOverlayImageFit": "How to fit the thumbnail image over the video element",
+    "feature_desc_hideCursorOnIdle": "Hide the cursor after a few seconds of inactivity over the video",
+    "feature_desc_hideCursorOnIdleDelay": "How many seconds of inactivity before the cursor should be hidden?",
 
     "feature_desc_lyricsQueueButton": "Add a button to each song in a queue to quickly open its lyrics page",
     "feature_desc_deleteFromQueueButton": "Add a button to each song in a queue to quickly remove it",

+ 2 - 1
contributing.md

@@ -284,7 +284,8 @@ The usage and example blocks on each are written in TypeScript but can be used i
   - [sanitizeArtists()](#sanitizeartists) - Sanitizes the specified artist string to be used in fetching a lyrics URL
   - [sanitizeSong()](#sanitizesong) - Sanitizes the specified song title string to be used in fetching a lyrics URL
 - Other:
-  - [NanoEmitter](#nanoemitte) - Abstract class for creating lightweight, type safe event emitting classes
+  - [NanoEmitter](#nanoemitter) - Abstract class for creating lightweight, type safe event emitting classes
+  - [compareVersions](#compareversions) - Compares two semver version strings and returns which one is newer
 
 <br><br>
 

+ 4 - 4
package-lock.json

@@ -9,7 +9,7 @@
       "version": "1.1.1",
       "license": "AGPL-3.0-only",
       "dependencies": {
-        "@sv443-network/userutils": "^6.0.1",
+        "@sv443-network/userutils": "^6.1.0",
         "fuse.js": "^7.0.0",
         "marked": "^12.0.0",
         "nanoevents": "^9.0.0"
@@ -600,9 +600,9 @@
       ]
     },
     "node_modules/@sv443-network/userutils": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/@sv443-network/userutils/-/userutils-6.0.1.tgz",
-      "integrity": "sha512-dWx3oTKGJ5DfSdQ2H4W9pnvp752QNuKj2yOxl/o8utEK8U8qArgpQT6FIRd3BpijcmATNE+wYyOkhBBPI31K+g=="
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/@sv443-network/userutils/-/userutils-6.1.0.tgz",
+      "integrity": "sha512-sEdUHELXv3LKfb31vnkjEAx3+xejko0urrsB/JGBgh0jXeiFlUu5gQPYMWskAb4jRGVkbYono15ipx1dSKZbHQ=="
     },
     "node_modules/@tsconfig/node10": {
       "version": "1.0.9",

+ 1 - 1
package.json

@@ -57,7 +57,7 @@
     "openuserjs": "https://openuserjs.org/scripts/Sv443/BetterYTM"
   },
   "dependencies": {
-    "@sv443-network/userutils": "^6.0.1",
+    "@sv443-network/userutils": "^6.1.0",
     "fuse.js": "^7.0.0",
     "marked": "^12.0.0",
     "nanoevents": "^9.0.0"

+ 21 - 0
src/features/index.ts

@@ -169,6 +169,27 @@ export const featInfo = {
     // TODO: to be reworked or removed in the big menu rework
     textAdornment: adornments.advanced,
   },
+  hideCursorOnIdle: {
+    type: "toggle",
+    category: "layout",
+    default: true,
+    enable: noopTODO,
+    disable: noopTODO,
+  },
+  hideCursorOnIdleDelay: {
+    type: "slider",
+    category: "layout",
+    min: 0.5,
+    max: 10,
+    step: 0.5,
+    default: 3,
+    unit: "s",
+    enable: noopTODO,
+    change: noopTODO,
+    advanced: true,
+    // TODO: to be reworked or removed in the big menu rework
+    textAdornment: adornments.advanced,
+  },
 
   //#SECTION volume
   volumeSliderLabel: {

+ 64 - 1
src/features/layout.ts

@@ -1,4 +1,4 @@
-import { addGlobalStyle, addParent, autoPlural, fetchAdvanced, insertAfter, openInNewTab, pauseFor } from "@sv443-network/userutils";
+import { addGlobalStyle, addParent, autoPlural, debounce, fetchAdvanced, insertAfter, openInNewTab, pauseFor } from "@sv443-network/userutils";
 import { getFeatures } from "../config";
 import { siteEvents } from "../siteEvents";
 import { addSelectorListener } from "../observers";
@@ -552,3 +552,66 @@ export async function initThumbnailOverlay() {
     },
   });
 }
+
+//#MARKER hide cursor on idle
+
+export async function initHideCursorOnIdle() {
+  addSelectorListener("mainPanel", "ytmusic-player#player", {
+    listener(vidContainer) {
+      const overlaySelector = "ytmusic-player #song-media-window";
+
+      const overlayElem = document.querySelector<HTMLElement>(overlaySelector);
+
+      if(!overlayElem)
+        return warn("Couldn't find overlay element while initializing cursor hiding");
+
+      let cursorHideTimer: ReturnType<typeof setTimeout>;
+      let cursorHidden = false;
+
+      const hide = () => {
+        if(cursorHidden)
+          return;
+        cursorHidden = true;
+
+        overlayElem.style.opacity = "0 !important";
+        setTimeout(() => {
+          overlayElem.style.display = "none";
+          vidContainer.style.cursor = "none";
+        }, 200);
+      };
+
+      const show = () => {
+        if(!cursorHidden)
+          return;
+        cursorHidden = false;
+
+        vidContainer.style.cursor = "initial";
+        overlayElem.style.display = "initial";
+        overlayElem.style.opacity = "1 !important";
+      };
+
+      const cursorHideTimerCb = () =>
+        cursorHideTimer = setTimeout(hide, getFeatures().hideCursorOnIdleDelay * 1000);
+
+      const onMove = () => {
+        clearTimeout(cursorHideTimer);
+        show();
+        cursorHideTimerCb();
+      };
+
+      vidContainer.addEventListener("mouseenter", onMove);
+      vidContainer.addEventListener("mousemove", debounce(onMove, 25, "rising"));
+      vidContainer.addEventListener("mouseleave", () => {
+        clearTimeout(cursorHideTimer);
+        hide();
+      });
+      vidContainer.addEventListener("click", () => {
+        show();
+        cursorHideTimerCb();
+        setTimeout(hide, 3000);
+      });
+
+      log("Initialized cursor hiding on idle");
+    },
+  });
+}

+ 4 - 0
src/index.ts

@@ -12,6 +12,7 @@ import {
   addWatermark, removeUpgradeTab,
   initRemShareTrackParam, fixSpacing,
   addScrollToActiveBtn, initThumbnailOverlay,
+  initHideCursorOnIdle,
   // volume
   initVolumeFeatures,
   // song lists
@@ -180,6 +181,9 @@ async function onDomLoad() {
 
       ftInit.push(initThumbnailOverlay());
 
+      if(features.hideCursorOnIdle)
+        ftInit.push(initHideCursorOnIdle());
+
       ftInit.push(initVolumeFeatures());
     }
 

+ 4 - 1
src/observers.ts

@@ -159,7 +159,10 @@ export function initObservers() {
   }
 }
 
-/** Interface function for adding listeners to the {@linkcode globservers} */
+/**
+ * Interface function for adding listeners to the {@linkcode globservers}  
+ * @param selector Relative to the observer's root element, so the selector can only start at of the root element's children at the earliest!
+ */
 export function addSelectorListener<TElem extends HTMLElement>(observerName: ObserverName, selector: string, options: SelectorListenerOptions<TElem>) {
   globservers[observerName].addListener(selector, options);
 }

+ 4 - 0
src/types.ts

@@ -327,6 +327,10 @@ export interface FeatureConfig {
   thumbnailOverlayShowIndicator: boolean;
   /** How to fit the thumbnail overlay image */
   thumbnailOverlayImageFit: "cover" | "contain" | "fill";
+  /** Hide the cursor when it's idling on the video element for a while */
+  hideCursorOnIdle: boolean;
+  /** Delay in seconds after which the cursor should be hidden */
+  hideCursorOnIdleDelay: number;
 
   //#SECTION volume
   /** Add a percentage label to the volume slider */