From 0c4fad033d8f704875215f17ac29cf8b71edb7b7 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Fri, 1 May 2026 17:49:34 -0700 Subject: [PATCH] Show artist breadcrumb on sidebar Library button when on artist-detail page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Artist-detail is a "pseudo-page" reachable from Library, the unified Search page, and the global search popover. It has no [data-page] match in the sidebar, so navigateToPage's bulk-active-removal left every nav button unhighlighted while the user was viewing an artist — the sidebar offered no visual anchor for where they were. Now: - navigateToPage('artist-detail') falls back to highlighting the Library button when no [data-page] match exists, anchoring the sidebar to the canonical home for artist detail views. - A new _updateSidebarLibraryBreadcrumb() helper rewrites the Library button label between plain "Library" and a "Library / Artist Name" breadcrumb based on currentPage + artistDetailPageState. Long names (>14 chars) truncate with an ellipsis; the full name shows on hover via the title attribute. - Called from navigateToPage (entering / leaving the page) and from loadArtistDetailData (covers same-page artist switches in the similar-artist chain where currentPage stays 'artist-detail'). CSS adds .nav-text-root / .nav-text-sep / .nav-text-context selectors so the "Library" anchor word stays visually dominant while the separator and artist name dim to a secondary tier — readable but not competing for attention. Pure visual change. No backend touched. No new tests (DOM-only). --- webui/static/helper.js | 5 ++++ webui/static/init.js | 13 +++++++++ webui/static/library.js | 65 +++++++++++++++++++++++++++++++++++++++++ webui/static/style.css | 36 +++++++++++++++++++++++ 4 files changed, 119 insertions(+) diff --git a/webui/static/helper.js b/webui/static/helper.js index b58876e1..f315d77a 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3441,6 +3441,11 @@ function closeHelperSearch() { // projects that span multiple commits before shipping. Strip the flag at // release time and add a real `date:` line at the top of the version block. const WHATS_NEW = { + '2.4.2': [ + // --- post-2.4.1 dev work — entries hidden by _getLatestWhatsNewVersion until the build version bumps --- + { date: 'Unreleased — 2.4.2 dev cycle' }, + { title: 'Sidebar Library Button Shows Artist Breadcrumb', desc: 'when you open an artist detail page (from library, search, or the global search popover), the sidebar Library button now lights up and rewrites its label to "Library / Artist Name" — long names truncate with an ellipsis and the full name shows on hover. revertes to plain "Library" when you leave. purely visual, no functionality change.', page: 'library' }, + ], '2.4.1': [ // --- May 1, 2026 — patch release --- { date: 'May 1, 2026 — 2.4.1 release' }, diff --git a/webui/static/init.js b/webui/static/init.js index 6b3b11eb..efd6baa1 100644 --- a/webui/static/init.js +++ b/webui/static/init.js @@ -2189,6 +2189,13 @@ function navigateToPage(pageId, options = {}) { const navButton = document.querySelector(`[data-page="${pageId}"]`); if (navButton) { navButton.classList.add('active'); + } else if (pageId === 'artist-detail') { + // Artist-detail is a "pseudo-page" reachable from Library, Search, + // or the global search popover — it has no [data-page] match. Treat + // it as a Library context so the sidebar still anchors the user + // somewhere instead of showing a blank active state. + const libraryBtn = document.querySelector('[data-page="library"]'); + if (libraryBtn) libraryBtn.classList.add('active'); } // Update pages @@ -2199,6 +2206,12 @@ function navigateToPage(pageId, options = {}) { currentPage = pageId; + // Refresh the Library button label so artist-detail shows a breadcrumb + // ("Library / Artist Name") and other pages show plain "Library". + if (typeof _updateSidebarLibraryBreadcrumb === 'function') { + _updateSidebarLibraryBreadcrumb(); + } + if (!options.skipPushState) { const urlPath = pageId === 'dashboard' ? '/' : '/' + pageId; if (window.location.pathname !== urlPath) { diff --git a/webui/static/library.js b/webui/static/library.js index 6c4e7a4c..0e6bfa56 100644 --- a/webui/static/library.js +++ b/webui/static/library.js @@ -707,6 +707,63 @@ let discographyFilterState = { ownership: 'all' // 'all', 'owned', 'missing' }; +// Maximum visible characters of an artist name in the sidebar Library +// breadcrumb. Names longer than this get truncated with an ellipsis so the +// nav button width stays consistent across the rest of the sidebar. +const _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN = 14; + + +function _updateSidebarLibraryBreadcrumb() { + // Rewrite the Library nav button label between plain "Library" and a + // "Library / " breadcrumb depending on whether the user is on + // the artist-detail pseudo-page. Pure visual — touches no app state. + const btn = document.querySelector('[data-page="library"]'); + if (!btn) return; + const textEl = btn.querySelector('.nav-text'); + if (!textEl) return; + + const onArtistDetail = (typeof currentPage === 'string' && currentPage === 'artist-detail'); + const artistName = onArtistDetail ? (artistDetailPageState.currentArtistName || '') : ''; + + if (!onArtistDetail || !artistName) { + // Default state: plain "Library" label. Use textContent so we wipe + // any previously-injected breadcrumb spans cleanly. + textEl.textContent = 'Library'; + textEl.removeAttribute('data-breadcrumb'); + return; + } + + // Truncate long names so the button width stays consistent. + let display = artistName; + if (display.length > _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN) { + display = display.slice(0, _SIDEBAR_BREADCRUMB_ARTIST_MAXLEN - 1).trimEnd() + '…'; + } + + // Render via inline spans so CSS can style the root / separator / context + // independently. Escape via textContent on individual spans. + textEl.setAttribute('data-breadcrumb', '1'); + textEl.textContent = ''; + const root = document.createElement('span'); + root.className = 'nav-text-root'; + root.textContent = 'Library'; + const sep = document.createElement('span'); + sep.className = 'nav-text-sep'; + sep.textContent = ' / '; + const ctx = document.createElement('span'); + ctx.className = 'nav-text-context'; + ctx.textContent = display; + ctx.title = artistName; // full name on hover + textEl.appendChild(root); + textEl.appendChild(sep); + textEl.appendChild(ctx); +} + +// Expose so init.js navigateToPage can call it without a circular import. +if (typeof window !== 'undefined') { + window._updateSidebarLibraryBreadcrumb = _updateSidebarLibraryBreadcrumb; +} + + // Friendly labels for the dynamic "← Back to X" button on the artist-detail page. // Page id (the value of currentPage) -> button label. const _ARTIST_DETAIL_BACK_LABELS = { @@ -895,6 +952,14 @@ function initializeArtistDetailPage() { async function loadArtistDetailData(artistId, artistName) { console.log(`🔄 Loading artist detail data for: ${artistName} (ID: ${artistId})`); + // Refresh the sidebar Library breadcrumb so it picks up the new artist + // name. Covers same-page navigation between artists (similar-artist + // chain) where navigateToPage doesn't fire because the page id stays + // 'artist-detail'. + if (typeof _updateSidebarLibraryBreadcrumb === 'function') { + _updateSidebarLibraryBreadcrumb(); + } + // Reset discography filters to defaults resetDiscographyFilters(); diff --git a/webui/static/style.css b/webui/static/style.css index a25df5ed..cbd549e2 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -491,6 +491,42 @@ body { font-weight: 600; } +/* Library breadcrumb — when on the artist-detail pseudo-page, the Library + button label rewrites to "Library / ". The accent color stays on + the "Library" root; the separator and artist name are dimmed so the + anchor word remains scannable at a glance. JS truncates long names and + sets the full name as a `title` attribute for hover. */ +.nav-text[data-breadcrumb] { + display: inline-flex; + align-items: baseline; + max-width: 100%; + min-width: 0; +} +.nav-text[data-breadcrumb] .nav-text-root { + color: inherit; + flex-shrink: 0; +} +.nav-text[data-breadcrumb] .nav-text-sep { + color: rgba(255, 255, 255, 0.4); + font-weight: 400; + flex-shrink: 0; +} +.nav-button.active .nav-text[data-breadcrumb] .nav-text-sep { + color: rgba(255, 255, 255, 0.45); +} +.nav-text[data-breadcrumb] .nav-text-context { + color: rgba(255, 255, 255, 0.75); + font-weight: 500; + font-style: italic; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} +.nav-button.active .nav-text[data-breadcrumb] .nav-text-context { + color: rgba(255, 255, 255, 0.85); +} + /* Sidebar Spacer */ .sidebar-spacer { flex: 1;