From ae5d77810dd1b7dc5f68943bb77d5d9a1b3b8649 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Wed, 18 Feb 2026 21:30:15 -0800 Subject: [PATCH] Add Deezer & AudioDB source badges to library artist cards and detail page --- database/music_database.py | 8 ++- webui/static/script.js | 109 +++++++++++++++++++++++++------------ webui/static/style.css | 35 ++++++++++++ 3 files changed, 114 insertions(+), 38 deletions(-) diff --git a/database/music_database.py b/database/music_database.py index 5f916855..ad4c05cb 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -4319,13 +4319,14 @@ class MusicDatabase: a.spotify_artist_id, a.itunes_artist_id, a.deezer_id, + a.audiodb_id, COUNT(DISTINCT al.id) as album_count, COUNT(DISTINCT t.id) as track_count FROM artists a LEFT JOIN albums al ON a.id = al.artist_id LEFT JOIN tracks t ON al.id = t.album_id 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 LIMIT ? OFFSET ? """ @@ -4371,6 +4372,7 @@ class MusicDatabase: 'genres': artist.genres, 'musicbrainz_id': row['musicbrainz_id'], 'deezer_id': row['deezer_id'], + 'audiodb_id': row['audiodb_id'], 'album_count': row['album_count'] or 0, 'track_count': row['track_count'] or 0, 'is_watched': bool(is_watched) @@ -4426,7 +4428,7 @@ class MusicDatabase: # Get artist information cursor.execute(""" 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 WHERE id = ? """, (artist_id,)) @@ -4564,6 +4566,8 @@ class MusicDatabase: 'genres': genres, 'server_source': artist_row['server_source'], 'musicbrainz_id': artist_row['musicbrainz_id'], + 'deezer_id': artist_row['deezer_id'], + 'audiodb_id': artist_row['audiodb_id'], 'album_count': album_count, 'track_count': track_count }, diff --git a/webui/static/script.js b/webui/static/script.js index 1a50c3a8..0211c84a 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -118,6 +118,8 @@ function observeLazyBackgrounds(container) { // --- 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 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 --- const WishlistModalState = { @@ -25615,26 +25617,47 @@ function createLibraryArtistCard(artist) { card.style.position = 'relative'; 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) { - const mbIcon = document.createElement('div'); - mbIcon.className = 'mb-card-icon'; - mbIcon.title = 'View on MusicBrainz'; - mbIcon.innerHTML = ``; - - mbIcon.onclick = (e) => { + badgeSources.push({ cls: 'mb-card-icon', logo: MUSICBRAINZ_LOGO_URL, fallback: 'MB', title: 'View on MusicBrainz', url: `https://musicbrainz.org/artist/${artist.musicbrainz_id}` }); + } + 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(`https://musicbrainz.org/artist/${artist.musicbrainz_id}`, '_blank'); + window.open(source.url, '_blank'); }; - card.appendChild(mbIcon); - } + card.appendChild(icon); + badgeOffset += 32; + }); // Add watchlist indicator if artist is on the watchlist if (artist.is_watched) { const watchIcon = document.createElement('div'); watchIcon.className = 'watchlist-card-icon'; watchIcon.title = 'On your watchlist'; - watchIcon.style.top = artist.musicbrainz_id ? '40px' : '8px'; + watchIcon.style.top = badgeOffset + 'px'; watchIcon.textContent = '👁️'; card.appendChild(watchIcon); } @@ -25917,33 +25940,47 @@ function updateArtistDetailPageHeaderWithData(artist) { if (mainTitle) { mainTitle.textContent = artist.name; - if (artist.musicbrainz_id) { - // Remove existing link if any (to prevent duplicates) - const existingMb = mainTitle.querySelector('.mb-link-btn'); - if (existingMb) existingMb.remove(); - - const mbLink = document.createElement('a'); - mbLink.className = 'mb-link-btn'; - mbLink.href = `https://musicbrainz.org/artist/${artist.musicbrainz_id}`; - mbLink.target = '_blank'; - mbLink.title = 'View on MusicBrainz'; + // Remove existing source links (to prevent duplicates) + mainTitle.querySelectorAll('.source-link-btn').forEach(el => el.remove()); - mbLink.innerHTML = ` - - View on MusicBrainz - `; - mbLink.style.padding = '5px 14px'; - mbLink.style.border = '1px solid rgba(255,255,255,0.1)'; - mbLink.style.background = 'rgba(0,0,0,0.4)'; - mbLink.style.borderRadius = '20px'; - mbLink.style.marginLeft = '16px'; - mbLink.style.display = 'inline-flex'; - mbLink.style.alignItems = 'center'; - mbLink.style.textDecoration = 'none'; - mbLink.style.verticalAlign = 'middle'; + const adbSlug = artist.name ? artist.name.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '') : ''; + const sources = [ + { id: artist.musicbrainz_id, url: `https://musicbrainz.org/artist/${artist.musicbrainz_id}`, logo: MUSICBRAINZ_LOGO_URL, label: 'MusicBrainz' }, + { id: artist.deezer_id, url: `https://www.deezer.com/artist/${artist.deezer_id}`, logo: DEEZER_LOGO_URL, label: 'Deezer' }, + { id: artist.audiodb_id, url: `https://www.theaudiodb.com/artist/${artist.audiodb_id}-${adbSlug}`, logo: AUDIODB_LOGO_URL, label: 'TheAudioDB' }, + ]; - mainTitle.appendChild(mbLink); - } + sources.forEach(source => { + if (!source.id) return; + const link = document.createElement('a'); + link.className = 'source-link-btn'; + link.href = source.url; + link.target = '_blank'; + link.title = `View on ${source.label}`; + link.style.padding = '5px 14px'; + link.style.border = '1px solid rgba(255,255,255,0.1)'; + link.style.background = 'rgba(0,0,0,0.4)'; + link.style.borderRadius = '20px'; + link.style.marginLeft = '16px'; + link.style.display = 'inline-flex'; + link.style.alignItems = 'center'; + link.style.textDecoration = 'none'; + link.style.verticalAlign = 'middle'; + + 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); + }); } } diff --git a/webui/static/style.css b/webui/static/style.css index 6318b4d3..82161dc0 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -22610,12 +22610,47 @@ body { 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 { transform: scale(1.1); background: #d466a9; opacity: 1 !important; } +.deezer-card-icon:hover { + background: #a238ff; +} + +.audiodb-card-icon:hover { + background: #1a6bc4; +} + .watchlist-card-icon { position: absolute; right: 8px;