Ver Fonte

feat: clear queue btn

Sv443 há 11 meses atrás
pai
commit
d9e7509a46

+ 1 - 0
assets/icons/clear_list.svg

@@ -0,0 +1 @@
+<svg height="24" viewBox="0 -960 960 960" width="24" version="1.1" id="svg1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M 362.31,-260.001 V -320 h -260 v 59.999 z m 0,-379.999 v -59.999 h -260 V -640 Z m 0,189.999 v -59.998 h -260 v 59.998 z M 817.69,-640 h 40 v -59.999 H 703.08 v -43.846 H 572.31 v 43.846 H 417.69 V -640 h 40 v 351.535 c 0,20.20667 7,37.31 21,51.31 14,14 31.10333,21 51.31,21 h 215.38 c 20.20667,0 37.31,-7 51.31,-21 14,-14 21,-31.10333 21,-51.31 z m -60,0 v 351.535 c 0,3.08 -1.28,5.9 -3.84,8.46 -2.56667,2.56667 -5.39,3.85 -8.47,3.85 H 530 c -3.07333,0 -5.89333,-1.28333 -8.46,-3.85 -2.56667,-2.56 -3.85,-5.38 -3.85,-8.46 V -640 Z m 0,0 v 363.845 z" /></svg>

+ 1 - 0
assets/resources.json

@@ -5,6 +5,7 @@
   "doc-changelog": "/changelog.md",
   "icon-advanced_mode": "icons/plus_circle_small.svg",
   "icon-arrow_down": "icons/arrow_down.svg",
+  "icon-clear_list": "icons/clear_list.svg",
   "icon-delete": "icons/delete.svg",
   "icon-error": "icons/error.svg",
   "icon-experimental": "icons/beaker_small.svg",

+ 79 - 10
assets/translations/README.md

