@@ -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