Ver código fonte

ref: extract menu code into own file

Sv443 1 ano atrás
pai
commit
c4beb4dd8f
5 arquivos alterados com 434 adições e 386 exclusões
  1. 1 4
      global.d.ts
  2. 12 1
      src/BetterYTM.user.ts
  3. 1 0
      src/features/index.ts
  4. 3 381
      src/features/layout.ts
  5. 417 0
      src/features/menu.ts

+ 1 - 4
global.d.ts

@@ -1,7 +1,4 @@
-/**
- * Import HTML as modules  
- * https://stackoverflow.com/a/47705264/3323672
- */
+/** Import HTML as modules - https://stackoverflow.com/a/47705264/3323672 */
 declare module "*.html" {
   /** Content of the HTML file as a string */
   const content: string;

+ 12 - 1
src/BetterYTM.user.ts

@@ -1,7 +1,18 @@
 import { getFeatures } from "./config";
 import { dbg, info } from "./constants";
-import { addMediaCtrlGeniusBtn, addMenu, addQueueGeniusBtns, addWatermark, geniUrlBase, initArrowKeySkip, initChangelog, initLayout as preInitLayout, initSiteSwitch, removeUpgradeTab, setVolSliderSize, setVolSliderStep } from "./features/index";
 import { getDomain } from "./utils";
+import {
+  // layout
+  addMediaCtrlGeniusBtn, addQueueGeniusBtns, addWatermark,
+  preInitLayout, removeUpgradeTab, setVolSliderSize,
+  setVolSliderStep,
+  // lyrics
+  geniUrlBase,
+  // input
+  initArrowKeySkip, initSiteSwitch,
+  // menu
+  addMenu, initChangelog,
+} from "./features/index";
 
 (async () => {
   //#MARKER init

+ 1 - 0
src/features/index.ts

@@ -1,6 +1,7 @@
 export * from "./input";
 export * from "./layout";
 export * from "./lyrics";
+export * from "./menu";
 
 /** Contains all possible features with their default values and other config */
 export const featInfo = {

+ 3 - 381
src/features/layout.ts

@@ -1,377 +1,15 @@
 import { dbg, info, triesInterval, triesLimit } from "../constants";
-import { defaultFeatures, getFeatures, saveFeatureConf } from "../config";
+import { getFeatures } from "../config";
 import { addGlobalStyle, insertAfter } from "../utils";
-import { featInfo } from "./index";
 import type { FeatureConfig } from "../types";
-import changelog from "../../changelog.md";
+import { openMenu } from "./menu";
 
 let features: FeatureConfig;
 
-export async function initLayout() {
+export async function preInitLayout() {
   features = await getFeatures();
 }
 
-//#MARKER menu
-
-const branch = dbg ? "develop" : "main";
-
-/** Adds an element to open the BetterYTM menu */
-export async function addMenu() {
-  // bg & menu
-  const backgroundElem = document.createElement("div");
-  backgroundElem.id = "betterytm-menu-bg";
-  backgroundElem.title = "Click here to close the menu";
-  backgroundElem.style.visibility = "hidden";
-  backgroundElem.style.display = "none";
-  backgroundElem.addEventListener("click", (e) => {
-    if((e.target as HTMLElement).id === "betterytm-menu-bg")
-      closeMenu();
-  });
-
-  const menuContainer = document.createElement("div");
-  menuContainer.title = "";
-  menuContainer.id = "betterytm-menu";
-  menuContainer.style.borderRadius = "15px";
-  menuContainer.style.display = "flex";
-  menuContainer.style.flexDirection = "column";
-  menuContainer.style.justifyContent = "space-between";
-
-
-  // title
-  const titleCont = document.createElement("div");
-  titleCont.style.padding = "8px 20px 15px 8px";
-  titleCont.style.display = "flex";
-  titleCont.style.justifyContent = "space-between";
-  titleCont.id = "betterytm-menu-titlecont";
-
-  const titleElem = document.createElement("h2");
-  titleElem.id = "betterytm-menu-title";
-  titleElem.innerText = "BetterYTM - Configuration";
-
-  const linksCont = document.createElement("div");
-  linksCont.id = "betterytm-menu-linkscont";
-
-  const addLink = (imgSrc: string, href: string, title: string) => {
-    const anchorElem = document.createElement("a");
-    anchorElem.className = "betterytm-menu-link";
-    anchorElem.rel = "noopener noreferrer";
-    anchorElem.target = "_blank";
-    anchorElem.href = href;
-    anchorElem.title = title;
-    anchorElem.style.marginLeft = "10px";
-        
-    const imgElem = document.createElement("img");
-    imgElem.className = "betterytm-menu-img";
-    imgElem.src = imgSrc;
-    imgElem.style.width = "32px";
-    imgElem.style.height = "32px";
-
-    anchorElem.appendChild(imgElem);
-    linksCont.appendChild(anchorElem);
-  };
-
-  addLink(`https://raw.githubusercontent.com/Sv443/BetterYTM/${branch}/resources/external/github.png`, info.namespace, `${info.name} on GitHub`);
-  addLink(`https://raw.githubusercontent.com/Sv443/BetterYTM/${branch}/resources/external/greasyfork.png`, "https://greasyfork.org/xyz", `${info.name} on GreasyFork`);
-
-  const closeElem = document.createElement("img");
-  closeElem.id = "betterytm-menu-close";
-  closeElem.src = `https://raw.githubusercontent.com/Sv443/BetterYTM/${branch}/resources/icon/close.png`;
-  closeElem.title = "Click to close the menu";
-  closeElem.style.marginLeft = "50px";
-  closeElem.style.width = "32px";
-  closeElem.style.height = "32px";
-  closeElem.addEventListener("click", closeMenu);
-
-  linksCont.appendChild(closeElem);
-
-  titleCont.appendChild(titleElem);
-  titleCont.appendChild(linksCont);
-
-
-  // TODO: features
-  const featuresCont = document.createElement("div");
-  featuresCont.id = "betterytm-menu-opts";
-  featuresCont.style.display = "flex";
-  featuresCont.style.flexDirection = "column";
-
-  /** Gets called whenever the feature config is changed */
-  const confChanged = async (key: keyof typeof defaultFeatures, initialVal: number | boolean, newVal: number | boolean) => {
-    dbg && console.info(`BetterYTM: Feature config changed, key '${key}' from value '${initialVal}' to '${newVal}'`);
-
-    const featConf = { ...await getFeatures() };
-
-    featConf[key] = newVal as never;
-
-    await saveFeatureConf(featConf);
-
-    dbg && console.log("BetterYTM: Saved feature config changes:\n", await GM.getValue("betterytm-config"));
-  };
-
-  const features = await getFeatures();
-
-  const featKeys = Object.keys(features);
-  for(const key of featKeys) {
-    const ftInfo = featInfo[key as keyof typeof features];
-
-    if(!ftInfo)
-      continue;
-
-    const { desc, type, default: ftDef } = ftInfo;
-
-    // @ts-ignore
-    const step = ftInfo?.step ?? undefined;
-    const val = features[key as keyof typeof features];
-
-    const initialVal = val || ftDef || undefined;
-
-    const ftConfElem = document.createElement("div");
-    ftConfElem.id = `betterytm-ftconf-${key}`;
-    ftConfElem.style.display = "flex";
-    ftConfElem.style.flexDirection = "row";
-    ftConfElem.style.justifyContent = "space-between";
-    ftConfElem.style.padding = "8px 20px";
-
-    {
-      const textElem = document.createElement("span");
-      textElem.style.display = "inline-block";
-      textElem.style.fontSize = "15px";
-      textElem.innerText = desc;
-
-      ftConfElem.appendChild(textElem);
-    }
-
-    {
-      let inputType = "text";
-      switch(type)
-      {
-      case "toggle":
-        inputType = "checkbox";
-        break;
-      case "slider":
-        inputType = "range";
-        break;
-      case "number":
-        inputType = "number";
-        break;
-      }
-
-      const inputElemId = `betterytm-ftconf-${key}-input`;
-
-      const ctrlElem = document.createElement("span");
-      ctrlElem.style.display = "inline-block";
-      ctrlElem.style.whiteSpace = "nowrap";
-
-      const inputElem = document.createElement("input");
-      inputElem.id = inputElemId;
-      inputElem.style.marginRight = "37px";
-      inputElem.type = inputType;
-      if(type === "toggle")
-        inputElem.style.marginLeft = "5px";
-      if(typeof initialVal !== "undefined")
-        inputElem.value = String(initialVal);
-      if(type === "number" && step)
-        inputElem.step = step;
-
-      // @ts-ignore
-      if(ftInfo.min && ftInfo.max) {
-        // @ts-ignore
-        inputElem.min = ftInfo.min;
-        // @ts-ignore
-        inputElem.max = ftInfo.max;
-      }
-
-      if(type === "toggle" && typeof initialVal !== "undefined")
-        inputElem.checked = Boolean(initialVal);
-
-      const fmtVal = (v: unknown) => String(v);
-      const toggleLabelText = (toggled: boolean) => toggled ? "On" : "Off";
-
-      let labelElem: HTMLLabelElement | undefined;
-      if(type === "slider") {
-        labelElem = document.createElement("label");
-        labelElem.classList.add("betterytm-ftconf-label");
-        labelElem.style.marginRight = "20px";
-        labelElem.style.fontSize = "16px";
-        labelElem.htmlFor = inputElemId;
-        labelElem.innerText = fmtVal(initialVal);
-
-        inputElem.addEventListener("input", () => {
-          if(labelElem)
-            labelElem.innerText = fmtVal(parseInt(inputElem.value));
-        });
-      }
-      else if(type === "toggle" && typeof initialVal !== "undefined") {
-        labelElem = document.createElement("label");
-        labelElem.classList.add("betterytm-ftconf-label");
-        labelElem.style.paddingLeft = "10px";
-        labelElem.style.paddingRight = "5px";
-        labelElem.style.fontSize = "16px";
-        labelElem.htmlFor = inputElemId;
-        labelElem.innerText = toggleLabelText(Boolean(initialVal));
-
-        inputElem.addEventListener("input", () => {
-          if(labelElem)
-            labelElem.innerText = toggleLabelText(inputElem.checked);
-        });
-      }
-
-      inputElem.addEventListener("input", ({ currentTarget }) => {
-        const elem = currentTarget as HTMLInputElement;
-        let v = parseInt(elem.value);
-        if(isNaN(v))
-          v = Number(elem.value);
-        if(typeof initialVal !== "undefined")
-          confChanged(key as keyof FeatureConfig, initialVal, (type !== "toggle" ? v : elem.checked));
-      });
-
-      const resetElem = document.createElement("button");
-      resetElem.innerText = "Reset";
-      resetElem.addEventListener("click", () => {
-        inputElem[type !== "toggle" ? "value" : "checked"] = ftDef as never;
-
-        if(labelElem) {
-          if(type === "toggle")
-            labelElem.innerText = toggleLabelText(inputElem.checked);
-          else
-            labelElem.innerText = fmtVal(parseInt(inputElem.value));
-        }
-
-        if(typeof initialVal !== "undefined")
-          confChanged(key as keyof FeatureConfig, initialVal, ftDef);
-      });
-
-      labelElem && ctrlElem.appendChild(labelElem);
-      ctrlElem.appendChild(inputElem);
-      ctrlElem.appendChild(resetElem);
-
-      ftConfElem.appendChild(ctrlElem);
-    }
-
-    featuresCont.appendChild(ftConfElem);
-  }
-
-  const footerElem = document.createElement("div");
-  footerElem.style.marginTop = "20px";
-  footerElem.style.fontSize = "17px";
-  footerElem.style.textDecoration = "underline";
-  footerElem.style.padding = "8px 20px";
-  footerElem.innerText = "You need to reload the page to apply changes.";
-
-  const reloadElem = document.createElement("button");
-  reloadElem.style.marginLeft = "20px";
-  reloadElem.innerText = "Reload now";
-  reloadElem.title = "Click to reload the page";
-  reloadElem.addEventListener("click", () => location.reload());
-
-  footerElem.appendChild(reloadElem);
-  featuresCont.appendChild(footerElem);
-
-
-  // finalize
-  const menuBody = document.createElement("div");
-  menuBody.id = "betterytm-menu-body";
-  menuBody.appendChild(titleCont);
-  menuBody.appendChild(featuresCont);
-
-  const versionCont = document.createElement("div");
-  versionCont.style.display = "flex";
-  versionCont.style.justifyContent = "space-around";
-  versionCont.style.fontSize = "1.15em";
-  versionCont.style.marginTop = "10px";
-  versionCont.style.marginBottom = "5px";
-
-  const versionElem = document.createElement("span");
-  versionElem.id = "betterytm-menu-version";
-  versionElem.innerText = `v${info.version}`;
-
-  versionCont.appendChild(versionElem);
-  featuresCont.appendChild(versionCont);
-
-  menuContainer.appendChild(menuBody);
-  menuContainer.appendChild(versionCont);
-
-  backgroundElem.appendChild(menuContainer);
-
-  document.body.appendChild(backgroundElem);
-
-
-  // add style
-  const menuStyle = `\
-#betterytm-menu-bg {
-  display: block;
-  position: fixed;
-  width: 100vw;
-  height: 100vh;
-  top: 0;
-  left: 0;
-  z-index: 15;
-  background-color: rgba(0, 0, 0, 0.6);
-}
-
-#betterytm-menu {
-  display: inline-block;
-  position: fixed;
-  width: 50vw;
-  height: auto;
-  min-height: 500px;
-  left: 25vw;
-  top: 25vh;
-  z-index: 16;
-  overflow: auto;
-  padding: 8px;
-  color: #fff;
-  background-color: #212121;
-}
-
-#betterytm-menu-titlecont {
-  display: flex;
-}
-
-#betterytm-menu-title {
-  font-size: 20px;
-  margin-top: 5px;
-  margin-bottom: 8px;
-}
-
-#betterytm-menu-linkscont {
-  display: flex;
-}
-
-.betterytm-menu-link {
-  display: inline-block;
-}
-
-/*.betterytm-menu-img {
-
-}*/
-
-#betterytm-menu-close {
-  cursor: pointer;
-}
-
-.betterytm-ftconf-label {
-  user-select: none;
-}`;
-
-  dbg && console.log("BetterYTM: Added menu elem:", backgroundElem);
-
-  addGlobalStyle(menuStyle, "menu");
-}
-
-export function closeMenu() {
-  const menuBg = document.querySelector("#betterytm-menu-bg") as HTMLElement;
-
-  menuBg.style.visibility = "hidden";
-  menuBg.style.display = "none";
-}
-
-export function openMenu() {
-  const menuBg = document.querySelector("#betterytm-menu-bg") as HTMLElement;
-
-  menuBg.style.visibility = "visible";
-  menuBg.style.display = "block";
-}
-
 //#MARKER watermark
 
 /**
@@ -462,19 +100,3 @@ export function setVolSliderStep() {
 
   sliderElem.setAttribute("step", String(features.volumeSliderStep));
 }
-
-//#MARKER changelog
-
-export async function initChangelog() {
-  // console.log("#DEBUG _CHANGELOG:", changelog);
-  // const cl = document.createElement("div");
-  // cl.style.position = "fixed";
-  // cl.style.top = "0";
-  // cl.style.left = "0";
-  // cl.style.minWidth = "500px";
-  // cl.style.minHeight = "500px";
-  // cl.style.overflowY = "scroll";
-  // cl.innerHTML = changelog;
-  // document.addEventListener("DOMContentLoaded", () => document.body.appendChild(cl));
-  void ["TODO", changelog];
-}

+ 417 - 0
src/features/menu.ts

@@ -0,0 +1,417 @@
+import { defaultFeatures, getFeatures, saveFeatureConf } from "../config";
+import { dbg, info } from "../constants";
+import { featInfo } from "./index";
+import { FeatureConfig } from "../types";
+import { addGlobalStyle } from "../utils";
+import changelog from "../../changelog.md";
+
+const branch = dbg ? "develop" : "main";
+
+//#MARKER menu
+
+/** Adds an element to open the BetterYTM menu */
+export async function addMenu() {
+  // bg & menu
+  const backgroundElem = document.createElement("div");
+  backgroundElem.id = "betterytm-menu-bg";
+  backgroundElem.title = "Click here to close the menu";
+  backgroundElem.style.visibility = "hidden";
+  backgroundElem.style.display = "none";
+  backgroundElem.addEventListener("click", (e) => {
+    if((e.target as HTMLElement).id === "betterytm-menu-bg")
+      closeMenu();
+  });
+
+  const menuContainer = document.createElement("div");
+  menuContainer.title = "";
+  menuContainer.id = "betterytm-menu";
+  menuContainer.style.borderRadius = "15px";
+  menuContainer.style.display = "flex";
+  menuContainer.style.flexDirection = "column";
+  menuContainer.style.justifyContent = "space-between";
+
+
+  // title
+  const titleCont = document.createElement("div");
+  titleCont.style.padding = "8px 20px 15px 8px";
+  titleCont.style.display = "flex";
+  titleCont.style.justifyContent = "space-between";
+  titleCont.id = "betterytm-menu-titlecont";
+
+  const titleElem = document.createElement("h2");
+  titleElem.id = "betterytm-menu-title";
+  titleElem.innerText = "BetterYTM - Configuration";
+
+  const linksCont = document.createElement("div");
+  linksCont.id = "betterytm-menu-linkscont";
+
+  const addLink = (imgSrc: string, href: string, title: string) => {
+    const anchorElem = document.createElement("a");
+    anchorElem.className = "betterytm-menu-link";
+    anchorElem.rel = "noopener noreferrer";
+    anchorElem.target = "_blank";
+    anchorElem.href = href;
+    anchorElem.title = title;
+    anchorElem.style.marginLeft = "10px";
+        
+    const imgElem = document.createElement("img");
+    imgElem.className = "betterytm-menu-img";
+    imgElem.src = imgSrc;
+    imgElem.style.width = "32px";
+    imgElem.style.height = "32px";
+
+    anchorElem.appendChild(imgElem);
+    linksCont.appendChild(anchorElem);
+  };
+
+  addLink(`https://raw.githubusercontent.com/Sv443/BetterYTM/${branch}/resources/external/github.png`, info.namespace, `${info.name} on GitHub`);
+  addLink(`https://raw.githubusercontent.com/Sv443/BetterYTM/${branch}/resources/external/greasyfork.png`, "https://greasyfork.org/xyz", `${info.name} on GreasyFork`);
+
+  const closeElem = document.createElement("img");
+  closeElem.id = "betterytm-menu-close";
+  closeElem.src = `https://raw.githubusercontent.com/Sv443/BetterYTM/${branch}/resources/icon/close.png`;
+  closeElem.title = "Click to close the menu";
+  closeElem.style.marginLeft = "50px";
+  closeElem.style.width = "32px";
+  closeElem.style.height = "32px";
+  closeElem.addEventListener("click", closeMenu);
+
+  linksCont.appendChild(closeElem);
+
+  titleCont.appendChild(titleElem);
+  titleCont.appendChild(linksCont);
+
+
+  // TODO: features
+  const featuresCont = document.createElement("div");
+  featuresCont.id = "betterytm-menu-opts";
+  featuresCont.style.display = "flex";
+  featuresCont.style.flexDirection = "column";
+
+  /** Gets called whenever the feature config is changed */
+  const confChanged = async (key: keyof typeof defaultFeatures, initialVal: number | boolean, newVal: number | boolean) => {
+    dbg && console.info(`BetterYTM: Feature config changed, key '${key}' from value '${initialVal}' to '${newVal}'`);
+
+    const featConf = { ...await getFeatures() };
+
+    featConf[key] = newVal as never;
+
+    await saveFeatureConf(featConf);
+
+    dbg && console.log("BetterYTM: Saved feature config changes:\n", await GM.getValue("betterytm-config"));
+  };
+
+  const features = await getFeatures();
+
+  const featKeys = Object.keys(features);
+  for(const key of featKeys) {
+    const ftInfo = featInfo[key as keyof typeof features];
+
+    if(!ftInfo)
+      continue;
+
+    const { desc, type, default: ftDef } = ftInfo;
+
+    // @ts-ignore
+    const step = ftInfo?.step ?? undefined;
+    const val = features[key as keyof typeof features];
+
+    const initialVal = val || ftDef || undefined;
+
+    const ftConfElem = document.createElement("div");
+    ftConfElem.id = `betterytm-ftconf-${key}`;
+    ftConfElem.style.display = "flex";
+    ftConfElem.style.flexDirection = "row";
+    ftConfElem.style.justifyContent = "space-between";
+    ftConfElem.style.padding = "8px 20px";
+
+    {
+      const textElem = document.createElement("span");
+      textElem.style.display = "inline-block";
+      textElem.style.fontSize = "15px";
+      textElem.innerText = desc;
+
+      ftConfElem.appendChild(textElem);
+    }
+
+    {
+      let inputType = "text";
+      switch(type)
+      {
+      case "toggle":
+        inputType = "checkbox";
+        break;
+      case "slider":
+        inputType = "range";
+        break;
+      case "number":
+        inputType = "number";
+        break;
+      }
+
+      const inputElemId = `betterytm-ftconf-${key}-input`;
+
+      const ctrlElem = document.createElement("span");
+      ctrlElem.style.display = "inline-block";
+      ctrlElem.style.whiteSpace = "nowrap";
+
+      const inputElem = document.createElement("input");
+      inputElem.id = inputElemId;
+      inputElem.style.marginRight = "37px";
+      inputElem.type = inputType;
+      if(type === "toggle")
+        inputElem.style.marginLeft = "5px";
+      if(typeof initialVal !== "undefined")
+        inputElem.value = String(initialVal);
+      if(type === "number" && step)
+        inputElem.step = step;
+
+      // @ts-ignore
+      if(ftInfo.min && ftInfo.max) {
+        // @ts-ignore
+        inputElem.min = ftInfo.min;
+        // @ts-ignore
+        inputElem.max = ftInfo.max;
+      }
+
+      if(type === "toggle" && typeof initialVal !== "undefined")
+        inputElem.checked = Boolean(initialVal);
+
+      const fmtVal = (v: unknown) => String(v);
+      const toggleLabelText = (toggled: boolean) => toggled ? "On" : "Off";
+
+      let labelElem: HTMLLabelElement | undefined;
+      if(type === "slider") {
+        labelElem = document.createElement("label");
+        labelElem.classList.add("betterytm-ftconf-label");
+        labelElem.style.marginRight = "20px";
+        labelElem.style.fontSize = "16px";
+        labelElem.htmlFor = inputElemId;
+        labelElem.innerText = fmtVal(initialVal);
+
+        inputElem.addEventListener("input", () => {
+          if(labelElem)
+            labelElem.innerText = fmtVal(parseInt(inputElem.value));
+        });
+      }
+      else if(type === "toggle" && typeof initialVal !== "undefined") {
+        labelElem = document.createElement("label");
+        labelElem.classList.add("betterytm-ftconf-label");
+        labelElem.style.paddingLeft = "10px";
+        labelElem.style.paddingRight = "5px";
+        labelElem.style.fontSize = "16px";
+        labelElem.htmlFor = inputElemId;
+        labelElem.innerText = toggleLabelText(Boolean(initialVal));
+
+        inputElem.addEventListener("input", () => {
+          if(labelElem)
+            labelElem.innerText = toggleLabelText(inputElem.checked);
+        });
+      }
+
+      inputElem.addEventListener("input", ({ currentTarget }) => {
+        const elem = currentTarget as HTMLInputElement;
+        let v = parseInt(elem.value);
+        if(isNaN(v))
+          v = Number(elem.value);
+        if(typeof initialVal !== "undefined")
+          confChanged(key as keyof FeatureConfig, initialVal, (type !== "toggle" ? v : elem.checked));
+      });
+
+      const resetElem = document.createElement("button");
+      resetElem.innerText = "Reset";
+      resetElem.addEventListener("click", () => {
+        inputElem[type !== "toggle" ? "value" : "checked"] = ftDef as never;
+
+        if(labelElem) {
+          if(type === "toggle")
+            labelElem.innerText = toggleLabelText(inputElem.checked);
+          else
+            labelElem.innerText = fmtVal(parseInt(inputElem.value));
+        }
+
+        if(typeof initialVal !== "undefined")
+          confChanged(key as keyof FeatureConfig, initialVal, ftDef);
+      });
+
+      labelElem && ctrlElem.appendChild(labelElem);
+      ctrlElem.appendChild(inputElem);
+      ctrlElem.appendChild(resetElem);
+
+      ftConfElem.appendChild(ctrlElem);
+    }
+
+    featuresCont.appendChild(ftConfElem);
+  }
+
+  const footerElem = document.createElement("div");
+  footerElem.style.marginTop = "20px";
+  footerElem.style.fontSize = "17px";
+  footerElem.style.textDecoration = "underline";
+  footerElem.style.padding = "8px 20px";
+  footerElem.innerText = "You need to reload the page to apply changes.";
+
+  const reloadElem = document.createElement("button");
+  reloadElem.style.marginLeft = "20px";
+  reloadElem.innerText = "Reload now";
+  reloadElem.title = "Click to reload the page";
+  reloadElem.addEventListener("click", () => location.reload());
+
+  footerElem.appendChild(reloadElem);
+  featuresCont.appendChild(footerElem);
+
+
+  // finalize
+  const menuBody = document.createElement("div");
+  menuBody.id = "betterytm-menu-body";
+  menuBody.appendChild(titleCont);
+  menuBody.appendChild(featuresCont);
+
+  const versionCont = document.createElement("div");
+  versionCont.style.display = "flex";
+  versionCont.style.justifyContent = "space-around";
+  versionCont.style.fontSize = "1.15em";
+  versionCont.style.marginTop = "10px";
+  versionCont.style.marginBottom = "5px";
+
+  const versionElem = document.createElement("span");
+  versionElem.id = "betterytm-menu-version";
+  versionElem.innerText = `v${info.version}`;
+
+  versionCont.appendChild(versionElem);
+  featuresCont.appendChild(versionCont);
+
+  menuContainer.appendChild(menuBody);
+  menuContainer.appendChild(versionCont);
+
+  backgroundElem.appendChild(menuContainer);
+
+  document.body.appendChild(backgroundElem);
+
+
+  // add style
+  const menuStyle = `\
+#betterytm-menu-bg {
+  display: block;
+  position: fixed;
+  width: 100vw;
+  height: 100vh;
+  top: 0;
+  left: 0;
+  z-index: 15;
+  background-color: rgba(0, 0, 0, 0.6);
+}
+
+#betterytm-menu {
+  display: inline-block;
+  position: fixed;
+  width: 50vw;
+  height: auto;
+  min-height: 500px;
+  left: 25vw;
+  top: 25vh;
+  z-index: 16;
+  overflow: auto;
+  padding: 8px;
+  color: #fff;
+  background-color: #212121;
+}
+
+#betterytm-menu-titlecont {
+  display: flex;
+}
+
+#betterytm-menu-title {
+  font-size: 20px;
+  margin-top: 5px;
+  margin-bottom: 8px;
+}
+
+#betterytm-menu-linkscont {
+  display: flex;
+}
+
+.betterytm-menu-link {
+  display: inline-block;
+}
+
+/*.betterytm-menu-img {
+
+}*/
+
+#betterytm-menu-close {
+  cursor: pointer;
+}
+
+.betterytm-ftconf-label {
+  user-select: none;
+}`;
+
+  dbg && console.log("BetterYTM: Added menu elem:", backgroundElem);
+
+  addGlobalStyle(menuStyle, "menu");
+}
+
+export function closeMenu() {
+  const menuBg = document.querySelector("#betterytm-menu-bg") as HTMLElement;
+
+  menuBg.style.visibility = "hidden";
+  menuBg.style.display = "none";
+}
+
+export function openMenu() {
+  const menuBg = document.querySelector("#betterytm-menu-bg") as HTMLElement;
+
+  menuBg.style.visibility = "visible";
+  menuBg.style.display = "block";
+}
+
+//#MARKER changelog
+
+export async function initChangelog() {
+  //   const clStyle = `\
+  // #betterytm-changelog-bg {
+  //   display: block;
+  //   position: fixed;
+  //   width: 100vw;
+  //   height: 100vh;
+  //   top: 0;
+  //   left: 0;
+  //   z-index: 15;
+  //   background-color: rgba(0, 0, 0, 0.6);
+  // }
+
+  // #betterytm-changelog {
+  //   display: inline-block;
+  //   position: fixed;
+  //   width: 50vw;
+  //   height: auto;
+  //   min-height: 500px;
+  //   left: 25vw;
+  //   top: 25vh;
+  //   z-index: 16;
+  //   overflow: auto;
+  //   padding: 8px;
+  //   color: #fff;
+  //   background-color: #212121;
+  // }
+
+  // #betterytm-changelog-close {
+  //   cursor: pointer;
+  // }`;
+
+
+  // console.log("#DEBUG _CHANGELOG:", changelog);
+  // const cl = document.createElement("div");
+  // cl.style.position = "fixed";
+  // cl.style.top = "0";
+  // cl.style.left = "0";
+  // cl.style.minWidth = "500px";
+  // cl.style.minHeight = "500px";
+  // cl.style.overflowY = "scroll";
+  // cl.innerHTML = changelog;
+  // document.addEventListener("DOMContentLoaded", () => document.body.appendChild(cl));
+
+
+  void ["TODO", changelog];
+}