Simplify service status and labels
- Flatten the Spotify service-status rendering so it shows rate-limit and recovery states explicitly, while otherwise displaying the active metadata provider directly. - Keep the Spotify auth controls and metadata-source picker aligned with the real session state after authenticate and disconnect flows. - Return "Unmapped" for unknown metadata source labels instead of implying iTunes. - Update the metadata registry tests to cover the new label fallback.
This commit is contained in:
parent
55603be14c
commit
74e3cc460c
4 changed files with 83 additions and 34 deletions
|
|
@ -309,7 +309,7 @@ def get_spotify_disconnect_source(configured_source: Optional[str] = None) -> st
|
||||||
|
|
||||||
def get_metadata_source_label(source: str) -> str:
|
def get_metadata_source_label(source: str) -> str:
|
||||||
"""Return a human-readable label for a metadata source."""
|
"""Return a human-readable label for a metadata source."""
|
||||||
return METADATA_SOURCE_LABELS.get(source, source.replace("_", " ").title())
|
return METADATA_SOURCE_LABELS.get(source, "Unmapped")
|
||||||
|
|
||||||
|
|
||||||
def get_source_priority(preferred_source: str):
|
def get_source_priority(preferred_source: str):
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,5 @@ def test_metadata_source_label_maps_known_sources():
|
||||||
assert registry.get_metadata_source_label("hydrabase") == "Hydrabase"
|
assert registry.get_metadata_source_label("hydrabase") == "Hydrabase"
|
||||||
|
|
||||||
|
|
||||||
def test_metadata_source_label_falls_back_to_title_case():
|
def test_metadata_source_label_falls_back_to_unmapped():
|
||||||
assert registry.get_metadata_source_label("apple_music") == "Apple Music"
|
assert registry.get_metadata_source_label("apple_music") == "Unmapped"
|
||||||
|
|
|
||||||
|
|
@ -2414,8 +2414,8 @@ async function saveSettings(quiet = false) {
|
||||||
|
|
||||||
const metadataSourceSelect = document.getElementById('metadata-fallback-source');
|
const metadataSourceSelect = document.getElementById('metadata-fallback-source');
|
||||||
let metadataSource = metadataSourceSelect?.value || 'itunes';
|
let metadataSource = metadataSourceSelect?.value || 'itunes';
|
||||||
const spotifyDisconnected = _lastServiceStatus?.spotify?.connected === false;
|
const spotifySessionActive = _lastServiceStatus?.spotify?.authenticated === true;
|
||||||
if (metadataSource === 'spotify' && spotifyDisconnected) {
|
if (metadataSource === 'spotify' && !spotifySessionActive) {
|
||||||
metadataSource = 'deezer';
|
metadataSource = 'deezer';
|
||||||
if (metadataSourceSelect) metadataSourceSelect.value = metadataSource;
|
if (metadataSourceSelect) metadataSourceSelect.value = metadataSource;
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
|
|
|
||||||
|
|
@ -3186,33 +3186,86 @@ function syncSpotifyMetadataSourceAvailability(statusData) {
|
||||||
const spotifyOption = select.querySelector('option[value="spotify"]');
|
const spotifyOption = select.querySelector('option[value="spotify"]');
|
||||||
if (!spotifyOption) return;
|
if (!spotifyOption) return;
|
||||||
|
|
||||||
const spotifyAvailable = statusData?.connected === true;
|
const spotifyAvailable = statusData?.authenticated === true;
|
||||||
spotifyOption.disabled = !spotifyAvailable;
|
spotifyOption.disabled = !spotifyAvailable;
|
||||||
spotifyOption.dataset.unavailable = spotifyAvailable ? 'false' : 'true';
|
spotifyOption.dataset.unavailable = spotifyAvailable ? 'false' : 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMetadataSourceLabel(source) {
|
||||||
|
if (source === 'deezer') return 'Deezer';
|
||||||
|
if (source === 'discogs') return 'Discogs';
|
||||||
|
if (source === 'itunes') return 'iTunes';
|
||||||
|
if (source === 'spotify') return 'Spotify';
|
||||||
|
return 'Unmapped';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSpotifyStatusPresentation(statusData) {
|
||||||
|
const sourceLabel = getMetadataSourceLabel(statusData?.source);
|
||||||
|
const rateLimited = !!(statusData?.rate_limited && statusData?.rate_limit);
|
||||||
|
const cooldown = !!(statusData?.post_ban_cooldown > 0);
|
||||||
|
const sessionActive = statusData?.authenticated === true || (statusData?.authenticated === undefined && statusData?.source === 'spotify');
|
||||||
|
|
||||||
|
if (rateLimited) {
|
||||||
|
const remaining = statusData.rate_limit?.remaining_seconds || 0;
|
||||||
|
return {
|
||||||
|
statusClass: 'rate-limited',
|
||||||
|
statusText: `Spotify paused \u2014 ${formatRateLimitDuration(remaining)}`,
|
||||||
|
dotClass: 'rate-limited',
|
||||||
|
dotTitle: `Spotify paused \u2014 ${formatRateLimitDuration(remaining)} remaining`,
|
||||||
|
sessionActive
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cooldown) {
|
||||||
|
const remaining = statusData.post_ban_cooldown;
|
||||||
|
return {
|
||||||
|
statusClass: 'rate-limited',
|
||||||
|
statusText: `Spotify recovering \u2014 ${formatRateLimitDuration(remaining)}`,
|
||||||
|
dotClass: 'rate-limited',
|
||||||
|
dotTitle: `Spotify recovering \u2014 ${formatRateLimitDuration(remaining)} cooldown`,
|
||||||
|
sessionActive
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusData?.source && statusData.source !== 'spotify') {
|
||||||
|
return {
|
||||||
|
statusClass: 'connected',
|
||||||
|
statusText: sourceLabel,
|
||||||
|
dotClass: 'connected',
|
||||||
|
dotTitle: sourceLabel,
|
||||||
|
sessionActive
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusClass: 'connected',
|
||||||
|
statusText: `Connected (${statusData?.response_time}ms)`,
|
||||||
|
dotClass: 'connected',
|
||||||
|
dotTitle: '',
|
||||||
|
sessionActive
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function updateServiceStatus(service, statusData) {
|
function updateServiceStatus(service, statusData) {
|
||||||
const indicator = document.getElementById(`${service}-status-indicator`);
|
const indicator = document.getElementById(`${service}-status-indicator`);
|
||||||
const statusText = document.getElementById(`${service}-status-text`);
|
const statusText = document.getElementById(`${service}-status-text`);
|
||||||
|
|
||||||
if (indicator && statusText) {
|
if (indicator && statusText) {
|
||||||
if (service === 'spotify' && (statusData.rate_limited || statusData.post_ban_cooldown)) {
|
if (service === 'spotify') {
|
||||||
indicator.className = 'service-card-indicator rate-limited';
|
const presentation = getSpotifyStatusPresentation(statusData || {});
|
||||||
const remaining = statusData.rate_limited
|
indicator.className = `service-card-indicator ${presentation.statusClass}`;
|
||||||
? formatRateLimitDuration(statusData.rate_limit?.remaining_seconds || 0)
|
statusText.textContent = presentation.statusText;
|
||||||
: formatRateLimitDuration(statusData.post_ban_cooldown);
|
statusText.className = `service-card-status-text ${presentation.statusClass}`;
|
||||||
const phase = statusData.rate_limited ? 'paused' : 'recovering';
|
|
||||||
const fallbackLabel = statusData.source === 'deezer' ? 'Deezer' : 'iTunes';
|
|
||||||
statusText.textContent = `${fallbackLabel} (Spotify ${phase} \u2014 ${remaining})`;
|
|
||||||
statusText.className = 'service-card-status-text rate-limited';
|
|
||||||
} else if (statusData.connected) {
|
|
||||||
indicator.className = 'service-card-indicator connected';
|
|
||||||
statusText.textContent = `Connected (${statusData.response_time}ms)`;
|
|
||||||
statusText.className = 'service-card-status-text connected';
|
|
||||||
} else {
|
} else {
|
||||||
indicator.className = 'service-card-indicator disconnected';
|
if (statusData.connected) {
|
||||||
statusText.textContent = 'Disconnected';
|
indicator.className = 'service-card-indicator connected';
|
||||||
statusText.className = 'service-card-status-text disconnected';
|
statusText.textContent = `Connected (${statusData.response_time}ms)`;
|
||||||
|
statusText.className = 'service-card-status-text connected';
|
||||||
|
} else {
|
||||||
|
indicator.className = 'service-card-indicator disconnected';
|
||||||
|
statusText.textContent = 'Disconnected';
|
||||||
|
statusText.className = 'service-card-status-text disconnected';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3220,13 +3273,13 @@ function updateServiceStatus(service, statusData) {
|
||||||
if (service === 'spotify' && statusData.source) {
|
if (service === 'spotify' && statusData.source) {
|
||||||
const musicSourceTitleElement = document.getElementById('music-source-title');
|
const musicSourceTitleElement = document.getElementById('music-source-title');
|
||||||
if (musicSourceTitleElement) {
|
if (musicSourceTitleElement) {
|
||||||
const sourceName = statusData.source === 'spotify' ? 'Spotify' : statusData.source === 'deezer' ? 'Deezer' : statusData.source === 'discogs' ? 'Discogs' : 'iTunes';
|
const sourceName = getMetadataSourceLabel(statusData.source);
|
||||||
musicSourceTitleElement.textContent = sourceName;
|
musicSourceTitleElement.textContent = sourceName;
|
||||||
currentMusicSourceName = sourceName;
|
currentMusicSourceName = sourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the Spotify action buttons aligned with the actual auth session.
|
// Keep the Spotify action buttons aligned with the actual auth session.
|
||||||
const spotifySessionActive = statusData.authenticated === true || (statusData.authenticated === undefined && statusData.source === 'spotify');
|
const spotifySessionActive = getSpotifyStatusPresentation(statusData || {}).sessionActive;
|
||||||
const authBtn = document.querySelector('button[onclick="authenticateSpotify()"]');
|
const authBtn = document.querySelector('button[onclick="authenticateSpotify()"]');
|
||||||
const disconnectBtn = document.getElementById('spotify-disconnect-btn');
|
const disconnectBtn = document.getElementById('spotify-disconnect-btn');
|
||||||
if (authBtn) {
|
if (authBtn) {
|
||||||
|
|
@ -3255,16 +3308,12 @@ function updateSidebarServiceStatus(service, statusData) {
|
||||||
const nameElement = indicator.querySelector('.status-name');
|
const nameElement = indicator.querySelector('.status-name');
|
||||||
|
|
||||||
if (dot) {
|
if (dot) {
|
||||||
if (service === 'spotify' && (statusData.rate_limited || statusData.post_ban_cooldown)) {
|
if (service === 'spotify') {
|
||||||
dot.className = 'status-dot rate-limited';
|
const presentation = getSpotifyStatusPresentation(statusData || {});
|
||||||
dot.title = statusData.rate_limited
|
dot.className = `status-dot ${presentation.dotClass}`;
|
||||||
? `Spotify paused \u2014 ${formatRateLimitDuration(statusData.rate_limit?.remaining_seconds || 0)} remaining`
|
dot.title = presentation.dotTitle;
|
||||||
: `Spotify recovering \u2014 ${formatRateLimitDuration(statusData.post_ban_cooldown)} cooldown`;
|
|
||||||
} else if (statusData.connected) {
|
|
||||||
dot.className = 'status-dot connected';
|
|
||||||
dot.title = '';
|
|
||||||
} else {
|
} else {
|
||||||
dot.className = 'status-dot disconnected';
|
dot.className = statusData?.connected ? 'status-dot connected' : 'status-dot disconnected';
|
||||||
dot.title = '';
|
dot.title = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3282,7 +3331,7 @@ function updateSidebarServiceStatus(service, statusData) {
|
||||||
if (service === 'spotify' && statusData.source) {
|
if (service === 'spotify' && statusData.source) {
|
||||||
const musicSourceNameElement = document.getElementById('music-source-name');
|
const musicSourceNameElement = document.getElementById('music-source-name');
|
||||||
if (musicSourceNameElement) {
|
if (musicSourceNameElement) {
|
||||||
const sourceName = statusData.source === 'spotify' ? 'Spotify' : statusData.source === 'deezer' ? 'Deezer' : statusData.source === 'discogs' ? 'Discogs' : 'iTunes';
|
const sourceName = getMetadataSourceLabel(statusData.source);
|
||||||
musicSourceNameElement.textContent = sourceName;
|
musicSourceNameElement.textContent = sourceName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue