diff --git a/database/music_database.py b/database/music_database.py index 254747c..ba27176 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -4033,13 +4033,14 @@ class MusicDatabase: a.name, a.thumb_url, a.genres, + a.musicbrainz_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 + GROUP BY a.id, a.name, a.thumb_url, a.genres, a.musicbrainz_id ORDER BY a.name COLLATE NOCASE LIMIT ? OFFSET ? """ @@ -4076,6 +4077,7 @@ class MusicDatabase: 'name': artist.name, 'image_url': artist.thumb_url, 'genres': artist.genres, + 'musicbrainz_id': row['musicbrainz_id'], 'album_count': row['album_count'] or 0, 'track_count': row['track_count'] or 0 } @@ -4130,7 +4132,7 @@ class MusicDatabase: # Get artist information cursor.execute(""" SELECT - id, name, thumb_url, genres, server_source + id, name, thumb_url, genres, server_source, musicbrainz_id FROM artists WHERE id = ? """, (artist_id,)) @@ -4173,6 +4175,7 @@ class MusicDatabase: a.year, SUM(a.track_count) as track_count, MAX(a.thumb_url) as thumb_url, + MAX(a.musicbrainz_release_id) as musicbrainz_release_id, COUNT(t.id) as owned_tracks FROM albums a LEFT JOIN tracks t ON a.id = t.album_id @@ -4225,6 +4228,7 @@ class MusicDatabase: 'owned': True, # All albums in our DB are owned 'track_count': album_row['track_count'], 'owned_tracks': owned_tracks, + 'musicbrainz_release_id': album_row['musicbrainz_release_id'], 'track_completion': completion_percentage } @@ -4265,6 +4269,7 @@ class MusicDatabase: 'image_url': artist_image_url, 'genres': genres, 'server_source': artist_row['server_source'], + 'musicbrainz_id': artist_row['musicbrainz_id'], 'album_count': album_count, 'track_count': track_count }, diff --git a/webui/static/script.js b/webui/static/script.js index ec8ec9b..205ed88 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -20011,8 +20011,20 @@ function createArtistCardHTML(artist) { // Track if image needs to be lazy loaded const needsImage = imageUrl ? 'false' : 'true'; + // Check for MusicBrainz ID + let mbIconHTML = ''; + if (artist.musicbrainz_id) { + const mbLogoUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/MusicBrainz_Logo_%282016%29.svg/500px-MusicBrainz_Logo_%282016%29.svg.png'; // Use official SVG logo + mbIconHTML = ` +
+ +
+ `; + } + return `
+ ${mbIconHTML}
@@ -20122,6 +20134,17 @@ async function loadArtistDiscography(artistId, artistName = null) { singles: data.singles || [] }; + // Update selected artist with full details from backend (includes MusicBrainz ID) + if (data.artist) { + console.log('✨ Updating artist details with fresh data from backend'); + artistsPageState.selectedArtist = { + ...artistsPageState.selectedArtist, + ...data.artist + }; + // Refresh header to show MusicBrainz link + updateArtistDetailHeader(artistsPageState.selectedArtist); + } + console.log(`✅ Loaded ${discography.albums.length} albums and ${discography.singles.length} singles`); // Cache the results @@ -21120,6 +21143,35 @@ function updateArtistDetailHeader(artist) { if (nameElement) { nameElement.textContent = artist.name; + + // DEBUG: Log the artist object to check for MBID + console.log(`🔍 [DEBUG] Updating header for ${artist.name}`, artist); + console.log(`🔍 [DEBUG] MBID present?`, artist.musicbrainz_id); + + // Add MusicBrainz link if available + if (artist.musicbrainz_id) { + console.log('✅ [DEBUG] Adding MusicBrainz link to header'); + // Remove existing MB link if any + const existingMb = nameElement.parentNode.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'; + mbLink.innerHTML = ` + + `; + // Simplified button style to just be the badge/icon for cleaner look header + mbLink.style.padding = '0'; + mbLink.style.border = 'none'; + mbLink.style.background = 'transparent'; + mbLink.style.marginLeft = '12px'; + mbLink.style.verticalAlign = 'middle'; + + nameElement.appendChild(mbLink); + } } if (genresElement) { @@ -25117,6 +25169,26 @@ function createLibraryArtistCard(artist) { const card = document.createElement("div"); card.className = "library-artist-card"; card.setAttribute("data-artist-id", artist.id); + // Add relative positioning for icon and smooth transition + card.style.position = 'relative'; + card.style.transition = 'transform 0.2s, box-shadow 0.2s'; + + // Add MusicBrainz icon if ID exists + if (artist.musicbrainz_id) { + const mbIcon = document.createElement('div'); + mbIcon.className = 'mb-card-icon'; + mbIcon.title = 'View on MusicBrainz'; + + // Use official SVG logo + const mbLogoUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/MusicBrainz_Logo_%282016%29.svg/500px-MusicBrainz_Logo_%282016%29.svg.png'; + mbIcon.innerHTML = ``; + + mbIcon.onclick = (e) => { + e.stopPropagation(); + window.open(`https://musicbrainz.org/artist/${artist.musicbrainz_id}`, '_blank'); + }; + card.appendChild(mbIcon); + } // Create image element const imageContainer = document.createElement("div"); @@ -25329,12 +25401,12 @@ async function loadArtistDetailData(artistId, artistName) { console.log(`🎨 Main content visibility:`, document.getElementById('artist-detail-main')); console.log(`🎨 Albums section:`, document.getElementById('albums-section')); - // Update header with artist name now that data is loaded - updateArtistDetailPageHeader(data.artist.name); - - // Populate the page with data + // Populate the page with data (which updates the hero section and sets textContent) populateArtistDetailPage(data); + // Update header with artist name and MusicBrainz link LAST to avoid overwrite + updateArtistDetailPageHeaderWithData(data.artist); + } catch (error) { console.error(`❌ Error loading artist detail data:`, error); @@ -25358,6 +25430,63 @@ function updateArtistDetailPageHeader(artistName) { const mainTitle = document.getElementById("artist-info-name"); if (mainTitle) { mainTitle.textContent = artistName; + + // Try to find the artist object in memory to get the MBID + // We can look at the data passed to populateArtistDetailPage if this function accepted the full object + // Or access the state if it was saved + + // Actually, let's look at how this is invoked. It's called from loadArtistDetailData which has the full 'data' object. + // But this function only accepts 'artistName'. + // We should query the state or modify the function signature. + // For now, let's try to find the MB link element and update it if we can find the artist in data. + } +} + +function updateArtistDetailPageHeaderWithData(artist) { + // Target the visible header element + const mainTitle = document.getElementById("artist-detail-name"); + + if (mainTitle) { + console.log('✅ [DEBUG] Updating header for:', artist.name); + // We assume textContent is set by updateArtistHeroSection, so we just APPEND the link + // But to be safe, we can ensure name is there. + // If we run AFTER populateArtistDetailPage, textContent is already set. + + // If we reset textContent here, we might lose other formatting? + // artist-detail-name usually just contains text. + mainTitle.textContent = artist.name; + + if (artist.musicbrainz_id) { + console.log('✅ [DEBUG] Adding MusicBrainz link to DETAIL page header for', artist.name); + + // 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'; + // Use the specific logo requested by user + const mbLogoUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/MusicBrainz_Logo_%282016%29.svg/500px-MusicBrainz_Logo_%282016%29.svg.png"; + + 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'; + + mainTitle.appendChild(mbLink); + } } } @@ -25602,6 +25731,28 @@ function createReleaseCard(release) { card.setAttribute("data-release-id", release.id || ""); card.setAttribute("data-spotify-id", release.spotify_id || ""); + // DEBUG: Log release to check for MBID + // console.log(`🔍 [DEBUG] Release: ${release.title}`, release); + + // Add MusicBrainz icon if available + let mbIcon = null; + if (release.musicbrainz_release_id) { + console.log(`✅ [DEBUG] Adding MB icon for release: ${release.title}`); + const mbLogoUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/MusicBrainz_Logo_%282016%29.svg/500px-MusicBrainz_Logo_%282016%29.svg.png"; + mbIcon = document.createElement("div"); + mbIcon.className = "mb-card-icon"; + mbIcon.title = "View on MusicBrainz"; + // Use image instead of text + mbIcon.innerHTML = ``; + mbIcon.onclick = (e) => { + e.stopPropagation(); + window.open(`https://musicbrainz.org/release/${release.musicbrainz_release_id}`, '_blank'); + }; + // Will append last + } + + + // Create image const imageContainer = document.createElement("div"); if (release.image_url && release.image_url.trim() !== "") { @@ -25733,6 +25884,11 @@ function createReleaseCard(release) { card.appendChild(year); card.appendChild(completion); + // Add MusicBrainz icon LAST to ensure it's on top + if (release.musicbrainz_release_id && mbIcon) { // Check if mbIcon was created + card.appendChild(mbIcon); + } + // Add click handler for release card card.addEventListener("click", async () => { console.log(`Clicked on release: ${release.title} (Owned: ${release.owned})`); diff --git a/webui/static/style.css b/webui/static/style.css index 425ca61..b6d3aa0 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -21714,4 +21714,75 @@ body { border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 8px solid rgba(30, 30, 30, 0.98); +} + +/* MusicBrainz Integration */ +.release-card { + position: relative !important; + /* Ensure positioning context */ +} + +.mb-link-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 6px 12px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 6px; + color: #fff; + text-decoration: none; + font-size: 13px; + font-weight: 500; + transition: all 0.2s ease; + margin-left: 10px; + cursor: pointer; + vertical-align: middle; +} + +.mb-link-btn:hover { + background: rgba(186, 71, 143, 0.4); + /* MusicBrainz Purple-ish */ + border-color: rgba(186, 71, 143, 0.6); + transform: translateY(-1px); +} + +.mb-link-btn img, +.mb-link-btn svg { + width: 16px; + height: 16px; + margin-right: 6px; + vertical-align: middle; +} + +.mb-card-icon { + position: absolute; + top: 8px; + right: 8px; + width: 28px; + height: 28px; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + /* MusicBrainz Purple - Force visible color */ + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.9 !important; + /* Force visible */ + transition: transform 0.2s; + cursor: pointer; + z-index: 100 !important; + /* Force on top */ + color: white; + font-size: 11px; + font-weight: 800; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); +} + +.mb-card-icon:hover { + transform: scale(1.1); + background: #d466a9; + opacity: 1 !important; } \ No newline at end of file