diff --git a/js/settings.js b/js/settings.js
index 1ebabdb..210673f 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -1306,6 +1306,14 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
+ const shuffleEditorsPicksToggle = document.getElementById('shuffle-editors-picks-toggle');
+ if (shuffleEditorsPicksToggle) {
+ shuffleEditorsPicksToggle.checked = homePageSettings.shouldShuffleEditorsPicks();
+ shuffleEditorsPicksToggle.addEventListener('change', (e) => {
+ homePageSettings.setShuffleEditorsPicks(e.target.checked);
+ });
+ }
+
// Sidebar Section Toggles
const sidebarShowHomeToggle = document.getElementById('sidebar-show-home-toggle');
if (sidebarShowHomeToggle) {
diff --git a/js/storage.js b/js/storage.js
index 6feda33..f3d8938 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -1187,6 +1187,21 @@ export const homePageSettings = {
setShowEditorsPicks(enabled) {
localStorage.setItem(this.SHOW_EDITORS_PICKS_KEY, enabled ? 'true' : 'false');
},
+
+ SHUFFLE_EDITORS_PICKS_KEY: 'home-shuffle-editors-picks',
+
+ shouldShuffleEditorsPicks() {
+ try {
+ const val = localStorage.getItem(this.SHUFFLE_EDITORS_PICKS_KEY);
+ return val === null ? true : val === 'true';
+ } catch {
+ return true;
+ }
+ },
+
+ setShuffleEditorsPicks(enabled) {
+ localStorage.setItem(this.SHUFFLE_EDITORS_PICKS_KEY, enabled ? 'true' : 'false');
+ },
};
export const sidebarSectionSettings = {
diff --git a/js/ui.js b/js/ui.js
index b2ac404..43c90b6 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1585,36 +1585,89 @@ export class UIRenderer {
const response = await fetch('/editors-picks.json');
if (!response.ok) throw new Error("Failed to load editor's picks");
- const items = await response.json();
+ let items = await response.json();
if (!Array.isArray(items) || items.length === 0) {
picksContainer.innerHTML = createPlaceholder("No editor's picks available.");
return;
}
- // Fetch details for each item
+ // Shuffle items if enabled
+ if (homePageSettings.shouldShuffleEditorsPicks()) {
+ items = [...items].sort(() => Math.random() - 0.5);
+ }
+
+ // Use cached metadata or fetch details for each item
const cardsHTML = [];
const itemsToStore = [];
for (const item of items.slice(0, 12)) {
try {
if (item.type === 'album') {
- const result = await this.api.getAlbum(item.id);
- if (result && result.album) {
- cardsHTML.push(this.createAlbumCardHTML(result.album));
- itemsToStore.push({ el: null, data: result.album, type: 'album' });
+ // Check if we have cached metadata
+ if (item.title && item.artist) {
+ // Use cached data directly
+ const album = {
+ id: item.id,
+ title: item.title,
+ artist: item.artist,
+ releaseDate: item.releaseDate,
+ cover: item.cover,
+ explicit: item.explicit,
+ audioQuality: item.audioQuality,
+ mediaMetadata: item.mediaMetadata,
+ type: 'ALBUM',
+ };
+ cardsHTML.push(this.createAlbumCardHTML(album));
+ itemsToStore.push({ el: null, data: album, type: 'album' });
+ } else {
+ // Fall back to API call for legacy format
+ const result = await this.api.getAlbum(item.id);
+ if (result && result.album) {
+ cardsHTML.push(this.createAlbumCardHTML(result.album));
+ itemsToStore.push({ el: null, data: result.album, type: 'album' });
+ }
}
} else if (item.type === 'artist') {
- const artist = await this.api.getArtist(item.id);
- if (artist) {
+ if (item.name && item.picture) {
+ // Use cached data directly
+ const artist = {
+ id: item.id,
+ name: item.name,
+ picture: item.picture,
+ };
cardsHTML.push(this.createArtistCardHTML(artist));
itemsToStore.push({ el: null, data: artist, type: 'artist' });
+ } else {
+ // Fall back to API call
+ const artist = await this.api.getArtist(item.id);
+ if (artist) {
+ cardsHTML.push(this.createArtistCardHTML(artist));
+ itemsToStore.push({ el: null, data: artist, type: 'artist' });
+ }
}
} else if (item.type === 'track') {
- const track = await this.api.getTrackMetadata(item.id);
- if (track) {
+ if (item.title && item.album) {
+ // Use cached data directly
+ const track = {
+ id: item.id,
+ title: item.title,
+ artist: item.artist,
+ album: item.album,
+ explicit: item.explicit,
+ audioQuality: item.audioQuality,
+ mediaMetadata: item.mediaMetadata,
+ duration: item.duration,
+ };
cardsHTML.push(this.createTrackCardHTML(track));
itemsToStore.push({ el: null, data: track, type: 'track' });
+ } else {
+ // Fall back to API call
+ const track = await this.api.getTrackMetadata(item.id);
+ if (track) {
+ cardsHTML.push(this.createTrackCardHTML(track));
+ itemsToStore.push({ el: null, data: track, type: 'track' });
+ }
}
}
} catch (e) {
diff --git a/public/editors-picks.json b/public/editors-picks.json
index 1cb4a38..20ca3d5 100644
--- a/public/editors-picks.json
+++ b/public/editors-picks.json
@@ -1,9 +1,112 @@
[
- { "type": "album", "id": "4527433" },
- { "type": "album", "id": "90502209" },
- { "type": "track", "id": "279082605" },
- { "type": "album", "id": "413189044"},
- { "type": "album", "id": "447277344"},
- { "type": "album", "id": "380211863"}
-
+ {
+ "type": "album",
+ "id": 4527433,
+ "title": "Flockaveli",
+ "artist": { "id": 3654061, "name": "Waka Flocka Flame" },
+ "releaseDate": "2010-10-05",
+ "cover": "05702b51-45cf-4157-b9ed-dd7ca7e7b7b3",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 90502209,
+ "title": "NASIR",
+ "artist": { "id": 1003, "name": "Nas" },
+ "releaseDate": "2018-06-15",
+ "cover": "503ea6b2-0829-438e-8e4e-9a988154b3bc",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 413189044,
+ "title": "Jump Out",
+ "artist": { "id": 27836827, "name": "OsamaSon" },
+ "releaseDate": "2025-01-24",
+ "cover": "ec4a4ef2-69fe-4d3c-aaba-05dc2d546e84",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 209061256,
+ "title": "Super Tecmo Bo",
+ "artist": { "id": 4839917, "name": "Boldy James" },
+ "releaseDate": "2021-12-17",
+ "cover": "f58ca804-1da7-4953-a26d-2b3258310db5",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 488297983,
+ "title": "So Much Country 'Till We Get There",
+ "artist": { "id": 51427239, "name": "Westside Cowboy" },
+ "releaseDate": "2026-01-23",
+ "cover": "dd9d4f23-1517-4a76-8e3f-31e341dcd525",
+ "explicit": false,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 447277344,
+ "title": "REST IN BASS",
+ "artist": { "id": 50418386, "name": "Che" },
+ "releaseDate": "2025-07-18",
+ "cover": "d1397066-72ed-4481-b508-77f7c7a03073",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 490209783,
+ "title": "My Ghosts Go Ghost",
+ "artist": { "id": 39920098, "name": "By Storm" },
+ "releaseDate": "2026-01-30",
+ "cover": "bc470b88-3583-4853-976e-593855672322",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 279082597,
+ "title": "Noktifer's Symphony",
+ "artist": { "id": 22055243, "name": "axxturel" },
+ "releaseDate": "2020-11-17",
+ "cover": "a886acd8-b915-4a77-9c1f-eecf7fa6091c",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 380211863,
+ "title": "Sayso Says",
+ "artist": { "id": 50418386, "name": "Che" },
+ "releaseDate": "2024-08-30",
+ "cover": "917c7e0f-3ebb-471e-9c88-fcdf6a9f21a5",
+ "explicit": true,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS"] }
+ },
+ {
+ "type": "album",
+ "id": 464463900,
+ "title": "A-Rhythm Absolute",
+ "artist": { "id": 30049790, "name": "Sunday Mourners" },
+ "releaseDate": "2026-01-16",
+ "cover": "d04e2ed8-f6d7-43c0-bcb2-fb99d3de0d5d",
+ "explicit": false,
+ "audioQuality": "LOSSLESS",
+ "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
+ }
]