diff --git a/webui/index.html b/webui/index.html index 218b0005..310b8fca 100644 --- a/webui/index.html +++ b/webui/index.html @@ -2307,11 +2307,19 @@ - + diff --git a/webui/static/library.js b/webui/static/library.js index b74a30ed..0cc1efc0 100644 --- a/webui/static/library.js +++ b/webui/static/library.js @@ -358,13 +358,60 @@ function showLibraryLoading(show) { function showLibraryEmpty(show) { const emptyElement = document.getElementById("library-empty"); - if (emptyElement) { - if (show) { - emptyElement.classList.remove("hidden"); - } else { - emptyElement.classList.add("hidden"); + if (!emptyElement) return; + if (!show) { + emptyElement.classList.add("hidden"); + return; + } + + // When a search query is active and returned zero library hits, swap the + // generic "no artists" copy for a CTA that hands the query off to /search + // so the user can look the artist up across metadata sources without + // retyping. + const query = (libraryPageState.currentSearch || '').trim(); + const iconEl = document.getElementById('library-empty-icon'); + const titleEl = document.getElementById('library-empty-title'); + const subtitleEl = document.getElementById('library-empty-subtitle'); + const ctaEl = document.getElementById('library-empty-search-cta'); + const ctaQueryEl = document.getElementById('library-empty-search-cta-query'); + + if (query) { + if (iconEl) iconEl.textContent = '🔎'; + if (titleEl) titleEl.textContent = `"${query}" isn't in your library`; + if (subtitleEl) subtitleEl.textContent = 'They might be available on a connected metadata source.'; + if (ctaQueryEl) ctaQueryEl.textContent = `"${query}"`; + if (ctaEl) { + ctaEl.classList.remove('hidden'); + // Rebind cleanly — onclick avoids duplicate listeners across renders. + ctaEl.onclick = () => _handoffLibrarySearchToEnhancedSearch(query); + } + } else { + if (iconEl) iconEl.textContent = '🎵'; + if (titleEl) titleEl.textContent = 'No artists found'; + if (subtitleEl) subtitleEl.textContent = 'Try adjusting your search or filters'; + if (ctaEl) { + ctaEl.classList.add('hidden'); + ctaEl.onclick = null; } } + + emptyElement.classList.remove("hidden"); +} + +// Navigate to /search and pre-fill the enhanced search input with the query +// the user had typed into the library search. Uses the same hand-off pattern +// the global widget uses for Soulseek — navigate, then dispatch an `input` +// event so the Search page's existing debounced search kicks in. +function _handoffLibrarySearchToEnhancedSearch(query) { + if (typeof navigateToPage !== 'function') return; + navigateToPage('search'); + setTimeout(() => { + const input = document.getElementById('enhanced-search-input'); + if (input && query) { + input.value = query; + input.dispatchEvent(new Event('input', { bubbles: true })); + } + }, 300); } async function openWatchAllUnwatchedModal() { diff --git a/webui/static/style.css b/webui/static/style.css index ada5555e..b2d70932 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -23054,6 +23054,39 @@ body.helper-mode-active #dashboard-activity-feed:hover { max-width: 400px; } +/* Hand-off CTA shown in the library empty state when the user's search + returns no library matches — offers to run the same query against the + configured metadata source on the /search page. */ +.library-empty-search-cta { + display: inline-flex; + align-items: center; + gap: 10px; + margin-top: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.22), rgba(var(--accent-rgb), 0.08)); + border: 1px solid rgba(var(--accent-rgb), 0.35); + border-radius: 10px; + color: rgb(var(--accent-light-rgb, var(--accent-rgb))); + font-family: inherit; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease; +} +.library-empty-search-cta:hover { + transform: translateY(-1px); + border-color: rgba(var(--accent-rgb), 0.55); + box-shadow: 0 6px 20px rgba(var(--accent-rgb), 0.22); +} +.library-empty-search-cta:active { transform: translateY(0); } +.library-empty-search-cta-icon { font-size: 16px; } +.library-empty-search-cta-arrow { + font-weight: 700; + transition: transform 0.15s ease; +} +.library-empty-search-cta:hover .library-empty-search-cta-arrow { transform: translateX(3px); } +#library-empty-search-cta-query { color: #fff; } + /* Pagination */ .library-pagination { display: flex;