update config to support areas / categories, number, text and slider inputs

This commit is contained in:
Send_Nukez 2021-10-08 06:05:38 +02:00
parent 14a0c60429
commit 5dbd1afbd7
3 changed files with 272 additions and 92 deletions

View file

@ -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', ' ');

View file

@ -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 {
<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>
</button>
<h1>Dribbblish Settings</h1>
<div class="dribbblish-config-items"></div>
</div>
<div class="dribbblish-config-backdrop"></div>
`;
@ -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 = `<h2>${options.area}</h2>`;
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 */ `
<h2 class="x-settings-title main-type-cello" as="h2">${name}</h2>
<label class="main-type-mesto" as="label" for="dribbblish-config-input-${key}" style="color: var(--spice-subtext);">${description}</label>
<h2 class="x-settings-title main-type-cello${!options.description ? " no-desc" : ""}" as="h2">${options.name}</h2>
<label class="main-type-mesto" as="label" for="dribbblish-config-input-${options.key}" style="color: var(--spice-subtext);">${options.description}</label>
<label class="x-toggle-wrapper x-settings-secondColumn">
${input}
${options.input}
</label>
`;
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 */ `
<input id="dribbblish-config-input-${key}" class="x-toggle-input" type="checkbox"${this.get(key, defaultValue) ? " checked" : ""}>
<input id="dribbblish-config-input-${options.key}" class="x-toggle-input" type="checkbox"${this.get(options.key, options.defaultValue) ? " checked" : ""}>
<span class="x-toggle-indicatorWrapper">
<span class="x-toggle-indicator"></span>
</span>
`;
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 */ `
<span class="x-settings-secondColumn">
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${key}">
${options.map((option, i) => `<option value="${i}"${this.get(key, defaultValue) == i ? " selected" : ""}>${option}</option>`).join("")}
</select>
</span>
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}">
${options.data.map((option, i) => `<option value="${i}"${this.get(options.key, options.defaultValue) == i ? " selected" : ""}>${option}</option>`).join("")}
</select>
`;
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 */ `
<span class="x-settings-secondColumn">
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${key}">
<div class="x-settings-buttonContainer">
<span>${name}</span>
</div>
</button>
</span>
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${options.key}">
<div class="x-settings-buttonContainer">
<span>${options.name}</span>
</div>
</button>
`;
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 */ `
<input type="number" id="dribbblish-config-input-${options.key}" value="${this.get(options.key, options.defaultValue)}">
`;
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 */ `
<input type="text" id="dribbblish-config-input-${options.key}" value="${this.get(options.key, options.defaultValue)}">
`;
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 */ `
<input
type="range"
id="dribbblish-config-input-${options.key}"
name="${options.name}"
min="${options.data?.min ?? "0"}"
max="${options.data?.max ?? "100"}"
step="${options.data?.step ?? "1"}"
value="${this.get(options.key, options.defaultValue)}"
tooltip="${this.get(options.key, options.defaultValue)}${options.data?.suffix ?? ""}"
>
`;
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");

View file

@ -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 {