feat: Implement Context Menu actions and Add Torrent modal
- Implemented Start, Stop, Delete actions in context menu using backend API. - Replaced with manual implementation for context menu reliability. - Added for adding torrents via Magnet Link/URL. - Integrated Add Torrent feature into the Toolbar.
This commit is contained in:
@@ -2,6 +2,8 @@ use leptos::*;
|
||||
|
||||
#[component]
|
||||
pub fn Toolbar() -> impl IntoView {
|
||||
let (show_add_modal, set_show_add_modal) = create_signal(false);
|
||||
|
||||
view! {
|
||||
<div class="h-14 min-h-14 flex items-center px-4 border-b border-base-300 bg-base-100 gap-4">
|
||||
<label for="my-drawer" class="btn btn-square btn-ghost lg:hidden drawer-button">
|
||||
@@ -14,7 +16,11 @@ pub fn Toolbar() -> impl IntoView {
|
||||
</svg>
|
||||
"Open"
|
||||
</button>
|
||||
<button class="join-item btn btn-sm btn-outline gap-2" title="Magnet Link">
|
||||
<button
|
||||
class="join-item btn btn-sm btn-outline gap-2"
|
||||
title="Magnet Link"
|
||||
on:click=move |_| set_show_add_modal.set(true)
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
|
||||
</svg>
|
||||
@@ -46,6 +52,10 @@ pub fn Toolbar() -> impl IntoView {
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<input type="text" placeholder="Filter..." class="input input-sm input-bordered w-full max-w-xs" />
|
||||
</div>
|
||||
|
||||
<Show when=move || show_add_modal.get()>
|
||||
<crate::components::torrent::add_torrent::AddTorrentModal on_close=move |_| set_show_add_modal.set(false) />
|
||||
</Show>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
118
frontend/src/components/torrent/add_torrent.rs
Normal file
118
frontend/src/components/torrent/add_torrent.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use leptos::*;
|
||||
use leptos::html::Dialog;
|
||||
|
||||
|
||||
#[component]
|
||||
pub fn AddTorrentModal(
|
||||
#[prop(into)]
|
||||
on_close: Callback<()>,
|
||||
) -> impl IntoView {
|
||||
let dialog_ref = create_node_ref::<Dialog>();
|
||||
let (uri, set_uri) = create_signal(String::new());
|
||||
let (is_loading, set_loading) = create_signal(false);
|
||||
let (error_msg, set_error_msg) = create_signal(Option::<String>::None);
|
||||
|
||||
// Effect to open the dialog when the component mounts/renders
|
||||
create_effect(move |_| {
|
||||
if let Some(dialog) = dialog_ref.get() {
|
||||
let _ = dialog.show_modal();
|
||||
}
|
||||
});
|
||||
|
||||
let handle_submit = move |_| {
|
||||
let uri_val = uri.get();
|
||||
if uri_val.is_empty() {
|
||||
set_error_msg.set(Some("Please enter a Magnet URI or URL".to_string()));
|
||||
return;
|
||||
}
|
||||
|
||||
set_loading.set(true);
|
||||
set_error_msg.set(None);
|
||||
|
||||
spawn_local(async move {
|
||||
let req_body = serde_json::json!({
|
||||
"uri": uri_val
|
||||
});
|
||||
|
||||
match gloo_net::http::Request::post("/api/torrents/add")
|
||||
.json(&req_body)
|
||||
{
|
||||
Ok(req) => {
|
||||
match req.send().await {
|
||||
Ok(resp) => {
|
||||
if resp.ok() {
|
||||
logging::log!("Torrent added successfully");
|
||||
set_loading.set(false);
|
||||
if let Some(dialog) = dialog_ref.get() {
|
||||
dialog.close();
|
||||
}
|
||||
on_close.call(());
|
||||
} else {
|
||||
let status = resp.status();
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
logging::error!("Failed to add torrent: {} - {}", status, text);
|
||||
set_error_msg.set(Some(format!("Error {}: {}", status, text)));
|
||||
set_loading.set(false);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging::error!("Network error: {}", e);
|
||||
set_error_msg.set(Some(format!("Network Error: {}", e)));
|
||||
set_loading.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging::error!("Serialization error: {}", e);
|
||||
set_error_msg.set(Some(format!("Request Error: {}", e)));
|
||||
set_loading.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let handle_close = move |_| {
|
||||
if let Some(dialog) = dialog_ref.get() {
|
||||
dialog.close();
|
||||
}
|
||||
on_close.call(());
|
||||
};
|
||||
|
||||
view! {
|
||||
<dialog node_ref=dialog_ref class="modal modal-bottom sm:modal-middle" on:close=move |_| on_close.call(())>
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">"Add Torrent"</h3>
|
||||
<p class="py-4">"Enter a Magnet URI or direct URL to a .torrent file."</p>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="magnet:?xt=urn:btih:..."
|
||||
class="input input-bordered w-full"
|
||||
prop:value=uri
|
||||
on:input=move |ev| set_uri.set(event_target_value(&ev))
|
||||
disabled=is_loading
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<button class="btn" on:click=handle_close disabled=is_loading>"Cancel"</button>
|
||||
<button class="btn btn-primary" on:click=handle_submit disabled=is_loading>
|
||||
{move || if is_loading.get() {
|
||||
view! { <span class="loading loading-spinner"></span> "Adding..." }.into_view()
|
||||
} else {
|
||||
view! { "Add" }.into_view()
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{move || error_msg.get().map(|msg| view! {
|
||||
<div class="text-error text-sm mt-2">{msg}</div>
|
||||
})}
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button type="button" on:click=handle_close>"close"</button>
|
||||
</form>
|
||||
</dialog>
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod table;
|
||||
pub mod add_torrent;
|
||||
|
||||
@@ -119,15 +119,35 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
|
||||
let on_action = move |(action, hash): (String, String)| {
|
||||
logging::log!("TorrentTable Action: {} on {}", action, hash);
|
||||
// TODO: Implement actual store calls here (start/stop/delete)
|
||||
match action.as_str() {
|
||||
"start" => { /* store.start_torrent(&hash) */ },
|
||||
"stop" => { /* store.stop_torrent(&hash) */ },
|
||||
"delete" => { /* store.delete_torrent(&hash, false) */ },
|
||||
"delete_with_data" => { /* store.delete_torrent(&hash, true) */ },
|
||||
_ => {}
|
||||
}
|
||||
set_menu_visible.set(false);
|
||||
set_menu_visible.set(false); // Close menu immediately
|
||||
|
||||
spawn_local(async move {
|
||||
let action_req = if action == "delete_with_data" { "delete_with_data" } else { &action };
|
||||
|
||||
let req_body = shared::TorrentActionRequest {
|
||||
hash: hash.clone(),
|
||||
action: action_req.to_string(),
|
||||
};
|
||||
|
||||
let client = gloo_net::http::Request::post("/api/torrents/action")
|
||||
.json(&req_body);
|
||||
|
||||
match client {
|
||||
Ok(req) => {
|
||||
match req.send().await {
|
||||
Ok(resp) => {
|
||||
if !resp.ok() {
|
||||
logging::error!("Failed to execute action: {} {}", resp.status(), resp.status_text());
|
||||
} else {
|
||||
logging::log!("Action {} executed successfully", action);
|
||||
}
|
||||
}
|
||||
Err(e) => logging::error!("Network error executing action: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => logging::error!("Failed to serialize request: {}", e),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
@@ -209,13 +229,15 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<crate::components::context_menu::ContextMenu
|
||||
visible=menu_visible.get()
|
||||
position=menu_position.get()
|
||||
torrent_hash=active_hash.get()
|
||||
on_close=Callback::from(move |_| set_menu_visible.set(false))
|
||||
on_action=Callback::from(on_action)
|
||||
/>
|
||||
<Show when=move || menu_visible.get() fallback=|| ()>
|
||||
<crate::components::context_menu::ContextMenu
|
||||
visible=true
|
||||
position=menu_position.get()
|
||||
torrent_hash=active_hash.get()
|
||||
on_close=Callback::from(move |_| set_menu_visible.set(false))
|
||||
on_action=Callback::from(on_action)
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user