dribbblish-dynamic-theme/dist/dribbblish-dynamic.js
2021-10-25 14:49:34 +02:00

109 lines
No EOL
53 KiB
JavaScript

/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/styles/main.scss":
/*!******************************!*\
!*** ./src/styles/main.scss ***!
\******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (__webpack_require__.p + \"user.css\");\n\n//# sourceURL=webpack:///./src/styles/main.scss?");
/***/ }),
/***/ "./src/js/main.js":
/*!************************!*\
!*** ./src/js/main.js ***!
\************************/
/***/ (() => {
eval("// Hide popover message\r\n// document.getElementById(\"popover-container\").style.height = 0;\r\nclass ConfigMenu {\r\n /**\r\n * @typedef {Object} DribbblishConfigItem\r\n * @property {\"checkbox\" | \"select\" | \"button\" | \"slider\" | \"number\" | \"text\" | \"time\" | \"color\"} type\r\n * @property {String|DribbblishConfigArea} [area={name: \"Main Settings\", order: 0}]\r\n * @property {any} [data={}]\r\n * @property {Number} [order=0] order < 0 = Higher up | order > 0 = Lower Down\r\n * @property {String} key\r\n * @property {String} name\r\n * @property {String} [description=\"\"]\r\n * @property {any} [defaultValue]\r\n * @property {Boolean} [hidden=false]\r\n * @property {Boolean} [insertOnTop=false]\r\n * @property {Boolean} [fireInitialChange=true]\r\n * @property {showChildren} [showChildren]\r\n * @property {onAppended} [onAppended]\r\n * @property {onChange} [onChange]\r\n * @property {DribbblishConfigItem[]} [children=[]]\r\n * @property {String} [childOf=null] key of parent (set automatically)\r\n */\r\n\r\n /**\r\n * @typedef DribbblishConfigArea\r\n * @property {String} name\r\n * @property {Number} [order=0] order < 0 = Higher up | order > 0 = Lower Down\r\n */\r\n\r\n /**\r\n * @callback showChildren\r\n * @param {any} value\r\n * @returns {Boolean | String[]}\r\n */\r\n\r\n /**\r\n * @callback onAppended\r\n * @returns {void}\r\n */\r\n\r\n /**\r\n * @callback onChange\r\n * @param {any} value\r\n * @returns {void}\r\n */\r\n\r\n /** @type {Object.<string, DribbblishConfigItem>} */\r\n #config;\r\n\r\n constructor() {\r\n this.#config = {};\r\n this.configButton = new Spicetify.Menu.Item(\"Dribbblish Settings\", false, () => DribbblishShared.config.open());\r\n this.configButton.register();\r\n\r\n const container = document.createElement(\"div\");\r\n container.id = \"dribbblish-config\";\r\n container.innerHTML = /* html */ `\r\n <div class=\"dribbblish-config-container\">\r\n <button aria-label=\"Close\" class=\"dribbblish-config-close main-trackCreditsModal-closeBtn\">\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M31.098 29.794L16.955 15.65 31.097 1.51 29.683.093 15.54 14.237 1.4.094-.016 1.508 14.126 15.65-.016 29.795l1.414 1.414L15.54 17.065l14.144 14.143\" fill=\"currentColor\" fill-rule=\"evenodd\"></path></svg>\r\n </button>\r\n <h1>Dribbblish Settings</h1>\r\n <div class=\"dribbblish-config-areas\"></div>\r\n </div>\r\n <div class=\"dribbblish-config-backdrop\"></div>\r\n `;\r\n\r\n document.body.appendChild(container);\r\n document.querySelector(\".dribbblish-config-close\").addEventListener(\"click\", () => DribbblishShared.config.close());\r\n document.querySelector(\".dribbblish-config-backdrop\").addEventListener(\"click\", () => DribbblishShared.config.close());\r\n }\r\n\r\n open() {\r\n document.getElementById(\"dribbblish-config\").setAttribute(\"active\", \"\");\r\n }\r\n\r\n close() {\r\n document.getElementById(\"dribbblish-config\").removeAttribute(\"active\");\r\n }\r\n\r\n /**\r\n * @private\r\n * @param {DribbblishConfigItem} options\r\n */\r\n addInputHTML(options) {\r\n this.registerArea(options.area);\r\n const parent = document.querySelector(`.dribbblish-config-area[name=\"${options.area.name}\"] .dribbblish-config-area-items`);\r\n\r\n const elem = document.createElement(\"div\");\r\n elem.style.order = options.order;\r\n elem.classList.add(\"dribbblish-config-item\");\r\n elem.setAttribute(\"key\", options.key);\r\n elem.setAttribute(\"type\", options.type);\r\n elem.setAttribute(\"hidden\", options.hidden);\r\n if (options.childOf) elem.setAttribute(\"parent\", options.childOf);\r\n elem.innerHTML = /* html */ `\r\n <h2 class=\"x-settings-title main-type-cello${!options.description ? \" no-desc\" : \"\"}\" as=\"h2\">${options.name}</h2>\r\n <label class=\"main-type-mesto\">${options.description.replace(/\\n/g, \"<br>\")}</label>\r\n <label class=\"x-toggle-wrapper x-settings-secondColumn\">\r\n ${options.input}\r\n </label>\r\n `;\r\n\r\n if (options.insertOnTop && parent.children.length > 0) {\r\n parent.insertBefore(elem, parent.children[0]);\r\n } else {\r\n parent.appendChild(elem);\r\n }\r\n }\r\n\r\n /**\r\n * @param {DribbblishConfigItem} options\r\n */\r\n register(options) {\r\n /** @type {DribbblishConfigItem} */\r\n const defaultOptions = {\r\n hidden: false,\r\n area: \"Main Settings\",\r\n order: 0,\r\n data: {},\r\n name: \"\",\r\n description: \"\",\r\n insertOnTop: false,\r\n fireInitialChange: true,\r\n showChildren: () => true,\r\n onAppended: () => {},\r\n onChange: () => {},\r\n children: [],\r\n childOf: null\r\n };\r\n // Set Defaults\r\n options = { ...defaultOptions, ...options };\r\n if (typeof options.area == \"string\") options.area = { name: options.area, order: 0 };\r\n options.description = options.description\r\n .split(\"\\n\")\r\n .filter((line) => line.trim() != \"\")\r\n .map((line) => line.trim())\r\n .join(\"\\n\");\r\n options._onChange = options.onChange;\r\n options.onChange = (val) => {\r\n options._onChange(val);\r\n const show = options.showChildren(val);\r\n options.children.forEach((child) => this.setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show));\r\n };\r\n options.children = options.children.map((child) => {\r\n return { ...child, area: options.area, childOf: options.key };\r\n });\r\n\r\n this.#config[options.key] = options;\r\n this.#config[options.key].value = localStorage.getItem(`dribbblish:config:${options.key}`) ?? JSON.stringify(options.defaultValue);\r\n\r\n if (options.type == \"checkbox\") {\r\n const input = /* html */ `\r\n <input id=\"dribbblish-config-input-${options.key}\" class=\"x-toggle-input\" type=\"checkbox\"${this.get(options.key) ? \" checked\" : \"\"}>\r\n <span class=\"x-toggle-indicatorWrapper\">\r\n <span class=\"x-toggle-indicator\"></span>\r\n </span>\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"change\", (e) => {\r\n this.set(options.key, e.target.checked);\r\n options.onChange(this.get(options.key));\r\n });\r\n } else if (options.type == \"select\") {\r\n // Validate\r\n const val = this.get(options.key);\r\n if (val < 0 || val > options.data.length - 1) this.set(options.key);\r\n\r\n const input = /* html */ `\r\n <select class=\"main-dropDown-dropDown\" id=\"dribbblish-config-input-${options.key}\">\r\n ${options.data.map((option, i) => `<option value=\"${i}\"${this.get(options.key) == i ? \" selected\" : \"\"}>${option}</option>`).join(\"\")}\r\n </select>\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"change\", (e) => {\r\n this.set(options.key, Number(e.target.value));\r\n options.onChange(this.get(options.key));\r\n });\r\n } else if (options.type == \"button\") {\r\n options.fireInitialChange = false;\r\n if (typeof options.data != \"string\") options.data = options.name;\r\n\r\n const input = /* html */ `\r\n <button class=\"main-buttons-button main-button-primary\" type=\"button\" id=\"dribbblish-config-input-${options.key}\">\r\n <div class=\"x-settings-buttonContainer\">\r\n <span>${options.data}</span>\r\n </div>\r\n </button>\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"click\", (e) => {\r\n options.onChange(true);\r\n });\r\n } else if (options.type == \"number\") {\r\n // Validate\r\n if (options.defaultValue == null) options.defaultValue = 0;\r\n const val = this.get(options.key);\r\n if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min);\r\n if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max);\r\n\r\n const input = /* html */ `\r\n <input type=\"number\" id=\"dribbblish-config-input-${options.key}\" value=\"${this.get(options.key)}\">\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n // Prevent inputting +, - and e. Why is it even possible in the first place?\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"keypress\", (e) => {\r\n if ([\"+\", \"-\", \"e\"].includes(e.key)) e.preventDefault();\r\n });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"input\", (e) => {\r\n if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min;\r\n if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max;\r\n\r\n this.set(options.key, Number(e.target.value));\r\n options.onChange(this.get(options.key));\r\n });\r\n } else if (options.type == \"text\") {\r\n if (options.defaultValue == null) options.defaultValue = \"\";\r\n\r\n const input = /* html */ `\r\n <input type=\"text\" id=\"dribbblish-config-input-${options.key}\" value=\"${this.get(options.key)}\">\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"input\", (e) => {\r\n // TODO: maybe add an validation function via `data.validate`\r\n this.set(options.key, e.target.value);\r\n options.onChange(this.get(options.key));\r\n });\r\n } else if (options.type == \"slider\") {\r\n // Validate\r\n if (options.defaultValue == null) options.defaultValue = 0;\r\n const val = this.get(options.key);\r\n if (options.data.min != null && val < options.data.min) this.set(options.key, options.data.min);\r\n if (options.data.max != null && val > options.data.max) this.set(options.key, options.data.max);\r\n\r\n const input = /* html */ `\r\n <input\r\n type=\"range\"\r\n id=\"dribbblish-config-input-${options.key}\"\r\n name=\"${options.name}\"\r\n min=\"${options.data?.min ?? \"0\"}\"\r\n max=\"${options.data?.max ?? \"100\"}\"\r\n step=\"${options.data?.step ?? \"1\"}\"\r\n value=\"${this.get(options.key)}\"\r\n tooltip=\"${this.get(options.key)}${options.data?.suffix ?? \"\"}\"\r\n >\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"input\", (e) => {\r\n document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute(\"tooltip\", `${e.target.value}${options.data?.suffix ?? \"\"}`);\r\n document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute(\"value\", e.target.value);\r\n this.set(options.key, Number(e.target.value));\r\n options.onChange(this.get(options.key));\r\n });\r\n } else if (options.type == \"time\") {\r\n // Validate\r\n if (options.defaultValue == null) options.defaultValue = \"00:00\";\r\n const input = /* html */ `\r\n <input type=\"time\" id=\"dribbblish-config-input-${options.key}\" name=\"${options.name}\" value=\"${this.get(options.key)}\">\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"input\", (e) => {\r\n document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute(\"value\", e.target.value);\r\n this.set(options.key, e.target.value);\r\n options.onChange(this.get(options.key));\r\n });\r\n } else if (options.type == \"color\") {\r\n // Validate\r\n if (options.defaultValue == null) options.defaultValue = \"#000000\";\r\n const input = /* html */ `\r\n <input type=\"color\" id=\"dribbblish-config-input-${options.key}\" name=\"${options.name}\" value=\"${this.get(options.key)}\">\r\n `;\r\n this.addInputHTML({ ...options, input });\r\n\r\n document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener(\"input\", (e) => {\r\n this.set(options.key, e.target.value);\r\n options.onChange(this.get(options.key));\r\n });\r\n } else {\r\n throw new Error(`Config Type \"${options.type}\" invalid`);\r\n }\r\n\r\n options.children.forEach((child) => this.register(child));\r\n\r\n options.onAppended();\r\n if (options.fireInitialChange) options.onChange(this.get(options.key));\r\n }\r\n\r\n /**\r\n * @param {DribbblishConfigArea} area\r\n */\r\n registerArea(area) {\r\n if (!document.querySelector(`.dribbblish-config-area[name=\"${area.name}\"]`)) {\r\n const areaElem = document.createElement(\"div\");\r\n areaElem.classList.add(\"dribbblish-config-area\");\r\n areaElem.style.order = area.order;\r\n const uncollapsedAreas = JSON.parse(localStorage.getItem(\"dribbblish:config-areas:uncollapsed\") ?? \"[]\");\r\n if (!uncollapsedAreas.includes(area.name)) areaElem.toggleAttribute(\"collapsed\");\r\n areaElem.setAttribute(\"name\", area.name);\r\n areaElem.innerHTML = /* html */ `\r\n <h2 class=\"dribbblish-config-area-header\">\r\n ${area.name}\r\n <svg height=\"24\" width=\"24\" viewBox=\"0 0 24 24\" class=\"main-topBar-icon\"><polyline points=\"16 4 7 12 16 20\" fill=\"none\" stroke=\"currentColor\"></polyline></svg>\r\n </h2>\r\n <div class=\"dribbblish-config-area-items\"></div>\r\n `;\r\n document.querySelector(\".dribbblish-config-areas\").appendChild(areaElem);\r\n areaElem.querySelector(\"h2\").addEventListener(\"click\", () => {\r\n areaElem.toggleAttribute(\"collapsed\");\r\n let uncollapsedAreas = JSON.parse(localStorage.getItem(\"dribbblish:config-areas:uncollapsed\") ?? \"[]\");\r\n if (areaElem.hasAttribute(\"collapsed\")) {\r\n uncollapsedAreas = uncollapsedAreas.filter((areaName) => areaName != area.name);\r\n } else {\r\n uncollapsedAreas.push(area.name);\r\n }\r\n localStorage.setItem(\"dribbblish:config-areas:uncollapsed\", JSON.stringify(uncollapsedAreas));\r\n });\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param {String} key\r\n * @param {any} defaultValueOverride\r\n * @returns {any}\r\n */\r\n get(key, defaultValueOverride) {\r\n const val = JSON.parse(this.#config[key].value ?? null); // Turn undefined into null because `JSON.parse()` dosen't like undefined\r\n if (val == null) return defaultValueOverride ?? this.#config[key].defaultValue;\r\n return val;\r\n }\r\n\r\n /**\r\n *\r\n * @param {String} key\r\n * @param {any} val\r\n */\r\n set(key, val) {\r\n this.#config[key].value = JSON.stringify(val);\r\n localStorage.setItem(`dribbblish:config:${key}`, JSON.stringify(val));\r\n }\r\n\r\n /**\r\n *\r\n * @param {String} key\r\n * @param {Boolean} hidden\r\n */\r\n setHidden(key, hidden) {\r\n this.#config[key].hidden = hidden;\r\n document.querySelector(`.dribbblish-config-item[key=\"${key}\"]`).setAttribute(\"hidden\", hidden);\r\n }\r\n\r\n getOptions(key) {\r\n return this.#config[key];\r\n }\r\n}\r\n\r\nclass _DribbblishShared {\r\n constructor() {\r\n this.config = new ConfigMenu();\r\n }\r\n}\r\nconst DribbblishShared = new _DribbblishShared();\r\n\r\nDribbblishShared.config.register({\r\n type: \"checkbox\",\r\n key: \"rightBigCover\",\r\n name: \"Right expanded cover\",\r\n description: \"Have the expanded cover Image on the right instead of on the left\",\r\n defaultValue: true,\r\n onChange: (val) => {\r\n if (val) {\r\n document.documentElement.classList.add(\"right-expanded-cover\");\r\n } else {\r\n document.documentElement.classList.remove(\"right-expanded-cover\");\r\n }\r\n }\r\n});\r\n\r\nDribbblishShared.config.register({\r\n type: \"checkbox\",\r\n key: \"roundSidebarIcons\",\r\n name: \"Round Sidebar Icons\",\r\n description: \"If the Sidebar Icons should be round instead of square\",\r\n defaultValue: false,\r\n onChange: (val) => document.documentElement.style.setProperty(\"--sidebar-icons-border-radius\", val ? \"50%\" : \"var(--image-radius)\")\r\n});\r\n\r\nDribbblishShared.config.register({\r\n area: \"Animations & Transitions\",\r\n type: \"checkbox\",\r\n key: \"sidebarHoverAnimation\",\r\n name: \"Sidebar Hover Animation\",\r\n description: \"If the Sidebar Icons should have an animated background on hover\",\r\n defaultValue: true,\r\n onChange: (val) => document.documentElement.style.setProperty(\"--sidebar-icons-hover-animation\", val ? \"1\" : \"0\")\r\n});\r\n\r\nwaitForElement([\"#main\"], () => {\r\n DribbblishShared.config.register({\r\n type: \"select\",\r\n data: [\"None\", \"None (With Top Padding)\", \"Solid\", \"Transparent\"],\r\n key: \"winTopBar\",\r\n name: \"Windows Top Bar\",\r\n description: \"Have different top Bars (or none at all)\",\r\n defaultValue: 0,\r\n onChange: (val) => {\r\n switch (val) {\r\n case 0:\r\n document.getElementById(\"main\").setAttribute(\"top-bar\", \"none\");\r\n break;\r\n case 1:\r\n document.getElementById(\"main\").setAttribute(\"top-bar\", \"none-padding\");\r\n break;\r\n case 2:\r\n document.getElementById(\"main\").setAttribute(\"top-bar\", \"solid\");\r\n break;\r\n case 3:\r\n document.getElementById(\"main\").setAttribute(\"top-bar\", \"transparent\");\r\n break;\r\n }\r\n }\r\n });\r\n\r\n DribbblishShared.config.register({\r\n type: \"select\",\r\n data: [\"Dribbblish\", \"Spotify\"],\r\n key: \"playerControlsStyle\",\r\n name: \"Player Controls Style\",\r\n description: \"Style of the Player Controls. Selecting Spotify basically changes Play / Pause back to the center\",\r\n defaultValue: 0,\r\n onChange: (val) => {\r\n switch (val) {\r\n case 0:\r\n document.getElementById(\"main\").setAttribute(\"player-controls\", \"dribbblish\");\r\n break;\r\n case 1:\r\n document.getElementById(\"main\").setAttribute(\"player-controls\", \"spotify\");\r\n break;\r\n }\r\n }\r\n });\r\n\r\n DribbblishShared.config.register({\r\n area: \"Ads\",\r\n type: \"checkbox\",\r\n key: \"hideAds\",\r\n name: \"Hide Ads\",\r\n description: `Hide ads / premium features (see: <a href=\"https://github.com/Daksh777/SpotifyNoPremium\">SpotifyNoPremium</a>)`,\r\n defaultValue: false,\r\n onAppended: () => {\r\n document.styleSheets[0].insertRule(/* css */ `\r\n /* Remove upgrade button*/\r\n #main[hide-ads] .main-topBar-UpgradeButton {\r\n display: none\r\n }\r\n `);\r\n document.styleSheets[0].insertRule(/* css */ `\r\n /* Remove upgrade to premium button in user menu */\r\n #main[hide-ads] .main-contextMenu-menuItemButton[href=\"https://www.spotify.com/premium/\"] {\r\n display: none\r\n }\r\n `);\r\n document.styleSheets[0].insertRule(/* css */ `\r\n /* Remove ad placeholder in main screen */\r\n #main[hide-ads] .main-leaderboardComponent-container {\r\n display: none\r\n }\r\n `);\r\n },\r\n onChange: (val) => document.getElementById(\"main\").toggleAttribute(\"hide-ads\", val)\r\n });\r\n});\r\n\r\nfunction waitForElement(els, func, timeout = 100) {\r\n const queries = els.map((el) => document.querySelector(el));\r\n if (queries.every((a) => a)) {\r\n func(queries);\r\n } else if (timeout > 0) {\r\n setTimeout(waitForElement, 300, els, func, --timeout);\r\n }\r\n}\r\n\r\nwaitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex=\"0\"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex=\"0\"] li`], ([root, firstItem]) => {\r\n const listElem = firstItem.parentElement;\r\n root.classList.add(\"dribs-playlist-list\");\r\n\r\n /** Replace Playlist name with their pictures */\r\n function loadPlaylistImage() {\r\n for (const item of listElem.children) {\r\n let link = item.querySelector(\"a\");\r\n if (!link) continue;\r\n\r\n let [_, app, uid] = link.pathname.split(\"/\");\r\n let uri;\r\n if (app === \"playlist\") {\r\n uri = Spicetify.URI.playlistV2URI(uid);\r\n } else if (app === \"folder\") {\r\n const base64 = localStorage.getItem(\"dribbblish:folder-image:\" + uid);\r\n let img = link.querySelector(\"img\");\r\n if (!img) {\r\n img = document.createElement(\"img\");\r\n img.classList.add(\"playlist-picture\");\r\n link.prepend(img);\r\n }\r\n img.src = base64 || \"/images/tracklist-row-song-fallback.svg\";\r\n continue;\r\n }\r\n\r\n Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, { policy: { picture: true } }).then((res) => {\r\n const meta = res.metadata;\r\n let img = link.querySelector(\"img\");\r\n if (!img) {\r\n img = document.createElement(\"img\");\r\n img.classList.add(\"playlist-picture\");\r\n link.prepend(img);\r\n }\r\n img.src = meta.picture || \"/images/tracklist-row-song-fallback.svg\";\r\n });\r\n }\r\n }\r\n\r\n DribbblishShared.loadPlaylistImage = loadPlaylistImage;\r\n loadPlaylistImage();\r\n\r\n new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true });\r\n});\r\n\r\nwaitForElement([\".main-rootlist-rootlist\", \".main-rootlist-wrapper > :nth-child(2) > :first-child\", \"#spicetify-show-list\"], ([rootlist]) => {\r\n function checkSidebarPlaylistScroll() {\r\n const topDist = rootlist.getBoundingClientRect().top - document.querySelector(\"#spicetify-show-list:not(:empty), .main-rootlist-wrapper > :nth-child(2) > :first-child\").getBoundingClientRect().top;\r\n const bottomDist = document.querySelector(\".main-rootlist-wrapper > :nth-child(2) > :last-child\").getBoundingClientRect().bottom - rootlist.getBoundingClientRect().bottom;\r\n\r\n rootlist.classList.remove(\"no-top-shadow\", \"no-bottom-shadow\");\r\n if (topDist < 10) rootlist.classList.add(\"no-top-shadow\");\r\n if (bottomDist < 10) rootlist.classList.add(\"no-bottom-shadow\");\r\n }\r\n checkSidebarPlaylistScroll();\r\n\r\n // Use Interval because scrolling takes a while and getBoundingClientRect() gets position at the moment of calling, so the interval keeps calling for 1s\r\n let c = 0;\r\n let interval;\r\n rootlist.addEventListener(\"wheel\", () => {\r\n checkSidebarPlaylistScroll();\r\n c = 0;\r\n if (interval == null)\r\n interval = setInterval(() => {\r\n if (c > 20) {\r\n clearInterval(interval);\r\n interval = null;\r\n return;\r\n }\r\n\r\n checkSidebarPlaylistScroll();\r\n c++;\r\n }, 50);\r\n });\r\n});\r\n\r\nwaitForElement([\".Root__main-view\"], ([mainView]) => {\r\n const shadow = document.createElement(\"div\");\r\n shadow.id = \"dribbblish-back-shadow\";\r\n mainView.prepend(shadow);\r\n});\r\n\r\nwaitForElement([\".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutResizer__resize-bar input\"], ([resizer]) => {\r\n const observer = new MutationObserver(updateVariable);\r\n observer.observe(resizer, { attributes: true, attributeFilter: [\"value\"] });\r\n function updateVariable() {\r\n let value = resizer.value;\r\n if (value < 121) {\r\n value = 72;\r\n document.documentElement.classList.add(\"sidebar-hide-text\");\r\n } else {\r\n document.documentElement.classList.remove(\"sidebar-hide-text\");\r\n }\r\n document.documentElement.style.setProperty(\"--sidebar-width\", value + \"px\");\r\n }\r\n updateVariable();\r\n});\r\n\r\nwaitForElement([\".Root__main-view .os-resize-observer-host\"], ([resizeHost]) => {\r\n const observer = new ResizeObserver(updateVariable);\r\n observer.observe(resizeHost);\r\n function updateVariable([event]) {\r\n document.documentElement.style.setProperty(\"--main-view-width\", event.contentRect.width + \"px\");\r\n document.documentElement.style.setProperty(\"--main-view-height\", event.contentRect.height + \"px\");\r\n if (event.contentRect.width < 700) {\r\n document.documentElement.classList.add(\"minimal-player\");\r\n } else {\r\n document.documentElement.classList.remove(\"minimal-player\");\r\n }\r\n if (event.contentRect.width < 550) {\r\n document.documentElement.classList.add(\"extra-minimal-player\");\r\n } else {\r\n document.documentElement.classList.remove(\"extra-minimal-player\");\r\n }\r\n }\r\n});\r\n\r\n(function Dribbblish() {\r\n const progBar = document.querySelector(\".playback-bar\");\r\n const root = document.querySelector(\".Root\");\r\n\r\n if (!Spicetify.Player.origin || !progBar || !root) {\r\n setTimeout(Dribbblish, 300);\r\n return;\r\n }\r\n\r\n const progKnob = progBar.querySelector(\".progress-bar__slider\");\r\n\r\n const tooltip = document.createElement(\"div\");\r\n tooltip.className = \"prog-tooltip\";\r\n progKnob.append(tooltip);\r\n\r\n function updateProgTime(timeOverride) {\r\n const newText = Spicetify.Player.formatTime(timeOverride || Spicetify.Player.getProgress()) + \" / \" + Spicetify.Player.formatTime(Spicetify.Player.getDuration());\r\n // To reduce DOM Updates when the Song is Paused\r\n if (tooltip.innerText != newText) tooltip.innerText = newText;\r\n }\r\n const knobPosObserver = new MutationObserver((muts) => {\r\n const progressPercentage = Number(getComputedStyle(document.querySelector(\".progress-bar\")).getPropertyValue(\"--progress-bar-transform\").replace(\"%\", \"\")) / 100;\r\n updateProgTime(Spicetify.Player.getDuration() * progressPercentage);\r\n });\r\n knobPosObserver.observe(document.querySelector(\".progress-bar\"), {\r\n attributes: true,\r\n attributeFilter: [\"style\"]\r\n });\r\n Spicetify.Player.addEventListener(\"songchange\", () => updateProgTime());\r\n updateProgTime();\r\n\r\n Spicetify.CosmosAsync.sub(\"sp://connect/v1\", (state) => {\r\n const isExternal = state.devices.some((a) => a.is_active);\r\n if (isExternal) {\r\n root.classList.add(\"is-connectBarVisible\");\r\n } else {\r\n root.classList.remove(\"is-connectBarVisible\");\r\n }\r\n });\r\n\r\n const filePickerForm = document.createElement(\"form\");\r\n filePickerForm.setAttribute(\"aria-hidden\", true);\r\n filePickerForm.innerHTML = '<input type=\"file\" class=\"hidden-visually\" />';\r\n document.body.appendChild(filePickerForm);\r\n /** @type {HTMLInputElement} */\r\n const filePickerInput = filePickerForm.childNodes[0];\r\n filePickerInput.accept = [\"image/jpeg\", \"image/apng\", \"image/avif\", \"image/gif\", \"image/png\", \"image/svg+xml\", \"image/webp\"].join(\",\");\r\n\r\n filePickerInput.onchange = () => {\r\n if (!filePickerInput.files.length) return;\r\n\r\n const file = filePickerInput.files[0];\r\n const reader = new FileReader();\r\n reader.onload = (event) => {\r\n const result = event.target.result;\r\n const id = Spicetify.URI.from(filePickerInput.uri).id;\r\n try {\r\n localStorage.setItem(\"dribbblish:folder-image:\" + id, result);\r\n } catch {\r\n Spicetify.showNotification(\"File too large\");\r\n }\r\n DribbblishShared.loadPlaylistImage?.call();\r\n };\r\n reader.readAsDataURL(file);\r\n };\r\n\r\n new Spicetify.ContextMenu.Item(\r\n \"Remove folder image\",\r\n ([uri]) => {\r\n const id = Spicetify.URI.from(uri).id;\r\n localStorage.removeItem(\"dribbblish:folder-image:\" + id);\r\n DribbblishShared.loadPlaylistImage?.call();\r\n },\r\n ([uri]) => Spicetify.URI.isFolder(uri),\r\n \"x\"\r\n ).register();\r\n new Spicetify.ContextMenu.Item(\r\n \"Choose folder image\",\r\n ([uri]) => {\r\n filePickerInput.uri = uri;\r\n filePickerForm.reset();\r\n filePickerInput.click();\r\n },\r\n ([uri]) => Spicetify.URI.isFolder(uri),\r\n \"edit\"\r\n ).register();\r\n})();\r\n\r\nlet current = \"2.6.0\";\r\n\r\n/* Config settings */\r\n\r\nDribbblishShared.config.register({\r\n area: \"Animations & Transitions\",\r\n type: \"slider\",\r\n key: \"fadeDuration\",\r\n name: \"Color Fade Duration\",\r\n description: \"Select the duration of the color fading transition\",\r\n defaultValue: 0.5,\r\n data: {\r\n min: 0,\r\n max: 10,\r\n step: 0.1,\r\n suffix: \"s\"\r\n },\r\n onChange: (val) => document.documentElement.style.setProperty(\"--song-transition-speed\", val + \"s\")\r\n});\r\n\r\n// waitForElement because Spicetify is not initialized at startup\r\nwaitForElement([\"#main\"], () => {\r\n DribbblishShared.config.register({\r\n area: { name: \"About\", order: 999 },\r\n type: \"button\",\r\n key: \"aboutDribbblish\",\r\n name: \"Info\",\r\n description: `\r\n OS: ${capitalizeFirstLetter(Spicetify.Platform.PlatformData.os_name)} v${Spicetify.Platform.PlatformData.os_version}\r\n Spotify: v${Spicetify.Platform.PlatformData.event_sender_context_information?.client_version_string ?? Spicetify.Platform.PlatformData.client_version_triple}\r\n Dribbblish: v${current}\r\n `,\r\n data: \"Copy\",\r\n onChange: (val) => {\r\n copyToClipboard(DribbblishShared.config.getOptions(\"aboutDribbblish\").description);\r\n Spicetify.showNotification(\"Copied Versions\");\r\n }\r\n });\r\n});\r\n\r\nfunction capitalizeFirstLetter(string) {\r\n return string.charAt(0).toUpperCase() + string.slice(1);\r\n}\r\n\r\nfunction copyToClipboard(text) {\r\n var input = document.createElement(\"textarea\");\r\n input.style.display = \"fixed\";\r\n input.innerHTML = text;\r\n document.body.appendChild(input);\r\n input.select();\r\n var result = document.execCommand(\"copy\");\r\n document.body.removeChild(input);\r\n return result;\r\n}\r\n\r\n/* js */\r\nfunction getAlbumInfo(uri) {\r\n return Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`);\r\n}\r\n\r\nfunction isLight(hex) {\r\n var [r, g, b] = hexToRgb(hex).map(Number);\r\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\r\n return brightness > 128;\r\n}\r\n\r\nfunction hexToRgb(hex) {\r\n var bigint = parseInt(hex.replace(\"#\", \"\"), 16);\r\n var r = (bigint >> 16) & 255;\r\n var g = (bigint >> 8) & 255;\r\n var b = bigint & 255;\r\n return [r, g, b];\r\n}\r\n\r\nfunction rgbToHex([r, g, b]) {\r\n const rgb = (r << 16) | (g << 8) | (b << 0);\r\n return \"#\" + (0x1000000 + rgb).toString(16).slice(1);\r\n}\r\n\r\nconst LightenDarkenColor = (h, p) =>\r\n \"#\" +\r\n [1, 3, 5]\r\n .map((s) => parseInt(h.substr(s, 2), 16))\r\n .map((c) => parseInt((c * (100 + p)) / 100))\r\n .map((c) => (c < 255 ? c : 255))\r\n .map((c) => c.toString(16).padStart(2, \"0\"))\r\n .join(\"\");\r\n\r\nfunction rgbToHsl([r, g, b]) {\r\n (r /= 255), (g /= 255), (b /= 255);\r\n var max = Math.max(r, g, b),\r\n min = Math.min(r, g, b);\r\n var h,\r\n s,\r\n l = (max + min) / 2;\r\n if (max == min) {\r\n h = s = 0; // achromatic\r\n } else {\r\n var d = max - min;\r\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\r\n switch (max) {\r\n case r:\r\n h = (g - b) / d + (g < b ? 6 : 0);\r\n break;\r\n case g:\r\n h = (b - r) / d + 2;\r\n break;\r\n case b:\r\n h = (r - g) / d + 4;\r\n break;\r\n }\r\n h /= 6;\r\n }\r\n return [h, s, l];\r\n}\r\n\r\nfunction hslToRgb([h, s, l]) {\r\n var r, g, b;\r\n if (s == 0) {\r\n r = g = b = l; // achromatic\r\n } else {\r\n function hue2rgb(p, q, t) {\r\n if (t < 0) t += 1;\r\n if (t > 1) t -= 1;\r\n if (t < 1 / 6) return p + (q - p) * 6 * t;\r\n if (t < 1 / 2) return q;\r\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\r\n return p;\r\n }\r\n var q = l < 0.5 ? l * (1 + s) : l + s - l * s;\r\n var p = 2 * l - q;\r\n r = hue2rgb(p, q, h + 1 / 3);\r\n g = hue2rgb(p, q, h);\r\n b = hue2rgb(p, q, h - 1 / 3);\r\n }\r\n return [r * 255, g * 255, b * 255];\r\n}\r\n\r\nfunction setLightness(hex, lightness) {\r\n hsl = rgbToHsl(hexToRgb(hex));\r\n hsl[2] = lightness;\r\n return rgbToHex(hslToRgb(hsl));\r\n}\r\n\r\nfunction parseComputedStyleColor(col) {\r\n if (col.startsWith(\"#\")) return col;\r\n if (col.startsWith(\"rgb(\"))\r\n return rgbToHex(\r\n col\r\n .replace(/rgb|(|)/g, \"\")\r\n .split(\",\")\r\n .map((part) => Number(part.trim()))\r\n );\r\n}\r\n\r\n// `parseComputedStyleColor()` beacuse \"--spice-sidebar\" is `rgb()`\r\nlet textColor = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue(\"--spice-text\"));\r\nlet textColorBg = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue(\"--spice-main\"));\r\nlet sidebarColor = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue(\"--spice-sidebar\"));\r\n\r\nfunction setRootColor(name, colHex) {\r\n let root = document.documentElement;\r\n if (root === null) return;\r\n root.style.setProperty(\"--spice-\" + name, colHex);\r\n root.style.setProperty(\"--spice-rgb-\" + name, hexToRgb(colHex).join(\",\"));\r\n}\r\n\r\nfunction toggleDark(setDark) {\r\n if (setDark === undefined) setDark = isLight(textColorBg);\r\n\r\n document.documentElement.style.setProperty(\"--is_light\", setDark ? 0 : 1);\r\n textColorBg = setDark ? \"#0A0A0A\" : \"#FAFAFA\";\r\n\r\n setRootColor(\"main\", textColorBg);\r\n setRootColor(\"player\", textColorBg);\r\n setRootColor(\"card\", setDark ? \"#040404\" : \"#ECECEC\");\r\n setRootColor(\"subtext\", setDark ? \"#EAEAEA\" : \"#3D3D3D\");\r\n setRootColor(\"notification\", setDark ? \"#303030\" : \"#DDDDDD\");\r\n\r\n updateColors(textColor, sidebarColor, false);\r\n}\r\n\r\nfunction checkDarkLightMode(colors) {\r\n const theme = DribbblishShared.config.get(\"theme\");\r\n if (theme == 2) {\r\n // Based on Time\r\n const start = 60 * parseInt(DribbblishShared.config.get(\"darkModeOnTime\").split(\":\")[0]) + parseInt(DribbblishShared.config.get(\"darkModeOnTime\").split(\":\")[1]);\r\n const end = 60 * parseInt(DribbblishShared.config.get(\"darkModeOffTime\").split(\":\")[0]) + parseInt(DribbblishShared.config.get(\"darkModeOffTime\").split(\":\")[1]);\r\n\r\n const now = new Date();\r\n const time = 60 * now.getHours() + now.getMinutes();\r\n\r\n if (end < start) dark = start <= time || time < end;\r\n else dark = start <= time && time < end;\r\n toggleDark(dark);\r\n } else if (theme == 3) {\r\n // Based on Color\r\n if (colors && colors.length > 0) toggleDark(isLight(colors[0]));\r\n }\r\n}\r\n// Run every Minute to check time and set dark / light mode\r\nsetInterval(checkDarkLightMode, 60000);\r\n\r\nDribbblishShared.config.register({\r\n area: \"Theme\",\r\n type: \"checkbox\",\r\n key: \"dynamicColors\",\r\n name: \"Dynamic\",\r\n description: \"If the Theme's Color should be extracted from Albumart\",\r\n defaultValue: true,\r\n onChange: (val) => updateColors(),\r\n showChildren: (val) => !val,\r\n children: [\r\n {\r\n type: \"color\",\r\n key: \"colorOverride\",\r\n name: \"Color\",\r\n description: \"The Color of the Theme\",\r\n defaultValue: \"#1ed760\",\r\n fireInitialChange: false,\r\n onChange: (val) => updateColors()\r\n }\r\n ]\r\n});\r\n\r\nDribbblishShared.config.register({\r\n area: \"Theme\",\r\n type: \"select\",\r\n data: [\"Dark\", \"Light\", \"Based on Time\", \"Based on Color\"],\r\n key: \"theme\",\r\n name: \"Theme\",\r\n description: \"Select Dark / Bright mode\",\r\n defaultValue: 0,\r\n showChildren: (val) => {\r\n if (val == 2) return [\"darkModeOnTime\", \"darkModeOffTime\"];\r\n //if (val == 3) return [\"\"];\r\n return false;\r\n },\r\n onChange: (val) => {\r\n switch (val) {\r\n case 0:\r\n toggleDark(true);\r\n break;\r\n case 1:\r\n toggleDark(false);\r\n break;\r\n case 2:\r\n checkDarkLightMode();\r\n break;\r\n case 3:\r\n checkDarkLightMode();\r\n break;\r\n }\r\n },\r\n children: [\r\n {\r\n type: \"time\",\r\n key: \"darkModeOnTime\",\r\n name: \"Dark Mode On Time\",\r\n description: \"Beginning of Dark mode time\",\r\n defaultValue: \"20:00\",\r\n fireInitialChange: false,\r\n onChange: checkDarkLightMode\r\n },\r\n {\r\n type: \"time\",\r\n key: \"darkModeOffTime\",\r\n name: \"Dark Mode Off Time\",\r\n description: \"End of Dark mode time\",\r\n defaultValue: \"06:00\",\r\n fireInitialChange: false,\r\n onChange: checkDarkLightMode\r\n }\r\n ]\r\n});\r\n\r\nvar currentColor;\r\nvar currentSideColor;\r\n\r\nfunction updateColors(textColHex, sideColHex, checkDarkMode = true) {\r\n if (textColHex && sideColHex) {\r\n currentColor = textColHex;\r\n currentSideColor = sideColHex;\r\n } else {\r\n if (!(currentColor && currentSideColor)) return; // If `updateColors()` is called early these vars are undefined and would break\r\n textColHex = currentColor;\r\n sideColHex = currentSideColor;\r\n }\r\n\r\n if (!DribbblishShared.config.get(\"dynamicColors\")) {\r\n const col = DribbblishShared.config.get(\"colorOverride\");\r\n textColHex = col;\r\n sideColHex = col;\r\n }\r\n\r\n let isLightBg = isLight(textColorBg);\r\n if (isLightBg) textColHex = LightenDarkenColor(textColHex, -15); // vibrant color is always too bright for white bg mode\r\n\r\n let darkColHex = LightenDarkenColor(textColHex, isLightBg ? 12 : -20);\r\n let darkerColHex = LightenDarkenColor(textColHex, isLightBg ? 30 : -40);\r\n let buttonBgColHex = setLightness(textColHex, isLightBg ? 0.9 : 0.14);\r\n setRootColor(\"text\", textColHex);\r\n setRootColor(\"button\", darkerColHex);\r\n setRootColor(\"button-active\", darkColHex);\r\n setRootColor(\"selected-row\", darkerColHex);\r\n setRootColor(\"tab-active\", buttonBgColHex);\r\n setRootColor(\"button-disabled\", buttonBgColHex);\r\n setRootColor(\"sidebar\", sideColHex);\r\n\r\n if (checkDarkMode) checkDarkLightMode([textColHex, sideColHex]);\r\n}\r\n\r\nlet nearArtistSpanText = \"\";\r\nlet coverListenerInstalled = true;\r\nasync function songchange() {\r\n try {\r\n // warning popup\r\n if (Spicetify.Platform.PlatformData.client_version_triple < \"1.1.68\") Spicetify.showNotification(`Your version of Spotify ${Spicetify.Platform.PlatformData.client_version_triple}) is un-supported`);\r\n } catch (err) {\r\n console.error(err);\r\n }\r\n\r\n let album_uri = Spicetify.Player.data.track.metadata.album_uri;\r\n let bgImage = Spicetify.Player.data.track.metadata.image_url;\r\n if (bgImage === undefined) {\r\n bgImage = \"/images/tracklist-row-song-fallback.svg\";\r\n textColor = \"#509bf5\";\r\n updateColors(textColor, textColor);\r\n coverListenerInstalled = false;\r\n }\r\n if (!coverListenerInstalled) hookCoverChange(true);\r\n\r\n if (album_uri !== undefined && !album_uri.includes(\"spotify:show\")) {\r\n const albumInfo = await getAlbumInfo(album_uri.replace(\"spotify:album:\", \"\"));\r\n\r\n let album_date = new Date(albumInfo.year, (albumInfo.month || 1) - 1, albumInfo.day || 0);\r\n let recent_date = new Date();\r\n recent_date.setMonth(recent_date.getMonth() - 6);\r\n album_date = album_date.toLocaleString(\"default\", album_date > recent_date ? { year: \"numeric\", month: \"short\" } : { year: \"numeric\" });\r\n album_link = '<a title=\"' + Spicetify.Player.data.track.metadata.album_title + '\" href=\"' + album_uri + '\" data-uri=\"' + album_uri + '\" data-interaction-target=\"album-name\" class=\"tl-cell__content\">' + Spicetify.Player.data.track.metadata.album_title + \"</a>\";\r\n\r\n nearArtistSpanText = album_link + \" • \" + album_date;\r\n } else if (Spicetify.Player.data.track.uri.includes(\"spotify:episode\")) {\r\n // podcast\r\n bgImage = bgImage.replace(\"spotify:image:\", \"https://i.scdn.co/image/\");\r\n nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title;\r\n } else if (Spicetify.Player.data.track.metadata.is_local == \"true\") {\r\n // local file\r\n nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title;\r\n } else if (Spicetify.Player.data.track.provider == \"ad\") {\r\n // ad\r\n nearArtistSpanText = \"advertisement\";\r\n coverListenerInstalled = false;\r\n return;\r\n } else {\r\n // When clicking a song from the homepage, songChange is fired with half empty metadata\r\n // todo: retry only once?\r\n setTimeout(songchange, 200);\r\n }\r\n\r\n if (document.querySelector(\"#main-trackInfo-year\") === null) {\r\n waitForElement([\".main-trackInfo-container\"], (queries) => {\r\n nearArtistSpan = document.createElement(\"div\");\r\n nearArtistSpan.id = \"main-trackInfo-year\";\r\n nearArtistSpan.classList.add(\"main-trackInfo-artists\", \"ellipsis-one-line\", \"main-type-finale\");\r\n nearArtistSpan.innerHTML = nearArtistSpanText;\r\n queries[0].append(nearArtistSpan);\r\n });\r\n } else {\r\n nearArtistSpan.innerHTML = nearArtistSpanText;\r\n }\r\n document.documentElement.style.setProperty(\"--image_url\", 'url(\"' + bgImage + '\")');\r\n}\r\n\r\nSpicetify.Player.addEventListener(\"songchange\", songchange);\r\n\r\nfunction pickCoverColor(img) {\r\n if (!img.currentSrc.startsWith(\"spotify:\")) return;\r\n var swatches = new Vibrant(img, 5).swatches();\r\n lightCols = [\"Vibrant\", \"DarkVibrant\", \"Muted\", \"LightVibrant\"];\r\n darkCols = [\"Vibrant\", \"LightVibrant\", \"Muted\", \"DarkVibrant\"];\r\n\r\n mainCols = isLight(textColorBg) ? lightCols : darkCols;\r\n textColor = \"#509bf5\";\r\n for (var col in mainCols)\r\n if (swatches[mainCols[col]]) {\r\n textColor = swatches[mainCols[col]].getHex();\r\n break;\r\n }\r\n\r\n sidebarColor = \"#509bf5\";\r\n for (var col in lightCols)\r\n if (swatches[lightCols[col]]) {\r\n sidebarColor = swatches[lightCols[col]].getHex();\r\n break;\r\n }\r\n updateColors(textColor, sidebarColor);\r\n}\r\n\r\nwaitForElement([\".main-nowPlayingBar-left\"], (queries) => {\r\n var observer = new MutationObserver(function (mutations) {\r\n mutations.forEach(function (mutation) {\r\n if (mutation.removedNodes.length > 0) coverListenerInstalled = false;\r\n });\r\n });\r\n observer.observe(queries[0], { childList: true });\r\n});\r\n\r\nfunction hookCoverChange(pick) {\r\n waitForElement([\".cover-art-image\"], (queries) => {\r\n coverListenerInstalled = true;\r\n if (pick && queries[0].complete && queries[0].naturalHeight !== 0) pickCoverColor(queries[0]);\r\n queries[0].addEventListener(\"load\", function () {\r\n try {\r\n pickCoverColor(queries[0]);\r\n } catch (error) {\r\n console.error(error);\r\n setTimeout(pickCoverColor, 300, queries[0]);\r\n }\r\n });\r\n });\r\n}\r\n\r\nhookCoverChange(false);\r\n\r\n(function Startup() {\r\n if (!Spicetify.showNotification) {\r\n setTimeout(Startup, 300);\r\n return;\r\n }\r\n // Check latest release\r\n fetch(\"https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest\")\r\n .then((response) => {\r\n return response.json();\r\n })\r\n .then((data) => {\r\n if (data.tag_name > current) {\r\n upd = document.createElement(\"div\");\r\n upd.innerText = `Theme UPD v${data.tag_name} avail.`;\r\n upd.classList.add(\"ellipsis-one-line\", \"main-type-finale\");\r\n upd.setAttribute(\"title\", `Changes: ${data.name}`);\r\n upd.style.setProperty(\"color\", \"var(--spice-button-active)\");\r\n document.querySelector(\".main-userWidget-box\").append(upd);\r\n document.querySelector(\".main-userWidget-box\").classList.add(\"update-avail\");\r\n new Spicetify.Menu.Item(\"Update Dribbblish\", false, () => window.open(\"https://github.com/JulienMaille/dribbblish-dynamic-theme/blob/main/README.md#install--update\", \"_blank\")).register();\r\n }\r\n })\r\n .catch((err) => {\r\n // Do something for an error here\r\n console.error(err);\r\n });\r\n})();\r\n\r\ndocument.documentElement.style.setProperty(\"--warning_message\", \" \");\r\n\n\n//# sourceURL=webpack:///./src/js/main.js?");
/***/ })
/******/ });
/************************************************************************/
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/global */
/******/ (() => {
/******/ __webpack_require__.g = (function() {
/******/ if (typeof globalThis === 'object') return globalThis;
/******/ try {
/******/ return this || new Function('return this')();
/******/ } catch (e) {
/******/ if (typeof window === 'object') return window;
/******/ }
/******/ })();
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/publicPath */
/******/ (() => {
/******/ var scriptUrl;
/******/ if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
/******/ var document = __webpack_require__.g.document;
/******/ if (!scriptUrl && document) {
/******/ if (document.currentScript)
/******/ scriptUrl = document.currentScript.src
/******/ if (!scriptUrl) {
/******/ var scripts = document.getElementsByTagName("script");
/******/ if(scripts.length) scriptUrl = scripts[scripts.length - 1].src
/******/ }
/******/ }
/******/ // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
/******/ // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
/******/ if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
/******/ scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
/******/ __webpack_require__.p = scriptUrl;
/******/ })();
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
/******/ __webpack_modules__["./src/js/main.js"](0, {}, __webpack_require__);
/******/ var __webpack_exports__ = {};
/******/ __webpack_modules__["./src/styles/main.scss"](0, __webpack_exports__, __webpack_require__);
/******/
/******/ })()
;