Treat Soulseek as configurable in source picker (require slskd_url)
Cin flagged that Soulseek was always rendered as configured in the source picker, even on dev instances with no slskd set up — letting users click it and fire searches that could never succeed. Three coordinated changes: 1. web_server.py SERVICE_CONFIG_REGISTRY: add Soulseek entry requiring `slskd_url`. /api/settings/config-status now reports its real state alongside every other service. 2. shared-helpers.js _ALWAYS_CONFIGURED_SOURCES: drop 'soulseek'. The set is now just MusicBrainz + YouTube Music Videos (sources that genuinely don't need user creds). Soulseek goes through the normal config-status code path. 3. shared-helpers.js openSettingsForSource: special-case Soulseek to route to Settings → Downloads tab (where slskd URL field lives, gated behind the download-source-mode dropdown) and scroll to the #soulseek-url input. Every other source still routes to Connections and scrolls to its .stg-service card. Without this, Soulseek's "click to configure" landed on a Connections card that doesn't exist (Soulseek's URL/key fields are scoped to the download-source selection on the Downloads tab).
This commit is contained in:
parent
005c6ad73a
commit
325292ce5a
3 changed files with 29 additions and 9 deletions
|
|
@ -4455,6 +4455,12 @@ SERVICE_CONFIG_REGISTRY = {
|
|||
'acoustid': {'required': ['api_key']},
|
||||
'listenbrainz': {'required': ['token']},
|
||||
'hydrabase': {'required': ['url', 'api_key']},
|
||||
# Soulseek (slskd) needs a base URL. Used by the search source picker
|
||||
# to dim Soulseek and redirect to Settings when the user has no slskd
|
||||
# configured — clicking it would otherwise fire searches that always
|
||||
# fail. URL field lives on Settings → Downloads, gated behind the
|
||||
# download-source-mode dropdown.
|
||||
'soulseek': {'required': ['slskd_url']},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3460,6 +3460,7 @@ const WHATS_NEW = {
|
|||
{ title: 'Cached Search Results Restore on Navigate-Back', desc: 'Previously, navigating away from /search via a sidebar link dismissed the dropdown (the click registered as outside-click). When you came back, the input still held your query but the results were hidden until you typed again or clicked Show Results. Now the per-query cache renders automatically when you re-enter /search, so your results are right where you left them. Cin flagged the round-trip during PR review', page: 'search', unreleased: true },
|
||||
{ title: 'Fix Soulseek Handoff from Global Search Going Through Metadata Flow', desc: 'When you clicked the Soulseek icon in the sidebar global search popover, it navigated to /search and wrote the query into the enhanced-search input — which then ran the metadata flow against whatever your default source was (Spotify, Deezer, etc.) instead of the raw Soulseek file search you actually wanted. Cin flagged it during PR review. Now the handoff pre-fills the basic-search input directly and clicks the Search page\'s Soulseek icon so the controller\'s onSoulseekSelected callback owns the section swap and runs performDownloadsSearch with the right query', page: 'search', unreleased: true },
|
||||
{ title: 'Stale Search Requests No Longer Flash Empty Results on Fast Retype', desc: 'Cin flagged a race in createSearchController: when you typed a query then quickly re-typed before the first fetch returned, the first fetch\'s catch block (firing on AbortError after the second submitQuery aborted it) cleared loadingSources and notified the UI, causing a brief flash of empty/error state while the new query\'s fetch was still mid-flight. Added a monotonic _requestSeq token — each fetch captures the next value, and stale completions bail before mutating shared state. The controller still aborts in-flight fetches on supersession; this just keeps the abort-cleanup of the old request from clobbering the new one\'s spinner', page: 'search', unreleased: true },
|
||||
{ title: 'Source Picker Dims Soulseek When slskd Isn\'t Configured', desc: 'Cin pointed out that the Soulseek icon was always rendered as configured, so users without slskd set up could click it and fire searches that would never succeed. Soulseek is now in the backend config-status registry as `required: [slskd_url]` and removed from the frontend\'s always-configured set. Without slskd, the icon dims and clicking it routes to Settings → Downloads tab (where the slskd URL field lives, gated behind the download-source dropdown) instead of Settings → Connections', page: 'search', unreleased: true },
|
||||
],
|
||||
'2.39': [
|
||||
// --- April 22, 2026 ---
|
||||
|
|
|
|||
|
|
@ -87,7 +87,10 @@ const SOURCE_ORDER = [
|
|||
|
||||
// Sources the config-status endpoint doesn't cover because they don't need
|
||||
// user-supplied credentials — they always render as "configured" in the picker.
|
||||
const _ALWAYS_CONFIGURED_SOURCES = new Set(['musicbrainz', 'youtube_videos', 'soulseek']);
|
||||
// Soulseek IS configurable (needs slskd URL), so it's intentionally not here:
|
||||
// /api/settings/config-status reports its real state and the picker dims it
|
||||
// when no slskd is set up, redirecting clicks to Settings → Downloads.
|
||||
const _ALWAYS_CONFIGURED_SOURCES = new Set(['musicbrainz', 'youtube_videos']);
|
||||
|
||||
// Fetch /api/settings/config-status and return a map { src -> bool }
|
||||
// covering every source in SOURCE_ORDER. Sources not present in the backend
|
||||
|
|
@ -438,22 +441,32 @@ function createSearchController({
|
|||
}
|
||||
|
||||
|
||||
// Navigate to Settings → Connections tab and scroll to the service card that
|
||||
// Navigate to Settings → relevant tab and scroll to the service card that
|
||||
// matches the picker's source id. Called when a user clicks an unconfigured
|
||||
// source icon.
|
||||
// source icon. Soulseek is special-cased to land on the Downloads tab where
|
||||
// its slskd URL field lives (gated behind the download-source-mode select);
|
||||
// every other source has a card on Connections.
|
||||
function openSettingsForSource(src) {
|
||||
if (typeof navigateToPage !== 'function') return;
|
||||
navigateToPage('settings');
|
||||
const targetTab = src === 'soulseek' ? 'downloads' : 'connections';
|
||||
setTimeout(() => {
|
||||
try {
|
||||
if (typeof switchSettingsTab === 'function') switchSettingsTab('connections');
|
||||
if (typeof switchSettingsTab === 'function') switchSettingsTab(targetTab);
|
||||
} catch (_) { /* best-effort */ }
|
||||
setTimeout(() => {
|
||||
const card = document.querySelector(`#settings-page .stg-service[data-service="${src}"]`);
|
||||
if (card) {
|
||||
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
card.classList.add('stg-service-flash');
|
||||
setTimeout(() => card.classList.remove('stg-service-flash'), 2200);
|
||||
// Soulseek doesn't have a .stg-service card — scroll to the
|
||||
// slskd URL input instead so the user lands on the right field.
|
||||
const target = src === 'soulseek'
|
||||
? document.querySelector('#settings-page #soulseek-url')
|
||||
: document.querySelector(`#settings-page .stg-service[data-service="${src}"]`);
|
||||
if (!target) return;
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
if (src === 'soulseek') {
|
||||
try { target.focus(); } catch (_) { /* best-effort */ }
|
||||
} else {
|
||||
target.classList.add('stg-service-flash');
|
||||
setTimeout(() => target.classList.remove('stg-service-flash'), 2200);
|
||||
}
|
||||
}, 120);
|
||||
}, 60);
|
||||
|
|
|
|||
Loading…
Reference in a new issue