fixed up the featured artist thing
This commit is contained in:
@@ -122,13 +122,15 @@ pub fn album_page(props: &Props) -> Html {
|
||||
let detail = detail.clone();
|
||||
let title = t.title.clone();
|
||||
let mbid = t.recording_mbid.clone();
|
||||
let artist = d.artist.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let detail = detail.clone();
|
||||
let title = title.clone();
|
||||
let mbid = mbid.clone();
|
||||
let artist = artist.clone();
|
||||
let idx = idx;
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
if let Ok(resp) = api::watch_track(&title, &mbid).await {
|
||||
if let Ok(resp) = api::watch_track(artist.as_deref(), &title, &mbid).await {
|
||||
if let Some(ref d) = *detail {
|
||||
let mut updated = d.clone();
|
||||
if let Some(track) = updated.tracks.get_mut(idx) {
|
||||
|
||||
@@ -279,7 +279,7 @@ pub fn artist_page(props: &Props) -> Html {
|
||||
<p class="text-muted">{ "No releases found on MusicBrainz." }</p>
|
||||
}
|
||||
|
||||
// Group albums by type
|
||||
// Group albums by type (primary credit only)
|
||||
{ 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)
|
||||
@@ -397,6 +397,56 @@ pub fn artist_page(props: &Props) -> Html {
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
|
||||
// Featured releases (collapsible, pre-collapsed)
|
||||
{ for ["Album", "EP", "Single"].iter().map(|release_type| {
|
||||
let featured: Vec<_> = d.featured_albums.iter()
|
||||
.filter(|a| a.release_type.as_deref().unwrap_or("Album") == *release_type)
|
||||
.collect();
|
||||
if featured.is_empty() {
|
||||
return html! {};
|
||||
}
|
||||
html! {
|
||||
<details class="mb-2">
|
||||
<summary class="text-muted" style="cursor: pointer;">
|
||||
{ format!("Featured {}s ({})", release_type, featured.len()) }
|
||||
</summary>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 60px;"></th>
|
||||
<th>{ "Title" }</th>
|
||||
<th>{ "Date" }</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ for featured.iter().map(|album| {
|
||||
let cover_url = format!("https://coverartarchive.org/release/{}/front-250", album.mbid);
|
||||
html! {
|
||||
<tr style="opacity: 0.6;">
|
||||
<td>
|
||||
<img class="album-art" src={cover_url}
|
||||
loading="lazy"
|
||||
onerror={Callback::from(|e: web_sys::Event| {
|
||||
if let Some(el) = e.target_dyn_into::<web_sys::HtmlElement>() {
|
||||
el.set_attribute("style", "display:none").ok();
|
||||
}
|
||||
})} />
|
||||
</td>
|
||||
<td>
|
||||
<Link<Route> to={Route::Album { mbid: album.mbid.clone() }}>
|
||||
{ &album.title }
|
||||
</Link<Route>>
|
||||
</td>
|
||||
<td class="text-muted">{ album.date.as_deref().unwrap_or("") }</td>
|
||||
</tr>
|
||||
}
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,72 +240,107 @@ pub fn dashboard() -> Html {
|
||||
.iter()
|
||||
.any(|t| t.status == "Pending" || t.status == "Running");
|
||||
|
||||
// Pre-compute scheduled task rows
|
||||
let scheduled_rows = {
|
||||
let mut rows = Vec::new();
|
||||
if let Some(ref sched) = s.scheduled {
|
||||
if let Some(ref next) = sched.next_pipeline {
|
||||
let on_skip = {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch_status.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::skip_scheduled_pipeline().await {
|
||||
Ok(_) => {
|
||||
message.set(Some("Next pipeline run skipped".into()));
|
||||
fetch.emit(());
|
||||
}
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
rows.push(html! {
|
||||
<tr>
|
||||
<td>{ "Auto Pipeline" }</td>
|
||||
<td><span class="badge badge-pending">{ "Scheduled" }</span></td>
|
||||
<td class="text-sm text-muted">{ format!("Next run: {}", format_next_run(next)) }</td>
|
||||
<td><button class="btn btn-sm btn-danger" onclick={on_skip}>{ "Skip" }</button></td>
|
||||
</tr>
|
||||
});
|
||||
}
|
||||
if let Some(ref next) = sched.next_monitor {
|
||||
let on_skip = {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch_status.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::skip_scheduled_monitor().await {
|
||||
Ok(_) => {
|
||||
message.set(Some("Next monitor check skipped".into()));
|
||||
fetch.emit(());
|
||||
}
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
rows.push(html! {
|
||||
<tr>
|
||||
<td>{ "Monitor Check" }</td>
|
||||
<td><span class="badge badge-pending">{ "Scheduled" }</span></td>
|
||||
<td class="text-sm text-muted">{ format!("Next run: {}", format_next_run(next)) }</td>
|
||||
<td><button class="btn btn-sm btn-danger" onclick={on_skip}>{ "Skip" }</button></td>
|
||||
</tr>
|
||||
});
|
||||
}
|
||||
}
|
||||
rows
|
||||
// Skip callbacks for scheduler
|
||||
let on_skip_pipeline = {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch_status.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::skip_scheduled_pipeline().await {
|
||||
Ok(_) => {
|
||||
message.set(Some("Next pipeline run skipped".into()));
|
||||
fetch.emit(());
|
||||
}
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
let on_skip_monitor = {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch_status.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let fetch = fetch.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::skip_scheduled_monitor().await {
|
||||
Ok(_) => {
|
||||
message.set(Some("Next monitor check skipped".into()));
|
||||
fetch.emit(());
|
||||
}
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
let scheduled_jobs_html = {
|
||||
let next_pipeline = s.scheduled.as_ref().and_then(|sc| sc.next_pipeline.as_ref());
|
||||
let next_monitor = s.scheduled.as_ref().and_then(|sc| sc.next_monitor.as_ref());
|
||||
let pipeline_next_str = next_pipeline.map(|n| format_next_run(n)).unwrap_or_default();
|
||||
let monitor_next_str = next_monitor.map(|n| format_next_run(n)).unwrap_or_default();
|
||||
let pipeline_last = s.scheduler.as_ref()
|
||||
.and_then(|sc| sc.get("pipeline"))
|
||||
.and_then(|j| j.get("last_result"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let monitor_last = s.scheduler.as_ref()
|
||||
.and_then(|sc| sc.get("monitor"))
|
||||
.and_then(|j| j.get("last_result"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
html! {
|
||||
<div class="card">
|
||||
<h3>{ "Scheduled Jobs" }</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>{ "Job" }</th><th>{ "Status" }</th><th>{ "Next Run" }</th><th>{ "Last Result" }</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{ "Auto Pipeline" }</td>
|
||||
<td>{ if next_pipeline.is_some() {
|
||||
html! { <span class="badge badge-pending">{ "Scheduled" }</span> }
|
||||
} else {
|
||||
html! { <span class="text-muted text-sm">{ "Idle" }</span> }
|
||||
}}</td>
|
||||
<td class="text-sm text-muted">{ pipeline_next_str }</td>
|
||||
<td class="text-sm text-muted">{ pipeline_last }</td>
|
||||
<td>{ if next_pipeline.is_some() {
|
||||
html! { <button class="btn btn-sm btn-danger" onclick={on_skip_pipeline}>{ "Skip" }</button> }
|
||||
} else {
|
||||
html! {}
|
||||
}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{ "Monitor Check" }</td>
|
||||
<td>{ if next_monitor.is_some() {
|
||||
html! { <span class="badge badge-pending">{ "Scheduled" }</span> }
|
||||
} else {
|
||||
html! { <span class="text-muted text-sm">{ "Idle" }</span> }
|
||||
}}</td>
|
||||
<td class="text-sm text-muted">{ monitor_next_str }</td>
|
||||
<td class="text-sm text-muted">{ monitor_last }</td>
|
||||
<td>{ if next_monitor.is_some() {
|
||||
html! { <button class="btn btn-sm btn-danger" onclick={on_skip_monitor}>{ "Skip" }</button> }
|
||||
} else {
|
||||
html! {}
|
||||
}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
};
|
||||
let has_scheduled = !scheduled_rows.is_empty();
|
||||
|
||||
html! {
|
||||
<div>
|
||||
@@ -394,8 +429,11 @@ pub fn dashboard() -> Html {
|
||||
}
|
||||
}
|
||||
|
||||
// Background Tasks (always show if there are tasks or scheduled items)
|
||||
if !s.tasks.is_empty() || has_scheduled {
|
||||
// Scheduled Jobs (always visible)
|
||||
{ scheduled_jobs_html }
|
||||
|
||||
// Background Tasks (one-off tasks like MB import)
|
||||
if !s.tasks.is_empty() {
|
||||
<div class="card">
|
||||
<h3>{ "Background Tasks" }</h3>
|
||||
<table class="tasks-table">
|
||||
@@ -403,7 +441,6 @@ pub fn dashboard() -> Html {
|
||||
<tr><th>{ "Type" }</th><th>{ "Status" }</th><th>{ "Progress" }</th><th>{ "Result" }</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ for scheduled_rows.into_iter() }
|
||||
{ for s.tasks.iter().map(|t| {
|
||||
let progress_html = if let Some(ref p) = t.progress {
|
||||
if p.total > 0 {
|
||||
|
||||
Reference in New Issue
Block a user