Browse Source

ref: use NanoEmitter instance instead of nano-events obj.

Sv443 11 months ago
parent
commit
37276501c7
8 changed files with 45 additions and 38 deletions
  1. 8 3
      changelog.md
  2. 8 2
      src/features/index.ts
  3. 4 6
      src/features/input.ts
  4. 1 2
      src/interface.ts
  5. 7 12
      src/menu/menu_old.ts
  6. 2 3
      src/siteEvents.ts
  7. 5 4
      src/types.ts
  8. 10 6
      src/utils/dom.ts

+ 8 - 3
changelog.md

@@ -7,6 +7,9 @@
   - 
 - **Changes / Fixes:**
   - Welcome menu is shown on YT too now
+  - Changed default settings for these features:
+    - Remember Song Time Sites: from `YTM only` to `both sites`
+    - Volume Slider Scroll Sensitivity: from `10%` to `4%`
 - **Internal Changes:**
   - Removed `compareVersions()` and `compareVersionArrays()` in favor of including the [`compare-versions`](https://npmjs.com/package/compare-versions) library
   - Added advanced feature to change the startup timeout
@@ -26,10 +29,12 @@
     - `bytm:featureInitialized` - emitted every time a feature has been initialized and is passed the feature's identifier string
     - `bytm:siteEvent:pathChanged` - emitted whenever the URL path (`location.pathname`) changes
   - Event `bytm:siteEvent:fullscreenToggled` will now only emit once per fullscreen change
+  - Changed `event` property returned by `registerPlugin()` from nanoevents Emitter to NanoEmitter instance (see [`src/utils/NanoEmitter.ts`](https://github.com/Sv443/BetterYTM/blob/develop/src/utils/NanoEmitter.ts))  
+    In practice this changes nothing, but it benefits from having the additional methods `once()` for immediately unsubscribing from an event after it was emitted once and `unsubscribeAll()` to remove all event listeners.
 
-<!-- <div class="pr-link-cont">
-  <a href="https://github.com/Sv443/BetterYTM/pull/TODO" rel="noopener noreferrer">See pull request for more info</a>
-</div> -->
+<div class="pr-link-cont">
+  <a href="https://github.com/Sv443/BetterYTM/pull/76" rel="noopener noreferrer">See pull request for more info</a>
+</div>
 
 <div class="split"></div>
 <br>

+ 8 - 2
src/features/index.ts

@@ -1,7 +1,7 @@
 import { getPreferredLocale, resourceToHTMLString, t, tp } from "../utils";
 import { clearLyricsCache, getLyricsCache } from "./lyricsCache";
 import { doVersionCheck } from "./versionCheck";
-import { getFeatures } from "../config";
+import { getFeatures, promptResetConfig } from "../config";
 import { FeatureInfo, type ResourceKey, type SiteSelection, type SiteSelectionOrNone } from "../types";
 import { emitSiteEvent } from "../siteEvents";
 import langMapping from "../../assets/locales.json" with { type: "json" };
@@ -255,7 +255,7 @@ export const featInfo = {
     category: "volume",
     min: 1,
     max: 25,
-    default: 10,
+    default: 4,
     unit: "%",
     textAdornment: adornments.reloadRequired,
   },
@@ -617,6 +617,12 @@ export const featInfo = {
     advanced: true,
     textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
   },
+  resetConfig: {
+    type: "button",
+    category: "general",
+    click: promptResetConfig,
+    textAdornment: adornments.reloadRequired,
+  },
   advancedMode: {
     type: "toggle",
     category: "general",

+ 4 - 6
src/features/input.ts

@@ -121,7 +121,7 @@ async function switchSite(newDomain: Domain) {
 //#region num keys skip
 
 const numKeysIgnoreTagNames = [...inputIgnoreTagNames, "TP-YT-PAPER-TAB"];
-const numKeysIgnoreIds = ["progress-bar", "song-media-window"];
+const numKeysIgnoreIds = ["song-media-window"];
 
 /** Adds the ability to skip to a certain time in the video by pressing a number key (0-9) */
 export async function initNumKeysSkip() {
@@ -133,11 +133,9 @@ export async function initNumKeysSkip() {
     if(isCfgMenuOpen)
       return;
     // discard the event when an unexpected element is currently active or in focus, like when editing a playlist or when the search bar is focused
-    if(
-      document.activeElement !== document.body // short-circuit if nothing is active
-      || numKeysIgnoreIds.includes(document.activeElement?.id ?? "") // video element or player bar active
-      || numKeysIgnoreTagNames.includes(document.activeElement?.tagName ?? "") // other element active
-    )
+    const ignoreElement = numKeysIgnoreIds.includes(document.activeElement?.id ?? "") // video element or player bar active
+      || numKeysIgnoreTagNames.includes(document.activeElement?.tagName ?? ""); // other element active
+    if((document.activeElement !== document.body && ignoreElement) || ignoreElement)
       return info("Captured valid key to skip video to, but ignored it since an unexpected element is active:", document.activeElement);
 
     const vidElem = document.querySelector<HTMLVideoElement>(getVideoSelector());

+ 1 - 2
src/interface.ts

@@ -1,6 +1,5 @@
 import * as UserUtils from "@sv443-network/userutils";
 import * as compareVersions from "compare-versions";
-import { createNanoEvents } from "nanoevents";
 import { mode, branch, host, buildNumber, compressionFormat, scriptInfo } from "./constants";
 import { getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, NanoEmitter, t, tp, type TrLocale, info, error, onInteraction, getThumbnailUrl, getBestThumbnailUrl } from "./utils";
 import { addSelectorListener } from "./observers";
@@ -351,7 +350,7 @@ export function registerPlugin(def: PluginDef): PluginRegisterResult {
     throw new Error(`Invalid plugin definition:\n- ${validationErrors.join("\n- ")}`);
   }
 
-  const events = createNanoEvents<PluginEventMap>();
+  const events = new NanoEmitter<PluginEventMap>({ publicEmit: true });
   const token = randomId(32, 36);
 
   const { plugin: { name } } = def;

+ 7 - 12
src/menu/menu_old.ts

@@ -1,5 +1,5 @@
 import { debounce, isScrollable, type Stringifiable } from "@sv443-network/userutils";
-import { defaultData, getFeatures, setFeatures, setDefaultFeatures } from "../config";
+import { defaultData, getFeatures, setFeatures, promptResetConfig } from "../config";
 import { buildNumber, host, mode, scriptInfo } from "../constants";
 import { featInfo, disableBeforeUnload } from "../features/index";
 import { error, getResourceUrl, info, log, resourceToHTMLString, getLocale, hasKey, initTranslations, setLocale, t, arrayWithSeparators, tp, type TrKey, onInteraction, getDomain, copyToClipboard } from "../utils";
@@ -173,32 +173,27 @@ async function addCfgMenu() {
 
   const resetElem = document.createElement("button");
   resetElem.classList.add("bytm-btn");
-  resetElem.ariaLabel = resetElem.title = t("reset_tooltip");
+  resetElem.ariaLabel = resetElem.title = t("reset_config_tooltip");
   resetElem.textContent = t("reset");
-  resetElem.addEventListener("click", async () => {
-    if(confirm(t("reset_confirm"))) {
-      await setDefaultFeatures();
-      closeCfgMenu();
-      disableBeforeUnload();
-      location.reload();
-    }
-  });
+  onInteraction(resetElem, promptResetConfig);
+
   const exportElem = document.createElement("button");
   exportElem.classList.add("bytm-btn");
   exportElem.ariaLabel = exportElem.title = t("export_tooltip");
   exportElem.textContent = t("export");
-  exportElem.addEventListener("click", async () => {
+  onInteraction(exportElem, async () => {
     const dlg = await getExportDialog();
     dlg.on("close", openCfgMenu);
     await dlg.mount();
     closeCfgMenu(undefined, false);
     await dlg.open();
   });
+
   const importElem = document.createElement("button");
   importElem.classList.add("bytm-btn");
   importElem.ariaLabel = importElem.title = t("import_tooltip");
   importElem.textContent = t("import");
-  importElem.addEventListener("click", async () => {
+  onInteraction(importElem, async () => {
     const dlg = await getImportDialog();
     dlg.on("close", openCfgMenu);
     await dlg.mount();

+ 2 - 3
src/siteEvents.ts

@@ -1,5 +1,4 @@
-import { createNanoEvents } from "nanoevents";
-import { error, getDomain, info } from "./utils";
+import { NanoEmitter, error, getDomain, info } from "./utils";
 import { FeatureConfig } from "./types";
 import { emitInterface } from "./interface";
 import { addSelectorListener } from "./observers";
@@ -65,7 +64,7 @@ export const allSiteEvents = [
 ] as const;
 
 /** EventEmitter instance that is used to detect changes to the site */
-export const siteEvents = createNanoEvents<SiteEventsMap>();
+export const siteEvents = new NanoEmitter<SiteEventsMap>({ publicEmit: true });
 
 let observers: MutationObserver[] = [];
 

+ 5 - 4
src/types.ts

@@ -1,10 +1,9 @@
-import type { Emitter } from "nanoevents";
 import type * as consts from "./constants";
 import type { scriptInfo } from "./constants";
 import type { addSelectorListener } from "./observers";
 import type resources from "../assets/resources.json";
 import type locales from "../assets/locales.json";
-import type { getResourceUrl, getSessionId, getVideoTime, TrLocale, t, tp } from "./utils";
+import type { getResourceUrl, getSessionId, getVideoTime, TrLocale, t, tp, NanoEmitter } from "./utils";
 import type { getFeatures, setFeatures } from "./config";
 import type { SiteEventsMap } from "./siteEvents";
 import type { InterfaceEventsMap } from "./interface";
@@ -105,8 +104,8 @@ export enum PluginIntent {
 export type PluginRegisterResult = {
   /** Public info about the registered plugin */
   info: PluginInfo;
-  /** Emitter for plugin events - see {@linkcode PluginEventMap} for a list of events */
-  events: Emitter<PluginEventMap>;
+  /** NanoEmitter instance for plugin events - see {@linkcode PluginEventMap} for a list of events */
+  events: NanoEmitter<PluginEventMap>;
   /** Authentication token for the plugin to use in certain restricted function calls */
   token: string;
 }
@@ -461,6 +460,8 @@ export interface FeatureConfig {
   logLevel: LogLevel;
   /** Amount of seconds until the feature initialization times out */
   initTimeout: number;
+  /** Button that resets the config to the default state */
+  resetConfig: undefined;
   /** Whether to show advanced settings in the config menu */
   advancedMode: boolean;
 }

+ 10 - 6
src/utils/dom.ts

@@ -2,6 +2,7 @@ import { addGlobalStyle, getUnsafeWindow, randomId, type Stringifiable } from "@
 import { error, fetchCss, getDomain, t } from ".";
 import { addSelectorListener } from "../observers";
 import type { ResourceKey } from "../types";
+import { siteEvents } from "src/siteEvents";
 
 /** Whether the DOM has finished loading and elements can be added or modified */
 export let domLoaded = false;
@@ -20,12 +21,10 @@ export const getVideoSelector = () => getDomain() === "ytm" ? "ytmusic-player vi
  */
 export function getVideoTime(precision = 2) {
   return new Promise<number | null>(async (res) => {
-    const domain = getDomain();
-
     await waitVideoElementReady();
 
     try {
-      if(domain === "ytm") {
+      if(getDomain() === "ytm") {
         const vidElem = document.querySelector<HTMLVideoElement>(getVideoSelector());
         if(vidElem)
           return res(Number(precision <= 0 ? Math.floor(vidElem.currentTime) : vidElem.currentTime.toFixed(precision)));
@@ -35,7 +34,7 @@ export function getVideoTime(precision = 2) {
             res(!isNaN(Number(pbEl.value)) ? Math.floor(Number(pbEl.value)) : null)
         });
       }
-      else if(domain === "yt") {
+      else if(getDomain() === "yt") {
         const vidElem = document.querySelector<HTMLVideoElement>(getVideoSelector());
         if(vidElem)
           return res(Number(precision <= 0 ? Math.floor(vidElem.currentTime) : vidElem.currentTime.toFixed(precision)));
@@ -114,10 +113,10 @@ function ytForceShowVideoTime() {
 /** Waits for the video element to be in its readyState 4 / canplay state and returns it - resolves immediately if the video is already ready */
 export function waitVideoElementReady(): Promise<HTMLVideoElement> {
   return new Promise((res) => {
-    addSelectorListener<HTMLVideoElement>("body", getVideoSelector(), {
+    const waitForEl = () => addSelectorListener<HTMLVideoElement>("body", getVideoSelector(), {
       listener: async (vidElem) => {
         if(vidElem) {
-          // this is just after YT has finished doing their own shenanigans with the video time and volume
+        // this is just after YT has finished doing their own shenanigans with the video time and volume
           if(vidElem.readyState === 4)
             res(vidElem);
           else
@@ -125,6 +124,11 @@ export function waitVideoElementReady(): Promise<HTMLVideoElement> {
         }
       },
     });
+  
+    if(location.pathname.startsWith("/watch"))
+      waitForEl();
+    else
+      siteEvents.once("watchIdChanged", waitForEl);
   });
 }