From a9eeb3517ac604b23c7655686aeb86da3a9a7e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20La=C3=ADn?= Date: Wed, 5 Jul 2023 20:29:17 +0200 Subject: [PATCH] spicetify added --- .config/spicetify/Extensions/adblock.js | 59 + .../Extensions/catppuccin-macchiato.js | 39 + .config/spicetify/Extensions/copyPlaylist.js | 654 ++++ .config/spicetify/Extensions/fullScreen.js | 3087 +++++++++++++++++ .config/spicetify/Extensions/genre.js | 125 + .config/spicetify/Extensions/hidePodcasts.js | 5 + .../spicetify/Extensions/historyShortcut.js | 127 + .../Extensions/keyboardShortcutMy.js | 532 +++ .config/spicetify/Extensions/playlistIcons.js | 3 + .../spicetify/Extensions/sortByPlayCount.js | 1306 +++++++ .../spicetify/Extensions/volumePercentage.js | 32 + .config/spicetify/Extensions/wikify.js | 96 + .config/spicetify/Themes/Comfy/color.ini | 406 +++ .config/spicetify/Themes/Comfy/theme.js | 9 + .config/spicetify/Themes/Comfy/user.css | 1 + .config/spicetify/config-xpui.ini | 32 + 16 files changed, 6513 insertions(+) create mode 100644 .config/spicetify/Extensions/adblock.js create mode 100644 .config/spicetify/Extensions/catppuccin-macchiato.js create mode 100644 .config/spicetify/Extensions/copyPlaylist.js create mode 100644 .config/spicetify/Extensions/fullScreen.js create mode 100644 .config/spicetify/Extensions/genre.js create mode 100644 .config/spicetify/Extensions/hidePodcasts.js create mode 100644 .config/spicetify/Extensions/historyShortcut.js create mode 100644 .config/spicetify/Extensions/keyboardShortcutMy.js create mode 100644 .config/spicetify/Extensions/playlistIcons.js create mode 100644 .config/spicetify/Extensions/sortByPlayCount.js create mode 100644 .config/spicetify/Extensions/volumePercentage.js create mode 100644 .config/spicetify/Extensions/wikify.js create mode 100644 .config/spicetify/Themes/Comfy/color.ini create mode 100644 .config/spicetify/Themes/Comfy/theme.js create mode 100644 .config/spicetify/Themes/Comfy/user.css create mode 100644 .config/spicetify/config-xpui.ini diff --git a/.config/spicetify/Extensions/adblock.js b/.config/spicetify/Extensions/adblock.js new file mode 100644 index 00000000..6220d04f --- /dev/null +++ b/.config/spicetify/Extensions/adblock.js @@ -0,0 +1,59 @@ +//@ts-check + +// NAME: adblock +// AUTHOR: CharlieS1103 +// DESCRIPTION: Block all audio and UI ads on Spotify + +/// + +(function adblock() { + const { Platform} = Spicetify; + if (!(Platform)) { + setTimeout(adblock, 300) + return + } + + var styleSheet = document.createElement("style") + + styleSheet.innerHTML = + ` + .MnW5SczTcbdFHxLZ_Z8j, .WiPggcPDzbwGxoxwLWFf, .ReyA3uE3K7oEz7PTTnAn, .main-leaderboardComponent-container, .sponsor-container, a.link-subtle.main-navBar-navBarLink.GKnnhbExo0U9l7Jz2rdc{ + display: none !important; + } + ` + document.body.appendChild(styleSheet) + delayAds() + var billboard = Spicetify.Platform.AdManagers.billboard.displayBillboard; + Spicetify.Platform.AdManagers.billboard.displayBillboard = function (arguments) { + Spicetify.Platform.AdManagers.billboard.finish() + // hook before call + var ret = billboard.apply(this, arguments); + // hook after call + console.log("Adblock.js: Billboard blocked! Leave a star!") + Spicetify.Platform.AdManagers.billboard.finish() + const observer = new MutationObserver((mutations, obs) => { + const billboardAd = document.getElementById('view-billboard-ad'); + if (billboardAd) { + Spicetify.Platform.AdManagers.billboard.finish() + obs.disconnect(); + return; + } + }); + + observer.observe(document, { + childList: true, + subtree: true + }); + return ret; + }; + function delayAds() { + console.log("Ads delayed: Adblock.js") + Spicetify.Platform.AdManagers.audio.audioApi.cosmosConnector.increaseStreamTime(-100000000000) + Spicetify.Platform.AdManagers.billboard.billboardApi.cosmosConnector.increaseStreamTime(-100000000000) + } + setInterval(delayAds, 720 *10000); + + +})() + + diff --git a/.config/spicetify/Extensions/catppuccin-macchiato.js b/.config/spicetify/Extensions/catppuccin-macchiato.js new file mode 100644 index 00000000..b0c536c4 --- /dev/null +++ b/.config/spicetify/Extensions/catppuccin-macchiato.js @@ -0,0 +1,39 @@ +// Color map +let colorPalette = { + rosewater: "#f4dbd6", + flamingo: "#f0c6c6", + pink: "#f5bde6", + maroon: "#ee99a0", + red: "#ed8796", + peach: "#f5a97f", + yellow: "#eed49f", + green: "#a6da95", + teal: "#8bd5ca", + blue: "#8aadf4", + sky: "#91d7e3", + lavender: "#b7bdf8", + white: "#d9e0ee" +} + +// waitForElement borrowed from: +// https://github.com/morpheusthewhite/spicetify-themes/blob/master/Dribbblish/dribbblish.js +function waitForElement(els, func, timeout = 100) { + const queries = els.map(el => document.querySelector(el)); + if (queries.every(a => a)) { + func(queries); + } else if (timeout > 0) { + setTimeout(waitForElement, 300, els, func, --timeout); + } +} + +// Return the color label for a given hex color value +function getKeyByValue(object, value) { + return Object.keys(object).find(key => object[key] === value.trim()); +} + +// Used to select matching equalizer-animated-COLOR.gif +waitForElement([".Root"], (root) => { + let spiceEq = getComputedStyle(document.querySelector(":root")).getPropertyValue("--spice-equalizer"); + let eqColor = getKeyByValue(colorPalette, spiceEq); + root[0].classList.add(`catppuccin-eq-${eqColor}`); +}); diff --git a/.config/spicetify/Extensions/copyPlaylist.js b/.config/spicetify/Extensions/copyPlaylist.js new file mode 100644 index 00000000..4616d75a --- /dev/null +++ b/.config/spicetify/Extensions/copyPlaylist.js @@ -0,0 +1,654 @@ +// NAME: Copy Playlists +// AUTHOR: einzigartigerName +// DESCRIPTION: copy/combine playlist/queue directly in Spotify + +(function CopyPlaylist() { + + const { CosmosAPI, BridgeAPI, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify + if (!(CosmosAPI || BridgeAPI)) { + setTimeout(CopyPlaylist, 1000); + return; + } + + const STORAGE_KEY = "combine_buffer_spicetify" + const TOP_BTN_TOOLTIP = "Combine Playlists" + const MENU_BTN_CREATE_NEW = "Create Playlist" + const MENU_BTN_INSERT_BUFFER = "Copy to Buffer" + + class PlaylistCollection { + constructor() { + const menu = createMenu() + this.container = menu.container + this.items = menu.menu + this.lastScroll = 0 + this.container.onclick = () => { + this.storeScroll() + this.container.remove() + } + this.pattern + this.apply() + } + + apply() { + this.items.textContent = '' // Remove all childs + this.items.append(createMenuItem("Create Playlist", () => highjackCreateDialog(mergePlaylists(this.pattern)))) + this.items.append(createMenuItem("Clear Buffer", () => LIST.clearStorage())) + + const select = createPatternSelect(this.filter); + select.onchange = (event) => { + this.pattern = event.srcElement.selectedIndex; + } + this.items.append(select); + + const collection = this.getStorage(); + collection.forEach((item) => this.items.append(new CardContainer(item))) + } + + getStorage() { + const storageRaw = LocalStorage.get(STORAGE_KEY); + let storage = []; + + if (storageRaw) { + storage = JSON.parse(storageRaw); + } else { + LocalStorage.set(STORAGE_KEY, "[]") + } + + return storage; + } + + addToStorage(data) { + + /** @type {Object[]} */ + const storage = this.getStorage(); + storage.push(data); + + LocalStorage.set(STORAGE_KEY, JSON.stringify(storage)); + this.apply() + } + + removeFromStorage(id) { + const storage = this.getStorage() + .filter(item => item.id !== id) + + LocalStorage.set(STORAGE_KEY, JSON.stringify(storage)); + this.apply() + } + + clearStorage() { + LocalStorage.set(STORAGE_KEY, "[]"); + this.apply() + } + + moveItem(uri, direction) { + var storage = this.getStorage() + + var from; + + for (var i = 0; i < storage.length; i++) { + if (storage[i].uri === uri) { + from = i + break; + } + } + if (!from) { return } + + var to = from + direction + if (to < 0 || to >= storage.length) { return } + + var tmp = storage[from] + storage[from] = storage[to] + storage[to] = tmp + + LocalStorage.set(STORAGE_KEY, JSON.stringify(storage)); + this.apply() + } + + changePosition(x, y) { + this.items.style.left = x + "px" + this.items.style.top = y + 10 + "px" + } + + storeScroll() { + this.lastScroll = this.items.scrollTop + } + + setScroll() { + this.items.scrollTop = this.lastScroll + } + } + + /* + * Displays Stored Playlist + * {id, uri, name, tracks, imgUri, owner} + */ + class CardContainer extends HTMLElement { + constructor(info) { + super() + + this.innerHTML = ` +` + + const up = this.querySelector(".order-controls-up") + up.onclick = (event) => { + LIST.moveItem(info.uri, -1) + event.stopPropagation() + } + + const remove = this.querySelector(".order-controls-remove") + remove.onclick = (event) => { + LIST.removeFromStorage(info.id) + event.stopPropagation() + } + + const down = this.querySelector(".order-controls-down") + down.onclick = (event) => { + LIST.moveItem(info.uri, +1) + event.stopPropagation() + } + + const imageLink = this.querySelector(".card-image-link"); + const infoLink = this.querySelector(".card-info-link"); + + if (imageLink) imageLink.addEventListener("click", ((e) => showPlaylist(e))); + + if (infoLink) infoLink.addEventListener("click", ((e) => showPlaylist(e))); + } + } + + customElements.define("combine-buffer-card-container", CardContainer) + + const LIST = new PlaylistCollection() + + // New Playlist Button + const playlistDialogButton = document.querySelector("#new-playlist-button-mount-point > div > button") + if (!playlistDialogButton) return; + + document.querySelector("#view-browser-navigation-top-bar") + .append(createTopBarButton()) + + createPlaylistContextMenu().register() + + + /************************************************************************** + UI Building + **************************************************************************/ + // If Queue Page add Buttons + const iframeInterval = setInterval(() => { + /** @type {HTMLIFrameElement} */ + const currentIframe = document.querySelector("iframe.active"); + if (!currentIframe || + currentIframe.id !== "app-queue" + ) { + return; + } + + const headers = currentIframe.contentDocument.querySelectorAll( + ".glue-page-header__buttons" + ); + + for (const e of headers) { + e.append(createQueueButton( + "Save as Playlist", + "Save the current Queue as a new Playlist", + () => { + let tracks = getQueueTracks(); + highjackCreateDialog(tracks); + }, + )); + + e.append(createQueueButton( + "Copy into Buffer", + "Insert the current Queue into the Buffer", + () => { queueToBuffer() }, + )); + } + + if (headers.length > 0) clearInterval(iframeInterval); + }, 500) + + // Creates the Main Menu + function createMenu() { + const container = document.createElement("div") + container.id = "combine-playlist-spicetify" + container.className = "context-menu-container" + container.style.zIndex = "1029" + + const style = document.createElement("style") + style.textContent = ` +#combine-menu { + display: inline-block; + width: 33%; + max-height: 70%; + overflow: hidden auto; + padding: 10px +} +.combine-pattern { + margin-top: 7px; +} +.order-controls { + position: absolute; + right: 0; + padding: 0 5px 5px 0; + z-index: 3 +} +.button.button-icon-only::before { + color: var(--modspotify_main_fg); +} +.order-controls-up { + position: relative; + top: 100%; +} +.order-controls-remove { + position: relative; + top: 50%; +} +.order-controls-down { + position: relative; + bottom: 100%; +} +.card-info-subtitle-owner { + color: var(--modspotify_secondary_fg); +} +.card-info-subtitle-tracks { + font-weight: lighter; + color: var(--modspotify_secondary_fg); +} +` + + const menu = document.createElement("ul") + menu.id = "combine-menu" + menu.className = "context-menu" + + container.append(style, menu) + + return { container, menu } + } + + // Creates a Button in the Combine Menu + function createMenuItem(name, callback) { + const item = document.createElement("div"); + item.classList.add("item"); + item.onclick = callback; + item.onmouseover = () => item.classList.add("hover"); + item.onmouseleave = () => item.classList.remove("hover"); + + const text = document.createElement("span"); + text.classList.add("text"); + text.innerText = name; + + item.append(text); + + return item; + } + + // Creates the SubMenu in Playlist Context + function createPlaylistContextMenu() { + var createFromCurrent = new Spicetify.ContextMenu.Item( + MENU_BTN_CREATE_NEW, + (uris) => { + if (uris.length === 1) { + fetchPlaylist(uris[0]) + .then((buffer) => highjackCreateDialog(buffer.tracks)) + .catch((err) => Spicetify.showNotification(`${err}`)); + return; + } else { + Spicetify.showNotification("Unable to find Playlist URI") + } + }, + (_) => true + ) + + var insertIntoBuffer = new Spicetify.ContextMenu.Item( + MENU_BTN_INSERT_BUFFER, + (uris) => { + + if (uris.length === 1) { + fetchPlaylist(uris[0]) + .then((buffer) => {LIST.addToStorage(buffer)}) + .catch((err) => Spicetify.showNotification(`${err}`)); + return; + } + }, + (_) => true + ) + + return new Spicetify.ContextMenu.SubMenu( + "Copy Playlist", + [ createFromCurrent, insertIntoBuffer], + (uris) => { + if (uris.length === 1) { + const uriObj = Spicetify.URI.fromString(uris[0]); + switch (uriObj.type) { + case Spicetify.URI.Type.PLAYLIST: + case Spicetify.URI.Type.PLAYLIST_V2: + return true; + } + return false; + } + // Multiple Items selected. + return false; + } + ) + } + + // Creates the Button to View Merge Buffer + function createTopBarButton() { + const button = document.createElement("button") + button.classList.add("button", "spoticon-copy-16", "merge-button") + button.setAttribute("data-tooltip", TOP_BTN_TOOLTIP) + button.setAttribute("data-contextmenu", "") + button.setAttribute("data-uri", "spotify:special:copy") + button.onclick = () => { + const bound = button.getBoundingClientRect() + LIST.changePosition(bound.left, bound.top) + document.body.append(LIST.container) + LIST.setScroll() + } + return button + } + + // Creates the Dropdown Menu for Merge Pattern + function createPatternSelect(defaultOpt = 0) { + const select = document.createElement("select"); + select.className = "GlueDropdown combine-pattern"; + + const appendOpt = document.createElement("option"); + appendOpt.text = "Append"; + + const shuffleOpt = document.createElement("option"); + shuffleOpt.text = "Shuffle"; + + const alternateOpt = document.createElement("option"); + alternateOpt.text = "Alternate"; + + select.onclick = (ev) => ev.stopPropagation(); + select.append(appendOpt, shuffleOpt, alternateOpt); + select.options[defaultOpt].selected = true; + + return select; + } + + // Queue button + function createQueueButton(name, tooltip, callback) { + const b = document.createElement("button"); + b.classList.add("button", "button-green"); + b.innerText = name; + b.setAttribute("data-tooltip", tooltip); + b.onclick = callback; + return b; + } + + // Highjack Spotifies 'New Playlist' Dialog + function highjackCreateDialog(tracks) { + playlistDialogButton.click() + + var createButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container > button") + var buttonContainer = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container") + + var highjackedButton = createButton.cloneNode(true) + highjackedButton.addEventListener("click", () => onCreateNewPlaylist(tracks)) + + window.addEventListener("keypress", (event) => { + if (event.code === `Enter`) { + // Cancel the default action, if needed + event.preventDefault(); + // Trigger the button element with a click + createButton.click(); + } + }); + + createButton.remove() + buttonContainer.insertAdjacentElement("afterbegin", highjackedButton) + } + + + /************************************************************************** + OnCLick Functions + **************************************************************************/ + // Create a new Playlist from Inputs + function onCreateNewPlaylist(tracks) { + var exitButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__close-button > button"); + var nameInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-name > input") + var descInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-description > textarea") + var imageInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__img-container > div > div.PlaylistAnnotationModal__img-holder > img") + + var name = nameInput.value + if (!name) { + name = nameInput.getAttribute("placeholder") + } + + var desc = descInput.value + + var img; + if (imageInput) { + img = imageInput.getAttribute("src") + } + + createPlaylist(name) + .then(res => addTracks(res.uri, tracks)) + .then((_) => Spicetify.showNotification(`Created Playlist: "${name}"`)) + .catch((err) => Spicetify.showNotification(`${err}`)); + + exitButton.click() + + if (exitButton) { + exitButton.click() + } + } + + // Get All Tracks in Queue and remove delimiter + function getQueueTracks() { + return Spicetify.Queue.next_tracks + .map((t) => t.uri) + .filter((t) => { return t != "spotify:delimiter"; }) + } + + // Copy the Queue to the Combine Buffer + function queueToBuffer() { + let tracks = getQueueTracks(); + + var date = new Date() + var year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date); + var month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date); + var day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date); + + const timeOptions = { hour: 'numeric', minute: 'numeric', hour12: false}; + var time = new Intl.DateTimeFormat(`en`, timeOptions).format(date); + + let queue = { + id: `spotify:queue-${date}`, + uri: `spotify:queue`, + name: "Queue", + imgUri: undefined, + tracks: tracks, + owner: `${time} - ${day} ${month} ${year}`, + } + + LIST.addToStorage(queue); + } + + // Show the clicked Playlist + async function showPlaylist(event) { + console.log(event) + } + + + /************************************************************************** + Merge Playlists + **************************************************************************/ + // Merge all Playlists + function mergePlaylists(pattern) { + var tracks = LIST.getStorage().map((pl) => pl.tracks) + + switch (pattern) { + case 1: return shuffle(tracks); + case 2: return alternate(tracks); + default: return append(tracks); + } + } + + // Alternate Playlists + function alternate(arrays) { + var combined = [] + + while (arrays.length != 0) { + var current = arrays.shift() + if (current.length != 0) { + combined.push(current.shift()) + + if (current.length != 0) { + arrays.push(current) + } + } + } + + return combined + } + + // Shuffle all tracks using the Durstenfeld Shuffle + function shuffle(arrays) { + var combined = append(arrays) + + for (var i = combined.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var temp = combined[i]; + combined[i] = combined[j]; + combined[j] = temp; + } + + return combined; + } + + // Simply Concat all Playlist + function append(arrays) { + var combined = [] + arrays.forEach((arr) => combined = combined.concat(arr)) + + return combined; + } + + /************************************************************************** + Calls to the CosmosAPI + **************************************************************************/ + // Fetch all Track from Playlist URI + async function fetchPlaylist(uri) { + return await new Promise((resolve, reject) => { + Spicetify.BridgeAPI.cosmosJSON( + { + method: "GET", + uri: `sp://core-playlist/v1/playlist/${uri}/`, + body: { + policy: { + link: true, + }, + }, + }, + (error, res) => { + if (error) { + reject(error); + return; + } + + let id = `${uri}-${new Date()}` + let tracks = res.items.map((track) => track.link) + let img = res.playlist.picture + let name = res.playlist.name + let owner = res.playlist.owner.name + + let playlist = {id: id, uri: uri, name: name, tracks: tracks, imgUri: img, owner: owner} + + resolve(playlist); + } + ); + }); + } + + // Create a new Playlist + async function createPlaylist(name) { + return await new Promise((resolve, reject) => { + Spicetify.BridgeAPI.cosmosJSON( + { + method: "POST", + uri: `sp://core-playlist/v1/rootlist`, + body: { + operation: "create", + playlist: !0, + before: "start", + name: name, + }, + }, + (error, res) => { + if (error) { + reject(error); + return; + } + + resolve(res); + } + ); + }); + } + + // add track list to playlist + async function addTracks(uri, tracks) { + return await new Promise((resolve, reject) => { + Spicetify.BridgeAPI.cosmosJSON( + { + method: "POST", + uri: `sp://core-playlist/v1/playlist/${uri}`, + body: { + operation: "add", + uris: tracks, + after: "end" + } + }, + (error, res) => { + if (error) { + reject(error); + return; + } + + resolve(res); + } + ); + }); + } +})(); diff --git a/.config/spicetify/Extensions/fullScreen.js b/.config/spicetify/Extensions/fullScreen.js new file mode 100644 index 00000000..fe6c5c50 --- /dev/null +++ b/.config/spicetify/Extensions/fullScreen.js @@ -0,0 +1,3087 @@ +// @ts-nocheck + +// NAME: Full Screen Mode +// AUTHOR: daksh2k +// DESCRIPTION: Fancy artwork and track status display. + +/// + +const translations = { + "en-US": { + langName: "English", + context: { + queue: "Playing from queue", + track: "Playing track", + album: "Playing from album", + artist: "Playing from artist", + playlist: "Playing from playlist", + playlistFolder: "Playing from playlist folder", + search: "Playing from search", + searchDest: "Songs", + collection: "Playing from collection", + likedSongs: "Liked Songs", + trackRadio: "Playing from track radio", + artistRadio: "Playing from artist radio", + albumRadio: "Playing from album radio", + playlistRadio: "Playing from playlist radio", + }, + upnext: "UP NEXT", + unknownArtist: "Artist (Unavailable)", + settings: { + switchToTV: "Switch to TV Mode", + switchToFullscreen: "Switch to Default Mode", + tvModeConfig: "TV Mode Configuration", + fullscreenConfig: "Full Screen Configuration", + exit: "Exit", + + pluginSettings: "Plugin Settings", + fsHideOriginal: "Hide Stock Button(Spotify Premium)", + language: "Language", + + lyricsHeader: "Lyrics Settings", + lyrics: "Lyrics", + lyricsAlignment: { + setting: "Lyrics Alignment", + left: "Left", + center: "Center", + right: "Right", + }, + lyricsAnimationTempo: "Lyrics Animation Tempo", + + generalHeader: "General Settings", + progressBar: "Progress Bar", + playerControls: "Player Controls", + trimTitle: "Trim Title", + trimTitleUpNext: "Trim Title(Up Next)", + showAlbum: { + setting: "Show Album", + never: "Never", + always: "Always", + date: "Show with Release Date", + }, + showAllArtists: "Show All Artists", + icons: "Icons", + songChangeAnimation: "Song Change Animation", + fullscreen: "Fullscreen", + + extraHeader: "Extra Functionality", + backgroundChoice: { + setting: "Background choice", + color: "Solid color", + artwork: "Artwork", + }, + extraControls: "Extra Controls", + upnextDisplay: "Upnext Display", + contextDisplay: { + setting: "Context Display", + always: "Always", + never: "Never", + mouse: "On mouse movement", + }, + volumeDisplay: { + setting: "Volume Bar Display", + always: "Always", + never: "Never", + mouse: "On mouse movement", + volume: "On volume change", + }, + + appearanceHeader: "Advanced/Appearance", + appearanceSubHeader: "Only change if you know what you are doing!", + backgroundColor: { + setting: "Color Choice on Colored Background", + vibrant: "Vibrant", + prominent: "Prominent", + desaturated: "Desaturated (recommended)", + lightVibrant: "Light Vibrant", + darkVibrant: "Dark Vibrant", + vibrantNonAlarming: "Vibrant Non Alarming", + }, + themedButtons: "Themed Buttons", + themedIcons: "Themed Icons", + invertColors: { + setting: "Invert Colors", + never: "Never", + always: "Always", + auto: "Automatic (Based on BG)", + }, + backAnimationTime: "Background Animation Time", + upnextScroll: { + setting: "Upnext Scroll Animation", + mq: "Marquee/Scrolling", + sp: "Spotify/Translating", + }, + upnextTime: "Upnext Time to Show", + backgroundBlur: "Background Blur", + backgroundBrightness: "Background Brightness", + + cgfReset: "Reset Config", + reload: "Reload Client", + }, + tvBtnDesc: "TV Mode Display", + fullscreenBtnDesc: "Full Screen", + }, + "it-IT": { + langName: "Italiano", + context: { + queue: "Riproduzione da coda", + track: "Riproduzione brano", + album: "Riproduzione album", + artist: "Riproduzione artista", + playlist: "Riproduzione playlist", + playlistFolder: "Riproduzione da cartella playlist", + search: "Riproduzione da ricerca", + searchDest: "Brani", + collection: "Riproduzione dalla libreria", + likedSongs: "Brani che ti piacciono", + trackRadio: "Radio dal brano", + artistRadio: "Radio da artista", + albumRadio: "Radio da album", + playlistRadio: "Radio da playlist", + }, + upnext: "In coda", + unknownArtist: "Artista sconosciuto", + settings: { + switchToTV: "Passa a modalità TV", + switchToFullscreen: "Passa a modalità fullscreen", + tvModeConfig: "Modalità TV", + fullscreenConfig: "Modalità fullscreen", + exit: "Esci", + + pluginSettings: "Impostazioni plugin", + fsHideOriginal: "Pulsante Nascondi Stock (Spotify Premium)", + language: "Lingua", + + lyricsHeader: "Impostazioni testo", + lyrics: "Mostra testo", + lyricsAlignment: { + setting: "Allineamento testo", + left: "Sinistra", + center: "Centro", + right: "Destra", + }, + lyricsAnimationTempo: "Durata animazione testo", + + generalHeader: "Impostazioni generali", + progressBar: "Barra di avanzamento", + playerControls: "Controlli player", + trimTitle: "Accorcia titolo", + trimTitleUpNext: "Accorcia titolo in Up next", + showAlbum: { + setting: "Mostra album", + never: "Mai", + always: "Sempre", + date: "Mostra con data di uscita", + }, + showAllArtists: "Mostra tutti gli artisti", + icons: "Icone", + songChangeAnimation: "Animazione cambio brano", + fullscreen: "Schermo intero", + + extraHeader: "Funzionalità aggiuntive", + backgroundChoice: { + setting: "Scelta sfondo", + color: "Tinta unita", + artwork: "Artwork", + }, + extraControls: "Controlli aggiuntivi", + upnextDisplay: "Mostra Up next", + contextDisplay: { + setting: "Mostra contesto", + always: "Sempre", + never: "Mai", + mouse: "Al movimento del mouse", + }, + volumeDisplay: { + setting: "Mostra barra volume", + always: "Sempre", + never: "Mai", + mouse: "Al movimento del mouse", + volume: "Al cambio volume", + }, + + appearanceHeader: "Avanzato/Aspetto", + appearanceSubHeader: "Cambia solo se sai cosa stai facendo!", + backgroundColor: { + setting: "Colore su sfondo tinta unita", + vibrant: "Vibrant", + prominent: "Prominent", + desaturated: "Desaturato (raccomandato)", + lightVibrant: "Vibrant chiaro", + darkVibrant: "Vibrant scuro", + vibrantNonAlarming: "Vibrant delicato", + }, + themedButtons: "Pulsanti a tema", + themedIcons: "Icone a tema", + invertColors: { + setting: "Inverti colori", + never: "Mai", + always: "Sempre", + auto: "Automatico (basato su sfondo)", + }, + backAnimationTime: "Durata animazione sfondo", + upnextScroll: { + setting: "Scorrimento Up next", + mq: "Scorrimento", + sp: "Traslazione", + }, + upnextTime: "Tempo per mostrare Up next", + backgroundBlur: "Sfocamento sfondo", + backgroundBrightness: "Luminosità sfondo", + + cgfReset: "Resetta configurazione", + reload: "Ricarica client", + }, + tvBtnDesc: "Modalità TV", + fullscreenBtnDesc: "Schermo intero", + }, + "zh-CN": { + langName: "简体中文", + context: { + queue: "正在从队列播放", + track: "正在播放", + album: "正在从专辑播放", + artist: "正在从艺人播放", + playlist: "正在从歌单播放", + playlistFolder: "正在从歌单文件夹播放", + search: "正在从搜索结果播放", + searchDest: "歌曲", + collection: "正在从合辑播放", + likedSongs: "已点赞的歌曲", + trackRadio: "正在从歌曲电台播放", + artistRadio: "正在从艺人电台播放", + albumRadio: "正在从专辑电台播放", + playlistRadio: "正在从歌单电台播放", + }, + upnext: "下一首", + unknownArtist: "未知歌手", + settings: { + switchToTV: "切换至电视模式", + switchToFullscreen: "切换至默认模式", + tvModeConfig: "电视模式配置", + fullscreenConfig: "全屏模式配置", + exit: "退出", + + pluginSettings: "插件设置", + fsHideOriginal: "隐藏切换至原版按钮 (Spotify Premium)", + language: "语言", + + lyricsHeader: "歌词设置", + lyrics: "歌词", + lyricsAlignment: { + setting: "歌词对齐方式", + left: "靠左", + center: "居中", + right: "靠右", + }, + lyricsAnimationTempo: "歌词动画速度", + + generalHeader: "通用设置", + progressBar: "播放进度条", + playerControls: "播放控制", + trimTitle: "缩短标题", + trimTitleUpNext: "缩短标题 (下一首提示)", + showAlbum: { + setting: "显示标题", + never: "永不", + always: "总是", + date: "与发布日期一起显示", + }, + showAllArtists: "显示所有艺人", + icons: "显示图标", + songChangeAnimation: "切歌动画", + fullscreen: "全屏", + + extraHeader: "额外功能", + backgroundChoice: { + setting: "背景", + color: "纯色", + artwork: "艺术画", + }, + extraControls: "额外控件", + upnextDisplay: "下一首提示", + contextDisplay: { + setting: "内容来源显示", + always: "总是", + never: "从不", + mouse: "当鼠标移动时", + }, + volumeDisplay: { + setting: "音量显示", + always: "总是", + never: "从不", + mouse: "当鼠标移动时", + volume: "当音量调节时", + }, + + appearanceHeader: "高级/外观", + appearanceSubHeader: "不要动除非你知道自己在做什么", + backgroundColor: { + setting: "纯色背景的颜色配置", + vibrant: "Vibrant", + prominent: "Prominent", + desaturated: "Desaturated (推荐)", + lightVibrant: "Light Vibrant", + darkVibrant: "Dark Vibrant", + vibrantNonAlarming: "Vibrant Non Alarming", + }, + themedButtons: "主题色按钮", + themedIcons: "主题色图标", + invertColors: { + setting: "反转颜色", + never: "从不", + always: "总是", + auto: "自动(基于背景)", + }, + backAnimationTime: "背景动画时间", + upnextScroll: { + setting: "下一首提示滚动", + mq: "Marquee/滚动", + sp: "Spotify/变换", + }, + upnextTime: "下一首提示显示时间", + backgroundBlur: "背景模糊", + backgroundBrightness: "背景亮度", + + cgfReset: "重置配置", + reload: "重新载入应用", + }, + tvBtnDesc: "电视模式显示", + fullscreenBtnDesc: "全屏", + }, +}; + +let INIT_RETRIES = 0; +function fullScreen() { + const extraBar = + document.querySelector(".main-nowPlayingBar-right")?.childNodes[0] || + document.querySelector(".ExtraControls") || + document.querySelector(".ClYTTKGdd9KB7D9MXicj"); + const topBar = document.querySelector(".main-topBar-historyButtons"); + const { CosmosAsync, Keyboard, Player, Platform } = Spicetify; + + let entriesToVerify = [topBar, extraBar, CosmosAsync, Keyboard, Player, Platform]; + + if (INIT_RETRIES > 50) { + entriesToVerify.forEach((entry) => { + if (!entry) { + console.error("Spicetify method not available. Report issue on GitHub or run Spicetify.test() to test."); + Spicetify.showNotification(`Error initializing "fullscreen.js" extension. Spicetify method not available. Report issue on GitHub.`); + } + }); + return; + } + if (entriesToVerify.some((it) => !it)) { + setTimeout(fullScreen, 300); + INIT_RETRIES += 1; + return; + } + Spicetify.Keyboard.registerShortcut( + { + key: Spicetify.Keyboard.KEYS["T"], + ctrl: false, + shift: false, + alt: false, + }, + openwithTV + ); + Spicetify.Keyboard.registerShortcut( + { + key: Spicetify.Keyboard.KEYS["F"], + ctrl: false, + shift: false, + alt: false, + }, + openwithDef + ); + + function openwithTV() { + if (!document.body.classList.contains("fsd-activated") || !CONFIG.tvMode || ACTIVE !== "tv") { + if (!CONFIG.tvMode || ACTIVE !== "tv") { + CONFIG["tvMode"] = true; + ACTIVE = "tv"; + saveConfig(); + render(); + } + activate(); + } else deactivate(); + } + + function openwithDef() { + if (!document.body.classList.contains("fsd-activated") || CONFIG.tvMode || ACTIVE !== "def") { + if (CONFIG.tvMode || ACTIVE !== "def") { + CONFIG["tvMode"] = false; + ACTIVE = "def"; + saveConfig(); + render(); + } + activate(); + } else deactivate(); + } + const DEFAULTS = { + tv: { + lyricsDisplay: true, + lyricsAlignment: "right", + animationTempo: 0.2, + progressBarDisplay: false, + playerControls: false, + trimTitle: true, + trimTitleUpNext: true, + showAlbum: "d", + showAllArtists: true, + icons: true, + titleMovingIcon: false, + enableFade: true, + enableFullscreen: true, + extraControls: false, + upnextDisplay: true, + contextDisplay: "a", + volumeDisplay: "o", + themedButtons: true, + themedIcons: true, + invertColors: "n", + backAnimationTime: 0.4, + upNextAnim: "sp", + upnextTimeToShow: 45, + blurSize: 0, + backgroundBrightness: 0.4, + }, + def: { + lyricsDisplay: true, + lyricsAlignment: "right", + animationTempo: 0.2, + progressBarDisplay: true, + playerControls: true, + trimTitle: true, + trimTitleUpNext: true, + showAlbum: "n", + showAllArtists: true, + icons: false, + titleMovingIcon: false, + enableFade: true, + enableFullscreen: true, + backgroundChoice: "a", + extraControls: true, + upnextDisplay: true, + contextDisplay: "m", + volumeDisplay: "o", + themedButtons: true, + themedIcons: false, + invertColors: "n", + backAnimationTime: 1, + upNextAnim: "sp", + upnextTimeToShow: 30, + coloredBackChoice: "DESATURATED", + blurSize: 24, + backgroundBrightness: 0.7, + }, + tvMode: false, + locale: "en-US", + fsHideOriginal: false, + }; + const CONFIG = getConfig(); + if (localStorage.getItem("full-screen:inverted") === null) { + localStorage.setItem("full-screen:inverted", "{}"); + } + + const INVERTED = JSON.parse(localStorage.getItem("full-screen:inverted") ?? "{}"); + let ACTIVE = CONFIG.tvMode ? "tv" : "def"; + let LOCALE = CONFIG.locale; + + const OFFLINESVG = `data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCI+CiAgPHJlY3Qgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiB4PSIwIiB5PSIwIiAvPgogIDxwYXRoIGZpbGw9IiNCM0IzQjMiIGQ9Ik0yNi4yNSAxNi4xNjJMMjEuMDA1IDEzLjEzNEwyMS4wMTIgMjIuNTA2QzIwLjU5NCAyMi4xOTIgMjAuMDgxIDIxLjk5OSAxOS41MTkgMjEuOTk5QzE4LjE0MSAyMS45OTkgMTcuMDE5IDIzLjEyMSAxNy4wMTkgMjQuNDk5QzE3LjAxOSAyNS44NzggMTguMTQxIDI2Ljk5OSAxOS41MTkgMjYuOTk5QzIwLjg5NyAyNi45OTkgMjIuMDE5IDI1Ljg3OCAyMi4wMTkgMjQuNDk5QzIyLjAxOSAyNC40MjIgMjIuMDA2IDE0Ljg2NyAyMi4wMDYgMTQuODY3TDI1Ljc1IDE3LjAyOUwyNi4yNSAxNi4xNjJaTTE5LjUxOSAyNS45OThDMTguNjkyIDI1Ljk5OCAxOC4wMTkgMjUuMzI1IDE4LjAxOSAyNC40OThDMTguMDE5IDIzLjY3MSAxOC42OTIgMjIuOTk4IDE5LjUxOSAyMi45OThDMjAuMzQ2IDIyLjk5OCAyMS4wMTkgMjMuNjcxIDIxLjAxOSAyNC40OThDMjEuMDE5IDI1LjMyNSAyMC4zNDYgMjUuOTk4IDE5LjUxOSAyNS45OThaIi8+Cjwvc3ZnPgo=`; + + const style = document.createElement("style"); + + const container = document.createElement("div"); + container.id = "full-screen-display"; + container.classList.add("Video", "VideoPlayer--fullscreen", "VideoPlayer--landscape"); + + let cover, + back, + title, + artist, + album, + prog, + elaps, + durr, + play, + ctx_container, + ctx_icon, + ctx_source, + ctx_name, + fsd_myUp, + fsd_nextCover, + fsd_up_next_text, + fsd_next_tit_art, + fsd_next_tit_art_inner, + fsd_first_span, + fsd_second_span, + volumeContainer, + volumeCurr, + volumeBarInner, + volumeIcon, + playingIcon, + pausedIcon, + nextControl, + backControl, + heart, + shuffle, + repeat, + invertButton, + lyrics; + const nextTrackImg = new Image(); + const artistImg = new Image(); + + function render() { + container.classList.toggle("lyrics-active", !!CONFIG[ACTIVE].lyricsDisplay); + if (!CONFIG[ACTIVE].lyricsDisplay || !CONFIG[ACTIVE].extraControls) container.classList.remove("lyrics-hide-force"); + const styleBase = ` +#full-screen-display { + display: none; + z-index: 100; + position: fixed; + width: 100%; + height: 100%; + cursor: default; + left: 0; + top: 0; + --transition-duration: .8s; + --transition-function: ease-in-out; + --main-color: 255,255,255; + --contrast-color: 0,0,0; + --primary-color: rgba(var(--main-color),1); + --secondary-color: rgba(var(--main-color),.7); + --tertiary-color: rgba(var(--main-color),.5); + --theme-color: 175,175,175; + --theme-background-color: rgba(175,175,175,.6); + --theme-hover-color: rgba(175,175,175,.3); + --theme-main-color: rgba(var(--theme-color),1); +} +#full-screen-display.themed-buttons{ + --theme-background-color: rgba(var(--theme-color),.6); + --theme-hover-color: rgba(var(--theme-color),.3); +} +.unavailable{ + color: var(--tertiary-color) !important; + pointer-events: none !important; + opacity: .5 !important; + background: transparent !important; +} +/* +.unavailable::after{ + content: ""; + display: block; + background-color: #FFF; + bottom: 50%; + left: 0; + right: 0; + position: absolute; + transform: rotateZ(45deg); + width: 25px; + height: 1.5px; +} +.unavailable::before{ + content: ""; + display: block; + background-color: #FFF; + bottom: 50%; + left: 0; + right: 0; + position: absolute; + transform: rotateZ(-45deg); + width: 25px; + height: 1.5px; +} +*/ +@keyframes fadeUp{ + 0%{ + opacity:0; + transform:translateY(-10px) + } + to{ + opacity:1; + transform:translateY(0) + } +} +@keyframes fadeDo{ + 0%{ + opacity:0; + transform:translateY(10px) + } + to{ + opacity:1; + transform:translateY(0) + } +} +@keyframes fadeRi{ + 0%{ + opacity:0; + transform:translateX(10px) + } + to{ + opacity:1; + transform:translateX(0) + } +} +@keyframes fadeLe{ + 0%{ + opacity:0; + transform:translateX(-10px) + } + to{ + opacity:1; + transform:translateX(0) + } +} +.fade-do{ + animation: fadeDo .5s cubic-bezier(.3, 0, 0, 1); +} +.fade-up{ + animation: fadeUp .5s cubic-bezier(.3, 0, 0, 1); +} +.fade-ri{ + animation: fadeRi .5s cubic-bezier(.3, 0, 0, 1); +} +.fade-le{ + animation: fadeLe .5s cubic-bezier(.3, 0, 0, 1); +} +button.dot-after{ + padding-bottom: 3px !important; +} +.dot-after:after{ + background-color: currentColor; + border-radius: 50%; + bottom: 3px; + content: ""; + display: block; + height: 4px; + left: 50%; + position: absolute; + transform: translateX(-50%); + width: 4px; +} +#fsd-ctx-container { + background-color: transparent; + color: var(--secondary-color); + position: fixed; + float: left; + top: 30px; + left: 50px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + text-align: left; + z-index: 50; + transition: all 1s ease-in-out; + opacity: 1; + max-width: 40%; +} +#fsd-ctx-details{ + padding-left: 18px; + line-height: initial; + font-size: 18px; + overflow: hidden; +} +#fsd-ctx-icon{ + width: 48px; + height : 48px; +} +#fsd-ctx-icon svg{ + fill: var(--primary-color) !important; +} +#fsd-ctx-source{ + text-transform: uppercase; +} +#fsd-ctx-name{ + font-weight: 700; + font-size: 20px; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} +.ctx-no-name{ + padding-bottom: 2px; + font-size: 24px; + font-weight: 600; +} +#fsd-upnext-container{ + float: right; + width: 472px; + height: 102px; + max-width: 45%; + position: fixed; + top: 45px; + right: 60px; + display: flex; + border: 1px solid rgba(130,130,130,.7); + border-radius: 10px; + background-color: rgba(20,20,20,1); + flex-direction: row; + text-align: left; + z-index: 50; + transition: transform .8s ease-in-out; + transform: translateX(600px); +} +#fsd_next_art_image{ + background-size: cover; + background-position: center; + width: 100px; + height: 100px; + border-radius: 9px 0 0 9px; +} +#fsd_next_details{ + padding-left: 18px; + padding-top: 17px; + line-height: initial; + width: calc(100% - 115px); + color: rgba(255,255,255,1); + font-size: 19px; + overflow: hidden; +} +#fsd_next_tit_art{ + padding-top: 9px; + font-size: 22px; + font-weight: 700; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +@keyframes fsd_cssmarquee { + 0% { + transform: translateX(0%); + } + 18% { + transform: translateX(0%); + } + 100% { + transform: translateX(var(--translate_width_fsd)); + } +} +@keyframes fsd_translate { + 0%,10% { + transform: translateX(0%); + } + 50%,55% { + transform: translateX(var(--translate_width_fsd)); + } + 100% { + transform: translateX(0%); + } +} +#fsd-volume-container{ + position: fixed; + text-align: center; + background-color: transparent; + color: var(--primary-color); + float: left; + top: 30%; + left: 30px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 50; + height: 200px; + max-height: 33vh; + transition: transform .8s var(--transition-function); +} +#fsd-volume-container.v-hidden{ + transform: translateX(-100px) scale(.1); +} +#fsd-volume-container:hover{ + transform: translateX(0px) scale(1); +} +#fsd-volume-bar{ + margin: 8px 0; + border-radius: 4px; + background-color: rgba(var(--main-color),.35); + overflow: hidden; + width: 8px; + height: 100%; + display: flex; + align-items: end; +} +#fsd-volume-bar-inner{ + width: 100%; + border-radius: 4px; + background-color: var(--primary-color); + box-shadow: 4px 0 12px rgba(0, 0, 0, 0.8); + transition: height .2s var(--transition-function); +} +#fsd-volume-icon svg{ + fill: var(--primary-color) !important; +} +.unavailable #fsd-volume-bar-inner{ + height: 100%; + background-color: var(--tertiary-color); +} +#fsd-volume{ + width: 50px; + font-size: 18px; +} +#fad-lyrics-plus-container{ + transition: transform var(--transition-duration) var(--transition-function); + position: absolute; + right: -50px; + max-width: 50%; + top: 7.5vh; +} +.lyrics-unavailable #fad-lyrics-plus-container, .lyrics-hide-force #fad-lyrics-plus-container{ + transform: translateX(1000px) scale3d(.1,.1,.1) rotate(45deg); +} +#fad-lyrics-plus-container .lyrics-lyricsContainer-LyricsContainer{ + --lyrics-color-active: var(--primary-color) !important; + --lyrics-color-inactive: var(--tertiary-color) !important; + --lyrics-highlight-background: rgba(var(--contrast-color),.7) !important; + --lyrics-align-text: ${CONFIG[ACTIVE].lyricsAlignment} !important; + --animation-tempo: ${CONFIG[ACTIVE].animationTempo}s !important; + height: 85vh !important; +} +.lyrics-config-button{ + margin-right: 20px; +} +#fsd-foreground { + position: relative; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: var(--primary-color); + transition: all var(--transition-duration) var(--transition-function); +} +#fsd-art-image { + position: relative; + width: 100%; + height: 100%; + padding-bottom: 100%; + border-radius: 8px; + background-size: cover; +} +#fsd-art-inner { + position: absolute; + left: 3%; + bottom: 0; + width: 94%; + height: 94%; + z-index: -1; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6) !important; + transform: translateZ(0); +} +#fsd-artist{ + font-weight: 500; + color: var(--secondary-color); +} +#fsd-album{ + font-weight: 400; + color: var(--tertiary-color); +} +.fsd-controls{ + display: flex; + flex-direction: row; + column-gap: 10px; +} +#fsd-progress { + width: 100%; + height: 6px; + border-radius: 4px; + background-color: rgba(var(--main-color),.35); + overflow: hidden; +} +#fsd-progress-inner { + height: 100%; + border-radius: 4px; + background-color: var(--primary-color); + box-shadow: 4px 0 12px rgba(0, 0, 0, 0.8); +} +#fsd-elapsed, #fsd-duration{ + min-width: 35px; + text-align: center; +} +#fsd-elapsed { + margin-right: 10px; +} +#fsd-duration { + margin-left: 10px; +} +#fsd-background { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: -2; +} + +#full-screen-display .fs-button{ + background: transparent; + border: 0; + border-radius: 8px; + color: var(--primary-color); + padding: 3px 5px 0 5px; + cursor: pointer; + position: relative; + transition: all .3s var(--transition-function), transform .1s var(--transition-function); +} +#full-screen-display .fs-button:hover{ + transform: scale(1.2); + filter: saturate(1.5) contrast(1.5) !important; + background: var(--theme-hover-color); +} +#full-screen-display .fs-button.button-active{ + background: var(--theme-background-color) !important; + filter: saturate(1.5) contrast(1.5) !important; +} + +#fsd-foreground svg{ + fill: var(--primary-color); + transition: all .3s var(--transition-function); +} +.themed-icons #fsd-foreground svg{ + fill: var(--theme-main-color); + filter: saturate(1.8); +} +.themed-icons.themed-buttons .fs-button.button-active svg{ + fill: var(--primary-color) !important; +} +.fsd-background-fade { + transition: background-image var(--fs-transition) linear; +} +body.fsd-activated #full-screen-display { + display: block; +} +.fsd-activated .Root__top-bar, .fsd-activated .Root__nav-bar, .fsd-activated .Root__main-view, .fsd-activated .Root__now-playing-bar{ + visibility: hidden; + display: none; +} +.main-notificationBubbleContainer-NotificationBubbleContainer{ + z-index: 1000; +}`; + + const styleChoices = [ + ` +#full-screen-display, +#full-screen-display.lyrics-unavailable, +#full-screen-display.lyrics-hide-force{ + --fsd-foreground-transform: 50%; + --fsd-art-max-width: 600px; + --fsd-items-max-width: 580px; + --fsd-title-size: 50px; + --fsd-sec-size: 28px; +} +#full-screen-display.lyrics-active :not(#full-screen-display.lyrics-unavailable *,#full-screen-display.lyrics-hide-force *){ + --fsd-foreground-transform: 0px; + --fsd-art-max-width: 500px; + --fsd-items-max-width: 480px; + --fsd-title-size: 40px; + --fsd-sec-size: 23px; +} + +#fsd-art, #fsd-details, #fsd-status, #fsd-progress-container{ + transition: all var(--transition-duration) var(--transition-function); +} + +#fsd-foreground { + transform: translateX(var(--fsd-foreground-transform)); + width: 50%; + flex-direction: column; + text-align: center; +} +#fsd-art { + width: calc(100vh - 300px); + max-width: var(--fsd-art-max-width); + min-width: 300px; +} + +#fsd-title{ + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + overflow: hidden; +} +#fsd-album, #fsd-artist{ + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; +} + +#fsd-progress-container { + width: 28vw; + max-width: var(--fsd-items-max-width); + display: flex; + align-items: center; +} +#fsd-details { + padding-top: 30px; + line-height: initial; + max-width: var(--fsd-items-max-width); + color: var(--primary-color); +} +#fsd-status { + display: flex; + width: 28vw; + max-width: var(--fsd-items-max-width); + align-items: center; + justify-content: space-between; + flex-direction: row; +} +#fsd-status.active { + margin: 5px auto 0; + gap: 10px; +} + +#fsd-title { + font-size: var(--fsd-title-size); + font-weight: 900; + transition: all var(--transition-duration) var(--transition-function); +} +#fsd-artist,#fsd-album{ + font-size: var(--fsd-sec-size); + transition: all var(--transition-duration) var(--transition-function); +} +@media (max-width: 900px), (max-height: 900px){ + #fsd-title{ + font-size: 35px; + font-weight: 600; + } +} + +#fsd-title svg{ + width: 35px; + height: 35px; +} +.lyrics-active #fsd-title svg{ + width: 30px; + height: 30px; +} +.lyrics-unavailable #fsd-title svg,.lyrics-hide-force #fsd-title svg{ + width: 35px; + height: 35px; +} +#playing-icon{ + width: 28px !important; + height: 28px !important; + margin-right: 7px; +} +.lyrics-active #playing-icon{ + margin-right: 2px; +} +.lyrics-unavailable #playing-icon,.lyrics-hide-force #playing-icon{ + margin-right: 7px; +} + +#fsd-artist svg, #fsd-album svg{ + width: calc(var(--fsd-sec-size) - 6px); + height: calc(var(--fsd-sec-size) - 6px); + margin-right: 5px; +} + +.fsd-controls { + margin-top: 10px; + margin-bottom: 5px; +} +.fsd-controls-left{ + width: 30%; + justify-content: flex-start; +} +.fsd-controls-center{ + width: 40%; + justify-content: center; + margin: 10px auto 5px; +} +.fsd-controls-right{ + width: 30%; + justify-content: flex-end; +} +`, + `#fsd-background-image { + height: 100%; + background-size: cover; + filter: brightness(${CONFIG[ACTIVE].backgroundBrightness}) blur(${CONFIG[ACTIVE].blurSize}px); + background-position: center; + transform: translateZ(0); +} + +#fsd-foreground { + flex-direction: row; + text-align: left; + justify-content: left; + align-items: flex-end; + position: absolute; + top: auto; + bottom: 75px; +} +.lyrics-active #fsd-foreground{ + width: max-content; + max-width: 65%; +} + +#fsd-art { + width: calc(100vw - 840px); + min-width: 180px; + max-width: 220px; + margin-left: 65px; +} + +#fsd-progress-container { + width: 100%; + max-width: 450px; + display: flex; + align-items: center; +} +.fsd-controls + #fsd-progress-container{ + padding-left: 10px; +} +#fsd-details { + padding-left: 30px; + line-height: initial; + width: 80%; + color: var(--primary-color); +} +#fsd-title, #fsd-album, #fsd-artist{ + display: flex; + justify-content: flex-start; + align-items: baseline; + gap: 5px; +} +#fsd-title span, #fsd-album span, #fsd-artist span{ + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; +} +#fsd-title span{ + -webkit-line-clamp: 3; +} +#fsd-title svg, #fsd-artist svg, #fsd-album svg{ + flex: 0 0 auto; +} +#fsd-album{ + position: relative; +} +#fsd-album svg{ + position: absolute; + top: 7px; +} +#fsd-album svg + span{ + margin-left: 40px; +} +#fsd-title { + font-size: 62px; + font-weight: 900; +} +@media (max-width: 900px), (max-height: 800px){ + #fsd-title{ + font-size: 40px; + font-weight: 600; + } + #fsd-artist, #fsd-album { + font-size: 20px; + } +} +#fsd-artist, #fsd-album { + font-size: 28px; +} +#fsd-title svg{ + width: 35px; + height: 45px; +} +#playing-icon{ + width: 30px !important; + height: 40px !important; + margin-right: 5px; +} +#fsd-artist svg, #fsd-album svg { + margin-right: 15px; + width: 22px; + height: 22px; +} +#fsd-status { + display: flex; + flex-direction: row; + min-width: 450px; + max-width: 450px; + align-items: center; + justify-content: space-between; +} +#fsd-status.active { + column-gap: 10px; + margin: 10px 0; +} +`, + ]; + const iconStyleChoices = [ + ` +#fsd-title svg, #fsd-artist svg, #fsd-album svg { + display: none; +}`, + ` +#fsd-title svg, #fsd-artist svg, #fsd-album svg { + transition: all var(--transition-duration) var(--transition-function); + display: inline-block; +}`, + ]; + Spicetify.Player.removeEventListener("songchange", updateInfo); + if (progressListener) clearInterval(progressListener); + Spicetify.Player.removeEventListener("onplaypause", updatePlayerControls); + Spicetify.Player.removeEventListener("onplaypause", updatePlayingIcon); + Spicetify.Player.origin._events.removeListener("update", updateExtraControls); + heartObserver.disconnect(); + + Spicetify.Player.origin._events.removeListener("queue_update", updateUpNext); + Spicetify.Player.origin._events.removeListener("update", updateUpNextShow); + window.removeEventListener("resize", updateUpNext); + upNextShown = false; + + if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.removeListener("volume", updateVolume); + else Spicetify.Platform.PlaybackAPI._events.removeListener("volume", updateVolume); + + if (origLoc !== "/lyrics-plus" && document.body.classList.contains("fsd-activated")) { + Spicetify.Platform.History.push(origLoc); + Spicetify.Platform.History.entries.splice(Spicetify.Platform.History.entries.length - 3, 2); + Spicetify.Platform.History.index = Spicetify.Platform.History.index > 0 ? Spicetify.Platform.History.index - 2 : -1; + Spicetify.Platform.History.length = Spicetify.Platform.History.length > 1 ? Spicetify.Platform.History.length - 2 : 0; + } + window.dispatchEvent(new Event("fad-request")); + window.removeEventListener("lyrics-plus-update", handleLyricsUpdate); + + container.removeEventListener("mousemove", hideCursor); + container.removeEventListener("mousemove", hideContext); + container.removeEventListener("mousemove", hideVolume); + + if (curTimer) clearTimeout(curTimer); + if (ctxTimer) clearTimeout(ctxTimer); + if (volTimer) clearTimeout(volTimer); + + style.innerHTML = styleBase + styleChoices[CONFIG.tvMode ? 1 : 0] + iconStyleChoices[CONFIG[ACTIVE].icons ? 1 : 0]; + + container.innerHTML = ` +${ + CONFIG.tvMode + ? `
+
+
` + : `` +} + ${ + CONFIG[ACTIVE].contextDisplay !== "n" + ? ` +
+
+
+
+
+
+
` + : "" + } + ${ + CONFIG[ACTIVE].upnextDisplay + ? ` +
+
+
+
+
+
+
+
+ + +
+
+
` + : "" + } +${ + CONFIG[ACTIVE].volumeDisplay !== "n" + ? ` +
+ +
+ +
` + : "" +} +${CONFIG[ACTIVE].lyricsDisplay ? `
` : ""} +
+
+
+
+
+
+
+
+ + + +
+
+ ${Spicetify.SVGIcons.artist} + +
+ ${ + CONFIG[ACTIVE].showAlbum !== "n" + ? `
+ ${CONFIG[ACTIVE].icons ? `${Spicetify.SVGIcons.album}` : ""} + +
` + : "" + } +
+ ${ + CONFIG[ACTIVE].extraControls + ? `
+ + +
` + : "" + } + ${ + CONFIG[ACTIVE].playerControls + ? ` +
+ + + +
` + : "" + } + ${ + CONFIG[ACTIVE].extraControls + ? `
+ ${ + CONFIG[ACTIVE].invertColors === "d" + ? `` + : "" + } + + ${ + CONFIG[ACTIVE].lyricsDisplay + ? `` + : "" + } +
` + : "" + } + ${ + CONFIG.tvMode && !(CONFIG[ACTIVE].playerControls && CONFIG[ACTIVE].extraControls) + ? `${ + CONFIG[ACTIVE].progressBarDisplay + ? `
+ +
+ +
` + : "" + }` + : "" + } +
+ ${ + CONFIG.tvMode && CONFIG[ACTIVE].playerControls && CONFIG[ACTIVE].extraControls + ? `${ + CONFIG[ACTIVE].progressBarDisplay + ? `
+ +
+ +
` + : "" + }` + : "" + } +
+ ${ + CONFIG.tvMode + ? "" + : `${ + CONFIG[ACTIVE].progressBarDisplay + ? `
+ +
+ +
` + : "" + }` + } +
`; + if (CONFIG.tvMode) back = container.querySelector("#fsd-background-image"); + else { + back = container.querySelector("canvas"); + back && (back.width = window.innerWidth); + back && (back.height = window.innerHeight); + } + cover = container.querySelector("#fsd-art-image"); + title = container.querySelector("#fsd-title span"); + artist = container.querySelector("#fsd-artist span"); + album = container.querySelector("#fsd-album span"); + + if (CONFIG[ACTIVE].contextDisplay !== "n") { + ctx_container = container.querySelector("#fsd-ctx-container"); + ctx_icon = container.querySelector("#fsd-ctx-icon"); + ctx_source = container.querySelector("#fsd-ctx-source"); + ctx_name = container.querySelector("#fsd-ctx-name"); + } + if (CONFIG[ACTIVE].upnextDisplay) { + fsd_myUp = container.querySelector("#fsd-upnext-container"); + fsd_myUp.onclick = Spicetify.Player.next; + fsd_nextCover = container.querySelector("#fsd_next_art_image"); + fsd_up_next_text = container.querySelector("#fsd_up_next_text"); + fsd_next_tit_art = container.querySelector("#fsd_next_tit_art"); + fsd_next_tit_art_inner = container.querySelector("#fsd_next_tit_art_inner"); + fsd_first_span = container.querySelector("#fsd_first_span"); + fsd_second_span = container.querySelector("#fsd_second_span"); + } + if (CONFIG[ACTIVE].volumeDisplay !== "n") { + volumeContainer = container.querySelector("#fsd-volume-container"); + volumeCurr = container.querySelector("#fsd-volume"); + volumeBarInner = container.querySelector("#fsd-volume-bar-inner"); + volumeIcon = container.querySelector("#fsd-volume-icon"); + volumeIcon && (volumeIcon.onclick = Spicetify.Player.toggleMute); + } + if (CONFIG[ACTIVE].progressBarDisplay) { + prog = container.querySelector("#fsd-progress-inner"); + durr = container.querySelector("#fsd-duration"); + elaps = container.querySelector("#fsd-elapsed"); + } + if (CONFIG[ACTIVE].icons) { + playingIcon = container.querySelector("#playing-icon"); + + //Clicking on playing icon disables it and remembers the config + playingIcon && (playingIcon.onclick = () => { + CONFIG[ACTIVE]["titleMovingIcon"] = false; + saveConfig(); + playingIcon.classList.add("hidden"); + pausedIcon.classList.remove("hidden"); + }); + pausedIcon = container.querySelector("#paused-icon"); + pausedIcon && (pausedIcon.onclick = () => { + CONFIG[ACTIVE]["titleMovingIcon"] = true; + saveConfig(); + playingIcon.classList.remove("hidden"); + pausedIcon.classList.add("hidden"); + updatePlayingIcon({ data: { is_paused: !Spicetify.Player.isPlaying() } }); + }); + } + if (CONFIG[ACTIVE].playerControls) { + play = container.querySelector("#fsd-play"); + play && (play.onclick = () => { + fadeAnimation(play); + Spicetify.Player.togglePlay(); + }); + nextControl = container.querySelector("#fsd-next"); + nextControl && (nextControl.onclick = () => { + fadeAnimation(nextControl, "fade-ri"); + Spicetify.Player.next(); + }); + backControl = container.querySelector("#fsd-back"); + backControl && (backControl.onclick = () => { + fadeAnimation(backControl, "fade-le"); + Spicetify.Player.back(); + }); + } + if (CONFIG[ACTIVE].extraControls) { + heart = container.querySelector("#fsd-heart"); + shuffle = container.querySelector("#fsd-shuffle"); + repeat = container.querySelector("#fsd-repeat"); + + heart && (heart.onclick = () => { + fadeAnimation(heart); + Spicetify.Player.toggleHeart(); + }); + shuffle && (shuffle.onclick = () => { + fadeAnimation(shuffle); + Spicetify.Player.toggleShuffle(); + }); + repeat && (repeat.onclick = () => { + fadeAnimation(repeat); + Spicetify.Player.toggleRepeat(); + }); + if (CONFIG[ACTIVE].invertColors === "d") { + invertButton = container.querySelector("#fsd-invert"); + invertButton && (invertButton.onclick = () => { + fadeAnimation(invertButton); + if (invertButton.classList.contains("button-active")) + invertButton.innerHTML = ``; + else + invertButton.innerHTML = ``; + invertButton.classList.toggle("button-active"); + if (getComputedStyle(container).getPropertyValue("--main-color").startsWith("0")) { + container.style.setProperty("--main-color", "255,255,255"); + container.style.setProperty("--contrast-color", "0,0,0"); + if (!CONFIG.tvMode && CONFIG.def.backgroundChoice === "a") + INVERTED[Spicetify.Player.data.track?.metadata?.album_uri?.split(":")[2]] = false; + } else { + container.style.setProperty("--main-color", "0,0,0"); + container.style.setProperty("--contrast-color", "255,255,255"); + if (!CONFIG.tvMode && CONFIG.def.backgroundChoice === "a") + INVERTED[Spicetify.Player.data.track?.metadata?.album_uri?.split(":")[2]] = true; + } + localStorage.setItem("full-screen:inverted", JSON.stringify(INVERTED)); + }); + } + if (CONFIG[ACTIVE].lyricsDisplay) { + lyrics = container.querySelector("#fsd-lyrics"); + lyrics && (lyrics.onclick = () => { + fadeAnimation(lyrics); + container.classList.toggle("lyrics-hide-force"); + lyrics.classList.toggle("button-active"); + lyrics.innerHTML = + container.classList.contains("lyrics-unavailable") || container.classList.contains("lyrics-hide-force") + ? ` + + + ` + : ``; + }); + } + } + } + + const classes = ["video", "video-full-screen", "video-full-window", "video-full-screen--hide-ui", "fsd-activated"]; + + function fullScreenOn() { + if (!document.fullscreen) document.documentElement.requestFullscreen(); + } + + function fullScreenOff() { + if (document.fullscreen) document.exitFullscreen(); + } + + function getToken() { + return Spicetify.Platform.AuthorizationAPI._tokenProvider({ + preferCached: true, + }).then((res) => res.accessToken); + } + + async function getTrackInfo(id) { + return fetch(`https://api.spotify.com/v1/tracks/${id}`, { + headers: { + Authorization: `Bearer ${await getToken()}`, + }, + }).then((res) => res.json()); + // return Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/tracks/${id}`) + } + + async function getAlbumInfo(id) { + return fetch(`https://api.spotify.com/v1/albums/${id}`, { + headers: { + Authorization: `Bearer ${await getToken()}`, + }, + }).then((res) => res.json()); + // return Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/albums/${id}`) + } + + function getPlaylistInfo(uri) { + return Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}`); + } + + async function getArtistInfo(id) { + return fetch( + `https://api-partner.spotify.com/pathfinder/v1/query?operationName=queryArtistOverview&variables=%7B%22uri%22%3A%22spotify%3Aartist%3A${id}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22d66221ea13998b2f81883c5187d174c8646e4041d67f5b1e103bc262d447e3a0%22%7D%7D`, + { + headers: { + Authorization: `Bearer ${await getToken()}`, + }, + } + ) + .then((res) => res.json()) + .then((res) => res.data.artist); + // return Spicetify.CosmosAsync.get(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=queryArtistOverview&variables=%7B%22uri%22%3A%22spotify%3Aartist%3A${id}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22d66221ea13998b2f81883c5187d174c8646e4041d67f5b1e103bc262d447e3a0%22%7D%7D`).then(res => res.data.artist) + } + + async function searchArt(name) { + return fetch(`https://api.spotify.com/v1/search?q="${name}"&type=artist&limit=2`, { + headers: { + Authorization: `Bearer ${await getToken()}`, + }, + }).then((res) => res.json()); + // return Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/search?q="${name}"&type=artist&limit=2`) + } + + // Add fade animation on button click + function fadeAnimation(ele, anim = "fade-do") { + ele.classList.remove(anim); + ele.classList.add(anim); + setTimeout(() => { + ele.classList.remove(anim); + }, 800); + } + + // Utility function to add a observer with wait for element support + function addObserver(observer, selector, options) { + const ele = document.querySelector(selector); + if (!ele) { + setTimeout(() => { + addObserver(observer, selector, options); + }, 2000); + return; + } + observer.observe(ele, options); + } + + // Converting hex to rgb + function hexToRgb(hex) { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? `${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)}` : null; + } + + async function getImageAndLoad(meta) { + if (meta.artist_uri == null) return meta.image_xlarge_url; + let arUri = meta.artist_uri.split(":")[2]; + if (meta.artist_uri.split(":")[1] === "local") { + let res = await searchArt(meta.artist_name).catch((err) => console.error(err)); + arUri = res ? res.artists.items[0].id : ""; + } + let artistInfo = await getArtistInfo(arUri).catch((err) => console.error(err)); + return artistInfo?.visuals?.headerImage?.sources[0].url ?? meta.image_xlarge_url; + } + + // Set the timeout to show upnext or hide when song ends + let upnextTimer, + upNextShown = false; + + function updateUpNextShow() { + setTimeout(() => { + let timetogo = getShowTime(); + if (upnextTimer) { + clearTimeout(upnextTimer); + upnextTimer = 0; + } + if (timetogo < 10) { + if (!upNextShown || fsd_myUp.style.transform !== "translateX(0px)") { + updateUpNext(); + } + upNextShown = true; + } else { + fsd_myUp.style.transform = "translateX(600px)"; + upNextShown = false; + if (!Spicetify.Player.origin._state.isPaused) { + upnextTimer = setTimeout(() => { + updateUpNext(); + upNextShown = true; + }, timetogo); + } + } + }, 100); + } + + // Return the total time left to show the upnext timer + function getShowTime() { + let showBefore = CONFIG[ACTIVE].upnextTimeToShow * 1000; + let dur = Spicetify.Player.data.duration; + let curProg = Spicetify.Player.getProgress(); + + if (dur - curProg <= showBefore) return -1; + else return dur - showBefore - curProg; + } + + let colorsCache = []; + async function colorExtractor(uri) { + const presentInCache = colorsCache.filter(obj => obj.uri === uri) + if(presentInCache.length > 0) return presentInCache[0].colors; + const body = await Spicetify.CosmosAsync.get(`wg://colorextractor/v1/extract-presets?uri=${uri}&format=json`); + if (body.entries && body.entries.length) { + const list = {}; + for (const color of body.entries[0].color_swatches) { + list[color.preset] = `#${color.color.toString(16).padStart(6, "0")}`; + } + if(colorsCache.length > 20) colorsCache.shift(); + colorsCache.push({uri, colors: list}); + return list; + } + throw "No colors returned."; + } + + async function updateInfo() { + const meta = Spicetify.Player.data.track?.metadata; + + if (CONFIG[ACTIVE].contextDisplay !== "n") updateContext().catch((err) => console.error("Error getting context: ", err)); + + // prepare title + let rawTitle = meta?.title; + if (CONFIG[ACTIVE].trimTitle) { + rawTitle = rawTitle + .replace(/\(.+?\)/g, "") + .replace(/\[.+?\]/g, "") + .replace(/\s\-\s.+?$/, "") + .trim(); + if (!rawTitle) rawTitle = meta?.title; + } + + // prepare artist + let artistName; + if (CONFIG[ACTIVE].showAllArtists) { + artistName = Object.keys(meta) + .filter((key) => key.startsWith("artist_name")) + .sort() + .map((key) => meta[key]) + .join(", "); + } else { + artistName = meta?.artist_name; + } + + // prepare album + let albumText; + if (CONFIG[ACTIVE].showAlbum !== "n") { + albumText = meta?.album_title || ""; + if (album) album.innerText = albumText || ""; + const albumURI = meta?.album_uri; + if (albumURI?.startsWith("spotify:album:") && CONFIG[ACTIVE].showAlbum === "d") { + getAlbumInfo(albumURI.replace("spotify:album:", "")) + .then((albumInfo) => { + if (!albumInfo?.release_date) throw Error("No release Date"); + const albumDate = new Date(albumInfo.release_date); + const recentDate = new Date(); + recentDate.setMonth(recentDate.getMonth() - 18); + const dateStr = albumDate.toLocaleString( + LOCALE, + albumDate > recentDate ? { year: "numeric", month: "short" } : { year: "numeric" } + ); + albumText += " • " + dateStr.charAt(0).toUpperCase() + dateStr.slice(1); + if (album) album.innerText = albumText || ""; + }) + .catch((err) => console.error(err)); + } + } + + // prepare duration + let durationText; + if (CONFIG[ACTIVE].progressBarDisplay) { + durationText = Spicetify.Player.formatTime(meta?.duration); + } + const previousImg = nextTrackImg.cloneNode(); + + nextTrackImg.src = meta?.image_xlarge_url; + + // Wait until next track image is downloaded then update UI text and images + nextTrackImg.onload = async () => { + if (!CONFIG.tvMode) { + updateMainColor(Spicetify.Player.data.track?.uri, meta); + updateThemeColor(Spicetify.Player.data.track?.uri); + if (CONFIG.def.backgroundChoice == "a") { + animateCanvas(previousImg, nextTrackImg); + } else { + animateColor(await getNextColor()); + } + } + cover.style.backgroundImage = `url("${nextTrackImg.src}")`; + title.innerText = rawTitle || ""; + artist.innerText = artistName || ""; + if (durr) { + durr.innerText = durationText || ""; + } + new Image().src = Spicetify?.Queue?.nextTracks[0]?.contextTrack?.metadata.image_xlarge_url; + }; + nextTrackImg.onerror = () => { + // Placeholder + console.error("Check your Internet! Unable to load Image"); + nextTrackImg.src = OFFLINESVG; + }; + if (CONFIG.tvMode) { + artistImg.src = await getImageAndLoad(meta); + updateMainColor(artistImg.src, meta); + updateThemeColor(artistImg.src); + artistImg.onload = async () => { + back.style.backgroundImage = `url("${artistImg.src}")`; + let newurl = await getImageAndLoad(Spicetify?.Queue?.nextTracks[0]?.contextTrack?.metadata); + new Image().src = newurl; + }; + } + } + + async function getNextColor() { + let nextColor; + const imageColors = await colorExtractor(Spicetify.Player.data.track?.uri).catch((err) => console.warn(err)); + if (!imageColors || !imageColors[CONFIG.def.coloredBackChoice]) nextColor = "#444444"; + else nextColor = imageColors[CONFIG.def.coloredBackChoice]; + return nextColor; + } + async function updateMainColor(imageURL, meta) { + switch (CONFIG[ACTIVE].invertColors) { + case "a": + container.style.setProperty("--main-color", "0,0,0"); + container.style.setProperty("--contrast-color", "255,255,255"); + break; + case "d": + let mainColor, contrastColor; + if (!CONFIG.tvMode && CONFIG.def.backgroundChoice === "a" && meta.album_uri.split(":")[2] in INVERTED) { + mainColor = INVERTED[meta.album_uri.split(":")[2]] ? "0,0,0" : "255,255,255"; + } else { + let imageProminentColor; + const imageColors = await colorExtractor(imageURL).catch((err) => console.warn(err)); + if (CONFIG.tvMode || CONFIG.def.backgroundChoice == "a") { + if (!imageColors?.PROMINENT) imageProminentColor = "0,0,0"; + else imageProminentColor = hexToRgb(imageColors.PROMINENT); + } else { + if (!imageColors || !imageColors[CONFIG.def.coloredBackChoice]) imageProminentColor = hexToRgb("#444444"); + else imageProminentColor = hexToRgb(imageColors[CONFIG.def.coloredBackChoice]); + } + const thresholdValue = 260 - CONFIG[ACTIVE].backgroundBrightness * 100; + const isLightBG = + Number(imageProminentColor?.split(",")[0]) * 0.299 + + Number(imageProminentColor?.split(",")[1]) * 0.587 + + Number(imageProminentColor?.split(",")[2]) * 0.114 > + thresholdValue; + mainColor = isLightBG && CONFIG[ACTIVE].backgroundBrightness > 0.3 ? "0,0,0" : "255,255,255"; + contrastColor = isLightBG && CONFIG[ACTIVE].backgroundBrightness > 0.3 ? "255,255,255" : "0,0,0"; + } + container.style.setProperty("--main-color", mainColor); + container.style.setProperty("--contrast-color", contrastColor ?? "0,0,0"); + if (CONFIG[ACTIVE].extraControls) { + invertButton.classList.remove("button-active"); + invertButton.innerHTML = ``; + } + break; + case "n": + default: + container.style.setProperty("--main-color", "255,255,255"); + container.style.setProperty("--contrast-color", "0,0,0"); + break; + } + } + //Set main theme color for the display + async function updateThemeColor(imageURL) { + if ( + !(!CONFIG.tvMode && CONFIG.def.backgroundChoice == "c" && CONFIG.def.coloredBackChoice == "VIBRANT") && + (CONFIG[ACTIVE].themedButtons || CONFIG[ACTIVE].themedIcons) + ) { + container.classList.toggle("themed-buttons", !!CONFIG[ACTIVE].themedButtons); + container.classList.toggle("themed-icons", !!CONFIG[ACTIVE].themedIcons); + let themeVibrantColor; + const artColors = await colorExtractor(imageURL).catch((err) => console.warn(err)); + if (!artColors?.VIBRANT) themeVibrantColor = "175,175,175"; + else themeVibrantColor = hexToRgb(artColors.VIBRANT); + container.style.setProperty("--theme-color", themeVibrantColor); + } else { + container.classList.remove("themed-buttons", "themed-icons"); + container.style.setProperty("--theme-color", "175,175,175"); + } + } + + function handleLyricsUpdate(evt) { + if (evt.detail.isLoading) return; + container.classList.toggle("lyrics-unavailable", !(evt.detail.available && evt.detail?.synced?.length > 1)); + if (CONFIG[ACTIVE].extraControls) { + lyrics.classList.toggle("hidden", container.classList.contains("lyrics-unavailable")); + } + } + + let prevColor = "#000000"; + async function animateColor(nextColor) { + const configTransitionTime = CONFIG[ACTIVE].backAnimationTime; + const { innerWidth: width, innerHeight: height } = window; + back.width = width; + back.height = height; + + const ctx = back.getContext("2d"); + + if (!CONFIG[ACTIVE].enableFade) { + ctx.globalAlpha = 1; + ctx.fillStyle = nextColor; + ctx.fillRect(0, 0, width, height); + return; + } + + let previousTimeStamp, + done = false, + start; + const animate = (timestamp) => { + if (start === undefined) start = timestamp; + const elapsed = timestamp - start; + + if (previousTimeStamp !== timestamp) { + const factor = Math.min(elapsed / (configTransitionTime * 1000), 1.0); + ctx.globalAlpha = 1; + ctx.fillStyle = prevColor; + ctx.fillRect(0, 0, width, height); + ctx.globalAlpha = Math.sin((Math.PI / 2) * factor); + ctx.fillStyle = nextColor; + ctx.fillRect(0, 0, width, height); + if (factor === 1.0) done = true; + } + if (elapsed < configTransitionTime * 1000) { + previousTimeStamp = timestamp; + !done && requestAnimationFrame(animate); + } else { + prevColor = nextColor; + } + }; + + requestAnimationFrame(animate); + } + + function animateCanvas(prevImg, nextImg) { + const configTransitionTime = CONFIG[ACTIVE].backAnimationTime; + const { innerWidth: width, innerHeight: height } = window; + back.width = width; + back.height = height; + + const ctx = back.getContext("2d"); + ctx.imageSmoothingEnabled = false; + ctx.filter = `brightness(${CONFIG[ACTIVE].backgroundBrightness}) blur(${CONFIG[ACTIVE].blurSize}px)`; + const blur = CONFIG[ACTIVE].blurSize; + + const x = -blur * 2; + + let y, dim; + if (width > height) { + dim = width; + y = x - (width - height) / 2; + } else { + dim = height; + y = x; + } + const size = dim + 4 * blur; + + if (!CONFIG[ACTIVE].enableFade) { + ctx.globalAlpha = 1; + ctx.drawImage(nextImg, x, y, size, size); + return; + } + + let prevTimeStamp, + start, + done = false; + + const animate = (timestamp) => { + if (start === undefined) start = timestamp; + + const elapsed = timestamp - start; + + if (prevTimeStamp !== timestamp) { + const factor = Math.min(elapsed / (configTransitionTime * 1000), 1.0); + ctx.globalAlpha = 1; + ctx.drawImage(prevImg, x, y, size, size); + ctx.globalAlpha = Math.sin((Math.PI / 2) * factor); + ctx.drawImage(nextImg, x, y, size, size); + if (factor === 1.0) done = true; + } + if (elapsed < configTransitionTime * 1000) { + prevTimeStamp = timestamp; + !done && requestAnimationFrame(animate); + } + }; + + requestAnimationFrame(animate); + } + + let prevUriObj; + async function getContext() { + let ctxIcon = "", + ctxSource, + ctxName; + if (Spicetify.Player.data.track?.provider === "queue") { + ctxIcon = ``; + ctxSource = translations[LOCALE].context.queue; + ctxName = ""; + } else { + const uriObj = Spicetify.URI.fromString(Spicetify.Player.data.context_uri); + if (JSON.stringify(uriObj) === JSON.stringify(prevUriObj) && ctxSource != undefined && ctxName != undefined) + return [ctxIcon, ctxSource, ctxName]; + prevUriObj = uriObj; + switch (uriObj.type) { + case Spicetify.URI.Type.TRACK: + ctxIcon = ``; + ctxSource = translations[LOCALE].context.track; + await getTrackInfo(uriObj._base62Id).then((meta) => (ctxName = `${meta.name} • ${meta.artists[0].name}`)); + break; + case Spicetify.URI.Type.SEARCH: + ctxIcon = Spicetify.SVGIcons["search-active"]; + ctxSource = translations[LOCALE].context.search; + ctxName = `"${uriObj.query}" in ${translations[LOCALE].context.searchDest}`; + break; + case Spicetify.URI.Type.COLLECTION: + ctxIcon = Spicetify.SVGIcons["heart-active"]; + ctxSource = translations[LOCALE].context.collection; + ctxName = translations[LOCALE].context.likedSongs; + break; + case Spicetify.URI.Type.PLAYLIST_V2: + ctxIcon = Spicetify.SVGIcons["playlist"]; + ctxSource = translations[LOCALE].context.playlist; + ctxName = Spicetify.Player.data.context_metadata?.context_description || ""; + break; + + case Spicetify.URI.Type.STATION: + case Spicetify.URI.Type.RADIO: + ctxIcon = ``; + const rType = uriObj.args[0]; + switch (rType) { + case "album": + ctxSource = translations[LOCALE].context.albumRadio; + await getAlbumInfo(uriObj.args[1]).then((meta) => (ctxName = meta.name)); + break; + case "track": + ctxSource = translations[LOCALE].context.trackRadio; + await getTrackInfo(uriObj.args[1]).then((meta) => (ctxName = `${meta.name} • ${meta.artists[0].name}`)); + break; + case "artist": + ctxSource = translations[LOCALE].context.artistRadio; + await getArtistInfo(uriObj.args[1]).then((meta) => (ctxName = meta?.profile?.name)); + break; + case "playlist": + case "playlist-v2": + ctxSource = translations[LOCALE].context.playlistRadio; + ctxIcon = ``; + await getPlaylistInfo("spotify:playlist:" + uriObj.args[1]).then((meta) => (ctxName = meta.playlist.name)); + break; + default: + ctxName = ""; + } + break; + + case Spicetify.URI.Type.PLAYLIST: + ctxIcon = Spicetify.SVGIcons[uriObj.type]; + ctxSource = translations[LOCALE].context.playlist; + ctxName = Spicetify.Player.data.context_metadata.context_description || ""; + break; + case Spicetify.URI.Type.ALBUM: + ctxIcon = Spicetify.SVGIcons[uriObj.type]; + ctxSource = translations[LOCALE].context.album; + ctxName = Spicetify.Player.data.context_metadata.context_description || ""; + break; + + case Spicetify.URI.Type.ARTIST: + ctxIcon = Spicetify.SVGIcons[uriObj.type]; + ctxSource = translations[LOCALE].context.artist; + ctxName = Spicetify.Player.data.context_metadata.context_description || ""; + break; + + case Spicetify.URI.Type.FOLDER: + ctxIcon = Spicetify.SVGIcons["playlist-folder"]; + ctxSource = translations[LOCALE].context.playlistFolder; + const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/rootlist`, { + policy: { folder: { rows: true, link: true, name: true } }, + }); + for (const item of res.rows) { + if (item.type === "folder" && item.link === Spicetify.Player.data.context_uri) { + ctxName = item.name; + break; + } + } + break; + default: + ctxSource = uriObj.type; + ctxName = Spicetify.Player.data?.context_metadata?.context_description || ""; + } + } + return [ctxIcon, ctxSource, ctxName]; + } + + // Get the context and update it + async function updateContext() { + let ctxIcon, ctxSource, ctxName; + [ctxIcon, ctxSource, ctxName] = await getContext().catch((err) => console.error(err)); + ctx_source.classList.toggle("ctx-no-name", !ctxName); + + //Set default icon if no icon is returned + if (!ctxIcon) ctxIcon = Spicetify.SVGIcons.spotify; + ctx_icon.innerHTML = /^${ctxIcon}` + : ctxIcon; + + //Only change the DOM if context is changed + if (ctx_source.innerText.toLowerCase() !== `${ctxSource}`.toLowerCase() || ctx_name.innerText.toLowerCase() !== ctxName.toLowerCase()) { + ctx_source.innerText = `${ctxSource}`; + ctx_name.innerText = ctxName; + if (CONFIG[ACTIVE].contextDisplay === "m") hideContext(); + } + } + + function updateUpNextInfo() { + fsd_up_next_text.innerText = translations[LOCALE].upnext.toUpperCase(); + let metadata = {}; + const queue_metadata = Spicetify.Queue.nextTracks[0]; + if (queue_metadata) { + metadata = queue_metadata?.contextTrack?.metadata; + } else { + metadata["artist_name"] = ""; + metadata["title"] = ""; + } + + let rawTitle = metadata.title; + if (CONFIG[ACTIVE].trimTitleUpNext) { + rawTitle = rawTitle + .replace(/\(.+?\)/g, "") + .replace(/\[.+?\]/g, "") + .replace(/\s\-\s.+?$/, "") + .trim(); + if (!rawTitle) rawTitle = metadata.title; + } + const artistNameNext = Object.keys(metadata) + .filter((key) => key.startsWith("artist_name")) + .sort() + .map((key) => metadata[key]) + .join(", "); + + let next_artist; + if (artistNameNext) { + next_artist = artistNameNext; + } else { + next_artist = translations[LOCALE].unknownArtist; + } + const next_image = metadata.image_xlarge_url; + if (next_image) { + fsd_nextCover.style.backgroundImage = `url("${next_image}")`; + } else { + if (metadata.image_url) fsd_nextCover.style.backgroundImage = `url("${metadata.image_url}")`; + else { + fsd_nextCover.style.backgroundImage = `url("${OFFLINESVG}")`; + } + } + fsd_first_span.innerText = rawTitle + " • " + next_artist; + fsd_second_span.innerText = rawTitle + " • " + next_artist; + } + + async function updateUpNext() { + if ( + Spicetify.Player.data.duration - Spicetify.Player.getProgress() <= CONFIG[ACTIVE].upnextTimeToShow * 1000 + 50 && + Spicetify.Queue?.nextTracks[0]?.contextTrack?.metadata?.title + ) { + await updateUpNextInfo(); + fsd_myUp.style.transform = "translateX(0px)"; + upNextShown = true; + let animTime; + if (fsd_second_span.offsetWidth > fsd_next_tit_art.offsetWidth - 2) { + switch (CONFIG[ACTIVE].upNextAnim) { + case "mq": + fsd_first_span.style.paddingRight = "80px"; + animTime = 5000 * (fsd_first_span.offsetWidth / 400); + fsd_myUp.style.setProperty("--translate_width_fsd", `-${fsd_first_span.offsetWidth + 3.5}px`); + fsd_next_tit_art_inner.style.animation = "fsd_cssmarquee " + animTime + "ms linear 800ms infinite"; + break; + case "sp": + default: + fsd_first_span.style.paddingRight = "0px"; + fsd_second_span.innerText = ""; + animTime = (fsd_first_span.offsetWidth - fsd_next_tit_art.offsetWidth - 2) / 0.05; + // animTime= 3000*(fsd_first_span.offsetWidth/fsd_next_tit_art.offsetWidth) + fsd_myUp.style.setProperty("--translate_width_fsd", `-${fsd_first_span.offsetWidth - fsd_next_tit_art.offsetWidth + 5}px`); + fsd_next_tit_art_inner.style.animation = `fsd_translate ${animTime > 1500 ? animTime : 1500}ms linear 800ms infinite`; + break; + } + } else { + fsd_first_span.style.paddingRight = "0px"; + fsd_next_tit_art_inner.style.animation = "none"; + fsd_second_span.innerText = ""; + } + } else { + upNextShown = false; + fsd_myUp.style.transform = "translateX(600px)"; + fsd_first_span.style.paddingRight = "0px"; + fsd_next_tit_art_inner.style.animation = "none"; + fsd_second_span.innerText = ""; + } + } + + let prevVolume = Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume; + + function updateVolume(data) { + const volume = !data ? Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume : data.data.volume; + if (volume !== prevVolume || !data) { + //Only update volume when there is a change or on initial fire + prevVolume = volume; + if (CONFIG[ACTIVE].volumeDisplay === "o" || CONFIG[ACTIVE].volumeDisplay === "m") { + volumeContainer.classList.remove("v-hidden"); + } + volumeBarInner.style.height = volume * 100 + "%"; + let currVol = Math.round(volume * 100) === -100 ? "%" : Math.round(volume * 100); + volumeCurr.innerText = currVol + "%"; + volumeContainer.classList.toggle("unavailable", typeof currVol !== "number"); + if (typeof currVol !== "number" || currVol > 60) + volumeIcon.innerHTML = `${Spicetify.SVGIcons["volume"]}`; + else if (currVol > 30) + volumeIcon.innerHTML = `${Spicetify.SVGIcons["volume-two-wave"]}`; + else if (currVol > 0) + volumeIcon.innerHTML = `${Spicetify.SVGIcons["volume-one-wave"]}`; + else + volumeIcon.innerHTML = `${Spicetify.SVGIcons["volume-off"]}`; + if (CONFIG[ACTIVE].volumeDisplay === "o" || CONFIG[ACTIVE].volumeDisplay === "m") { + hideVolume(); + } + } + } + + function updateProgress() { + const progress = Spicetify.Player.formatTime(Spicetify.Player.getProgress()); + if (!Spicetify.Player.origin._state.isPaused || elaps.innerText !== progress) { + prog.style.width = Spicetify.Player.getProgressPercent() * 100 + "%"; + elaps.innerText = progress; + } + } + + function updatePlayingIcon({ data }) { + if (data.is_paused) { + pausedIcon.classList.remove("hidden"); + playingIcon.classList.add("hidden"); + } else { + pausedIcon.classList.toggle("hidden", CONFIG[ACTIVE].titleMovingIcon); + playingIcon.classList.toggle("hidden", !CONFIG[ACTIVE].titleMovingIcon); + } + } + + function updatePlayerControls({ data }) { + fadeAnimation(play); + if (data.is_paused) { + play.innerHTML = `${Spicetify.SVGIcons.play}`; + } else { + play.innerHTML = `${Spicetify.SVGIcons.pause}`; + } + } + + let prevControlData = { + shuffle: Spicetify?.Player?.origin?._state?.shuffle, + repeat: Spicetify?.Player?.origin?._state?.repeat, + }; + + function updateExtraControls(data) { + data = !data ? Spicetify.Player.origin._state : data.data; + updateHeart(); + if (prevControlData?.shuffle !== data?.shuffle) fadeAnimation(shuffle); + if (prevControlData?.repeat !== data?.repeat) fadeAnimation(repeat); + prevControlData = { + shuffle: data?.shuffle, + repeat: data?.repeat, + }; + repeat.classList.toggle("dot-after", data?.repeat !== 0); + repeat.classList.toggle("button-active", data?.repeat !== 0); + + shuffle.classList.toggle("dot-after", data?.shuffle); + shuffle.classList.toggle("button-active", data?.shuffle); + if (data?.repeat === 2) { + repeat.innerHTML = `${Spicetify.SVGIcons["repeat-once"]}`; + } else { + repeat.innerHTML = `${Spicetify.SVGIcons["repeat"]}`; + } + if (data.restrictions) { + shuffle.classList.toggle("unavailable", !data?.restrictions?.canToggleShuffle); + repeat.classList.toggle("unavailable", !data?.restrictions?.canToggleRepeatTrack && !data?.restrictions?.canToggleRepeatContext); + } + } + + let prevHeartData = Spicetify?.Player?.origin?._state?.item?.metadata["collection.in_collection"]; + + function updateHeart() { + const meta = Spicetify?.Player?.origin?._state?.item; + heart.classList.toggle("unavailable", meta?.metadata["collection.can_add"] !== "true"); + if (prevHeartData !== meta?.metadata["collection.in_collection"]) fadeAnimation(heart); + prevHeartData = meta?.metadata["collection.in_collection"]; + if (meta?.metadata["collection.in_collection"] === "true" || Spicetify.Player.getHeart()) { + heart.innerHTML = `${Spicetify.SVGIcons["heart-active"]}`; + heart.classList.add("button-active"); + } else { + heart.innerHTML = `${Spicetify.SVGIcons["heart"]}`; + heart.classList.remove("button-active"); + } + } + + let curTimer, ctxTimer, volTimer; + + function hideCursor() { + if (curTimer) { + clearTimeout(curTimer); + curTimer = 0; + } + container.style.cursor = "default"; + curTimer = setTimeout(() => (container.style.cursor = "none"), 2000); + } + + function hideContext() { + if (ctxTimer) { + clearTimeout(ctxTimer); + ctxTimer = 0; + } + ctx_container.style.opacity = 1; + ctxTimer = setTimeout(() => (ctx_container.style.opacity = 0), 3000); + } + + function hideVolume() { + if (volTimer) { + clearTimeout(volTimer); + volTimer = 0; + } + volumeContainer.classList.remove("v-hidden"); + volTimer = setTimeout(() => volumeContainer.classList.add("v-hidden"), 3000); + } + + let origLoc, progressListener; + const heartObserver = new MutationObserver(updateHeart); + + function activate() { + container.style.setProperty("--fs-transition", `${CONFIG[ACTIVE].backAnimationTime}s`); + updateInfo(); + Spicetify.Player.addEventListener("songchange", updateInfo); + container.addEventListener("mousemove", hideCursor); + hideCursor(); + container.querySelector("#fsd-foreground").oncontextmenu = openConfig; + container.querySelector("#fsd-foreground").ondblclick = deactivate; + back.oncontextmenu = openConfig; + back.ondblclick = deactivate; + if (CONFIG[ACTIVE].contextDisplay === "m") { + container.addEventListener("mousemove", hideContext); + hideContext(); + } + if (CONFIG[ACTIVE].upnextDisplay) { + updateUpNextShow(); + Spicetify.Player.origin._events.addListener("queue_update", updateUpNext); + Spicetify.Player.origin._events.addListener("update", updateUpNextShow); + window.addEventListener("resize", updateUpNext); + } + if (CONFIG[ACTIVE].volumeDisplay !== "n") { + if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.addListener("volume", updateVolume); + else Spicetify.Platform.PlaybackAPI._events.addListener("volume", updateVolume); + updateVolume(); + if (CONFIG[ACTIVE].volumeDisplay === "m") { + container.addEventListener("mousemove", hideVolume); + hideVolume(); + } + } + if (CONFIG[ACTIVE].enableFade) { + cover.classList.add("fsd-background-fade"); + if (CONFIG.tvMode) back.classList.add("fsd-background-fade"); + } else { + cover.classList.remove("fsd-background-fade"); + if (CONFIG.tvMode) back.classList.remove("fsd-background-fade"); + } + if (CONFIG[ACTIVE].icons) { + updatePlayingIcon({ data: { is_paused: !Spicetify.Player.isPlaying() } }); + Spicetify.Player.addEventListener("onplaypause", updatePlayingIcon); + } + if (CONFIG[ACTIVE].progressBarDisplay) { + updateProgress(); + progressListener = setInterval(updateProgress, 500); + } + if (CONFIG[ACTIVE].playerControls) { + updatePlayerControls({ data: { is_paused: !Spicetify.Player.isPlaying() } }); + Spicetify.Player.addEventListener("onplaypause", updatePlayerControls); + } + if (CONFIG[ACTIVE].extraControls) { + updateExtraControls(); + addObserver(heartObserver, ".control-button-heart", { attributes: true, attributeFilter: ["aria-checked"] }); + Spicetify.Player.origin._events.addListener("update", updateExtraControls); + } + document.body.classList.add(...classes); + if (CONFIG[ACTIVE].enableFullscreen) fullScreenOn(); + else fullScreenOff(); + document.querySelector(".Root__top-container")?.append(style, container); + if (CONFIG[ACTIVE].lyricsDisplay) { + window.addEventListener("lyrics-plus-update", handleLyricsUpdate); + origLoc = Spicetify.Platform.History.location.pathname; + if (origLoc !== "/lyrics-plus") { + Spicetify.Platform.History.push("/lyrics-plus"); + } + window.dispatchEvent(new Event("fad-request")); + } + Spicetify.Keyboard.registerShortcut( + { + key: Spicetify.Keyboard.KEYS["F11"], + ctrl: false, + shift: false, + alt: false, + }, + fsToggle + ); + Spicetify.Keyboard.registerShortcut( + { + key: Spicetify.Keyboard.KEYS["ESCAPE"], + ctrl: false, + shift: false, + alt: false, + }, + deactivate + ); + } + + function deactivate() { + Spicetify.Player.removeEventListener("songchange", updateInfo); + container.removeEventListener("mousemove", hideCursor); + if (CONFIG[ACTIVE].contextDisplay === "m") { + container.removeEventListener("mousemove", hideContext); + } + if (CONFIG[ACTIVE].upnextDisplay) { + upNextShown = false; + Spicetify.Player.origin._events.removeListener("queue_update", updateUpNext); + Spicetify.Player.origin._events.removeListener("update", updateUpNextShow); + window.removeEventListener("resize", updateUpNext); + } + if (CONFIG[ACTIVE].volumeDisplay !== "n") { + if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.removeListener("volume", updateVolume); + else Spicetify.Platform.PlaybackAPI._events.removeListener("volume", updateVolume); + if (CONFIG[ACTIVE].volumeDisplay === "m") container.removeEventListener("mousemove", hideVolume); + } + if (CONFIG[ACTIVE].progressBarDisplay) { + clearInterval(progressListener); + } + if (CONFIG[ACTIVE].icons) { + Spicetify.Player.removeEventListener("onplaypause", updatePlayingIcon); + } + if (CONFIG[ACTIVE].playerControls) { + Spicetify.Player.removeEventListener("onplaypause", updatePlayerControls); + } + if (CONFIG[ACTIVE].extraControls) { + heartObserver.disconnect(); + Spicetify.Player.origin._events.removeListener("update", updateExtraControls); + } + document.body.classList.remove(...classes); + upNextShown = false; + if (CONFIG[ACTIVE].enableFullscreen) { + fullScreenOff(); + } + let popup = document.querySelector("body > generic-modal"); + if (popup) popup.remove(); + style.remove(); + container.remove(); + if (CONFIG[ACTIVE].lyricsDisplay) { + window.removeEventListener("lyrics-plus-update", handleLyricsUpdate); + if (origLoc !== "/lyrics-plus") { + Spicetify.Platform.History.push(origLoc); + Spicetify.Platform.History.entries.splice(Spicetify.Platform.History.entries.length - 3, 2); + Spicetify.Platform.History.index = Spicetify.Platform.History.index > 0 ? Spicetify.Platform.History.index - 2 : -1; + Spicetify.Platform.History.length = Spicetify.Platform.History.length > 1 ? Spicetify.Platform.History.length - 2 : 0; + } + window.dispatchEvent(new Event("fad-request")); + } + Spicetify.Keyboard._deregisterShortcut({ + key: Spicetify.Keyboard.KEYS["F11"], + ctrl: false, + shift: false, + alt: false, + }); + Spicetify.Keyboard._deregisterShortcut({ + key: Spicetify.Keyboard.KEYS["ESCAPE"], + ctrl: false, + shift: false, + alt: false, + }); + } + + function fsToggle() { + if (CONFIG[ACTIVE].enableFullscreen) { + CONFIG[ACTIVE]["enableFullscreen"] = false; + saveConfig(); + render(); + activate(); + } else { + CONFIG[ACTIVE]["enableFullscreen"] = true; + saveConfig(); + render(); + activate(); + } + } + + function getAvailableLanguages(translations) { + let languages = {}; + for (const lang in translations) { + languages[lang] = translations[lang].langName; + } + return languages; + } + + /** + * Merges keys from one object into another recursively. + */ + function mergeDefaultsInConfig(defaultObject, objectToMergeIn) { + Object.keys(defaultObject).forEach((key) => { + if (objectToMergeIn[key] === undefined) { + objectToMergeIn[key] = defaultObject[key]; + } else { + if (typeof defaultObject[key] === "object") { + mergeDefaultsInConfig(defaultObject[key], objectToMergeIn[key]); + } + } + }); + } + function getConfig() { + try { + const parsed = JSON.parse(localStorage.getItem("full-screen-config") ?? "{}"); + if (!!parsed && typeof parsed === "object") { + // mergeDefaultsInConfig(DEFAULTS, parsed); + Object.keys(DEFAULTS).forEach((key) => { + if (parsed[key] === undefined) { + parsed[key] = DEFAULTS[key]; + } else { + if (typeof DEFAULTS[key] === "object") { + Object.keys(DEFAULTS[key]) + .filter((subkey) => parsed[key][subkey] === undefined) + .forEach((subkey) => { + parsed[key][subkey] = DEFAULTS[key][subkey]; + }); + } + } + }); + localStorage.setItem("full-screen-config", JSON.stringify(parsed)); + return parsed; + } + throw ""; + } catch { + localStorage.setItem("full-screen-config", JSON.stringify(DEFAULTS)); + return DEFAULTS; + } + } + + function saveConfig() { + localStorage.setItem("full-screen-config", JSON.stringify(CONFIG)); + } + + function saveOption(key, value) { + CONFIG[ACTIVE][key] = value; + saveConfig(); + render(); + if (document.body.classList.contains("fsd-activated")) activate(); + } + + // Saves an option independent from TV or Fullscreen mode + function saveGlobalOption(key, value) { + CONFIG[key] = value; + LOCALE = CONFIG.locale; // Update locale (avoids reloading client to apply setting) + saveConfig(); + render(); + if (document.body.classList.contains("fsd-activated")) activate(); + } + + function createAdjust(name, key, unit = "", defaultValue, step, min, max, onChange = (val) => {}) { + let value = key in CONFIG[ACTIVE] ? CONFIG[ACTIVE][key] : defaultValue; + + function adjustValue(dir) { + let temp = value + dir * step; + if (temp < min) { + temp = min; + } else if (temp > max) { + temp = max; + } + value = Number(Number(temp).toFixed(step >= 1 ? 0 : 1)); + container.querySelector(".adjust-value").innerText = `${value}${unit}`; + plus && plus.classList.toggle("disabled", value === max); + minus && minus.classList.toggle("disabled", value === min); + onChange(value); + } + const container = document.createElement("div"); + container.innerHTML = ` +
+ +
+ +

${value}${unit}

+ +
+
+ `; + const minus = container.querySelector(".minus"); + const plus = container.querySelector(".plus"); + minus && minus.classList.toggle("disabled", value === min); + plus && plus.classList.toggle("disabled", value === max); + minus && (minus.onclick = () => adjustValue(-1)); + plus && (plus.onclick = () => adjustValue(1)); + return container; + } + + function createOptions(name, options, defaultValue, callback) { + const container = document.createElement("div"); + container.innerHTML = ` +
+ +
+ +
+
`; + const select = container.querySelector("select"); + select && (select.value = defaultValue); + select && (select.onchange = (e) => { + callback(e?.target?.value); + }); + return container; + } + + function createToggle(name, key, callback = (a,b) => {}) { + const container = document.createElement("div"); + container.innerHTML = ` +
+ +
+ +
+
`; + + const toggle = container.querySelector("input"); + toggle && (toggle.checked = CONFIG[ACTIVE][key]); + toggle && (toggle.onchange = (evt) => { + CONFIG[ACTIVE][key] = evt?.target?.checked; + saveConfig(); + render(); + callback(container, evt?.target?.checked); + if (document.body.classList.contains("fsd-activated")) { + activate(); + } + }); + return container; + } + + function headerText(text, subtext = "") { + const container = document.createElement("div"); + container.classList.add("subhead"); + const listHeader = document.createElement("h2"); + listHeader.innerText = text; + container.append(listHeader); + if (subtext) { + const listSub = document.createElement("i"); + listSub.innerText = "(" + subtext + ")"; + container.append(listSub); + } + return container; + } + let configContainer; + + function openConfig(evt) { + evt?.preventDefault(); + configContainer = document.createElement("div"); + configContainer.id = "full-screen-config-container"; + const style = document.createElement("style"); + style.innerHTML = ` + .GenericModal ::-webkit-scrollbar{ + width: 7px; + } + .GenericModal ::-webkit-scrollbar-thumb{ + border-radius: 2rem; + } + .main-trackCreditsModal-container{ + height: 75vh; + max-height: 600px; + width: clamp(500px,50vw,600px); + } + .setting-row::after { + content: ""; + display: table; + clear: both; + } + .setting-row-but{ + display: flex; + align-items: center; + justify-content: center; + } + .subhead{ + text-align: center; + padding: 5px 0; + } + .setting-row-but button{ + margin: 5px 10px; + } + .setting-row .col { + display: flex; + padding: 10px 0; + align-items: center; + } + .setting-row .col.description { + float: left; + padding-right: 15px; + } + .setting-row .col.action { + float: right; + text-align: right; + } + .switch { + position: relative; + display: inline-block; + width: 44px; + height: 20px; + } + .switch input { + opacity: 0; + width: 0; + height: 0; + } + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(150,150,150,.5); + transition: all .3s ease-in-out; + border-radius: 34px; + } + .slider:before { + position: absolute; + content: ""; + height: 24px; + width: 24px; + left: -2px; + bottom: -2px; + background-color: #EEE; + transition: all .3s ease-in-out; + border-radius: 50%; + } + input:checked + .slider { + background-color: rgba(var(--spice-rgb-button-disabled),.6); + } + input:focus + .slider { + box-shadow: 0 0 1px rgba(var(--spice-rgb-button-disabled),.6); + } + input:checked + .slider:before { + transform: translateX(24px); + background-color: var(--spice-button); + filter: brightness(1.1) saturate(1.2); + } + #full-screen-config-container select { + color: var(--spice-text); + background: var(--spice-card); + border: none; + height: 32px; + } + #full-screen-config-container option { + color: var(--spice-text); + background: var(--spice-card); + } + button.switch { + align-items: center; + border: 0px; + border-radius: 50%; + background-color: rgba(var(--spice-rgb-shadow), 0.7); + color: var(--spice-text); + cursor: pointer; + margin-inline-start: 12px; + padding: 8px; + width: 32px; + height: 32px; + } + button.switch.disabled, + button.switch[disabled] { + color: rgba(var(--spice-rgb-text), 0.3); + pointer-events: none; + } + button.switch.small { + width: 22px; + height: 22px; + padding: 3px; + } + .main-buttons-button{ + border: 2px solid transparent; + border-radius: 500px; + color: var(--spice-text); + cursor: pointer; + display: inline-block; + font-size: 12px; + font-weight: 700; + letter-spacing: 1.76px; + line-height: 18px; + padding: 8px 34px; + text-align: center; + text-transform: uppercase; + -webkit-transition: all 33ms cubic-bezier(.3,0,0,1); + transition: all 33ms cubic-bezier(.3,0,0,1); + white-space: nowrap; + will-change: transform; + } + .main-button-primary{ + background-color: var(--spice-button); + } + .main-button-secondary{ + background-color: transparent; + color: var(--spice-button); + border: 2px solid var(--spice-button); + } + #full-screen-config-container .adjust-value { + margin-inline: 12px; + width: 22px; + text-align: center; + } + `; + configContainer.append( + style, + (() => { + const container = document.createElement("div"); + container.innerHTML = ` +
+ + +
`; + container.querySelector("#mode-exit").onclick = deactivate; + container.querySelector("#mode-switch").onclick = () => { + CONFIG.tvMode ? openwithDef() : openwithTV(); + document.querySelector("body > generic-modal")?.remove(); + }; + return document.body.classList.contains("fsd-activated") ? container : ""; + })(), + + headerText(translations[LOCALE].settings.pluginSettings), + createOptions(translations[LOCALE].settings.language, getAvailableLanguages(translations), CONFIG["locale"], (value) => + saveGlobalOption("locale", value) + ), + + headerText(translations[LOCALE].settings.lyricsHeader), + createToggle(translations[LOCALE].settings.lyrics, "lyricsDisplay", (row, status) => { + container.classList.remove("lyrics-unavailable"); + let nextEle = row.nextElementSibling; + while (!nextEle.classList.contains("subhead")) { + nextEle.classList.toggle("hidden", !status); + nextEle = nextEle.nextElementSibling; + } + }), + createOptions( + translations[LOCALE].settings.lyricsAlignment.setting, + { + left: translations[LOCALE].settings.lyricsAlignment.left, + center: translations[LOCALE].settings.lyricsAlignment.center, + right: translations[LOCALE].settings.lyricsAlignment.right, + }, + CONFIG[ACTIVE].lyricsAlignment, + (value) => saveOption("lyricsAlignment", value) + ), + createAdjust(translations[LOCALE].settings.lyricsAnimationTempo, "animationTempo", "s", 0.3, 0.1, 0, 1, (state) => { + CONFIG[ACTIVE]["animationTempo"] = state; + saveConfig(); + render(); + if (document.body.classList.contains("fsd-activated")) activate(); + }), + headerText(translations[LOCALE].settings.generalHeader), + createToggle(translations[LOCALE].settings.progressBar, "progressBarDisplay"), + createToggle(translations[LOCALE].settings.playerControls, "playerControls"), + createToggle(translations[LOCALE].settings.trimTitle, "trimTitle"), + createToggle(translations[LOCALE].settings.trimTitleUpNext, "trimTitleUpNext"), + createOptions( + translations[LOCALE].settings.showAlbum.setting, + { + n: translations[LOCALE].settings.showAlbum.never, + a: translations[LOCALE].settings.showAlbum.always, + d: translations[LOCALE].settings.showAlbum.date, + }, + CONFIG[ACTIVE].showAlbum, + (value) => saveOption("showAlbum", value) + ), + createToggle(translations[LOCALE].settings.showAllArtists, "showAllArtists"), + createToggle(translations[LOCALE].settings.icons, "icons"), + createToggle(translations[LOCALE].settings.songChangeAnimation, "enableFade"), + document.fullscreenEnabled ? createToggle(translations[LOCALE].settings.fullscreen, "enableFullscreen") : "", + headerText(translations[LOCALE].settings.extraHeader), + ACTIVE !== "tv" + ? createOptions( + translations[LOCALE].settings.backgroundChoice.setting, + { + c: translations[LOCALE].settings.backgroundChoice.color, + a: translations[LOCALE].settings.backgroundChoice.artwork, + }, + CONFIG.def.backgroundChoice, + (value) => saveOption("backgroundChoice", value) + ) + : "", + createToggle(translations[LOCALE].settings.extraControls, "extraControls"), + createToggle(translations[LOCALE].settings.upnextDisplay, "upnextDisplay"), + createOptions( + translations[LOCALE].settings.contextDisplay.setting, + { + n: translations[LOCALE].settings.contextDisplay.never, + m: translations[LOCALE].settings.contextDisplay.mouse, + a: translations[LOCALE].settings.contextDisplay.always, + }, + CONFIG[ACTIVE].contextDisplay, + (value) => saveOption("contextDisplay", value) + ), + createOptions( + translations[LOCALE].settings.volumeDisplay.setting, + { + a: translations[LOCALE].settings.volumeDisplay.always, + n: translations[LOCALE].settings.volumeDisplay.never, + m: translations[LOCALE].settings.volumeDisplay.mouse, + o: translations[LOCALE].settings.volumeDisplay.volume, + }, + CONFIG[ACTIVE].volumeDisplay, + (value) => saveOption("volumeDisplay", value) + ), + headerText(translations[LOCALE].settings.appearanceHeader, translations[LOCALE].settings.appearanceSubHeader), + ACTIVE !== "tv" + ? createOptions( + translations[LOCALE].settings.backgroundColor.setting, + { + VIBRANT: translations[LOCALE].settings.backgroundColor.vibrant, + PROMINENT: translations[LOCALE].settings.backgroundColor.prominent, + DESATURATED: translations[LOCALE].settings.backgroundColor.desaturated, + LIGHT_VIBRANT: translations[LOCALE].settings.backgroundColor.lightVibrant, + DARK_VIBRANT: translations[LOCALE].settings.backgroundColor.darkVibrant, + VIBRANT_NON_ALARMING: translations[LOCALE].settings.backgroundColor.vibrantNonAlarming, + }, + CONFIG.def.coloredBackChoice, + (value) => saveOption("coloredBackChoice", value) + ) + : "", + createToggle(translations[LOCALE].settings.themedButtons, "themedButtons"), + createToggle(translations[LOCALE].settings.themedIcons, "themedIcons"), + createOptions( + translations[LOCALE].settings.invertColors.setting, + { + n: translations[LOCALE].settings.invertColors.never, + a: translations[LOCALE].settings.invertColors.always, + d: translations[LOCALE].settings.invertColors.auto, + }, + CONFIG[ACTIVE].invertColors, + (value) => saveOption("invertColors", value) + ), + createAdjust(translations[LOCALE].settings.backAnimationTime, "backAnimationTime", "s", 0.8, 0.1, 0, 5, (state) => { + CONFIG[ACTIVE]["backAnimationTime"] = state; + saveConfig(); + render(); + if (document.body.classList.contains("fsd-activated")) activate(); + }), + createOptions( + translations[LOCALE].settings.upnextScroll.setting, + { + mq: translations[LOCALE].settings.upnextScroll.mq, + sp: translations[LOCALE].settings.upnextScroll.sp, + }, + CONFIG[ACTIVE].upNextAnim, + (value) => saveOption("upNextAnim", value) + ), + createAdjust(translations[LOCALE].settings.upnextTime, "upnextTimeToShow", "s", 30, 1, 5, 60, (state) => { + CONFIG[ACTIVE]["upnextTimeToShow"] = state; + saveConfig(); + updateUpNextShow(); + // render() + // if (document.body.classList.contains('fsd-activated')) + // activate() + }), + createAdjust(translations[LOCALE].settings.backgroundBlur, "blurSize", "px", 20, 4, 0, 100, (state) => { + CONFIG[ACTIVE]["blurSize"] = state; + saveConfig(); + render(); + if (document.body.classList.contains("fsd-activated")) activate(); + }), + createOptions( + translations[LOCALE].settings.backgroundBrightness, + { + 0: "0%", + 0.1: "10%", + 0.2: "20%", + 0.3: "30%", + 0.4: "40%", + 0.5: "50%", + 0.6: "60%", + 0.7: "70%", + 0.8: "80%", + 0.9: "90%", + 1: "100%", + }, + CONFIG[ACTIVE].backgroundBrightness, + (value) => saveOption("backgroundBrightness", Number(value)) + ), + (() => { + const container = document.createElement("div"); + container.innerHTML = ` +
+ + +
`; + container.querySelector("#reset-switch").onclick = () => { + CONFIG[ACTIVE] = DEFAULTS[ACTIVE]; + saveConfig(); + render(); + if (document.body.classList.contains("fsd-activated")) { + activate(); + } + configContainer = ""; + setTimeout(openConfig, 5); + }; + container.querySelector("#reload-switch").onclick = () => { + location.reload(); + }; + return container; + })() + ); + Spicetify.PopupModal.display({ + title: ACTIVE === "tv" ? translations[LOCALE].settings.tvModeConfig : translations[LOCALE].settings.fullscreenConfig, + content: configContainer, + }); + } + + // container.ondblclick = deactivatemenu + + // Add Full Screen Button on bottom bar + const defButton = document.createElement("button"); + defButton.classList.add("button", "fsd-button", "control-button", "InvalidDropTarget"); + defButton.id = "fs-button"; + defButton.setAttribute("title", translations[LOCALE].fullscreenBtnDesc); + + defButton.innerHTML = ``; + defButton.onclick = openwithDef; + + defButton.oncontextmenu = (evt) => { + evt.preventDefault(); + ACTIVE = "def"; + openConfig(); + }; + + // if (CONFIG.fsHideOriginal) { + // if (extraBar.lastChild.classList.contains("control-button") || extraBar.lastChild.title == "Full screen") extraBar.lastChild.remove(); + // } + + extraBar?.append(defButton); + + // Add TV Mode Button on top bar + const tvButton = document.createElement("button"); + tvButton.classList.add("button", "tm-button", "main-topBar-button", "InvalidDropTarget"); + tvButton.innerHTML = ``; + tvButton.id = "TV-button"; + tvButton.setAttribute("title", translations[LOCALE].tvBtnDesc); + + tvButton.onclick = openwithTV; + + topBar?.append(tvButton); + tvButton.oncontextmenu = (evt) => { + evt.preventDefault(); + ACTIVE = "tv"; + openConfig(); + }; + + render(); +} + +fullScreen(); diff --git a/.config/spicetify/Extensions/genre.js b/.config/spicetify/Extensions/genre.js new file mode 100644 index 00000000..85c603bd --- /dev/null +++ b/.config/spicetify/Extensions/genre.js @@ -0,0 +1,125 @@ +// + +(function Genre() { + const { CosmosAsync, Player } = Spicetify; + + /** + * Fetch genre from artist + * + * @param artistURI {string} + * @return {Promise} + */ + const fetchGenres = async (artistURI) => { + const res = await CosmosAsync.get( + `https://api.spotify.com/v1/artists/${artistURI}` + ); + // noinspection JSUnresolvedVariable + return res.genres.slice(0, 3) // Only keep the first 3 genres + }; + + /** + * Fetch playlist from The Sound of Spotify for a given genre + * @param {String} genre + * @return {String|null} + */ + const fetchSoundOfSpotifyPlaylist = async (genre) => { + const query = encodeURIComponent(`The Sound of ${genre}`); + // Check localStorage for playlist + const cached = localStorage.getItem(`everynoise:${query}`); + if (cached !== null) { + return cached; + } + + // Search for playlist and check results for the everynoise account + const re = new RegExp(`^the sound of ${genre.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i'); + const res = await CosmosAsync.get(`https://api.spotify.com/v1/search?q=${query}&type=playlist`) + for (const item of res.playlists.items) { + if (item.owner.id === "thesoundsofspotify" && re.test(item.name)) { + localStorage.setItem(`everynoise:${genre}`, item.uri); + return item.uri + } + } + return null; + }; + + + // Store the current playback id + let playback = null; + + /** + * + * @type {Node} + */ + let genreContainer = null; + + let infoContainer = document.querySelector('div.main-trackInfo-container'); + + /** + * Remove genre injection in the UI + */ + const cleanInjection = () => { + if (genreContainer !== null) { + try { + infoContainer.removeChild(genreContainer); + } catch (e) {} + } + }; + + /** + * Inject genres to UI + */ + const inject = () => { + Player.addEventListener('onprogress', async () => { + if (Player.data.track.metadata.hasOwnProperty('artist_uri')) { + // If the registered song isn't the same as the one currently being played then fetch genres + if (playback !== Player.data.playback_id) { + // Save the new track + playback = Player.data.playback_id; + const id = Player.data.track.metadata.artist_uri.split(':')[2]; + const genres = await fetchGenres(id); + + cleanInjection(); + + genreContainer = document.createElement('div'); + // noinspection JSUndefinedPropertyAssignment + genreContainer.className = 'main-trackInfo-genres ellipsis-one-line main-type-finale'; + // noinspection JSUnresolvedVariable + genreContainer.style.color = 'var(--spice-extratext)'; + + for (const i in genres) { + let element; + const uri = await fetchSoundOfSpotifyPlaylist(genres[i]); + if (uri !== null) { + element = document.createElement('a'); + element.innerHTML = genres[i]; + element.href = uri; + } else { + element = document.createElement('span'); + } + element.innerHTML = genres[i]; + element.style.fontSize ="11px"; + genreContainer.appendChild(element); + if (i < genres.length-1) { + const separator = document.createElement('span'); + separator.innerHTML = ', '; + genreContainer.appendChild(separator); + } + } + + infoContainer = document.querySelector('div.main-trackInfo-container'); + if(!infoContainer) cleanInjection(); + infoContainer.appendChild(genreContainer); + + } + } else { + cleanInjection(); + } + }); + }; + + if (!CosmosAsync) { + setTimeout(Genre, 500); + } else { + inject(); + } +})(); diff --git a/.config/spicetify/Extensions/hidePodcasts.js b/.config/spicetify/Extensions/hidePodcasts.js new file mode 100644 index 00000000..6bc4a454 --- /dev/null +++ b/.config/spicetify/Extensions/hidePodcasts.js @@ -0,0 +1,5 @@ +var hidePodcasts=(()=>{var T=(e=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(e,{get:(e,t)=>("undefined"!=typeof require?require:e)[t]}):e)(function(e){if("react"===e)return Spicetify.React;if("react-dom"===e)return Spicetify.ReactDOM;if("undefined"!=typeof require)return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});function C(e){return(C="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function U(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,o=new Array(t);n":">",'"':""","'":"'","/":"/"};function W(e){return"string"==typeof e?e.replace(/[&<>"'\/]/g,function(e){return $[e]}):e}var h="undefined"!=typeof window&&window.navigator&&void 0===window.navigator.userAgentData&&window.navigator.userAgent&&-1s+c;)c++,u=a[l=i.slice(s,s+c).join(r)];if(void 0===u)return;if(null===u)return null;if(n.endsWith(l)){if("string"==typeof u)return u;if(l&&"string"==typeof u[l])return u[l]}var p=i.slice(s+c).join(r);return p?e(u,p,r):void 0}a=a[i[s]]}return a}}(this.data&&this.data[e]&&this.data[e][t],n,r)}},{key:"addResource",value:function(e,t,n,o){var r=4=p.maxReplaces)break}var n,o}),r}},{key:"nest",value:function(e,t){var n,o=this,r=2=this.maxParallelReads?void this.waitingReads.push({lng:o,ns:r,fcName:i,tried:s,wait:c,callback:l}):(this.readingCalls++,this.backend[i](o,r,function(e,t){var n;a.readingCalls--,0",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"};function Ne(t,e){var n,o=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),o.push.apply(o,n)),o}function Ce(t){for(var e=1;e{t=localStorage.getItem(t);if(!t)return e;try{return JSON.parse(t)}catch(e){return t}}),Ye=(je.use(T).use(I).init({resources:{en:{translation:{menuTitle:"Hide podcasts",enabled:"Enabled",aggressiveMode:"Aggressive mode",searchPageShelfAriaLabel:"Browse all"}},fr:{translation:{menuTitle:"Masquer les balados",enabled:"Activé",aggressiveMode:"Mode agressif",searchPageShelfAriaLabel:"Paarcourir tout"}},de:{translation:{menuTitle:"Podcasts verstecken",enabled:"Aktiviert",aggressiveMode:"Aggressiver Modus",searchPageShelfAriaLabel:"Alles durchsuchen"}},it:{translation:{menuTitle:"Nascondi podcast",enabled:"Attivato",aggressiveMode:"Modalità aggressiva",searchPageShelfAriaLabel:"Sfoglia tutto"}}},detection:{order:["navigator","htmlTag"]},fallbackLng:"en",interpolation:{escapeValue:!1}}),"HidePodcastsEnabled"),Ge="HidePodcastsAggressiveMode";var Qe=async function(){const o=je["t"];let{Player:e,Menu:t,Platform:n}=Spicetify,r=document.querySelector(".main-view-container__scroll-node-child");for(;!(e&&t&&n&&r);)await new Promise(e=>setTimeout(e,100)),e=Spicetify.Player,t=Spicetify.Menu,n=Spicetify.Platform,r=document.querySelector(".main-view-container__scroll-node-child");let a=We(Ye,!0),i=We(Ge,!1);function s(){var e;e=a,document.body.classList.toggle("hide-podcasts-enabled",e);{const t=document.body;if(!t.classList.contains("hide-podcasts--style-injected")){const n=document.createElement("style");n.innerHTML=`.hide-podcasts-enabled .podcast-item { + display: none !important; + }.hide-podcasts-enabled .queue-tabBar-header a[href="/collection/podcasts"] { + display: none !important; + }`,t.appendChild(n),t.classList.add("hide-podcasts--style-injected")}}{const o=null==(e=document.querySelector('a[href="/collection/episodes"]'))?void 0:e.parentElement,r=(o&&o.classList.add("podcast-item"),document.querySelectorAll(".main-shelf-shelf")),i=(r.forEach(e=>{var t;0<[...e.querySelectorAll('.main-cardHeader-link[href^="/episode"]'),...e.querySelectorAll('.main-cardHeader-link[href^="/show"]')].length&&(t=e.getAttribute("aria-label"),console.log("Tagging carousel: "+t),e.classList.add("podcast-item"))}),document.querySelector('.x-categoryCard-CategoryCard[href="/genre/podcasts-web"]'));i&&(console.log("Tagging browsePodcastsCard: "+i),i.classList.add("podcast-item"))}}function c(t){const n=new MutationObserver(function(){var e;!r||(e="/search"===t?r.querySelector(`#searchPage .main-shelf-shelf[aria-label="${o("searchPageShelfAriaLabel")}"]`):r.querySelector("section"))&&(console.log(t,e),s(),i||n.disconnect())});n.observe(r,{childList:!0,subtree:!0})}new t.SubMenu(o("menuTitle"),[new t.Item(o("enabled"),a,e=>{a=!a,localStorage.setItem(Ye,a),e.setState(a),s()}),new t.Item(o("aggressiveMode"),i,e=>{i=!i,localStorage.setItem(Ge,i),e.setState(i),location.reload()})]).register(),c(n.History.location.pathname),n.History.listen(({pathname:e})=>{c(e)})};(async()=>{await Qe()})()})(); \ No newline at end of file diff --git a/.config/spicetify/Extensions/historyShortcut.js b/.config/spicetify/Extensions/historyShortcut.js new file mode 100644 index 00000000..a4c65228 --- /dev/null +++ b/.config/spicetify/Extensions/historyShortcut.js @@ -0,0 +1,127 @@ +// NAME: History Shortcut +// AUTHOR: einzigartigerName +// DESCRIPTION: Adds a Shortcut to your Listening History to the Sidebar + +(function HistoryShortcut() { + + const { CosmosAPI, Player, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify + if (!(CosmosAPI && Player && LocalStorage && PlaybackControl && ContextMenu && URI)) { + setTimeout(HistoryShortcut, 300) + return + } + + const ITEM_LABEL = "History" + + const HISTORY_DIV_CLASS = "SidebarListItem" + const HISTORY_DIV_CLASS_ACTIVE = "SidebarListItem SidebarListItem--is-active" + + const HISTORY_ANKER_CLASS = "SidebarListItemLink SidebarListItemLink--tall spoticon-time-24" + const HISTORY_ANKER_CLASS_ACTIVE = "SidebarListItemLink SidebarListItemLink--is-highlighted SidebarListItemLink--tall spoticon-time-24" + + let historyItem = createHistoyItem() + // Get Sidebar Lists + var topicList = document.querySelector("#view-navigation-bar > div > div.LeftSidebar__section > div > ul") + if (topicList) { + // Add to first in list + // On default layout this would be the Home/Browse/Radio List + topicList.appendChild(historyItem.div) + } else { + return + } + + const toCheckMutate = document.getElementById('view-content'); + const config = { attributes: true, childList: true, subtree: true }; + + let observerCallback = function(_, _) { + appQueue = document.getElementById("app-queue") + if (!appQueue){ return } + + if (appQueue.getAttribute("class") === "active" + && appQueue.getAttribute("data-app-uri") === "spotify:app:queue:history" + ) { + onClickHistory() + } else { + onLeaveHistory() + } + }; + + let observer = new MutationObserver(observerCallback) + observer.observe(toCheckMutate, config) + + + + // Deactivate Active Status for History Item + function onLeaveHistory() { + historyItem.div.setAttribute("class",HISTORY_DIV_CLASS) + historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS) + } + + // Activate Active Status for History Item + function onClickHistory() { + historyItem.div.setAttribute("class", HISTORY_DIV_CLASS_ACTIVE) + historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS_ACTIVE) + } + + // Construct the List Item + function createHistoyItem() { + /* List Item + *
  • + */ + let listItem = document.createElement("li") + listItem.setAttribute("class", HISTORY_DIV_CLASS) + + /* Outer Div Element + *
    + */ + let outer = document.createElement("div") + outer.setAttribute("class", "DropTarget SidebarListItem__drop-target") + + /* Middle Div Element + *
    + */ + let inner = document.createElement("div") + inner.setAttribute("class", "SidebarListItem__inner") + + /* Link Div Element + *