@@ -20,15 +20,15 @@ To submit or edit a translation, please follow [this guide](../../contributing.m
 ### Translation progress:
 | &nbsp; | Locale | Translated keys | Based on |
 | :----: | ------ | --------------- | :------: |
-| ─ | [`en_US`](./en_US.json) | 205 (default locale) |  |
-| ✅ | [`de_DE`](./de_DE.json) | `205/205` (100%) | ─ |
-| ─ | [`en_UK`](./en_UK.json) | `205/205` (100%) | `en_US` |
-| ✅ | [`es_ES`](./es_ES.json) | `205/205` (100%) | ─ |
-| ✅ | [`fr_FR`](./fr_FR.json) | `205/205` (100%) | ─ |
-| ✅ | [`hi_IN`](./hi_IN.json) | `205/205` (100%) | ─ |
-| ✅ | [`ja_JA`](./ja_JA.json) | `205/205` (100%) | ─ |
-| ✅ | [`pt_BR`](./pt_BR.json) | `205/205` (100%) | ─ |
-| ✅ | [`zh_CN`](./zh_CN.json) | `205/205` (100%) | ─ |
+| ─ | [`en_US`](./en_US.json) | 208 (default locale) |  |
+| ⚠ | [`de_DE`](./de_DE.json) | `205/208` (98.6%) | ─ |
+| ─ | [`en_UK`](./en_UK.json) | `208/208` (100%) | `en_US` |
+| ⚠ | [`es_ES`](./es_ES.json) | `205/208` (98.6%) | ─ |
+| ⚠ | [`fr_FR`](./fr_FR.json) | `205/208` (98.6%) | ─ |
+| ⚠ | [`hi_IN`](./hi_IN.json) | `205/208` (98.6%) | ─ |
+| ⚠ | [`ja_JA`](./ja_JA.json) | `205/208` (98.6%) | ─ |
+| ⚠ | [`pt_BR`](./pt_BR.json) | `205/208` (98.6%) | ─ |
+| ⚠ | [`zh_CN`](./zh_CN.json) | `205/208` (98.6%) | ─ |
 
 <sub>
 ✅ - Fully translated
@@ -48,4 +48,73 @@ This means to figure out which keys are untranslated, you will need to manually
 <br>
 
 ### Missing keys:
-No missing keys
+
+<details><summary><code>de_DE</code> - 3 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `clear_queue` | `Clear the queue` |
+| `clear_queue_confirm` | `Do you really want to clear the queue and leave only the currently playing song?` |
+| `feature_desc_clearQueueBtn` | `Add a button to the currently playing queue (or playlist) to quickly clear it` |
+
+<br></details>
+
+<details><summary><code>es_ES</code> - 3 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `clear_queue` | `Clear the queue` |
+| `clear_queue_confirm` | `Do you really want to clear the queue and leave only the currently playing song?` |
+| `feature_desc_clearQueueBtn` | `Add a button to the currently playing queue (or playlist) to quickly clear it` |
+
+<br></details>
+
+<details><summary><code>fr_FR</code> - 3 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `clear_queue` | `Clear the queue` |
+| `clear_queue_confirm` | `Do you really want to clear the queue and leave only the currently playing song?` |
+| `feature_desc_clearQueueBtn` | `Add a button to the currently playing queue (or playlist) to quickly clear it` |
+
+<br></details>
+
+<details><summary><code>hi_IN</code> - 3 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `clear_queue` | `Clear the queue` |
+| `clear_queue_confirm` | `Do you really want to clear the queue and leave only the currently playing song?` |
+| `feature_desc_clearQueueBtn` | `Add a button to the currently playing queue (or playlist) to quickly clear it` |
+
+<br></details>
+
+<details><summary><code>ja_JA</code> - 3 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `clear_queue` | `Clear the queue` |
+| `clear_queue_confirm` | `Do you really want to clear the queue and leave only the currently playing song?` |
+| `feature_desc_clearQueueBtn` | `Add a button to the currently playing queue (or playlist) to quickly clear it` |
+
+<br></details>
+
+<details><summary><code>pt_BR</code> - 3 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `clear_queue` | `Clear the queue` |
+| `clear_queue_confirm` | `Do you really want to clear the queue and leave only the currently playing song?` |
+| `feature_desc_clearQueueBtn` | `Add a button to the currently playing queue (or playlist) to quickly clear it` |
+
+<br></details>
+
+<details><summary><code>zh_CN</code> - 3 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `clear_queue` | `Clear the queue` |
+| `clear_queue_confirm` | `Do you really want to clear the queue and leave only the currently playing song?` |
+| `feature_desc_clearQueueBtn` | `Add a button to the currently playing queue (or playlist) to quickly clear it` |
+
+<br></details>

+ 3 - 0
assets/translations/en_US.json

@@ -50,6 +50,8 @@
     "delete_from_list": "Delete this song from the list",
     "couldnt_remove_from_queue": "Couldn't remove this song from the queue",
     "couldnt_delete_from_list": "Couldn't delete this song from the list",
+    "clear_queue": "Clear the queue",
+    "clear_queue_confirm": "Do you really want to clear the queue and leave only the currently playing song?",
     "scroll_to_playing": "Scroll to the currently playing song",
     "scroll_to_bottom": "Click to scroll to the bottom",
     "volume_tooltip": "Volume: %1% (Sensitivity: %2%)",
@@ -172,6 +174,7 @@
     "feature_desc_listButtonsPlacement": "Where should the queue buttons show up?",
     "feature_helptext_listButtonsPlacement": "There are various song lists on the site like album pages, playlists and the currently playing queue. With this option you can choose where the queue buttons should show up.",
     "feature_desc_scrollToActiveSongBtn": "Add a button to the queue to scroll to the currently playing song",
+    "feature_desc_clearQueueBtn": "Add a button to the currently playing queue (or playlist) to quickly clear it",
 
     "feature_desc_disableBeforeUnloadPopup": "Prevent the confirmation popup that appears when trying to leave the site while a song is playing",
     "feature_helptext_disableBeforeUnloadPopup": "When trying to leave the site while a few seconds into a song that is actively playing, a popup will appear asking you to confirm that you want to leave the site. It might say something along the lines of \"you have unsaved data\" or \"this site is asking if you want to close it\".\nThis feature disables that popup entirely.",

+ 3 - 1
changelog.md

@@ -5,6 +5,7 @@
 	- Hide the cursor after a set amount of inactivity while hovering over the video
 	- Show a thumbnail overlay over the video element (or open the thumbnail in a new tab) automatically and/or manually, depending on configuration
 	- `?si` parameter is removed in YT's share menu too now
+	- Added an "above-queue" button to clear the currently playing queue / playlist
 - **Changes / Fixes:**
   - Improved the config menu
     - Created new toggle input (because checkboxes don't come close to looking as good)
@@ -14,6 +15,7 @@
     - Made a bunch of features not require a page reload anymore
   - Fixed tooltip that is set on the wrong element
   - Fixed queue buttons not being shown when navigating with tab key
+  - Added a feature to fix rendering issues when using HDR
 - **Internal Changes:**
   - Improved script performance
     - Implemented new [SelectorObserver](https://github.com/Sv443-Network/UserUtils#selectorobserver) instances to improve overall performance by quite a lot
@@ -21,7 +23,7 @@
     - Added a cache to save lyrics in. 1000 of the most listened to songs are saved throughout sessions for 30 days to save time and reduce server load.
   - Implemented new class BytmDialog for less duplicate code, better maintainability, the ability to make more menus easier and for them to have better accessibility
   - Expanded plugin interface
-    - Added function to register plugins (see [contrib guide](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#registerplugin))
+    - Added function to register plugins (see [contributing guide](https://github.com/Sv443/BetterYTM/blob/develop/contributing.md#registerplugin))
     - Plugins are now given access to the classes [`BytmDialog`](https://github.com/Sv443/BetterYTM/blob/0f2563541289e383a87b1dd34e6b3f86baf34c63/contributing.md#bytmdialog) and [`NanoEmitter`](https://github.com/Sv443/BetterYTM/blob/0f2563541289e383a87b1dd34e6b3f86baf34c63/contributing.md#nanoemitter), and the functions [`createHotkeyInput()`](https://github.com/Sv443/BetterYTM/blob/0f2563541289e383a87b1dd34e6b3f86baf34c63/contributing.md#createhotkeyinput) and [`createToggleInput()`](https://github.com/Sv443/BetterYTM/blob/0f2563541289e383a87b1dd34e6b3f86baf34c63/contributing.md#createtoggleinput)
   - Added an experimental fuzzy filtering algorithm when fetching lyrics to eventually yield more accurate results (hidden behind advanced mode because it's far from perfect)
 

+ 1 - 1
src/config.ts

@@ -56,7 +56,7 @@ export const migrations: DataMigrationsDict = {
       "thumbnailOverlayBehavior", "thumbnailOverlayToggleBtnShown",
       "thumbnailOverlayShowIndicator", "thumbnailOverlayIndicatorOpacity",
       "thumbnailOverlayImageFit", "removeShareTrackingParamSites",
-      "fixHdrIssues",
+      "fixHdrIssues", "clearQueueBtn",
     ], oldData),
   }),
   // TODO: once advanced filtering is fully implemented, clear cache on migration to fv6

+ 6 - 0
src/features/index.ts

@@ -133,6 +133,12 @@ export const featInfo = {
     default: true,
     textAdornment: adornments.reloadRequired,
   },
+  clearQueueBtn: {
+    type: "toggle",
+    category: "layout",
+    default: true,
+    textAdornment: adornments.reloadRequired,
+  },
   removeUpgradeTab: {
     type: "toggle",
     category: "layout",

+ 10 - 3
src/features/layout.css

@@ -202,6 +202,16 @@ yt-multi-page-menu-section-renderer.ytd-multi-page-menu-renderer {
   text-decoration: underline;
 }
 
+/* #region above queue btns */
+
+#bytm-above-queue-btn-cont {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+}
+
 /* #region scroll to active */
 
 #bytm-scroll-to-active-btn-cont {
@@ -209,9 +219,6 @@ yt-multi-page-menu-section-renderer.ytd-multi-page-menu-renderer {
   flex-direction: column;
   justify-content: center;
   align-items: center;
-  position: absolute;
-  right: 5px;
-  top: 0;
   height: 100%;
 }
 

+ 102 - 38
src/features/layout.ts

@@ -2,7 +2,7 @@ import { addParent, autoPlural, debounce, fetchAdvanced, insertAfter, pauseFor }
 import { getFeatures } from "../config";
 import { siteEvents } from "../siteEvents";
 import { addSelectorListener } from "../observers";
-import { error, getResourceUrl, log, warn, t, onInteraction, openInTab, getBestThumbnailUrl, getDomain, addStyle, currentMediaType, domLoaded, waitVideoElementReady, hdrEnabled } from "../utils";
+import { error, getResourceUrl, log, warn, t, onInteraction, openInTab, getBestThumbnailUrl, getDomain, addStyle, currentMediaType, domLoaded, waitVideoElementReady, hdrEnabled, insertBefore } from "../utils";
 import { scriptInfo } from "../constants";
 import { openCfgMenu } from "../menu/menu_old";
 import "./layout.css";
@@ -382,52 +382,116 @@ export async function fixSpacing() {
   }
 }
 
-//#region scroll to active
+//#region above queue btns
 
-/** Adds a button to the queue to scroll to the active song */
-export async function addScrollToActiveBtn() {
-  addSelectorListener("sidePanel", "#tabsContent tp-yt-paper-tab:nth-of-type(1)", {
-    listener: async (tabElem) => {
-      const containerElem = document.createElement("div");
-      containerElem.id = "bytm-scroll-to-active-btn-cont";
+export async function addAboveQueueBtns() {
+  const { scrollToActiveSongBtn, clearQueueBtn } = getFeatures();
 
-      const linkElem = document.createElement("div");
-      linkElem.id = "bytm-scroll-to-active-btn";
-      linkElem.tabIndex = 0;
-      linkElem.classList.add("ytmusic-player-bar", "bytm-generic-btn");
-      linkElem.ariaLabel = linkElem.title = t("scroll_to_playing");
-      linkElem.role = "button";
+  if(!scrollToActiveSongBtn && !clearQueueBtn)
+    return;
 
-      const imgElem = document.createElement("img");
-      imgElem.classList.add("bytm-generic-btn-img");
-      imgElem.src = await getResourceUrl("icon-skip_to");
+  addSelectorListener("sidePanel", "ytmusic-tab-renderer ytmusic-queue-header-renderer #buttons", {
+    async listener(rightBtnsEl) {
+      const aboveQueueBtnCont = document.createElement("div");
+      aboveQueueBtnCont.id = "bytm-above-queue-btn-cont";
 
-      const scrollToActiveInteraction = () => {
-        const activeItem = document.querySelector<HTMLElement>("#side-panel .ytmusic-player-queue ytmusic-player-queue-item[play-button-state=\"loading\"], #side-panel .ytmusic-player-queue ytmusic-player-queue-item[play-button-state=\"playing\"], #side-panel .ytmusic-player-queue ytmusic-player-queue-item[play-button-state=\"paused\"]");
-        if(!activeItem)
-          return;
+      addParent(rightBtnsEl, aboveQueueBtnCont);
 
-        activeItem.scrollIntoView({
-          behavior: "smooth",
-          block: "center",
-          inline: "center",
-        });
-      };
+      if(scrollToActiveSongBtn)
+        await addScrollToActiveBtn(rightBtnsEl);
+      if(clearQueueBtn)
+        await addClearQueueBtn(rightBtnsEl);
+    },
+  });
+}
 
-      siteEvents.on("fullscreenToggled", (isFullscreen) => {
-        if(isFullscreen)
-          containerElem.classList.add("hidden");
-        else
-          containerElem.classList.remove("hidden");
-      });
+/** Adds a button above the queue to scroll to the active song */
+export async function addScrollToActiveBtn(rightBtnsEl: HTMLElement) {
+  const containerElem = document.createElement("div");
+  containerElem.id = "bytm-scroll-to-active-btn-cont";
+
+  const linkElem = document.createElement("div");
+  linkElem.id = "bytm-scroll-to-active-btn";
+  linkElem.tabIndex = 0;
+  linkElem.classList.add("ytmusic-player-bar", "bytm-generic-btn");
+  linkElem.ariaLabel = linkElem.title = t("scroll_to_playing");
+  linkElem.role = "button";
+
+  const imgElem = document.createElement("img");
+  imgElem.classList.add("bytm-generic-btn-img");
+  imgElem.src = await getResourceUrl("icon-skip_to");
+
+  const scrollToActiveInteraction = () => {
+    const activeItem = document.querySelector<HTMLElement>("#side-panel .ytmusic-player-queue ytmusic-player-queue-item[play-button-state=\"loading\"], #side-panel .ytmusic-player-queue ytmusic-player-queue-item[play-button-state=\"playing\"], #side-panel .ytmusic-player-queue ytmusic-player-queue-item[play-button-state=\"paused\"]");
+    if(!activeItem)
+      return;
 
-      onInteraction(linkElem, scrollToActiveInteraction, { capture: true });
+    activeItem.scrollIntoView({
+      behavior: "smooth",
+      block: "center",
+      inline: "center",
+    });
+  };
 
-      linkElem.appendChild(imgElem);
-      containerElem.appendChild(linkElem);
-      tabElem.appendChild(containerElem);
-    },
+  siteEvents.on("fullscreenToggled", (isFullscreen) => {
+    if(isFullscreen)
+      containerElem.classList.add("hidden");
+    else
+      containerElem.classList.remove("hidden");
+  });
+
+  onInteraction(linkElem, scrollToActiveInteraction, { capture: true });
+
+  linkElem.appendChild(imgElem);
+  containerElem.appendChild(linkElem);
+  insertBefore(rightBtnsEl, containerElem);
+}
+
+/** Adds a button above the queue to clear it */
+export async function addClearQueueBtn(rightBtnsEl: HTMLElement) {
+  const containerElem = document.createElement("div");
+  containerElem.id = "bytm-clear-queue-btn-cont";
+
+  const linkElem = document.createElement("div");
+  linkElem.id = "bytm-clear-queue-btn";
+  linkElem.tabIndex = 0;
+  linkElem.classList.add("ytmusic-player-bar", "bytm-generic-btn");
+  linkElem.ariaLabel = linkElem.title = t("clear_queue");
+  linkElem.role = "button";
+
+  const imgElem = document.createElement("img");
+  imgElem.classList.add("bytm-generic-btn-img");
+  imgElem.src = await getResourceUrl("icon-clear_list");
+
+  siteEvents.on("fullscreenToggled", (isFullscreen) => {
+    if(isFullscreen)
+      containerElem.classList.add("hidden");
+    else
+      containerElem.classList.remove("hidden");
   });
+
+  onInteraction(
+    linkElem,
+    () => {
+      try {
+        // TODO: better confirmation dialog?
+        if(!confirm(t("clear_queue_confirm")))
+          return;
+        const url = new URL(location.href);
+        url.searchParams.delete("list");
+        location.href = String(url);
+      }
+      catch(err) {
+        error("Couldn't clear queue due to an error:", err);
+      }
+    }, {
+      capture: true,
+    }
+  );
+
+  linkElem.appendChild(imgElem);
+  containerElem.appendChild(linkElem);
+  insertBefore(rightBtnsEl, containerElem);
 }
 
 //#region thumbnail overlay

+ 2 - 2
src/index.ts

@@ -10,7 +10,7 @@ import {
   // layout
   addWatermark, removeUpgradeTab,
   initRemShareTrackParam, fixSpacing,
-  addScrollToActiveBtn, initThumbnailOverlay,
+  addAboveQueueBtns, initThumbnailOverlay,
   initHideCursorOnIdle, fixHdrIssues,
   // volume
   initVolumeFeatures,
@@ -159,7 +159,7 @@ async function onDomLoad() {
         ftInit.push(fixSpacing());
 
       if(features.scrollToActiveSongBtn)
-        ftInit.push(addScrollToActiveBtn());
+        ftInit.push(addAboveQueueBtns());
 
       if(features.removeUpgradeTab)
         ftInit.push(removeUpgradeTab());

+ 3 - 1
src/types.ts

@@ -370,8 +370,10 @@ export interface FeatureConfig {
   deleteFromQueueButton: boolean;
   /** Where to place the buttons in the queue */
   listButtonsPlacement: "queueOnly" | "everywhere";
-  /** Add a button to the queue to scroll to the currently playing song */
+  /** Add a button above the queue to scroll to the currently playing song */
   scrollToActiveSongBtn: boolean;
+  /** Add a button above the queue to clear it */
+  clearQueueBtn: boolean;
 
   //#region behavior
   /** Whether to completely disable the popup that sometimes appears before leaving the site */

+ 9 - 0
src/utils/dom.ts

@@ -182,3 +182,12 @@ export function currentMediaType(): "video" | "song" {
     throw new Error("Couldn't find the song image element. Use this function only after `await waitVideoElementReady()`!");
   return getUnsafeWindow().getComputedStyle(songImgElem).display !== "none" ? "song" : "video";
 }
+
+/**
+ * Inserts {@linkcode beforeElement} as a sibling just before the provided {@linkcode afterElement}
+ * @returns Returns the {@linkcode beforeElement}
+ */
+export function insertBefore(afterElement: Element, beforeElement: Element) {
+  afterElement.parentNode?.insertBefore(beforeElement, afterElement);
+  return beforeElement;
+}