feat(TagEditor): add track count and descriptive result status

Backend:
- Add media/track-count fields to MusicBrainz ReleaseResult model
- Extract track_count and format from search results

Frontend:
- Show "{n} results" or "No results, try different search"
- Auto-expand results panel when search returns data
- Track hasSearched state for proper status display
This commit is contained in:
vicrodh committed 2026-01-26 20:14:33 -06:00
1 parent 2c8efa64c7
commit fd08dc8f2c
3 files changed
+61 -27

No files matched your search

+16 -2
View File
@@ -97,18 +97,32 @@ pub fn musicbrainz_release_to_search_result(
})
.unwrap_or((None, None));
// Get track count - either from direct field or sum from media
let track_count = release.track_count.or_else(|| {
release.media.as_ref().map(|media| {
media.iter().filter_map(|m| m.track_count).sum()
})
});
// Get format from first medium
let format = release
.media
.as_ref()
.and_then(|m| m.first())
.and_then(|m| m.format.clone());
RemoteAlbumSearchResult {
provider: RemoteProvider::MusicBrainz,
provider_id: release.id.clone(),
title: release.title.clone(),
artist,
year,
track_count: None, // Not available in search results
track_count,
country: release.country.clone(),
label,
catalog_number,
confidence: release.score.map(|s| s.min(100) as u8),
format: None,
format,
}
}
+13
View File
@@ -222,6 +222,19 @@ pub struct ReleaseResult {
pub release_group: Option<ReleaseGroupRef>,
#[serde(rename = "artist-credit")]
pub artist_credit: Option<Vec<ArtistCredit>>,
/// Media info (contains track counts per disc)
pub media: Option<Vec<ReleaseSearchMedium>>,
/// Total track count across all media
#[serde(rename = "track-count")]
pub track_count: Option<u16>,
}
/// Simplified medium info for search results
#[derive(Debug, Deserialize)]
pub struct ReleaseSearchMedium {
pub format: Option<String>,
#[serde(rename = "track-count")]
pub track_count: Option<u16>,
}
/// Label information
@@ -98,6 +98,7 @@
let remoteResults = $state<RemoteAlbumSearchResult[]>([]);
let selectedRemoteId = $state<string | null>(null);
let showRemotePanel = $state(false);
let hasSearched = $state(false);
type TrackEdit = {
id: number;
@@ -143,6 +144,7 @@
remoteResults = [];
selectedRemoteId = null;
showRemotePanel = false;
hasSearched = false;
}
});
@@ -165,12 +167,12 @@
limit: 10
});
remoteResults = results;
if (results.length === 0) {
showToast('No results found', 'info');
}
hasSearched = true;
showRemotePanel = results.length > 0;
} catch (err) {
console.error('Remote search failed:', err);
showToast(`Search failed: ${err}`, 'error');
hasSearched = true;
} finally {
remoteSearching = false;
}
@@ -429,18 +431,21 @@
Search
{/if}
</button>
{#if remoteResults.length > 0}
<button
class="btn btn-ghost btn-sm chevron-toggle"
onclick={() => showRemotePanel = !showRemotePanel}
type="button"
title={showRemotePanel ? 'Hide results' : 'Show results'}
>
<svg class="icon-inline chevron" class:rotated={showRemotePanel} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
<span class="result-count">{remoteResults.length}</span>
</button>
{#if hasSearched}
{#if remoteResults.length > 0}
<button
class="btn btn-ghost btn-sm result-status"
onclick={() => showRemotePanel = !showRemotePanel}
type="button"
>
<span class="result-text">{remoteResults.length} results</span>
<svg class="icon-inline chevron" class:rotated={showRemotePanel} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
{:else}
<span class="no-results">No results, try different search</span>
{/if}
{/if}
</div>
@@ -871,30 +876,32 @@ input[type="number"] {
background: var(--bg-secondary);
}
.chevron-toggle {
.result-status {
margin-left: auto;
display: flex;
align-items: center;
gap: 4px;
padding: 4px 8px !important;
padding: 4px 10px !important;
}
.chevron-toggle .chevron {
.result-status .chevron {
transition: transform 0.2s ease;
}
.chevron-toggle .chevron.rotated {
.result-status .chevron.rotated {
transform: rotate(180deg);
}
.result-count {
.result-text {
font-size: 12px;
color: var(--accent-primary);
}
.no-results {
margin-left: auto;
font-size: 11px;
color: var(--text-muted);
background: var(--bg-tertiary);
padding: 2px 6px;
border-radius: 10px;
min-width: 18px;
text-align: center;
font-style: italic;
}
.remote-panel {