single song watching
This commit is contained in:
@@ -176,6 +176,11 @@ pub async fn add_album(
|
|||||||
post_json(&format!("{BASE}/albums"), &body).await
|
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 ---
|
// --- Downloads ---
|
||||||
pub async fn get_downloads(status: Option<&str>) -> Result<Vec<DownloadItem>, ApiError> {
|
pub async fn get_downloads(status: Option<&str>) -> Result<Vec<DownloadItem>, ApiError> {
|
||||||
let mut url = format!("{BASE}/downloads/queue");
|
let mut url = format!("{BASE}/downloads/queue");
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub fn album_page(props: &Props) -> Html {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ for d.tracks.iter().map(|t| {
|
{ for d.tracks.iter().enumerate().map(|(idx, t)| {
|
||||||
let duration = t.duration_ms
|
let duration = t.duration_ms
|
||||||
.map(&fmt_duration)
|
.map(&fmt_duration)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
@@ -88,6 +88,7 @@ pub fn album_page(props: &Props) -> Html {
|
|||||||
let track_key = t.recording_mbid.clone();
|
let track_key = t.recording_mbid.clone();
|
||||||
let is_active = active_lyrics.as_ref() == Some(&track_key);
|
let is_active = active_lyrics.as_ref() == Some(&track_key);
|
||||||
let cached = lyrics_cache.get(&track_key).cloned();
|
let cached = lyrics_cache.get(&track_key).cloned();
|
||||||
|
let has_status = t.status.is_some();
|
||||||
|
|
||||||
let on_lyrics_click = {
|
let on_lyrics_click = {
|
||||||
let active = active_lyrics.clone();
|
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! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -130,7 +154,13 @@ pub fn album_page(props: &Props) -> Html {
|
|||||||
<span class="text-muted text-sm">{ "\u{2014}" }</span>
|
<span class="text-muted text-sm">{ "\u{2014}" }</span>
|
||||||
}
|
}
|
||||||
</td>
|
</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"
|
<button class="btn btn-sm btn-secondary"
|
||||||
onclick={on_lyrics_click}>
|
onclick={on_lyrics_click}>
|
||||||
{ if is_active { "Hide" } else { "Lyrics" } }
|
{ if is_active { "Hide" } else { "Lyrics" } }
|
||||||
|
|||||||
@@ -282,6 +282,14 @@ pub struct AddSummary {
|
|||||||
pub errors: u64,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
pub struct SyncStats {
|
pub struct SyncStats {
|
||||||
pub found: u64,
|
pub found: u64,
|
||||||
|
|||||||
@@ -21,8 +21,16 @@ pub struct SearchParams {
|
|||||||
offset: u64,
|
offset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct WatchTrackRequest {
|
||||||
|
artist: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
mbid: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
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)));
|
.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?;
|
let track = queries::tracks::get_by_id(state.db.conn(), id).await?;
|
||||||
Ok(HttpResponse::Ok().json(track))
|
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,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user