Add Deezer & AudioDB source badges to library artist cards and detail page

This commit is contained in:
Broque Thomas 2026-02-18 21:30:15 -08:00
parent 8f9851c50f
commit ae5d77810d
3 changed files with 114 additions and 38 deletions

View file

@ -4319,13 +4319,14 @@ class MusicDatabase:
a.spotify_artist_id, a.spotify_artist_id,
a.itunes_artist_id, a.itunes_artist_id,
a.deezer_id, a.deezer_id,
a.audiodb_id,
COUNT(DISTINCT al.id) as album_count, COUNT(DISTINCT al.id) as album_count,
COUNT(DISTINCT t.id) as track_count COUNT(DISTINCT t.id) as track_count
FROM artists a FROM artists a
LEFT JOIN albums al ON a.id = al.artist_id LEFT JOIN albums al ON a.id = al.artist_id
LEFT JOIN tracks t ON al.id = t.album_id LEFT JOIN tracks t ON al.id = t.album_id
WHERE {where_clause} WHERE {where_clause}
GROUP BY a.id, a.name, a.thumb_url, a.genres, a.musicbrainz_id, a.deezer_id GROUP BY a.id, a.name, a.thumb_url, a.genres, a.musicbrainz_id, a.deezer_id, a.audiodb_id
ORDER BY a.name COLLATE NOCASE ORDER BY a.name COLLATE NOCASE
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
""" """
@ -4371,6 +4372,7 @@ class MusicDatabase:
'genres': artist.genres, 'genres': artist.genres,
'musicbrainz_id': row['musicbrainz_id'], 'musicbrainz_id': row['musicbrainz_id'],
'deezer_id': row['deezer_id'], 'deezer_id': row['deezer_id'],
'audiodb_id': row['audiodb_id'],
'album_count': row['album_count'] or 0, 'album_count': row['album_count'] or 0,
'track_count': row['track_count'] or 0, 'track_count': row['track_count'] or 0,
'is_watched': bool(is_watched) 'is_watched': bool(is_watched)
@ -4426,7 +4428,7 @@ class MusicDatabase:
# Get artist information # Get artist information
cursor.execute(""" cursor.execute("""
SELECT SELECT
id, name, thumb_url, genres, server_source, musicbrainz_id id, name, thumb_url, genres, server_source, musicbrainz_id, deezer_id, audiodb_id
FROM artists FROM artists
WHERE id = ? WHERE id = ?
""", (artist_id,)) """, (artist_id,))
@ -4564,6 +4566,8 @@ class MusicDatabase:
'genres': genres, 'genres': genres,
'server_source': artist_row['server_source'], 'server_source': artist_row['server_source'],
'musicbrainz_id': artist_row['musicbrainz_id'], 'musicbrainz_id': artist_row['musicbrainz_id'],
'deezer_id': artist_row['deezer_id'],
'audiodb_id': artist_row['audiodb_id'],
'album_count': album_count, 'album_count': album_count,
'track_count': track_count 'track_count': track_count
}, },

View file

