top songs flow
This commit is contained in:
@@ -5,6 +5,7 @@ use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
use crate::api;
|
||||
use crate::components::status_badge::StatusBadge;
|
||||
use crate::pages::Route;
|
||||
use crate::types::FullArtistDetail;
|
||||
|
||||
@@ -18,6 +19,8 @@ pub fn artist_page(props: &Props) -> Html {
|
||||
let detail = use_state(|| None::<FullArtistDetail>);
|
||||
let error = use_state(|| None::<String>);
|
||||
let message = use_state(|| None::<String>);
|
||||
let active_tab = use_state(|| "discography".to_string());
|
||||
let top_songs_limit = use_state(|| 25usize);
|
||||
let id = props.id.clone();
|
||||
|
||||
// Flag to prevent the background enrichment from overwriting a user-triggered refresh
|
||||
@@ -259,6 +262,151 @@ pub fn artist_page(props: &Props) -> Html {
|
||||
}
|
||||
};
|
||||
|
||||
let tab_bar_html = {
|
||||
let has_top_songs = d.lastfm_available && !d.top_songs.is_empty();
|
||||
let disco_active = *active_tab == "discography";
|
||||
let top_active = *active_tab == "top_songs";
|
||||
let on_disco = {
|
||||
let tab = active_tab.clone();
|
||||
Callback::from(move |_: MouseEvent| tab.set("discography".to_string()))
|
||||
};
|
||||
let on_top = {
|
||||
let tab = active_tab.clone();
|
||||
Callback::from(move |_: MouseEvent| tab.set("top_songs".to_string()))
|
||||
};
|
||||
html! {
|
||||
<div class="tab-bar">
|
||||
<button class={if disco_active { "tab-btn active" } else { "tab-btn" }}
|
||||
onclick={on_disco}>{ "Discography" }</button>
|
||||
if has_top_songs {
|
||||
<button class={if top_active { "tab-btn active" } else { "tab-btn" }}
|
||||
onclick={on_top}>{ "Top Songs" }</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
};
|
||||
|
||||
let top_songs_html = {
|
||||
let limit = *top_songs_limit;
|
||||
let visible: Vec<_> = d.top_songs.iter().take(limit).collect();
|
||||
let has_more = d.top_songs.len() > limit;
|
||||
let artist_name = d.artist.name.clone();
|
||||
|
||||
let on_show_more = {
|
||||
let top_songs_limit = top_songs_limit.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
top_songs_limit.set(*top_songs_limit + 25);
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:3rem;">{ "#" }</th>
|
||||
<th>{ "Title" }</th>
|
||||
<th>{ "Plays" }</th>
|
||||
<th>{ "Status" }</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ for visible.iter().enumerate().map(|(i, song)| {
|
||||
let rank = i + 1;
|
||||
let has_status = song.status.is_some();
|
||||
|
||||
let on_watch = {
|
||||
let detail = detail.clone();
|
||||
let name = song.name.clone();
|
||||
let mbid = song.mbid.clone();
|
||||
let artist = artist_name.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let detail = detail.clone();
|
||||
let name = name.clone();
|
||||
let mbid = mbid.clone();
|
||||
let artist = artist.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
if let Ok(resp) = api::watch_track(Some(&artist), &name, mbid.as_deref().unwrap_or("")).await {
|
||||
if let Some(ref d) = *detail {
|
||||
let mut updated = d.clone();
|
||||
if let Some(s) = updated.top_songs.get_mut(i) {
|
||||
s.status = Some(resp.status);
|
||||
}
|
||||
detail.set(Some(updated));
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
let on_unwatch = {
|
||||
let detail = detail.clone();
|
||||
let mbid = song.mbid.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let detail = detail.clone();
|
||||
let mbid = mbid.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
if let Some(ref m) = mbid {
|
||||
if api::unwatch_track(m).await.is_ok() {
|
||||
if let Some(ref d) = *detail {
|
||||
let mut updated = d.clone();
|
||||
if let Some(s) = updated.top_songs.get_mut(i) {
|
||||
s.status = None;
|
||||
}
|
||||
detail.set(Some(updated));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
let plays = if song.playcount >= 1_000_000 {
|
||||
format!("{}M", song.playcount / 1_000_000)
|
||||
} else if song.playcount >= 1_000 {
|
||||
format!("{}K", song.playcount / 1_000)
|
||||
} else {
|
||||
song.playcount.to_string()
|
||||
};
|
||||
|
||||
html! {
|
||||
<tr>
|
||||
<td class="text-muted">{ rank }</td>
|
||||
<td>{ &song.name }</td>
|
||||
<td class="text-muted text-sm">{ plays }</td>
|
||||
<td>
|
||||
if let Some(ref status) = song.status {
|
||||
<StatusBadge status={status.clone()} />
|
||||
}
|
||||
</td>
|
||||
<td class="actions">
|
||||
if !has_status {
|
||||
<button class="btn btn-sm" onclick={on_watch}>
|
||||
{ "Watch" }
|
||||
</button>
|
||||
} else {
|
||||
<button class="btn btn-sm btn-secondary" onclick={on_unwatch}>
|
||||
{ "Unwatch" }
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
if has_more {
|
||||
<div style="text-align:center;margin-top:0.5rem;">
|
||||
<button class="btn btn-sm btn-secondary" onclick={on_show_more}>
|
||||
{ format!("Show More ({} total)", d.top_songs.len()) }
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<div class={if d.artist_banner.is_some() { "artist-banner-wrap" } else { "" }}
|
||||
@@ -343,6 +491,14 @@ pub fn artist_page(props: &Props) -> Html {
|
||||
</div>
|
||||
}
|
||||
|
||||
// Tab bar
|
||||
{ tab_bar_html }
|
||||
|
||||
if *active_tab == "top_songs" && !d.top_songs.is_empty() {
|
||||
// Top Songs tab content
|
||||
{ top_songs_html }
|
||||
} else {
|
||||
|
||||
if d.albums.is_empty() {
|
||||
<p class="text-muted">{ "No releases found on MusicBrainz." }</p>
|
||||
}
|
||||
@@ -575,6 +731,8 @@ pub fn artist_page(props: &Props) -> Html {
|
||||
</details>
|
||||
}
|
||||
})}
|
||||
|
||||
} // close else (discography tab)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,19 @@ pub struct FullArtistDetail {
|
||||
pub artist_bio: Option<String>,
|
||||
#[serde(default)]
|
||||
pub artist_banner: Option<String>,
|
||||
#[serde(default)]
|
||||
pub top_songs: Vec<TopSongFe>,
|
||||
#[serde(default)]
|
||||
pub lastfm_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct TopSongFe {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub playcount: u64,
|
||||
pub mbid: Option<String>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
|
||||
Reference in New Issue
Block a user