Fix "Invalid Date" display in album details
- Backfill album release date from tracks if missing in API response. - Gracefully handle invalid or missing dates in UI rendering. - Fix potential NaN in download folder naming due to invalid dates.
This commit is contained in:
parent
0aee58b823
commit
43f04e7454
4 changed files with 62 additions and 13 deletions
|
|
@ -138,9 +138,9 @@
|
||||||
<header class="detail-header">
|
<header class="detail-header">
|
||||||
<img id="album-detail-image" src="" alt="" class="detail-header-image">
|
<img id="album-detail-image" src="" alt="" class="detail-header-image">
|
||||||
<div class="detail-header-info">
|
<div class="detail-header-info">
|
||||||
<div class="type">Album</div>
|
|
||||||
<h1 class="title" id="album-detail-title"></h1>
|
<h1 class="title" id="album-detail-title"></h1>
|
||||||
<div class="meta" id="album-detail-meta"></div>
|
<div class="meta" id="album-detail-meta"></div>
|
||||||
|
<div class="meta" id="album-detail-producer"></div>
|
||||||
<div class="detail-header-actions">
|
<div class="detail-header-actions">
|
||||||
<button id="play-album-btn" class="btn-primary">
|
<button id="play-album-btn" class="btn-primary">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
|
|
||||||
14
js/api.js
14
js/api.js
|
|
@ -324,6 +324,20 @@ export class LosslessAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If album exists but has no releaseDate, try to extract from tracks
|
||||||
|
if (album && !album.releaseDate && tracksSection?.items && tracksSection.items.length > 0) {
|
||||||
|
const firstTrack = tracksSection.items[0];
|
||||||
|
const track = firstTrack.item || firstTrack;
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
if (track.album && track.album.releaseDate) {
|
||||||
|
album = { ...album, releaseDate: track.album.releaseDate };
|
||||||
|
} else if (track.streamStartDate) {
|
||||||
|
album = { ...album, releaseDate: track.streamStartDate.split('T')[0] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tracks = (tracksSection?.items || []).map(i => this.prepareTrack(i.item || i));
|
const tracks = (tracksSection?.items || []).map(i => this.prepareTrack(i.item || i));
|
||||||
const result = { album, tracks };
|
const result = { album, tracks };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,10 +207,13 @@ export async function downloadAlbumAsZip(album, tracks, api, quality, lyricsMana
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
|
|
||||||
const template = localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist} - monochrome.tf';
|
const template = localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist} - monochrome.tf';
|
||||||
|
const releaseDate = album.releaseDate ? new Date(album.releaseDate) : null;
|
||||||
|
const year = (releaseDate && !isNaN(releaseDate.getTime())) ? releaseDate.getFullYear() : '';
|
||||||
|
|
||||||
const folderName = formatTemplate(template, {
|
const folderName = formatTemplate(template, {
|
||||||
albumTitle: album.title,
|
albumTitle: album.title,
|
||||||
albumArtist: album.artist?.name,
|
albumArtist: album.artist?.name,
|
||||||
year: new Date(album.releaseDate).getFullYear()
|
year: year
|
||||||
});
|
});
|
||||||
|
|
||||||
const notification = createBulkDownloadNotification('album', album.title, tracks.length);
|
const notification = createBulkDownloadNotification('album', album.title, tracks.length);
|
||||||
|
|
@ -380,10 +383,13 @@ export async function downloadDiscography(artist, api, quality, lyricsManager =
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { album: fullAlbum, tracks } = await api.getAlbum(album.id);
|
const { album: fullAlbum, tracks } = await api.getAlbum(album.id);
|
||||||
|
const releaseDate = fullAlbum.releaseDate ? new Date(fullAlbum.releaseDate) : null;
|
||||||
|
const year = (releaseDate && !isNaN(releaseDate.getTime())) ? releaseDate.getFullYear() : '';
|
||||||
|
|
||||||
const albumFolder = formatTemplate(template, {
|
const albumFolder = formatTemplate(template, {
|
||||||
albumTitle: fullAlbum.title,
|
albumTitle: fullAlbum.title,
|
||||||
albumArtist: fullAlbum.artist?.name,
|
albumArtist: fullAlbum.artist?.name,
|
||||||
year: new Date(fullAlbum.releaseDate).getFullYear()
|
year: year
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const track of tracks) {
|
for (const track of tracks) {
|
||||||
|
|
|
||||||
49
js/ui.js
49
js/ui.js
|
|
@ -40,6 +40,15 @@ export class UIRenderer {
|
||||||
const trackArtists = getTrackArtists(track);
|
const trackArtists = getTrackArtists(track);
|
||||||
const trackTitle = getTrackTitle(track);
|
const trackTitle = getTrackTitle(track);
|
||||||
|
|
||||||
|
let yearDisplay = '';
|
||||||
|
const releaseDate = track.album?.releaseDate || track.streamStartDate;
|
||||||
|
if (releaseDate) {
|
||||||
|
const date = new Date(releaseDate);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
yearDisplay = ` • ${date.getFullYear()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="track-item" data-track-id="${track.id}">
|
<div class="track-item" data-track-id="${track.id}">
|
||||||
${trackNumberHTML}
|
${trackNumberHTML}
|
||||||
|
|
@ -49,7 +58,7 @@ export class UIRenderer {
|
||||||
${trackTitle}
|
${trackTitle}
|
||||||
${explicitBadge}
|
${explicitBadge}
|
||||||
</div>
|
</div>
|
||||||
<div class="artist">${trackArtists}</div>
|
<div class="artist">${trackArtists}${yearDisplay}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-item-duration">${formatTime(track.duration)}</div>
|
<div class="track-item-duration">${formatTime(track.duration)}</div>
|
||||||
|
|
@ -66,13 +75,21 @@ export class UIRenderer {
|
||||||
|
|
||||||
createAlbumCardHTML(album) {
|
createAlbumCardHTML(album) {
|
||||||
const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : '';
|
const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : '';
|
||||||
|
let yearDisplay = '';
|
||||||
|
if (album.releaseDate) {
|
||||||
|
const date = new Date(album.releaseDate);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
yearDisplay = `${date.getFullYear()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
return `
|
return `
|
||||||
<a href="#album/${album.id}" class="card">
|
<a href="#album/${album.id}" class="card">
|
||||||
<div class="card-image-wrapper">
|
<div class="card-image-wrapper">
|
||||||
<img src="${this.api.getCoverUrl(album.cover, '320')}" alt="${album.title}" class="card-image" loading="lazy">
|
<img src="${this.api.getCoverUrl(album.cover, '320')}" alt="${album.title}" class="card-image" loading="lazy">
|
||||||
</div>
|
</div>
|
||||||
<h3 class="card-title">${album.title} ${explicitBadge}</h3>
|
<h3 class="card-title">${album.title} ${explicitBadge}</h3>
|
||||||
<p class="card-subtitle">Album • ${album.artist?.name ?? ''}</p>
|
<p class="card-subtitle">${album.artist?.name ?? ''}</p>
|
||||||
|
<p class="card-subtitle">${yearDisplay}</p>
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +101,6 @@ export class UIRenderer {
|
||||||
<img src="${this.api.getArtistPictureUrl(artist.picture, '320')}" alt="${artist.name}" class="card-image" loading="lazy">
|
<img src="${this.api.getArtistPictureUrl(artist.picture, '320')}" alt="${artist.name}" class="card-image" loading="lazy">
|
||||||
</div>
|
</div>
|
||||||
<h3 class="card-title">${artist.name}</h3>
|
<h3 class="card-title">${artist.name}</h3>
|
||||||
<p class="card-subtitle">Artist</p>
|
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +125,7 @@ export class UIRenderer {
|
||||||
<div class="skeleton-card ${isArtist ? 'artist' : ''}">
|
<div class="skeleton-card ${isArtist ? 'artist' : ''}">
|
||||||
<div class="skeleton skeleton-card-image"></div>
|
<div class="skeleton skeleton-card-image"></div>
|
||||||
<div class="skeleton skeleton-card-title"></div>
|
<div class="skeleton skeleton-card-title"></div>
|
||||||
<div class="skeleton skeleton-card-subtitle"></div>
|
${!isArtist ? '<div class="skeleton skeleton-card-subtitle"></div>' : ''}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -257,12 +273,14 @@ export class UIRenderer {
|
||||||
const imageEl = document.getElementById('album-detail-image');
|
const imageEl = document.getElementById('album-detail-image');
|
||||||
const titleEl = document.getElementById('album-detail-title');
|
const titleEl = document.getElementById('album-detail-title');
|
||||||
const metaEl = document.getElementById('album-detail-meta');
|
const metaEl = document.getElementById('album-detail-meta');
|
||||||
|
const prodEl = document.getElementById('album-detail-producer');
|
||||||
const tracklistContainer = document.getElementById('album-detail-tracklist');
|
const tracklistContainer = document.getElementById('album-detail-tracklist');
|
||||||
|
|
||||||
imageEl.src = '';
|
imageEl.src = '';
|
||||||
imageEl.style.backgroundColor = 'var(--muted)';
|
imageEl.style.backgroundColor = 'var(--muted)';
|
||||||
titleEl.innerHTML = '<div class="skeleton" style="height: 48px; width: 300px; max-width: 90%;"></div>';
|
titleEl.innerHTML = '<div class="skeleton" style="height: 48px; width: 300px; max-width: 90%;"></div>';
|
||||||
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
|
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
|
||||||
|
prodEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
|
||||||
tracklistContainer.innerHTML = `
|
tracklistContainer.innerHTML = `
|
||||||
<div class="track-list-header">
|
<div class="track-list-header">
|
||||||
<span style="width: 40px; text-align: center;">#</span>
|
<span style="width: 40px; text-align: center;">#</span>
|
||||||
|
|
@ -282,15 +300,26 @@ export class UIRenderer {
|
||||||
titleEl.innerHTML = `${album.title} ${explicitBadge}`;
|
titleEl.innerHTML = `${album.title} ${explicitBadge}`;
|
||||||
|
|
||||||
const totalDuration = calculateTotalDuration(tracks);
|
const totalDuration = calculateTotalDuration(tracks);
|
||||||
const releaseDate = new Date(album.releaseDate);
|
let dateDisplay = '';
|
||||||
const year = releaseDate.getFullYear();
|
if (album.releaseDate) {
|
||||||
|
const releaseDate = new Date(album.releaseDate);
|
||||||
|
if (!isNaN(releaseDate.getTime())) {
|
||||||
|
const year = releaseDate.getFullYear();
|
||||||
|
dateDisplay = window.innerWidth > 768
|
||||||
|
? releaseDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
|
||||||
|
: year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const dateDisplay = window.innerWidth > 768
|
const firstCopyright = tracks.find(track => track.copyright)?.copyright;
|
||||||
? releaseDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
|
|
||||||
: year;
|
|
||||||
|
|
||||||
metaEl.innerHTML =
|
metaEl.innerHTML =
|
||||||
`By <a href="#artist/${album.artist.id}">${album.artist.name}</a> • ${dateDisplay} • ${tracks.length} tracks • ${formatDuration(totalDuration)}`;
|
(dateDisplay ? `${dateDisplay} • ` : '') +
|
||||||
|
`${tracks.length} tracks • ${formatDuration(totalDuration)}`;
|
||||||
|
|
||||||
|
prodEl.innerHTML =
|
||||||
|
`By <a href="#artist/${album.artist.id}">${album.artist.name}</a>` +
|
||||||
|
(firstCopyright ? ` • ${firstCopyright}` : '');
|
||||||
|
|
||||||
tracklistContainer.innerHTML = `
|
tracklistContainer.innerHTML = `
|
||||||
<div class="track-list-header">
|
<div class="track-list-header">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue