use yew::prelude::*; use yew_router::prelude::*; use crate::api; use crate::components::watch_indicator::WatchIndicator; use crate::pages::Route; use crate::types::FullArtistDetail; #[derive(Properties, PartialEq)] pub struct Props { pub id: String, } #[function_component(ArtistPage)] pub fn artist_page(props: &Props) -> Html { let detail = use_state(|| None::); let error = use_state(|| None::); let message = use_state(|| None::); let id = props.id.clone(); // Full fetch (with track counts) — used for refresh after actions let fetch = { let detail = detail.clone(); let error = error.clone(); Callback::from(move |id: String| { let detail = detail.clone(); let error = error.clone(); wasm_bindgen_futures::spawn_local(async move { match api::get_artist_full(&id).await { Ok(d) => detail.set(Some(d)), Err(e) => error.set(Some(e.0)), } }); }) }; // Two-phase load: quick first (release groups only), then enriched (with track counts) { let detail = detail.clone(); let error = error.clone(); let id = id.clone(); use_effect_with(id.clone(), move |_| { wasm_bindgen_futures::spawn_local(async move { // Phase 1: quick load (instant for browsing artists) match api::get_artist_full_quick(&id).await { Ok(d) => { let needs_enrich = !d.enriched; detail.set(Some(d)); // Phase 2: if not enriched, fetch full data in background if needs_enrich { match api::get_artist_full(&id).await { Ok(full) => detail.set(Some(full)), Err(_) => {} // quick data is still showing, don't overwrite with error } } } Err(e) => error.set(Some(e.0)), } }); }); } if let Some(ref err) = *error { return html! {
{ format!("Error: {err}") }
}; } let Some(ref d) = *detail else { return html! {

{ "Loading discography from MusicBrainz..." }

}; }; html! {
if let Some(ref msg) = *message {

{ msg }

} if d.albums.is_empty() {

{ "No releases found on MusicBrainz." }

} // Group albums by type { for ["Album", "EP", "Single"].iter().map(|release_type| { let type_albums: Vec<_> = d.albums.iter() .filter(|a| a.release_type.as_deref().unwrap_or("Album") == *release_type) .collect(); if type_albums.is_empty() { return html! {}; } html! {

{ format!("{}s ({})", release_type, type_albums.len()) }

{ for type_albums.iter().map(|album| { let is_unwatched = album.status == "unwatched"; let row_style = if is_unwatched { "opacity: 0.6;" } else { "" }; let album_link = html! { to={Route::Album { mbid: album.mbid.clone() }}> { &album.title } > }; let tc = album.track_count; // Watch button for unwatched albums let watch_btn = if is_unwatched { let artist_name = d.artist.name.clone(); let album_title = album.title.clone(); let album_mbid = album.mbid.clone(); let message = message.clone(); let error = error.clone(); let fetch = fetch.clone(); let artist_id = id.clone(); html! { } } else { html! {} }; html! { } })}
{ "Title" } { "Date" } { "Owned" } { "Watched" }
{ album_link } { album.date.as_deref().unwrap_or("") } if tc > 0 { = tc { "color: var(--success);" } else if album.owned_tracks > 0 { "color: var(--warning);" } else { "color: var(--text-muted);" } }> { format!("{}/{}", album.owned_tracks, tc) } } if tc > 0 { = tc { "color: var(--accent);" } else if album.watched_tracks > 0 { "color: var(--accent);" } else { "color: var(--text-muted);" } }> { format!("{}/{}", album.watched_tracks, tc) } } { watch_btn }
} })}
} }