diff --git a/dribbblish-dynamic.js b/dribbblish-dynamic.js index 4c5713c..2571c34 100644 --- a/dribbblish-dynamic.js +++ b/dribbblish-dynamic.js @@ -178,7 +178,7 @@ let systemDark = parseInt(getComputedStyle(document.documentElement).getProperty DribbblishShared.config.register({ type: "select", - options: ["System", "Dark", "Light"], + data: ["System", "Dark", "Light"], key: "theme", name: "Theme", description: "Select Dark / Bright mode", @@ -325,19 +325,13 @@ hookCoverChange(false); }).then(data => { if (data.tag_name > current) { upd = document.createElement("div") + upd.innerText = `Theme UPD v${data.tag_name} avail.` upd.classList.add("ellipsis-one-line", "main-type-finale") - document.querySelector(".main-userWidget-box").append(upd) - upd.append(`Theme UPD v${data.tag_name} avail.`) upd.setAttribute("title", `Changes: ${data.name}`) - DribbblishShared.config.register({ - insertOnTop: true, - type: "button", - name: "Update", - description: "Open the GitHub Page with Installation instructions / Commands.", - onChange: () => { - window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme#install", "_blank"); - } - }); + upd.style.setProperty("color", "var(--spice-main)"); + document.querySelector(".main-userWidget-box").append(upd) + document.querySelector(".main-userWidget-box").classList.add("update-avail") + new Spicetify.Menu.Item("Update Dribbblish", false, () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme#install", "_blank")).register(); } }).catch(err => { // Do something for an error here @@ -346,23 +340,25 @@ hookCoverChange(false); })() /* translucid background cover */ -document.styleSheets[0].addRule('.Root__top-container::before', -` content: ''; - background-image: var(--image_url); - background-repeat: no-repeat; - background-size: cover; - background-position: center center; - position: fixed; - display: block; - top: 0; - left: 0; - right: 0; - bottom: 0; - filter: blur(15px); - pointer-events: none; - backface-visibility: hidden; - will-change: transform; - opacity: calc(0.07 + 0.03 * var(--is_light, 0)); - z-index: +3;`) +document.styleSheets[0].insertRule(` + .Root__top-container::before { + content: ''; + background-image: var(--image_url); + background-repeat: no-repeat; + background-size: cover; + background-position: center center; + position: fixed; + display: block; + top: 0; + left: 0; + right: 0; + bottom: 0; + filter: blur(15px); + pointer-events: none; + backface-visibility: hidden; + will-change: transform; + opacity: calc(0.07 + 0.03 * var(--is_light, 0)); + z-index: +3; + }`) document.documentElement.style.setProperty('--warning_message', ' '); diff --git a/dribbblish.js b/dribbblish.js index 9c57af4..392b513 100644 --- a/dribbblish.js +++ b/dribbblish.js @@ -1,6 +1,20 @@ // Hide popover message // document.getElementById("popover-container").style.height = 0; class ConfigMenu { + /** + * @typedef {Object} DribbblishConfigOptions + * @property {"checkbox" | "select" | "button" | "slider" | "number" | "text"} type + * @property {String?} area + * @property {any?} data + * @property {String?} key + * @property {String?} name + * @property {String?} description + * @property {any?} defaultValue + * @property {Boolean?} insertOnTop + * @property {Function?} onAppended + * @property {Function?} onChange + */ + constructor() { this.config = {}; this.configButton = new Spicetify.Menu.Item("Dribbblish config", false, () => DribbblishShared.config.open()); @@ -14,6 +28,7 @@ class ConfigMenu {

Dribbblish Settings

+
`; @@ -31,79 +46,164 @@ class ConfigMenu { document.getElementById("dribbblish-config").removeAttribute("active"); } - /** @private */ - addInputHTML({ type, key, name, description, input, insertOnTop }) { + /** + * @private + * @param {DribbblishConfigOptions} options + */ + addInputHTML(options) { + let parent; + if (options.area != null) { + if (!document.querySelector(`.dribbblish-config-area[name="${options.area}"]`)) { + const areaElem = document.createElement("div"); + areaElem.classList.add("dribbblish-config-area"); + areaElem.setAttribute("name", options.area); + areaElem.innerHTML = `

${options.area}

`; + document.querySelector(".dribbblish-config-items").appendChild(areaElem); + } + parent = document.querySelector(`.dribbblish-config-area[name="${options.area}"]`); + } else { + parent = document.querySelector(".dribbblish-config-items"); + } + const elem = document.createElement("div"); elem.classList.add("dribbblish-config-item"); - elem.setAttribute("key", `dribbblish:config:${key}`); - elem.setAttribute("type", type); + elem.setAttribute("key", `dribbblish:config:${options.key}`); + elem.setAttribute("type", options.type); elem.innerHTML = /* html */ ` -

${name}

- +

${options.name}

+ `; - if (insertOnTop && document.querySelector(".dribbblish-config-item")) { - console.log("before"); - document.querySelector(".dribbblish-config-container").insertBefore(elem, document.querySelector(".dribbblish-config-item:first-of-type")); + + if (options.insertOnTop && parent.children.length > 0) { + parent.insertBefore(elem, parent.children[0]); } else { - document.querySelector(".dribbblish-config-container").appendChild(elem); + parent.appendChild(elem); } } - register({ type, options, key, name, description, defaultValue, insertOnTop, onChange }) { - if (!key) key = cyrb53Hash(name); + /** + * @param {DribbblishConfigOptions} options + */ + register(options) { + options = { + ...{ + area: "Main Settings", + data: {}, + key: cyrb53Hash(options.name ?? ""), + name: "", + description: "", + insertOnTop: false, + onAppended: () => {}, + onChange: () => {} + }, + ...options + }; var fireChange = true; - if (type == "checkbox") { + if (options.type == "checkbox") { const input = /* html */ ` - + `; - this.addInputHTML({ type, key, name, description, input, insertOnTop }); + this.addInputHTML({ ...options, input }); - document.getElementById(`dribbblish-config-input-${key}`).addEventListener("change", (e) => { - this.set(key, e.target.checked); - onChange(this.get(key)); + document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => { + this.set(options.key, e.target.checked); + options.onChange(this.get(options.key)); }); - } else if (type == "select") { + } else if (options.type == "select") { const input = /* html */ ` - - - + `; - this.addInputHTML({ type, key, name, description, input, insertOnTop }); + this.addInputHTML({ ...options, input }); - document.getElementById(`dribbblish-config-input-${key}`).addEventListener("change", (e) => { - this.set(key, e.target.value); - onChange(this.get(key)); + document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => { + this.set(options.key, e.target.value); + options.onChange(this.get(options.key)); }); - } else if (type == "button") { + } else if (options.type == "button") { const input = /* html */ ` - - - + `; - this.addInputHTML({ type, key, name, description, input, insertOnTop }); + this.addInputHTML({ ...options, input }); - document.getElementById(`dribbblish-config-input-${key}`).addEventListener("click", (e) => { - onChange(true); + document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("click", (e) => { + options.onChange(true); }); fireChange = false; + } else if (options.type == "number") { + if (options.defaultValue == null) options.defaultValue = 0; + + const input = /* html */ ` + + `; + this.addInputHTML({ ...options, input }); + + // Prevent inputting +, - and e. Why is it even possible in the first place? + document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("keypress", (e) => { + if (["+", "-", "e"].includes(e.key)) e.preventDefault(); + }); + + document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => { + if (options.data.min != null && e.target.value < options.data.min) e.target.value = options.data.min; + if (options.data.max != null && e.target.value > options.data.max) e.target.value = options.data.max; + + this.set(options.key, e.target.value); + options.onChange(this.get(options.key)); + }); + } else if (options.type == "text") { + if (options.defaultValue == null) options.defaultValue = ""; + + const input = /* html */ ` + + `; + this.addInputHTML({ ...options, input }); + + document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => { + // TODO: maybe add an validation function via `data.validate` + this.set(options.key, e.target.value); + options.onChange(this.get(options.key)); + }); + } else if (options.type == "slider") { + if (options.defaultValue == null) options.defaultValue = 0; + + const input = /* html */ ` + + `; + this.addInputHTML({ ...options, input }); + + document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("input", (e) => { + document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("tooltip", `${e.target.value}${options.data?.suffix ?? ""}`); + document.getElementById(`dribbblish-config-input-${options.key}`).setAttribute("value", e.target.value); + this.set(options.key, e.target.value); + options.onChange(e.target.value); + }); } else { - throw new Error(`Config Type "${type}" invalid`); + throw new Error(`Config Type "${options.type}" invalid`); } - if (fireChange) onChange(this.get(key, defaultValue)); + options.onAppended(); + if (fireChange) options.onChange(this.get(options.key, options.defaultValue)); } get(key, defaultValue) { @@ -111,7 +211,8 @@ class ConfigMenu { if (val == null) return defaultValue; if (val == "true" || val == "false") return val == "true"; // Boolean - if (!isNaN(val) && !isNaN(parseInt(val))) return parseInt(val); // Number + if (!isNaN(val) && /\d+\.\d+/.test(val) && !isNaN(parseFloat(val))) return parseFloat(val); // Float + if (!isNaN(val) && /\d+/.test(val) && !isNaN(parseInt(val))) return parseInt(val); // Int return val; // String } @@ -131,7 +232,7 @@ DribbblishShared.config.register({ type: "checkbox", key: "rightBigCover", name: "Right expanded cover", - description: "Have the expanded cover Image on the right instead of onn the left.", + description: "Have the expanded cover Image on the right instead of on the left.", defaultValue: true, onChange: (val) => { if (val) { @@ -160,7 +261,7 @@ DribbblishShared.config.register({ waitForElement(["#main"], () => { DribbblishShared.config.register({ type: "select", - options: ["None", "None (With Top Padding)", "Solid", "Transparent"], + data: ["None", "None (With Top Padding)", "Solid", "Transparent"], key: "winTopBar", name: "Windows Top Bar", description: "Have differennt top Bars (Ore none at all)", @@ -206,10 +307,7 @@ function cyrb53Hash(str, seed = 0) { return 4294967296 * (2097151 & h2) + (h1 >>> 0); } -waitForElement([ - `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, - `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li` -], ([root, firstItem]) => { +waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`], ([root, firstItem]) => { const listElem = firstItem.parentElement; root.classList.add("dribs-playlist-list"); @@ -219,7 +317,7 @@ waitForElement([ let link = item.querySelector("a"); if (!link) continue; - let [_, app, uid ] = link.pathname.split("/"); + let [_, app, uid] = link.pathname.split("/"); let uri; if (app === "playlist") { uri = Spicetify.URI.playlistV2URI(uid); @@ -231,14 +329,11 @@ waitForElement([ img.classList.add("playlist-picture"); link.prepend(img); } - img.src = base64 || "/images/tracklist-row-song-fallback.svg"; + img.src = base64 || "/images/tracklist-row-song-fallback.svg"; continue; } - Spicetify.CosmosAsync.get( - `sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, - { policy: { picture: true } } - ).then(res => { + Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, { policy: { picture: true } }).then((res) => { const meta = res.metadata; let img = link.querySelector("img"); if (!img) { @@ -254,13 +349,12 @@ waitForElement([ DribbblishShared.loadPlaylistImage = loadPlaylistImage; loadPlaylistImage(); - new MutationObserver(loadPlaylistImage) - .observe(listElem, {childList: true}); + new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true }); }); -waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child"], ([rootlist]) => { +waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => { function checkSidebarPlaylistScroll() { - const topDist = rootlist.getBoundingClientRect().top - document.querySelector(".main-rootlist-wrapper > :nth-child(2) > :first-child").getBoundingClientRect().top; + const topDist = rootlist.getBoundingClientRect().top - document.querySelector("#spicetify-show-list:not(:empty), .main-rootlist-wrapper > :nth-child(2) > :first-child").getBoundingClientRect().top; const bottomDist = document.querySelector(".main-rootlist-wrapper > :nth-child(2) > :last-child").getBoundingClientRect().bottom - rootlist.getBoundingClientRect().bottom; rootlist.classList.remove("no-top-shadow", "no-bottom-shadow"); diff --git a/user.css b/user.css index 368c0ce..fb06482 100644 --- a/user.css +++ b/user.css @@ -91,12 +91,81 @@ body { input { background-color: unset !important; - border-bottom: solid 1px var(--spice-text) !important; border-radius: 0 !important; padding: 6px 10px 6px 48px; color: var(--spice-text) !important; } +input[type=range] { + -webkit-appearance: none; + background: transparent; + padding: 0px; +} + +input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + margin-top: -4px; + border-radius: 50%; + background-color: var(--spice-text); +} + +input[type=range]::-webkit-slider-thumb:hover, +input[type=range]::-webkit-slider-thumb:active { + filter: brightness(80%); +} + +input[type=range]::after { + z-index: 9999; + content: attr(tooltip); + position: absolute; + min-width: 50px; + top: -10px; + left: 50%; + transform: translateX(calc(-50%)); + padding: 0 5px; + border-radius: 4px; + text-align: center; + color: var(--spice-sidebar-text); + background-color: var(--spice-button); + transition: opacity 0.25s ease; + opacity: 0; +} + +input[type=range]:hover::after, +input[type=range]:active::after { + opacity: 1; +} + +input[type=range]:focus { + outline: none; +} + +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 8px; + background-color: rgba(var(--spice-rgb-text), .2); + border-radius: 50vw; +} + +input[type=number], +input[type=text] { + height: 32px; + border: none; + border-radius: 4px !important; + padding: 0px 10px; + background-color: rgba(var(--spice-rgb-selected-row), .4) !important; + color: var(--spice-subtext) !important; +} + +input[type=number]:hover, +input[type=number]:active, +input[type=text]:hover, +input[type=text]:active { + background-color: rgba(var(--spice-rgb-selected-row), .6) !important; +} + .x-searchInput-searchInputSearchIcon, .x-searchInput-searchInputClearButton { color: var(--spice-text) !important; @@ -717,10 +786,11 @@ li.GlueDropTarget { z-index: 1; position: relative; max-width: 80%; - background-color: rgba(var(--spice-rgb-main), 0.85); + background-color: rgba(var(--spice-rgb-main), 0.9); backdrop-filter: blur(3px); padding: 20px 15px; border-radius: var(--main-corner-radius); + box-shadow: 0 0 10px 3px #0000003b; display: flex; gap: 5px; flex-direction: column; @@ -728,10 +798,21 @@ li.GlueDropTarget { justify-content: center; } +#dribbblish-config .dribbblish-config-items { + max-height: 60vh; + overflow-y: auto; + padding: 0px 50px; +} + +.dribbblish-config-area > h2 { + text-align: center; +} + #dribbblish-config .dribbblish-config-item { position: relative; width: 100%; - padding: 0px 50px; + padding: 5px 0px; + height: min-content; display: grid; grid-template-columns: 1fr auto; grid-template-rows: 1fr 1fr; @@ -743,10 +824,19 @@ li.GlueDropTarget { #dribbblish-config .dribbblish-config-item > .x-settings-title { grid-area: header; + margin: 0px; + height: min-content; + position: relative; + bottom: 0px; +} + +#dribbblish-config .dribbblish-config-item > .x-settings-title.no-desc { + bottom: -10px; } #dribbblish-config .dribbblish-config-item > .main-type-mesto { grid-area: description; + height: min-content; } #dribbblish-config .dribbblish-config-item > .x-settings-secondColumn {