Enhance artist page by fetching EPs and Singles via search fallback
This commit is contained in:
parent
77ccfc8234
commit
9533155656
5 changed files with 113 additions and 39 deletions
|
|
@ -226,6 +226,10 @@
|
|||
<h2 class="section-title">Albums</h2>
|
||||
<div class="card-grid" id="artist-detail-albums"></div>
|
||||
</section>
|
||||
<section class="content-section" id="artist-section-eps" style="display: none;">
|
||||
<h2 class="section-title">EPs and Singles</h2>
|
||||
<div class="card-grid" id="artist-detail-eps"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="page-settings" class="page">
|
||||
|
|
|
|||
33
js/api.js
33
js/api.js
|
|
@ -484,15 +484,42 @@ export class LosslessAPI {
|
|||
|
||||
entries.forEach(entry => scan(entry));
|
||||
|
||||
const albums = Array.from(albumMap.values()).sort((a, b) =>
|
||||
// Attempt to find more albums/EPs via search since the direct feed might be limited
|
||||
try {
|
||||
const searchResults = await this.searchAlbums(artist.name);
|
||||
if (searchResults && searchResults.items) {
|
||||
const numericArtistId = Number(artistId);
|
||||
|
||||
for (const item of searchResults.items) {
|
||||
const itemArtistId = item.artist?.id;
|
||||
const matchesArtist = itemArtistId === numericArtistId ||
|
||||
(Array.isArray(item.artists) && item.artists.some(a => a.id === numericArtistId));
|
||||
|
||||
if (matchesArtist && !albumMap.has(item.id)) {
|
||||
albumMap.set(item.id, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to fetch additional albums via search:', e);
|
||||
}
|
||||
|
||||
const allReleases = Array.from(albumMap.values()).sort((a, b) =>
|
||||
new Date(b.releaseDate || 0) - new Date(a.releaseDate || 0)
|
||||
);
|
||||
|
||||
const eps = allReleases.filter(a =>
|
||||
a.type === 'EP' ||
|
||||
a.type === 'SINGLE' ||
|
||||
(a.numberOfTracks < 7 && !a.type)
|
||||
);
|
||||
const albums = allReleases.filter(a => !eps.includes(a));
|
||||
|
||||
const tracks = Array.from(trackMap.values())
|
||||
.sort((a, b) => (b.popularity || 0) - (a.popularity || 0))
|
||||
.slice(0, 10);
|
||||
.slice(0, 15);
|
||||
|
||||
const result = { ...artist, albums, tracks };
|
||||
const result = { ...artist, albums, eps, tracks };
|
||||
|
||||
await this.cache.set('artist', artistId, result);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -418,8 +418,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
try {
|
||||
const artist = await api.getArtist(artistId);
|
||||
|
||||
if (!artist.albums || artist.albums.length === 0) {
|
||||
throw new Error("No albums found for this artist");
|
||||
const allReleases = [...(artist.albums || []), ...(artist.eps || [])];
|
||||
if (allReleases.length === 0) {
|
||||
throw new Error("No albums or EPs found for this artist");
|
||||
}
|
||||
|
||||
const trackSet = new Set();
|
||||
|
|
@ -427,7 +428,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
|
||||
const chunks = [];
|
||||
const chunkSize = 3;
|
||||
const albums = artist.albums;
|
||||
const albums = allReleases;
|
||||
|
||||
for (let i = 0; i < albums.length; i += chunkSize) {
|
||||
chunks.push(albums.slice(i, i + chunkSize));
|
||||
|
|
|
|||
|
|
@ -387,12 +387,13 @@ export async function downloadDiscography(artist, api, quality, lyricsManager =
|
|||
const template = localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist} - monochrome.tf';
|
||||
const rootFolder = `${sanitizeForFilename(artist.name)} discography - monochrome.tf`;
|
||||
|
||||
const totalAlbums = artist.albums.length;
|
||||
const allReleases = [...(artist.albums || []), ...(artist.eps || [])];
|
||||
const totalAlbums = allReleases.length;
|
||||
const notification = createBulkDownloadNotification('discography', artist.name, totalAlbums);
|
||||
|
||||
try {
|
||||
for (let albumIndex = 0; albumIndex < artist.albums.length; albumIndex++) {
|
||||
const album = artist.albums[albumIndex];
|
||||
for (let albumIndex = 0; albumIndex < allReleases.length; albumIndex++) {
|
||||
const album = allReleases[albumIndex];
|
||||
|
||||
updateBulkDownloadProgress(notification, albumIndex, totalAlbums, album.title);
|
||||
|
||||
|
|
|
|||
101
js/ui.js
101
js/ui.js
|
|
@ -140,6 +140,17 @@ export class UIRenderer {
|
|||
yearDisplay = `${date.getFullYear()}`;
|
||||
}
|
||||
}
|
||||
|
||||
let typeLabel = '';
|
||||
if (album.type === 'EP') {
|
||||
typeLabel = ' • EP';
|
||||
} else if (album.type === 'SINGLE') {
|
||||
typeLabel = ' • Single';
|
||||
} else if (!album.type && album.numberOfTracks) {
|
||||
if (album.numberOfTracks <= 3) typeLabel = ' • Single';
|
||||
else if (album.numberOfTracks <= 6) typeLabel = ' • EP';
|
||||
}
|
||||
|
||||
return `
|
||||
<a href="#album/${album.id}" class="card">
|
||||
<div class="card-image-wrapper">
|
||||
|
|
@ -147,7 +158,7 @@ export class UIRenderer {
|
|||
</div>
|
||||
<h3 class="card-title">${album.title} ${explicitBadge}</h3>
|
||||
<p class="card-subtitle">${album.artist?.name ?? ''}</p>
|
||||
<p class="card-subtitle">${yearDisplay}</p>
|
||||
<p class="card-subtitle">${yearDisplay}${typeLabel}</p>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
|
@ -557,43 +568,57 @@ export class UIRenderer {
|
|||
|
||||
document.title = `${album.title} - ${album.artist.name} - Monochrome`;
|
||||
|
||||
// "More from Artist" Section
|
||||
// "More from Artist" Sections
|
||||
try {
|
||||
// Remove any existing "More from" section if re-rendering
|
||||
const existingMoreSection = document.getElementById('album-more-from-artist');
|
||||
if (existingMoreSection) existingMoreSection.remove();
|
||||
// Remove any existing "More from" sections if re-rendering
|
||||
document.querySelectorAll('.album-more-section').forEach(el => el.remove());
|
||||
document.getElementById('album-more-from-artist')?.remove(); // Legacy cleanup
|
||||
|
||||
const moreSection = document.createElement('section');
|
||||
moreSection.id = 'album-more-from-artist';
|
||||
moreSection.className = 'content-section';
|
||||
moreSection.style.marginTop = '3rem';
|
||||
moreSection.innerHTML = `
|
||||
// Create placeholder section while loading
|
||||
const placeholderSection = document.createElement('section');
|
||||
placeholderSection.className = 'content-section album-more-section';
|
||||
placeholderSection.style.marginTop = '3rem';
|
||||
placeholderSection.innerHTML = `
|
||||
<h2 class="section-title">More from ${album.artist.name}</h2>
|
||||
<div class="card-grid" id="album-more-albums">
|
||||
<div class="card-grid">
|
||||
${this.createSkeletonCards(6, false)}
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('page-album').appendChild(moreSection);
|
||||
document.getElementById('page-album').appendChild(placeholderSection);
|
||||
|
||||
const artistData = await this.api.getArtist(album.artist.id);
|
||||
// Filter out current album and duplicates
|
||||
const otherAlbums = artistData.albums
|
||||
.filter(a => a.id != album.id)
|
||||
.filter((a, index, self) =>
|
||||
index === self.findIndex((t) => t.title === a.title) // Dedup by title
|
||||
)
|
||||
.slice(0, 12); // Limit to 12
|
||||
|
||||
const moreContainer = document.getElementById('album-more-albums');
|
||||
|
||||
if (otherAlbums.length > 0) {
|
||||
moreContainer.innerHTML = otherAlbums.map(a => this.createAlbumCardHTML(a)).join('');
|
||||
} else {
|
||||
moreSection.remove(); // Remove section if no other albums
|
||||
}
|
||||
// Remove placeholder
|
||||
placeholderSection.remove();
|
||||
|
||||
const renderSection = (title, items) => {
|
||||
const filtered = (items || [])
|
||||
.filter(a => a.id != album.id)
|
||||
.filter((a, index, self) =>
|
||||
index === self.findIndex((t) => t.title === a.title) // Dedup by title
|
||||
)
|
||||
.slice(0, 12);
|
||||
|
||||
if (filtered.length === 0) return;
|
||||
|
||||
const section = document.createElement('section');
|
||||
section.className = 'content-section album-more-section';
|
||||
section.style.marginTop = '3rem';
|
||||
section.innerHTML = `
|
||||
<h2 class="section-title">${title}</h2>
|
||||
<div class="card-grid">
|
||||
${filtered.map(a => this.createAlbumCardHTML(a)).join('')}
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('page-album').appendChild(section);
|
||||
};
|
||||
|
||||
renderSection(`More albums from ${album.artist.name}`, artistData.albums);
|
||||
renderSection(`EPs and Singles from ${album.artist.name}`, artistData.eps);
|
||||
|
||||
} catch (err) {
|
||||
console.warn('Failed to load "More from artist":', err);
|
||||
document.getElementById('album-more-from-artist')?.remove();
|
||||
document.querySelectorAll('.album-more-section').forEach(el => el.remove());
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -672,6 +697,8 @@ async renderPlaylistPage(playlistId) {
|
|||
const metaEl = document.getElementById('artist-detail-meta');
|
||||
const tracksContainer = document.getElementById('artist-detail-tracks');
|
||||
const albumsContainer = document.getElementById('artist-detail-albums');
|
||||
const epsContainer = document.getElementById('artist-detail-eps');
|
||||
const epsSection = document.getElementById('artist-section-eps');
|
||||
const dlBtn = document.getElementById('download-discography-btn');
|
||||
if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}<span>Download Discography</span>`;
|
||||
|
||||
|
|
@ -681,6 +708,8 @@ async renderPlaylistPage(playlistId) {
|
|||
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 150px;"></div>';
|
||||
tracksContainer.innerHTML = this.createSkeletonTracks(5, true);
|
||||
albumsContainer.innerHTML = this.createSkeletonCards(6, false);
|
||||
if (epsContainer) epsContainer.innerHTML = this.createSkeletonCards(6, false);
|
||||
if (epsSection) epsSection.style.display = 'none';
|
||||
|
||||
try {
|
||||
const artist = await this.api.getArtist(artistId);
|
||||
|
|
@ -702,9 +731,21 @@ async renderPlaylistPage(playlistId) {
|
|||
`;
|
||||
|
||||
this.renderListWithTracks(tracksContainer, artist.tracks, true);
|
||||
albumsContainer.innerHTML = artist.albums.map(album =>
|
||||
this.createAlbumCardHTML(album)
|
||||
).join('');
|
||||
|
||||
// Render Albums
|
||||
albumsContainer.innerHTML = artist.albums.length
|
||||
? artist.albums.map(album => this.createAlbumCardHTML(album)).join('')
|
||||
: createPlaceholder('No albums found.');
|
||||
|
||||
// Render EPs and Singles
|
||||
if (epsContainer && epsSection) {
|
||||
if (artist.eps && artist.eps.length > 0) {
|
||||
epsContainer.innerHTML = artist.eps.map(album => this.createAlbumCardHTML(album)).join('');
|
||||
epsSection.style.display = 'block';
|
||||
} else {
|
||||
epsSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
recentActivityManager.addArtist(artist);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue