Skip to content

Commit

Permalink
feat(theme.js): custom images for the playlists folders support
Browse files Browse the repository at this point in the history
  • Loading branch information
SunsetTechuila committed May 19, 2023
1 parent 8d0dc82 commit a3b751b
Showing 1 changed file with 149 additions and 82 deletions.
231 changes: 149 additions & 82 deletions src/theme.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// credits for the folder images code: harbassan
(function bloom() {
function waitForElement(els, func, timeout = 100) {
const queries = els.map((el) => document.querySelector(el));
Expand All @@ -8,102 +9,111 @@
}
}

waitForElement(['.main-rootlist-rootlistItem'], () => {
const mainRootlistWrapper = document.getElementsByClassName('main-rootlist-wrapper')[0];
mainRootlistWrapper.style.height = `${mainRootlistWrapper.offsetHeight * 2}px`;
const cache = new Map();
function updatePlaylistsImages() {
waitForElement(['.main-rootlist-rootlistItem'], () => {
const mainRootlistWrapper = document.getElementsByClassName('main-rootlist-wrapper')[0];
mainRootlistWrapper.style.height = `${mainRootlistWrapper.offsetHeight * 2}px`;
const cache = new Map();

async function fetchPlaylistData(url) {
const response = await Spicetify.CosmosAsync.get(url);
const { items, next } = response;
return [...items, ...(next ? await fetchPlaylistData(next) : [])];
}

async function addPlaylistIcons() {
while (!Spicetify || !Spicetify.Platform || !Spicetify.CosmosAsync) {
// eslint-disable-next-line no-await-in-loop, no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, 100));
async function fetchPlaylistData(url) {
const response = await Spicetify.CosmosAsync.get(url);
const { items, next } = response;
return [...items, ...(next ? await fetchPlaylistData(next) : [])];
}

async function updatePlaylistList(playlistData) {
const playlistElements = await new Promise((resolve) => {
const interval = setInterval(() => {
const elements = document.querySelectorAll('#spicetify-playlist-list li a');
if (elements.length > 0) {
clearInterval(interval);
resolve(Array.from(elements));
}
}, 100);
});
async function addPlaylistIcons() {
while (!Spicetify || !Spicetify.Platform || !Spicetify.CosmosAsync) {
// eslint-disable-next-line no-await-in-loop, no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, 100));
}

for (let i = 0; i < playlistElements.length; i += 1) {
const [id] = playlistElements[i].href.split('/').slice(-1);
const [type] = playlistElements[i].href.split('/').slice(-2, -1);
let icon = cache.get(id);
if (!icon) {
switch (type) {
case 'playlist': {
const playlist = playlistData.find((p) => p.id === id);
const image = playlist ? playlist.images[0] || {} : {};
icon = {
src:
image.url ||
'https://cdn.jsdelivr.net/gh/nimsandu/spicetify-bloom@master/assets/fluentui-system-icons/ic_fluent_music_note_2_24_filled.svg',
size: '50px',
};
if (!image.url) {
icon.size = '45px';
}
cache.set(id, icon);
break;
async function updatePlaylistList(playlistData) {
const playlistElements = await new Promise((resolve) => {
const interval = setInterval(() => {
const elements = document.querySelectorAll('#spicetify-playlist-list li a');
if (elements.length > 0) {
clearInterval(interval);
resolve(Array.from(elements));
}
}, 100);
});

for (let i = 0; i < playlistElements.length; i += 1) {
const [id] = playlistElements[i].href.split('/').slice(-1);
const [type] = playlistElements[i].href.split('/').slice(-2, -1);
let icon = cache.get(id);
if (!icon) {
switch (type) {
case 'playlist': {
const playlist = playlistData.find((p) => p.id === id);
const image = playlist ? playlist.images[0] || {} : {};
icon = {
src:
image.url ||
'https://cdn.jsdelivr.net/gh/nimsandu/spicetify-bloom@master/assets/fluentui-system-icons/ic_fluent_music_note_2_24_filled.svg',
size: '50px',
};
if (!image.url) {
icon.size = '45px';
}
cache.set(id, icon);
break;
}

case 'folder':
icon = {
src: 'https://cdn.jsdelivr.net/gh/nimsandu/spicetify-bloom@master/assets/fluentui-system-icons/ic_fluent_folder_24_filled.svg',
};
icon.size = '45px';
cache.set(id, icon);
break;

default:
break;
case 'folder':
const base64 = localStorage.getItem('bloom:folder-image:' + id);
icon = {
src:
base64 ||
'https://cdn.jsdelivr.net/gh/nimsandu/spicetify-bloom@master/assets/fluentui-system-icons/ic_fluent_folder_24_filled.svg',
size: '50px',
};
if (!base64) {
icon.size = '45px';
}
cache.set(id, icon);
break;

default:
break;
}
}
}

if (icon.src) {
playlistElements[i].style.backgroundImage = `url('${icon.src}')`;
playlistElements[i].style.backgroundRepeat = 'no-repeat';
playlistElements[i].style.backgroundSize = `${icon.size}`;
playlistElements[i].style.backgroundPosition = 'center';
if (icon.src) {
playlistElements[i].style.backgroundImage = `url('${icon.src}')`;
playlistElements[i].style.backgroundRepeat = 'no-repeat';
playlistElements[i].style.backgroundSize = `${icon.size}`;
playlistElements[i].style.backgroundPosition = 'center';
}
}
}
}

const playlistList = await new Promise((resolve) => {
const interval = setInterval(() => {
const element = document.getElementById('spicetify-playlist-list');
if (element) {
clearInterval(interval);
resolve(element);
}
}, 100);
});

const playlistData = await fetchPlaylistData(
'https://api.spotify.com/v1/me/playlists?limit=50'
);
const observer = new MutationObserver(async () => {
observer.disconnect();
const playlistList = await new Promise((resolve) => {
const interval = setInterval(() => {
const element = document.getElementById('spicetify-playlist-list');
if (element) {
clearInterval(interval);
resolve(element);
}
}, 100);
});

const playlistData = await fetchPlaylistData(
'https://api.spotify.com/v1/me/playlists?limit=50'
);
const observer = new MutationObserver(async () => {
observer.disconnect();
await updatePlaylistList(playlistData);
observer.observe(playlistList, { childList: true, subtree: true });
});
await updatePlaylistList(playlistData);
observer.observe(playlistList, { childList: true, subtree: true });
});
await updatePlaylistList(playlistData);
observer.observe(playlistList, { childList: true, subtree: true });
}
}

addPlaylistIcons();
});
addPlaylistIcons();
});
}
updatePlaylistsImages();

waitForElement(['.main-navBar-navBarLink'], () => {
const navBarItems = document.getElementsByClassName('main-navBar-navBarLink');
Expand Down Expand Up @@ -505,4 +515,61 @@
setLyricsMaxWidth();
}
window.onresize = onResize;

// filepicker for custom folder images
const filePickerForm = document.createElement('form');
filePickerForm.setAttribute('aria-hidden', true);
filePickerForm.innerHTML = '<input type="file" class="hidden-visually" />';
document.body.appendChild(filePickerForm);
/** @type {HTMLInputElement} */
const filePickerInput = filePickerForm.childNodes[0];
filePickerInput.accept = [
'image/jpeg',
'image/apng',
'image/avif',
'image/gif',
'image/png',
'image/svg+xml',
'image/webp',
].join(',');

filePickerInput.onchange = () => {
if (!filePickerInput.files.length) return;

const file = filePickerInput.files[0];
const reader = new FileReader();
reader.onload = (event) => {
const result = event.target.result;
const id = Spicetify.URI.from(filePickerInput.uri).id;
try {
localStorage.setItem('bloom:folder-image:' + id, result);
} catch {
Spicetify.showNotification('File is too large');
}
updatePlaylistsImages();
};
reader.readAsDataURL(file);
};

// context menu items for custom folder images
new Spicetify.ContextMenu.Item(
'Remove folder image',
([uri]) => {
const id = Spicetify.URI.from(uri).id;
localStorage.removeItem('bloom:folder-image:' + id);
updatePlaylistsImages();
},
([uri]) => Spicetify.URI.isFolder(uri),
'x'
).register();
new Spicetify.ContextMenu.Item(
'Choose folder image',
([uri]) => {
filePickerInput.uri = uri;
filePickerForm.reset();
filePickerInput.click();
},
([uri]) => Spicetify.URI.isFolder(uri),
'edit'
).register();
})();

0 comments on commit a3b751b

Please sign in to comment.