Major early updates
This commit is contained in:
191
frontend/src/pages/downloads.rs
Normal file
191
frontend/src/pages/downloads.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::api;
|
||||
use crate::components::status_badge::StatusBadge;
|
||||
use crate::types::DownloadItem;
|
||||
|
||||
#[function_component(DownloadsPage)]
|
||||
pub fn downloads_page() -> Html {
|
||||
let items = use_state(|| None::<Vec<DownloadItem>>);
|
||||
let error = use_state(|| None::<String>);
|
||||
let message = use_state(|| None::<String>);
|
||||
let dl_query = use_state(|| String::new());
|
||||
|
||||
let refresh = {
|
||||
let items = items.clone();
|
||||
let error = error.clone();
|
||||
Callback::from(move |_: ()| {
|
||||
let items = items.clone();
|
||||
let error = error.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::get_downloads(None).await {
|
||||
Ok(d) => items.set(Some(d)),
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
{
|
||||
let refresh = refresh.clone();
|
||||
use_effect_with((), move |_| {
|
||||
refresh.emit(());
|
||||
});
|
||||
}
|
||||
|
||||
let on_sync = {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let refresh = refresh.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let refresh = refresh.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::sync_downloads().await {
|
||||
Ok(s) => {
|
||||
message.set(Some(format!(
|
||||
"Synced: {} found, {} enqueued, {} skipped",
|
||||
s.found, s.enqueued, s.skipped
|
||||
)));
|
||||
refresh.emit(());
|
||||
}
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
let on_process = {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::process_downloads().await {
|
||||
Ok(t) => message.set(Some(format!("Processing started (task: {})", t.task_id))),
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
let on_manual_dl = {
|
||||
let dl_query = dl_query.clone();
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let refresh = refresh.clone();
|
||||
Callback::from(move |e: SubmitEvent| {
|
||||
e.prevent_default();
|
||||
let q = (*dl_query).clone();
|
||||
let message = message.clone();
|
||||
let error = error.clone();
|
||||
let refresh = refresh.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match api::enqueue_download(&q).await {
|
||||
Ok(item) => {
|
||||
message.set(Some(format!("Enqueued: {}", item.query)));
|
||||
refresh.emit(());
|
||||
}
|
||||
Err(e) => error.set(Some(e.0)),
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<h2>{ "Downloads" }</h2>
|
||||
</div>
|
||||
|
||||
<div class="actions mb-2">
|
||||
<button class="btn btn-primary" onclick={on_sync}>{ "Sync from Watchlist" }</button>
|
||||
<button class="btn btn-success" onclick={on_process}>{ "Process Queue" }</button>
|
||||
</div>
|
||||
|
||||
<form onsubmit={on_manual_dl}>
|
||||
<div class="search-bar">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Manual download (search query or URL)..."
|
||||
value={(*dl_query).clone()}
|
||||
oninput={let q = dl_query.clone(); Callback::from(move |e: InputEvent| {
|
||||
let input: HtmlInputElement = e.target_unchecked_into();
|
||||
q.set(input.value());
|
||||
})}
|
||||
/>
|
||||
<button type="submit" class="btn btn-primary">{ "Download" }</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
if let Some(ref msg) = *message {
|
||||
<div class="card" style="border-color: var(--success);">{ msg }</div>
|
||||
}
|
||||
if let Some(ref err) = *error {
|
||||
<div class="card error">{ err }</div>
|
||||
}
|
||||
|
||||
{ match &*items {
|
||||
None => html! { <p class="loading">{ "Loading..." }</p> },
|
||||
Some(items) if items.is_empty() => html! { <p class="text-muted">{ "Queue is empty." }</p> },
|
||||
Some(items) => html! {
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>{ "ID" }</th><th>{ "Query" }</th><th>{ "Status" }</th><th>{ "Retries" }</th><th>{ "Error" }</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ for items.iter().map(|item| {
|
||||
let id = item.id;
|
||||
let refresh = refresh.clone();
|
||||
html! {
|
||||
<tr>
|
||||
<td>{ item.id }</td>
|
||||
<td>{ &item.query }</td>
|
||||
<td><StatusBadge status={item.status.clone()} /></td>
|
||||
<td>{ item.retry_count }</td>
|
||||
<td class="text-sm text-muted">{ item.error_message.as_deref().unwrap_or("") }</td>
|
||||
<td>
|
||||
if item.status == "Failed" {
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
onclick={{
|
||||
let refresh = refresh.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let refresh = refresh.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let _ = api::retry_download(id).await;
|
||||
refresh.emit(());
|
||||
});
|
||||
})
|
||||
}}>
|
||||
{ "Retry" }
|
||||
</button>
|
||||
}
|
||||
if item.status == "Pending" {
|
||||
<button class="btn btn-sm btn-danger"
|
||||
onclick={{
|
||||
let refresh = refresh.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
let refresh = refresh.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let _ = api::cancel_download(id).await;
|
||||
refresh.emit(());
|
||||
});
|
||||
})
|
||||
}}>
|
||||
{ "Cancel" }
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
},
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user