@ -118,6 +118,8 @@ function observeLazyBackgrounds(container) {
// --- MusicBrainz Integration Constants --- // --- MusicBrainz Integration Constants ---
const MUSICBRAINZ_LOGO_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/MusicBrainz_Logo_%282016%29.svg/500px-MusicBrainz_Logo_%282016%29.svg.png'; const MUSICBRAINZ_LOGO_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/MusicBrainz_Logo_%282016%29.svg/500px-MusicBrainz_Logo_%282016%29.svg.png';
const DEEZER_LOGO_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/Deezer_logo.svg/120px-Deezer_logo.svg.png';
const AUDIODB_LOGO_URL = null; // No reliable external logo — uses text fallback
// --- Wishlist Modal Persistence State Management --- // --- Wishlist Modal Persistence State Management ---
const WishlistModalState = { const WishlistModalState = {
@ -25615,26 +25617,47 @@ function createLibraryArtistCard(artist) {
card.style.position = 'relative'; card.style.position = 'relative';
card.style.transition = 'transform 0.2s, box-shadow 0.2s'; card.style.transition = 'transform 0.2s, box-shadow 0.2s';
// Add MusicBrainz icon if ID exists // Add source badges (MusicBrainz, Deezer, AudioDB) stacked on top-right
const badgeSources = [];
if (artist.musicbrainz_id) { if (artist.musicbrainz_id) {
const mbIcon = document.createElement('div'); badgeSources.push({ cls: 'mb-card-icon', logo: MUSICBRAINZ_LOGO_URL, fallback: 'MB', title: 'View on MusicBrainz', url: `https://musicbrainz.org/artist/${artist.musicbrainz_id}` });
mbIcon.className = 'mb-card-icon';
mbIcon.title = 'View on MusicBrainz';
mbIcon.innerHTML = `<img src="${MUSICBRAINZ_LOGO_URL}" style="width: 20px; height: auto; display: block;">`;
mbIcon.onclick = (e) => {
e.stopPropagation();
window.open(`https://musicbrainz.org/artist/${artist.musicbrainz_id}`, '_blank');
};
card.appendChild(mbIcon);
} }
if (artist.deezer_id) {
badgeSources.push({ cls: 'deezer-card-icon', logo: DEEZER_LOGO_URL, fallback: 'Dz', title: 'View on Deezer', url: `https://www.deezer.com/artist/${artist.deezer_id}` });
}
if (artist.audiodb_id) {
const adbSlug = artist.name ? artist.name.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '') : '';
badgeSources.push({ cls: 'audiodb-card-icon', logo: AUDIODB_LOGO_URL, fallback: 'ADB', title: 'View on TheAudioDB', url: `https://www.theaudiodb.com/artist/${artist.audiodb_id}-${adbSlug}` });
}
let badgeOffset = 8;
badgeSources.forEach(source => {
const icon = document.createElement('div');
icon.className = `${source.cls} source-card-icon`;
icon.style.top = badgeOffset + 'px';
icon.title = source.title;
if (source.logo) {
const img = document.createElement('img');
img.src = source.logo;
img.style.cssText = 'width: 20px; height: auto; display: block;';
img.onerror = () => { icon.textContent = source.fallback; };
icon.appendChild(img);
} else {
icon.textContent = source.fallback;
}
icon.onclick = (e) => {
e.stopPropagation();
window.open(source.url, '_blank');
};
card.appendChild(icon);
badgeOffset += 32;
});
// Add watchlist indicator if artist is on the watchlist // Add watchlist indicator if artist is on the watchlist
if (artist.is_watched) { if (artist.is_watched) {
const watchIcon = document.createElement('div'); const watchIcon = document.createElement('div');
watchIcon.className = 'watchlist-card-icon'; watchIcon.className = 'watchlist-card-icon';
watchIcon.title = 'On your watchlist'; watchIcon.title = 'On your watchlist';
watchIcon.style.top = artist.musicbrainz_id ? '40px' : '8px'; watchIcon.style.top = badgeOffset + 'px';
watchIcon.textContent = '👁️'; watchIcon.textContent = '👁️';
card.appendChild(watchIcon); card.appendChild(watchIcon);
} }
@ -25917,33 +25940,47 @@ function updateArtistDetailPageHeaderWithData(artist) {
if (mainTitle) { if (mainTitle) {
mainTitle.textContent = artist.name; mainTitle.textContent = artist.name;
if (artist.musicbrainz_id) { // Remove existing source links (to prevent duplicates)
// Remove existing link if any (to prevent duplicates) mainTitle.querySelectorAll('.source-link-btn').forEach(el => el.remove());
const existingMb = mainTitle.querySelector('.mb-link-btn');
if (existingMb) existingMb.remove();
const mbLink = document.createElement('a'); const adbSlug = artist.name ? artist.name.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '') : '';
mbLink.className = 'mb-link-btn'; const sources = [
mbLink.href = `https://musicbrainz.org/artist/${artist.musicbrainz_id}`; { id: artist.musicbrainz_id, url: `https://musicbrainz.org/artist/${artist.musicbrainz_id}`, logo: MUSICBRAINZ_LOGO_URL, label: 'MusicBrainz' },
mbLink.target = '_blank'; { id: artist.deezer_id, url: `https://www.deezer.com/artist/${artist.deezer_id}`, logo: DEEZER_LOGO_URL, label: 'Deezer' },
mbLink.title = 'View on MusicBrainz'; { id: artist.audiodb_id, url: `https://www.theaudiodb.com/artist/${artist.audiodb_id}-${adbSlug}`, logo: AUDIODB_LOGO_URL, label: 'TheAudioDB' },
];
mbLink.innerHTML = ` sources.forEach(source => {
<img src="${MUSICBRAINZ_LOGO_URL}" style="height: 24px; width: auto; vertical-align: middle; display: inline-block;"> if (!source.id) return;
<span style="color:rgba(255,255,255,0.9); margin-left:10px; font-size: 13px; font-weight: 600; vertical-align: middle;">View on MusicBrainz</span> const link = document.createElement('a');
`; link.className = 'source-link-btn';
mbLink.style.padding = '5px 14px'; link.href = source.url;
mbLink.style.border = '1px solid rgba(255,255,255,0.1)'; link.target = '_blank';
mbLink.style.background = 'rgba(0,0,0,0.4)'; link.title = `View on ${source.label}`;
mbLink.style.borderRadius = '20px'; link.style.padding = '5px 14px';
mbLink.style.marginLeft = '16px'; link.style.border = '1px solid rgba(255,255,255,0.1)';
mbLink.style.display = 'inline-flex'; link.style.background = 'rgba(0,0,0,0.4)';
mbLink.style.alignItems = 'center'; link.style.borderRadius = '20px';
mbLink.style.textDecoration = 'none'; link.style.marginLeft = '16px';
mbLink.style.verticalAlign = 'middle'; link.style.display = 'inline-flex';
link.style.alignItems = 'center';
link.style.textDecoration = 'none';
link.style.verticalAlign = 'middle';
mainTitle.appendChild(mbLink); const span = document.createElement('span');
} span.style.cssText = `color:rgba(255,255,255,0.9); margin-left:${source.logo ? '10px' : '0'}; font-size: 13px; font-weight: 600; vertical-align: middle;`;
span.textContent = `View on ${source.label}`;
if (source.logo) {
const img = document.createElement('img');
img.src = source.logo;
img.style.cssText = 'height: 24px; width: auto; vertical-align: middle; display: inline-block;';
img.onerror = () => { img.style.display = 'none'; span.style.marginLeft = '0'; };
link.appendChild(img);
}
link.appendChild(span);
mainTitle.appendChild(link);
});
} }
} }

View file

@ -22610,12 +22610,47 @@ body {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
} }
.source-card-icon {
position: absolute;
right: 8px;
width: 28px;
height: 28px;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.9 !important;
transition: transform 0.2s;
cursor: pointer;
z-index: 100 !important;
color: white;
font-size: 11px;
font-weight: 800;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.source-card-icon:hover {
transform: scale(1.1);
opacity: 1 !important;
}
.mb-card-icon:hover { .mb-card-icon:hover {
transform: scale(1.1); transform: scale(1.1);
background: #d466a9; background: #d466a9;
opacity: 1 !important; opacity: 1 !important;
} }
.deezer-card-icon:hover {
background: #a238ff;
}
.audiodb-card-icon:hover {
background: #1a6bc4;
}
.watchlist-card-icon { .watchlist-card-icon {
position: absolute; position: absolute;
right: 8px; right: 8px;