feat(library): genre sliders, artist links, and navigation utils
- music landing: diverse per-genre album sliders (online counts / offline wide-probe fallback) and home-screen library shortcuts - add ArtistLinks component and shared navigation/genreDiversity utils - player/playback-mode refinements across Rust and frontend
This commit is contained in:
@@ -864,7 +864,7 @@ impl MediaRepository for OfflineRepository {
|
||||
|
||||
let genres = genre_set
|
||||
.into_iter()
|
||||
.map(|name| Genre { id: name.clone(), name })
|
||||
.map(|name| Genre { id: name.clone(), name, album_count: None })
|
||||
.collect();
|
||||
|
||||
Ok(genres)
|
||||
|
||||
@@ -811,7 +811,12 @@ impl MediaRepository for OnlineRepository {
|
||||
}
|
||||
|
||||
async fn get_genres(&self, parent_id: Option<&str>) -> Result<Vec<Genre>, RepoError> {
|
||||
let mut endpoint = format!("/Genres?UserId={}", self.user_id);
|
||||
// Ask Jellyfin to scope counts to albums and include them, so the
|
||||
// frontend can rank genres by popularity without probing each one.
|
||||
let mut endpoint = format!(
|
||||
"/Genres?UserId={}&IncludeItemTypes=MusicAlbum&Recursive=true&Fields=ItemCounts",
|
||||
self.user_id
|
||||
);
|
||||
|
||||
if let Some(pid) = parent_id {
|
||||
endpoint.push_str(&format!("&ParentId={}", pid));
|
||||
@@ -828,17 +833,34 @@ impl MediaRepository for OnlineRepository {
|
||||
struct JellyfinGenre {
|
||||
id: String,
|
||||
name: String,
|
||||
// Which count field Jellyfin populates for a genre under
|
||||
// Fields=ItemCounts varies by server/version: scoped queries may
|
||||
// fill AlbumCount, others only ChildCount. Read whichever is
|
||||
// present so ranking still works. Absent on servers that ignore
|
||||
// Fields=ItemCounts entirely, so all stay optional.
|
||||
album_count: Option<u32>,
|
||||
child_count: Option<u32>,
|
||||
}
|
||||
|
||||
let response: GenresResponse = self.get_json(&endpoint).await?;
|
||||
Ok(response
|
||||
let genres: Vec<Genre> = response
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|g| Genre {
|
||||
id: g.id,
|
||||
name: g.name,
|
||||
album_count: g.album_count.or(g.child_count),
|
||||
})
|
||||
.collect())
|
||||
.collect();
|
||||
|
||||
let with_counts = genres.iter().filter(|g| g.album_count.is_some()).count();
|
||||
log::debug!(
|
||||
"get_genres: {} genres, {} carry counts (AlbumCount/ChildCount)",
|
||||
genres.len(),
|
||||
with_counts
|
||||
);
|
||||
|
||||
Ok(genres)
|
||||
}
|
||||
|
||||
async fn search(
|
||||
|
||||
@@ -255,6 +255,10 @@ pub struct PlaybackInfo {
|
||||
pub struct Genre {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
/// Number of albums tagged with this genre, when the backend can supply it
|
||||
/// (online only). Lets the frontend rank/pick genres without probing each
|
||||
/// one. `None` when unknown (e.g. offline).
|
||||
pub album_count: Option<u32>,
|
||||
}
|
||||
|
||||
/// Image type
|
||||
|
||||
Reference in New Issue
Block a user