layout.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { scriptInfo, triesInterval, triesLimit } from "../constants";
  2. import { getFeatures } from "../config";
  3. import { addGlobalStyle, autoPlural, error, getAssetUrl, getEvtData, insertAfter, log, openInNewTab, siteEvents } from "../utils";
  4. import type { FeatureConfig } from "../types";
  5. import { openMenu } from "./menu/menu_old";
  6. import "./layout.css";
  7. import { getGeniusUrl, getLyricsBtn, sanitizeArtists, sanitizeSong } from "./lyrics";
  8. let features: FeatureConfig;
  9. export async function preInitLayout() {
  10. features = await getFeatures();
  11. }
  12. //#MARKER watermark
  13. /** Adds a watermark beneath the logo */
  14. export function addWatermark() {
  15. const watermark = document.createElement("a");
  16. watermark.role = "button";
  17. watermark.id = "betterytm-watermark";
  18. watermark.className = "style-scope ytmusic-nav-bar";
  19. watermark.innerText = scriptInfo.name;
  20. watermark.title = "Open menu";
  21. watermark.tabIndex = 1000;
  22. watermark.addEventListener("click", () => openMenu());
  23. watermark.addEventListener("keydown", (e) => e.key === "Enter" && openMenu());
  24. const logoElem = document.querySelector("#left-content") as HTMLElement;
  25. insertAfter(logoElem, watermark);
  26. log("Added watermark element:", watermark);
  27. }
  28. //#MARKER remove upgrade tab
  29. let removeUpgradeTries = 0;
  30. /** Removes the "Upgrade" / YT Music Premium tab from the title / nav bar */
  31. export function removeUpgradeTab() {
  32. const tabElem = document.querySelector(".ytmusic-nav-bar ytmusic-pivot-bar-item-renderer[tab-id=\"SPunlimited\"]");
  33. if(tabElem) {
  34. tabElem.remove();
  35. log(`Removed upgrade tab after ${removeUpgradeTries} tries`);
  36. }
  37. else if(removeUpgradeTries < triesLimit) {
  38. setTimeout(removeUpgradeTab, triesInterval); // TODO: improve this
  39. removeUpgradeTries++;
  40. }
  41. else
  42. error(`Couldn't find upgrade tab to remove after ${removeUpgradeTries} tries`);
  43. }
  44. //#MARKER volume slider
  45. /** Sets the volume slider to a set size */
  46. export function setVolSliderSize() {
  47. const { volumeSliderSize: size } = features;
  48. if(typeof size !== "number" || isNaN(Number(size)))
  49. return;
  50. const style = `\
  51. .volume-slider.ytmusic-player-bar, .expand-volume-slider.ytmusic-player-bar {
  52. width: ${size}px !important;
  53. }`;
  54. addGlobalStyle(style, "vol-slider");
  55. }
  56. /** Sets the `step` attribute of the volume slider */
  57. export function setVolSliderStep() {
  58. const sliderElem = document.querySelector("tp-yt-paper-slider#volume-slider") as HTMLInputElement;
  59. sliderElem.setAttribute("step", String(features.volumeSliderStep));
  60. }
  61. //#MARKER queue buttons
  62. // TODO: account for the fact initially the elements might not exist, if the site was not opened directly with a video playing or via the /watch path
  63. export function initQueueButtons() {
  64. siteEvents.on("queueChanged", (evt) => {
  65. let amt = 0;
  66. for(const queueItm of getEvtData<HTMLElement>(evt).childNodes as NodeListOf<HTMLElement>) {
  67. if(!queueItm.classList.contains("bytm-has-queue-btns")) {
  68. addQueueButtons(queueItm);
  69. amt++;
  70. }
  71. }
  72. if(amt > 0)
  73. log(`Added buttons to ${amt} new queue ${autoPlural("item", amt)}`);
  74. });
  75. const queueItems = document.querySelectorAll("#contents.ytmusic-player-queue > ytmusic-player-queue-item");
  76. if(queueItems.length === 0)
  77. return;
  78. queueItems.forEach(itm => addQueueButtons(itm as HTMLElement));
  79. log(`Added buttons to ${queueItems.length} existing queue items`);
  80. }
  81. /**
  82. * Adds the buttons to each item in the current song queue.
  83. * Also observes for changes to add new buttons to new items in the queue.
  84. */
  85. async function addQueueButtons(queueItem: HTMLElement) {
  86. const queueBtnsCont = document.createElement("div");
  87. queueBtnsCont.className = "bytm-queue-btn-container";
  88. const songInfo = queueItem.querySelector(".song-info") as HTMLElement;
  89. if(!songInfo)
  90. return false;
  91. const [songEl, artistEl] = (songInfo.querySelectorAll("yt-formatted-string") as NodeListOf<HTMLElement>);
  92. const song = songEl.innerText;
  93. const artist = artistEl.innerText;
  94. if(!song || !artist)
  95. return false;
  96. // TODO: display "currently loading" icon
  97. const lyricsBtnElem = getLyricsBtn(undefined, false);
  98. lyricsBtnElem.title = "Open this song's lyrics in a new tab";
  99. lyricsBtnElem.style.cursor = "pointer";
  100. lyricsBtnElem.style.visibility = "initial";
  101. lyricsBtnElem.style.display = "inline-flex";
  102. lyricsBtnElem.style.pointerEvents = "initial";
  103. lyricsBtnElem.addEventListener("click", async () => {
  104. let lyricsUrl;
  105. if(songInfo.dataset.bytmLyrics && songInfo.dataset.bytmLyrics.length > 0)
  106. lyricsUrl = songInfo.dataset.bytmLyrics;
  107. else if(songInfo.dataset.bytmLoading !== "true") {
  108. songInfo.dataset.bytmLoading = "true";
  109. const imgEl = lyricsBtnElem.querySelector("img") as HTMLImageElement;
  110. imgEl.src = getAssetUrl("loading.gif");
  111. lyricsUrl = await getGeniusUrl(sanitizeArtists(artist), sanitizeSong(song));
  112. songInfo.dataset.bytmLoading = "false";
  113. imgEl.src = getAssetUrl("external/genius.png");
  114. if(!lyricsUrl) {
  115. if(confirm("Couldn't find a lyrics page for this song.\nDo you want to open genius.com to manually search for it?"))
  116. openInNewTab("https://genius.com/search");
  117. return;
  118. }
  119. songInfo.dataset.bytmLyrics = lyricsUrl;
  120. }
  121. lyricsUrl && openInNewTab(lyricsUrl);
  122. });
  123. queueBtnsCont.appendChild(lyricsBtnElem);
  124. songInfo.appendChild(queueBtnsCont);
  125. queueItem.classList.add("bytm-has-queue-btns");
  126. return true;
  127. }
  128. //#MARKER better clickable stuff
  129. // TODO: account for the fact initially the elements might not exist, if the site was opened directly with the /watch path
  130. export function addAnchorImprovements() {
  131. void 0;
  132. }