mirror of
https://github.com/danbulant/dribbblish-dynamic-theme
synced 2026-06-06 08:11:39 +00:00
Merge branch 'main' into patch-1
This commit is contained in:
commit
3766db4ab4
4 changed files with 1507 additions and 1192 deletions
|
|
@ -23,7 +23,7 @@ Right click at folder and choose images for your playlist folder. Every image fo
|
||||||
### Left/Right expanded cover
|
### Left/Right expanded cover
|
||||||
In profile menu, toggle option "Right expanded cover" to change expaned current track cover image to left or right side, whereever you prefer.
|
In profile menu, toggle option "Right expanded cover" to change expaned current track cover image to left or right side, whereever you prefer.
|
||||||
|
|
||||||
## Install
|
## Install / Update
|
||||||
Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67.
|
Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67.
|
||||||
|
|
||||||
Run these commands:
|
Run these commands:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
let current = '2.3'
|
let current = '2.4.3'
|
||||||
|
|
||||||
/* css is injected so this works with untouched user.css from Dribbblish */
|
/* css is injected so this works with untouched user.css from Dribbblish */
|
||||||
/* dark theme */
|
/* dark theme */
|
||||||
|
|
@ -71,6 +71,25 @@ document.styleSheets[0].insertRule(`
|
||||||
color: var(--spice-sidebar-text) !important;
|
color: var(--spice-sidebar-text) !important;
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
|
/* Config settings */
|
||||||
|
|
||||||
|
DribbblishShared.config.register({
|
||||||
|
type: "slider",
|
||||||
|
data: {
|
||||||
|
"min": 0,
|
||||||
|
"max": 10,
|
||||||
|
"step": 0.1,
|
||||||
|
"suffix": "s"
|
||||||
|
},
|
||||||
|
key: "fadeDuration",
|
||||||
|
name: "Color Fade Duration",
|
||||||
|
description: "Select the duration of the color fading transition",
|
||||||
|
defaultValue: 0.5,
|
||||||
|
onChange: (val) => {
|
||||||
|
document.documentElement.style.setProperty("--song-transition-speed", val+"s");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* js */
|
/* js */
|
||||||
function getAlbumInfo(uri) {
|
function getAlbumInfo(uri) {
|
||||||
return Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`)
|
return Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`)
|
||||||
|
|
@ -170,7 +189,7 @@ function toggleDark(setDark) {
|
||||||
setRootColor('subtext', setDark ? "#EAEAEA" : "#3D3D3D")
|
setRootColor('subtext', setDark ? "#EAEAEA" : "#3D3D3D")
|
||||||
setRootColor('notification', setDark ? "#303030" : "#DDDDDD")
|
setRootColor('notification', setDark ? "#303030" : "#DDDDDD")
|
||||||
|
|
||||||
updateColors(textColor, sidebarColor)
|
updateColors(textColor, sidebarColor, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init with current system light/dark mode */
|
/* Init with current system light/dark mode */
|
||||||
|
|
@ -178,7 +197,7 @@ let systemDark = parseInt(getComputedStyle(document.documentElement).getProperty
|
||||||
|
|
||||||
DribbblishShared.config.register({
|
DribbblishShared.config.register({
|
||||||
type: "select",
|
type: "select",
|
||||||
options: ["System", "Dark", "Light"],
|
data: ["System", "Dark", "Light"],
|
||||||
key: "theme",
|
key: "theme",
|
||||||
name: "Theme",
|
name: "Theme",
|
||||||
description: "Select Dark / Bright mode",
|
description: "Select Dark / Bright mode",
|
||||||
|
|
@ -198,20 +217,69 @@ DribbblishShared.config.register({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateColors(textColHex, sideColHex) {
|
var currentColor;
|
||||||
let isLightBg = isLight(textColorBg)
|
var currentSideColor;
|
||||||
if (isLightBg) textColHex = LightenDarkenColor(textColHex, -15) // vibrant color is always too bright for white bg mode
|
var colorFadeInterval = false;
|
||||||
|
|
||||||
let darkColHex = LightenDarkenColor(textColHex, isLightBg ? 12 : -20)
|
function updateColors(textColHex, sideColHex, animate=false) {
|
||||||
let darkerColHex = LightenDarkenColor(textColHex, isLightBg ? 30 : -40)
|
let update = (textColHex, sideColHex) => {
|
||||||
let buttonBgColHex = setLightness(textColHex, isLightBg ? 0.90 : 0.14)
|
currentColor = textColHex;
|
||||||
setRootColor('text', textColHex)
|
currentSideColor = sideColHex;
|
||||||
setRootColor('button', darkerColHex)
|
|
||||||
setRootColor('button-active', darkColHex)
|
let isLightBg = isLight(textColorBg)
|
||||||
setRootColor('selected-row', darkerColHex)
|
if (isLightBg) textColHex = LightenDarkenColor(textColHex, -15) // vibrant color is always too bright for white bg mode
|
||||||
setRootColor('tab-active', buttonBgColHex)
|
|
||||||
setRootColor('button-disabled', buttonBgColHex)
|
let darkColHex = LightenDarkenColor(textColHex, isLightBg ? 12 : -20)
|
||||||
setRootColor('sidebar', sideColHex)
|
let darkerColHex = LightenDarkenColor(textColHex, isLightBg ? 30 : -40)
|
||||||
|
let buttonBgColHex = setLightness(textColHex, isLightBg ? 0.90 : 0.14)
|
||||||
|
setRootColor('text', textColHex)
|
||||||
|
setRootColor('button', darkerColHex)
|
||||||
|
setRootColor('button-active', darkColHex)
|
||||||
|
setRootColor('selected-row', darkerColHex)
|
||||||
|
setRootColor('tab-active', buttonBgColHex)
|
||||||
|
setRootColor('button-disabled', buttonBgColHex)
|
||||||
|
setRootColor('sidebar', sideColHex)
|
||||||
|
};
|
||||||
|
|
||||||
|
clearInterval(colorFadeInterval); // clear any interval that might be running
|
||||||
|
|
||||||
|
if(!animate) {
|
||||||
|
update(textColHex, sideColHex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clamp = (num,min,max) => Math.min(Math.max(num, min), max);
|
||||||
|
let lerp = (a,b,u) => (1-u) * a + u * b;
|
||||||
|
let toMS = s => parseFloat(s) * (/\ds$/.test(s) ? 1000 : 1);
|
||||||
|
|
||||||
|
let interval = 10; // 10 ms between each call
|
||||||
|
var duration = toMS(getComputedStyle(document.documentElement).getPropertyValue("--song-transition-speed"));
|
||||||
|
if(duration < 1) duration = 1;
|
||||||
|
let startC1 = hexToRgb(currentColor);
|
||||||
|
let startC2 = hexToRgb(currentSideColor);
|
||||||
|
|
||||||
|
let endC1 = hexToRgb(textColHex);
|
||||||
|
let endC2 = hexToRgb(sideColHex);
|
||||||
|
|
||||||
|
var start;
|
||||||
|
|
||||||
|
colorFadeInterval = setInterval(function(){
|
||||||
|
if(!start) { start = performance.now() }
|
||||||
|
let elapsed = performance.now()-start;
|
||||||
|
let ratio = clamp(elapsed/duration, 0, 1)
|
||||||
|
|
||||||
|
let currentC1 = [];
|
||||||
|
let currentC2 = [];
|
||||||
|
for(var i = 0; i < 3; i++){
|
||||||
|
currentC1[i] = lerp(startC1[i], endC1[i], ratio);
|
||||||
|
currentC2[i] = lerp(startC2[i], endC2[i], ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(rgbToHex(currentC1), rgbToHex(currentC2));
|
||||||
|
|
||||||
|
if (elapsed>duration){ clearInterval(colorFadeInterval) }
|
||||||
|
|
||||||
|
}, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
let nearArtistSpanText = ""
|
let nearArtistSpanText = ""
|
||||||
|
|
@ -244,7 +312,7 @@ async function songchange() {
|
||||||
recent_date.setMonth(recent_date.getMonth() - 6)
|
recent_date.setMonth(recent_date.getMonth() - 6)
|
||||||
album_date = album_date.toLocaleString('default', album_date>recent_date ? { year: 'numeric', month: 'short' } : { year: 'numeric' })
|
album_date = album_date.toLocaleString('default', album_date>recent_date ? { year: 'numeric', month: 'short' } : { year: 'numeric' })
|
||||||
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>"
|
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>"
|
||||||
|
|
||||||
nearArtistSpanText = album_link + " • " + album_date
|
nearArtistSpanText = album_link + " • " + album_date
|
||||||
} else if (Spicetify.Player.data.track.uri.includes('spotify:episode')) {
|
} else if (Spicetify.Player.data.track.uri.includes('spotify:episode')) {
|
||||||
// podcast
|
// podcast
|
||||||
|
|
@ -294,7 +362,7 @@ function pickCoverColor(img) {
|
||||||
sidebarColor = swatches[lightCols[col]].getHex()
|
sidebarColor = swatches[lightCols[col]].getHex()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
updateColors(textColor, sidebarColor)
|
updateColors(textColor, sidebarColor, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function hookCoverChange(pick) {
|
function hookCoverChange(pick) {
|
||||||
|
|
@ -325,19 +393,13 @@ hookCoverChange(false);
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
if (data.tag_name > current) {
|
if (data.tag_name > current) {
|
||||||
upd = document.createElement("div")
|
upd = document.createElement("div")
|
||||||
|
upd.innerText = `Theme UPD v${data.tag_name} avail.`
|
||||||
upd.classList.add("ellipsis-one-line", "main-type-finale")
|
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}`)
|
upd.setAttribute("title", `Changes: ${data.name}`)
|
||||||
DribbblishShared.config.register({
|
upd.style.setProperty("color", "var(--spice-button-active)");
|
||||||
insertOnTop: true,
|
document.querySelector(".main-userWidget-box").append(upd)
|
||||||
type: "button",
|
document.querySelector(".main-userWidget-box").classList.add("update-avail")
|
||||||
name: "Update",
|
new Spicetify.Menu.Item("Update Dribbblish", false, () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/blob/main/README.md#install--update", "_blank")).register();
|
||||||
description: "Open the GitHub Page with Installation instructions / Commands.",
|
|
||||||
onChange: () => {
|
|
||||||
window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme#install", "_blank");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
// Do something for an error here
|
// Do something for an error here
|
||||||
|
|
@ -346,23 +408,26 @@ hookCoverChange(false);
|
||||||
})()
|
})()
|
||||||
|
|
||||||
/* translucid background cover */
|
/* translucid background cover */
|
||||||
document.styleSheets[0].addRule('.Root__top-container::before',
|
document.styleSheets[0].insertRule(`
|
||||||
` content: '';
|
.Root__top-container::before {
|
||||||
background-image: var(--image_url);
|
content: '';
|
||||||
background-repeat: no-repeat;
|
background-image: var(--image_url);
|
||||||
background-size: cover;
|
background-repeat: no-repeat;
|
||||||
background-position: center center;
|
background-size: cover;
|
||||||
position: fixed;
|
background-position: center center;
|
||||||
display: block;
|
position: fixed;
|
||||||
top: 0;
|
display: block;
|
||||||
left: 0;
|
top: 0;
|
||||||
right: 0;
|
left: 0;
|
||||||
bottom: 0;
|
right: 0;
|
||||||
filter: blur(15px);
|
bottom: 0;
|
||||||
pointer-events: none;
|
filter: blur(15px);
|
||||||
backface-visibility: hidden;
|
pointer-events: none;
|
||||||
will-change: transform;
|
backface-visibility: hidden;
|
||||||
opacity: calc(0.07 + 0.03 * var(--is_light, 0));
|
will-change: transform;
|
||||||
z-index: +3;`)
|
opacity: calc(0.07 + 0.03 * var(--is_light, 0));
|
||||||
|
z-index: +3;
|
||||||
|
transition: background-image var(--song-transition-speed) linear;
|
||||||
|
}`)
|
||||||
|
|
||||||
document.documentElement.style.setProperty('--warning_message', ' ');
|
document.documentElement.style.setProperty('--warning_message', ' ');
|
||||||
|
|
|
||||||
216
dribbblish.js
216
dribbblish.js
|
|
@ -1,6 +1,20 @@
|
||||||
// Hide popover message
|
// Hide popover message
|
||||||
// document.getElementById("popover-container").style.height = 0;
|
// document.getElementById("popover-container").style.height = 0;
|
||||||
class ConfigMenu {
|
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() {
|
constructor() {
|
||||||
this.config = {};
|
this.config = {};
|
||||||
this.configButton = new Spicetify.Menu.Item("Dribbblish config", false, () => DribbblishShared.config.open());
|
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>
|
<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>
|
</button>
|
||||||
<h1>Dribbblish Settings</h1>
|
<h1>Dribbblish Settings</h1>
|
||||||
|
<div class="dribbblish-config-items"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dribbblish-config-backdrop"></div>
|
<div class="dribbblish-config-backdrop"></div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -31,79 +46,164 @@ class ConfigMenu {
|
||||||
document.getElementById("dribbblish-config").removeAttribute("active");
|
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");
|
const elem = document.createElement("div");
|
||||||
elem.classList.add("dribbblish-config-item");
|
elem.classList.add("dribbblish-config-item");
|
||||||
elem.setAttribute("key", `dribbblish:config:${key}`);
|
elem.setAttribute("key", `dribbblish:config:${options.key}`);
|
||||||
elem.setAttribute("type", type);
|
elem.setAttribute("type", options.type);
|
||||||
elem.innerHTML = /* html */ `
|
elem.innerHTML = /* html */ `
|
||||||
<h2 class="x-settings-title main-type-cello" as="h2">${name}</h2>
|
<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-${key}" style="color: var(--spice-subtext);">${description}</label>
|
<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">
|
<label class="x-toggle-wrapper x-settings-secondColumn">
|
||||||
${input}
|
${options.input}
|
||||||
</label>
|
</label>
|
||||||
`;
|
`;
|
||||||
if (insertOnTop && document.querySelector(".dribbblish-config-item")) {
|
|
||||||
console.log("before");
|
if (options.insertOnTop && parent.children.length > 0) {
|
||||||
document.querySelector(".dribbblish-config-container").insertBefore(elem, document.querySelector(".dribbblish-config-item:first-of-type"));
|
parent.insertBefore(elem, parent.children[0]);
|
||||||
} else {
|
} 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;
|
var fireChange = true;
|
||||||
|
|
||||||
if (type == "checkbox") {
|
if (options.type == "checkbox") {
|
||||||
const input = /* html */ `
|
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-indicatorWrapper">
|
||||||
<span class="x-toggle-indicator"></span>
|
<span class="x-toggle-indicator"></span>
|
||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
this.addInputHTML({ type, key, name, description, input, insertOnTop });
|
this.addInputHTML({ ...options, input });
|
||||||
|
|
||||||
document.getElementById(`dribbblish-config-input-${key}`).addEventListener("change", (e) => {
|
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => {
|
||||||
this.set(key, e.target.checked);
|
this.set(options.key, e.target.checked);
|
||||||
onChange(this.get(key));
|
options.onChange(this.get(options.key));
|
||||||
});
|
});
|
||||||
} else if (type == "select") {
|
} else if (options.type == "select") {
|
||||||
const input = /* html */ `
|
const input = /* html */ `
|
||||||
<span class="x-settings-secondColumn">
|
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${options.key}">
|
||||||
<select class="main-dropDown-dropDown" id="dribbblish-config-input-${key}">
|
${options.data.map((option, i) => `<option value="${i}"${this.get(options.key, options.defaultValue) == i ? " selected" : ""}>${option}</option>`).join("")}
|
||||||
${options.map((option, i) => `<option value="${i}"${this.get(key, defaultValue) == i ? " selected" : ""}>${option}</option>`).join("")}
|
</select>
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
`;
|
`;
|
||||||
this.addInputHTML({ type, key, name, description, input, insertOnTop });
|
this.addInputHTML({ ...options, input });
|
||||||
|
|
||||||
document.getElementById(`dribbblish-config-input-${key}`).addEventListener("change", (e) => {
|
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => {
|
||||||
this.set(key, e.target.value);
|
this.set(options.key, e.target.value);
|
||||||
onChange(this.get(key));
|
options.onChange(this.get(options.key));
|
||||||
});
|
});
|
||||||
} else if (type == "button") {
|
} else if (options.type == "button") {
|
||||||
const input = /* html */ `
|
const input = /* html */ `
|
||||||
<span class="x-settings-secondColumn">
|
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${options.key}">
|
||||||
<button class="main-buttons-button main-button-primary" type="button" id="dribbblish-config-input-${key}">
|
<div class="x-settings-buttonContainer">
|
||||||
<div class="x-settings-buttonContainer">
|
<span>${options.name}</span>
|
||||||
<span>${name}</span>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
`;
|
`;
|
||||||
this.addInputHTML({ type, key, name, description, input, insertOnTop });
|
this.addInputHTML({ ...options, input });
|
||||||
|
|
||||||
document.getElementById(`dribbblish-config-input-${key}`).addEventListener("click", (e) => {
|
document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("click", (e) => {
|
||||||
onChange(true);
|
options.onChange(true);
|
||||||
});
|
});
|
||||||
fireChange = false;
|
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 {
|
} 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) {
|
get(key, defaultValue) {
|
||||||
|
|
@ -111,7 +211,8 @@ class ConfigMenu {
|
||||||
if (val == null) return defaultValue;
|
if (val == null) return defaultValue;
|
||||||
|
|
||||||
if (val == "true" || val == "false") return val == "true"; // Boolean
|
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
|
return val; // String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +232,7 @@ DribbblishShared.config.register({
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
key: "rightBigCover",
|
key: "rightBigCover",
|
||||||
name: "Right expanded cover",
|
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,
|
defaultValue: true,
|
||||||
onChange: (val) => {
|
onChange: (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
|
|
@ -146,7 +247,7 @@ DribbblishShared.config.register({
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
key: "roundSidebarIcons",
|
key: "roundSidebarIcons",
|
||||||
name: "Round Sidebar Icons",
|
name: "Round Sidebar Icons",
|
||||||
description: "If the Sidebar Iconns should be round instead of square",
|
description: "If the Sidebar Icons should be round instead of square",
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
onChange: (val) => {
|
onChange: (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
|
|
@ -160,10 +261,10 @@ DribbblishShared.config.register({
|
||||||
waitForElement(["#main"], () => {
|
waitForElement(["#main"], () => {
|
||||||
DribbblishShared.config.register({
|
DribbblishShared.config.register({
|
||||||
type: "select",
|
type: "select",
|
||||||
options: ["None", "None (With Top Padding)", "Solid", "Transparent"],
|
data: ["None", "None (With Top Padding)", "Solid", "Transparent"],
|
||||||
key: "winTopBar",
|
key: "winTopBar",
|
||||||
name: "Windows Top Bar",
|
name: "Windows Top Bar",
|
||||||
description: "Have differennt top Bars (Ore none at all)",
|
description: "Have different top Bars (or none at all)",
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
onChange: (val) => {
|
onChange: (val) => {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
|
|
@ -206,10 +307,7 @@ function cyrb53Hash(str, seed = 0) {
|
||||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForElement([
|
waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`], ([root, firstItem]) => {
|
||||||
`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`,
|
|
||||||
`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`
|
|
||||||
], ([root, firstItem]) => {
|
|
||||||
const listElem = firstItem.parentElement;
|
const listElem = firstItem.parentElement;
|
||||||
root.classList.add("dribs-playlist-list");
|
root.classList.add("dribs-playlist-list");
|
||||||
|
|
||||||
|
|
@ -219,7 +317,7 @@ waitForElement([
|
||||||
let link = item.querySelector("a");
|
let link = item.querySelector("a");
|
||||||
if (!link) continue;
|
if (!link) continue;
|
||||||
|
|
||||||
let [_, app, uid ] = link.pathname.split("/");
|
let [_, app, uid] = link.pathname.split("/");
|
||||||
let uri;
|
let uri;
|
||||||
if (app === "playlist") {
|
if (app === "playlist") {
|
||||||
uri = Spicetify.URI.playlistV2URI(uid);
|
uri = Spicetify.URI.playlistV2URI(uid);
|
||||||
|
|
@ -231,14 +329,11 @@ waitForElement([
|
||||||
img.classList.add("playlist-picture");
|
img.classList.add("playlist-picture");
|
||||||
link.prepend(img);
|
link.prepend(img);
|
||||||
}
|
}
|
||||||
img.src = base64 || "/images/tracklist-row-song-fallback.svg";
|
img.src = base64 || "/images/tracklist-row-song-fallback.svg";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Spicetify.CosmosAsync.get(
|
Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, { policy: { picture: true } }).then((res) => {
|
||||||
`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`,
|
|
||||||
{ policy: { picture: true } }
|
|
||||||
).then(res => {
|
|
||||||
const meta = res.metadata;
|
const meta = res.metadata;
|
||||||
let img = link.querySelector("img");
|
let img = link.querySelector("img");
|
||||||
if (!img) {
|
if (!img) {
|
||||||
|
|
@ -254,13 +349,12 @@ waitForElement([
|
||||||
DribbblishShared.loadPlaylistImage = loadPlaylistImage;
|
DribbblishShared.loadPlaylistImage = loadPlaylistImage;
|
||||||
loadPlaylistImage();
|
loadPlaylistImage();
|
||||||
|
|
||||||
new MutationObserver(loadPlaylistImage)
|
new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true });
|
||||||
.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() {
|
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;
|
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");
|
rootlist.classList.remove("no-top-shadow", "no-bottom-shadow");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue