single song watching

This commit is contained in:
Connor Johnstone
2026-03-23 17:19:34 -04:00
parent 673de39918
commit 59a26c18f3
4 changed files with 83 additions and 3 deletions

View File

@@ -176,6 +176,11 @@ pub async fn add_album(
post_json(&format!("{BASE}/albums"), &body).await
}
pub async fn watch_track(title: &str, mbid: &str) -> Result<WatchTrackResponse, ApiError> {
let body = serde_json::json!({"title": title, "mbid": mbid}).to_string();
post_json(&format!("{BASE}/tracks/watch"), &body).await
}
// --- Downloads ---
pub async fn get_downloads(status: Option<&str>) -> Result<Vec<DownloadItem>, ApiError> {
let mut url = format!("{BASE}/downloads/queue");

View File

@@ -80,7 +80,7 @@ pub fn album_page(props: &Props) -> Html {
</tr>
</thead>
<tbody>
{ for d.tracks.iter().map(|t| {
{ for d.tracks.iter().enumerate().map(|(idx, t)| {
let duration = t.duration_ms
.map(&fmt_duration)
.unwrap_or_default();
@@ -88,6 +88,7 @@ pub fn album_page(props: &Props) -> Html {
let track_key = t.recording_mbid.clone();
let is_active = active_lyrics.as_ref() == Some(&track_key);
let cached = lyrics_cache.get(&track_key).cloned();
let has_status = t.status.is_some();
let on_lyrics_click = {
let active = active_lyrics.clone();
@@ -117,6 +118,29 @@ pub fn album_page(props: &Props) -> Html {
})
};
let on_watch_click = {
let detail = detail.clone();
let title = t.title.clone();
let mbid = t.recording_mbid.clone();
Callback::from(move |_: MouseEvent| {
let detail = detail.clone();
let title = title.clone();
let mbid = mbid.clone();
let idx = idx;
wasm_bindgen_futures::spawn_local(async move {
if let Ok(resp) = api::watch_track(&title, &mbid).await {
if let Some(ref d) = *detail {
let mut updated = d.clone();
if let Some(track) = updated.tracks.get_mut(idx) {
track.status = Some(resp.status);
}
detail.set(Some(updated));
}
}
});
})
};
html! {
<>
<tr>
@@ -130,7 +154,13 @@ pub fn album_page(props: &Props) -> Html {
<span class="text-muted text-sm">{ "\u{2014}" }</span>
}
</td>
<td>
<td class="actions">
if !has_status {
<button class="btn btn-sm"
onclick={on_watch_click}>
{ "Watch" }
</button>
}
<button class="btn btn-sm btn-secondary"
onclick={on_lyrics_click}>
{ if is_active { "Hide" } else { "Lyrics" } }

View File

@@ -282,6 +282,14 @@ pub struct AddSummary {
pub errors: u64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct WatchTrackResponse {
pub id: i32,
pub status: String,
pub name: String,
pub artist_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct SyncStats {
pub found: u64,

View File

@@ -21,8 +21,16 @@ pub struct SearchParams {
offset: u64,
}
#[derive(Deserialize)]
pub struct WatchTrackRequest {
artist: Option<String>,
title: Option<String>,
mbid: Option<String>,
}
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(web::resource("/tracks").route(web::get().to(list_tracks)))
cfg.service(web::resource("/tracks/watch").route(web::post().to(watch_track)))
.service(web::resource("/tracks").route(web::get().to(list_tracks)))
.service(web::resource("/tracks/{id}").route(web::get().to(get_track)));
}
@@ -50,3 +58,32 @@ async fn get_track(
let track = queries::tracks::get_by_id(state.db.conn(), id).await?;
Ok(HttpResponse::Ok().json(track))
}
async fn watch_track(
state: web::Data<AppState>,
session: Session,
body: web::Json<WatchTrackRequest>,
) -> Result<HttpResponse, ApiError> {
let (user_id, _, _) = auth::require_auth(&session)?;
if body.title.is_none() && body.mbid.is_none() {
return Err(ApiError::BadRequest(
"provide title or recording mbid".into(),
));
}
let entry = shanty_watch::add_track(
state.db.conn(),
body.artist.as_deref(),
body.title.as_deref(),
body.mbid.as_deref(),
&state.mb_client,
Some(user_id),
)
.await?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"id": entry.id,
"status": entry.status,
"name": entry.name,
"artist_name": entry.artist_name,
})))
}