use std::rc::Rc; use std::cell::Cell; use gloo_timers::callback::Interval; use yew::prelude::*; use yew_router::prelude::*; use crate::api; 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(); // Flag to prevent the background enrichment from overwriting a user-triggered refresh let user_acted: Rc> = use_memo((), |_| Cell::new(false)); // Full fetch (with track counts) — used for refresh after actions let fetch = { let detail = detail.clone(); let error = error.clone(); let user_acted = user_acted.clone(); Callback::from(move |id: String| { user_acted.set(true); 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(); let user_acted = user_acted.clone(); use_effect_with(id.clone(), move |_| { user_acted.set(false); 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 && !user_acted.get() { match api::get_artist_full(&id).await { Ok(full) => { // Only apply if user hasn't triggered a refresh if !user_acted.get() { detail.set(Some(full)); } } Err(_) => {} // quick data is still showing, don't overwrite with error } } } Err(e) => error.set(Some(e.0)), } }); }); } // Auto-refresh when background tasks complete (downloads, organize, etc.) { let fetch = fetch.clone(); let id = id.clone(); let had_tasks: Rc> = use_memo((), |_| Cell::new(false)); use_effect_with((), move |_| { let interval = Interval::new(3_000, move || { let fetch = fetch.clone(); let id = id.clone(); let had_tasks = had_tasks.clone(); wasm_bindgen_futures::spawn_local(async move { if let Ok(status) = api::get_status().await { let has_running = status.tasks.iter().any(|t| t.status == "Running"); if had_tasks.get() && !has_running { // Tasks just finished — refresh artist data fetch.emit(id); } had_tasks.set(has_running); } }); }); move || drop(interval) }); } if let Some(ref err) = *error { return html! {
{ format!("Error: {err}") }
}; } let Some(ref d) = *detail else { return html! {

{ "Loading discography from MusicBrainz..." }

}; }; let watch_all_btn = { let artist_status = d.artist_status.clone(); let show = artist_status != "owned"; if show { let artist_name = d.artist.name.clone(); let artist_mbid = d.artist.musicbrainz_id.clone(); let message = message.clone(); let error = error.clone(); let fetch = fetch.clone(); let artist_id = id.clone(); html! { } } else { html! {} } }; 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 }
} })}
} }