From 68d46c5aba132de1c270ffe86af5acdd3fd3c01e Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:06:13 -0700 Subject: [PATCH] Replace Enhanced/Basic toggle with source picker, bump to 2.42 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 of the Search/Artists unification. The Search page's two-mode toggle is replaced by a single 'Search from' dropdown: All sources (Auto), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files). Auto keeps today's fan-out behavior for backwards compatibility; picking a specific source hits only that provider. 'Soulseek' routes to the raw-file basic section, so one picker covers both old modes. Loading text and the enhanced fetch now respect the selected source. Zero API changes — uses the source param added in 2.40 and the shared fetch helper from 2.41. --- web_server.py | 15 ++++++++- webui/index.html | 27 ++++++++-------- webui/static/helper.js | 5 +++ webui/static/search.js | 59 +++++++++++++++++++---------------- webui/static/style.css | 70 ++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 135 insertions(+), 41 deletions(-) diff --git a/web_server.py b/web_server.py index d2e879b4..40052fc3 100644 --- a/web_server.py +++ b/web_server.py @@ -37,7 +37,7 @@ _log_dir = Path(_log_path).parent logger = setup_logging(_log_level, _log_path) # App version — single source of truth for backup metadata, version-info endpoint, etc. -_SOULSYNC_BASE_VERSION = "2.41" +_SOULSYNC_BASE_VERSION = "2.42" def _build_version_string(): """Append short commit hash to version when available (e.g. 2.35+abc1234).""" @@ -22809,6 +22809,19 @@ def get_version_info(): "title": "What's New in SoulSync", "subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes", "sections": [ + { + "title": "Search Source Picker — Pick Where You're Searching", + "description": "The Search page's Enhanced/Basic toggle is replaced by a single 'Search from' dropdown so you can explicitly pick which source to query instead of fanning out to every provider", + "features": [ + "• Choose from: All sources (Auto), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files)", + "• Auto keeps today's multi-source fan-out behavior — no change if you want the current results", + "• Picking a specific source hits only that provider — no more surprise Spotify rate-limit hits from flows that didn't need Spotify", + "• 'Soulseek' routes to the raw-file search (what 'Basic' used to do) — one picker now covers both modes", + "• Loading text shows the selected source (e.g., 'Searching across Apple Music and your library...')", + "• Phase 3 of the Search/Artists unification project — builds on the shared fetch helper from 2.41", + ], + "usage_note": "Sidebar → Search → pick a source in the 'Search from' dropdown above the search bar." + }, { "title": "Shared Enhanced-Search Fetch Helper", "description": "Internal refactor — the Search page and the global search widget now route through one shared fetch helper, so future source-picker work only needs wiring in one place", diff --git a/webui/index.html b/webui/index.html index 10c63f68..08f4b85c 100644 --- a/webui/index.html +++ b/webui/index.html @@ -1869,18 +1869,21 @@ - -
-
- - -
+ +
+ +
+ +
diff --git a/webui/static/helper.js b/webui/static/helper.js index eff92aec..9ee59750 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3599,6 +3599,11 @@ function closeHelperSearch() { // ═══════════════════════════════════════════════════════════════════════════ const WHATS_NEW = { + '2.42': [ + // --- April 23, 2026 --- + { date: 'April 23, 2026' }, + { title: 'Search Source Picker — Pick Where You\'re Searching', desc: 'The Search page\'s Enhanced/Basic toggle is replaced by a single "Search from" dropdown at the top. Choose All sources (Auto — keeps today\'s multi-source fan-out), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, or Soulseek (raw files). Picking a specific source hits only that provider — no more surprise Spotify rate-limit hits from flows that didn\'t need Spotify. "Soulseek" routes to the raw-file search (what "Basic" used to do), so one picker now covers both modes. Loading text reflects the selected source (e.g., "Searching across Apple Music..."). Phase 3 of the Search/Artists unification project', page: 'downloads' }, + ], '2.41': [ // --- April 22, 2026 (late night) --- { date: 'April 22, 2026 (late night)' }, diff --git a/webui/static/search.js b/webui/static/search.js index cbf19504..c6b38573 100644 --- a/webui/static/search.js +++ b/webui/static/search.js @@ -56,41 +56,38 @@ function initializeSearchModeToggle() { return; } - const toggleContainer = document.querySelector('.search-mode-toggle'); - const modeBtns = document.querySelectorAll('.search-mode-btn'); + const sourceSelect = document.getElementById('search-source-select'); const basicSection = document.getElementById('basic-search-section'); const enhancedSection = document.getElementById('enhanced-search-section'); - if (!toggleContainer || !modeBtns.length || !basicSection || !enhancedSection) { - console.warn('Search mode toggle elements not found'); + if (!sourceSelect || !basicSection || !enhancedSection) { + console.warn('Search source picker elements not found'); return; } searchModeToggleInitialized = true; - console.log('✅ Initializing search mode toggle (first time only)'); + console.log('✅ Initializing search source picker (first time only)'); - modeBtns.forEach(btn => { - btn.addEventListener('click', () => { - const mode = btn.dataset.mode; + // Current source selection — 'auto' (fan-out) by default. Soulseek routes + // to the raw-file basic search; everything else routes to enhanced. + let currentSearchSource = sourceSelect.value || 'auto'; - // Update button active states - modeBtns.forEach(b => b.classList.remove('active')); - btn.classList.add('active'); + const applySourceSelection = (value) => { + currentSearchSource = value; + if (value === 'soulseek') { + basicSection.classList.add('active'); + enhancedSection.classList.remove('active'); + } else { + basicSection.classList.remove('active'); + enhancedSection.classList.add('active'); + } + }; - // Update toggle slider position - toggleContainer.setAttribute('data-active', mode); + applySourceSelection(currentSearchSource); - // Toggle sections - if (mode === 'basic') { - basicSection.classList.add('active'); - enhancedSection.classList.remove('active'); - console.log('Switched to basic search mode'); - } else { - basicSection.classList.remove('active'); - enhancedSection.classList.add('active'); - console.log('Switched to enhanced search mode'); - } - }); + sourceSelect.addEventListener('change', (e) => { + applySourceSelection(e.target.value); + console.log('Search source →', currentSearchSource); }); // Initialize enhanced search @@ -221,7 +218,14 @@ function initializeSearchModeToggle() { showDropdown(); const loadingText = document.getElementById('enhanced-loading-text'); if (loadingText) { - loadingText.textContent = `Searching across ${currentMusicSourceName} and your library...`; + const _sourceLabelMap = { + spotify: 'Spotify', itunes: 'Apple Music', deezer: 'Deezer', + discogs: 'Discogs', hydrabase: 'Hydrabase', musicbrainz: 'MusicBrainz', + }; + const _sourceName = currentSearchSource && currentSearchSource !== 'auto' + ? (_sourceLabelMap[currentSearchSource] || currentSearchSource) + : currentMusicSourceName; + loadingText.textContent = `Searching across ${_sourceName} and your library...`; } loadingState.classList.remove('hidden'); emptyState.classList.add('hidden'); @@ -241,7 +245,10 @@ function initializeSearchModeToggle() { _enhancedSearchData = { db_artists: [], primary_source: null, sources: {}, searchId, query }; try { - const data = await enhancedSearchFetch(query, { signal: abortController.signal }); + const data = await enhancedSearchFetch(query, { + source: currentSearchSource, + signal: abortController.signal, + }); console.log('Enhanced results:', data); // Store multi-source state diff --git a/webui/static/style.css b/webui/static/style.css index d71b03ef..5cc7d083 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -32832,10 +32832,76 @@ div.artist-hero-badge { } /* ========================================= */ -/* SEARCH MODE TOGGLE SYSTEM */ +/* ========================================= */ +/* SEARCH SOURCE PICKER (replaces Enhanced/Basic toggle) */ /* ========================================= */ -/* Toggle Container */ +.search-source-picker-container { + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + margin: 20px 0; +} + +.search-source-picker-label { + color: rgba(255, 255, 255, 0.7); + font-family: 'Segoe UI', sans-serif; + font-size: 13px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.search-source-picker-wrapper { + position: relative; + display: inline-flex; + align-items: center; +} + +.search-source-picker-select { + appearance: none; + -webkit-appearance: none; + background: rgba(30, 30, 30, 0.8); + border: 1px solid rgba(64, 64, 64, 0.4); + border-radius: 10px; + color: #ffffff; + padding: 10px 40px 10px 16px; + font-family: 'Segoe UI', sans-serif; + font-size: 14px; + font-weight: 500; + cursor: pointer; + min-width: 220px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.search-source-picker-select:hover { + border-color: rgba(138, 43, 226, 0.6); +} + +.search-source-picker-select:focus { + outline: none; + border-color: rgba(138, 43, 226, 0.9); + box-shadow: 0 2px 10px rgba(138, 43, 226, 0.35); +} + +.search-source-picker-select option { + background: #1e1e1e; + color: #ffffff; +} + +.search-source-picker-caret { + position: absolute; + right: 14px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: rgba(255, 255, 255, 0.6); + font-size: 12px; +} + +/* Legacy toggle (kept for any other references, but unused by the Search page) */ .search-mode-toggle-container { display: flex; justify-content: center;