diff --git a/frontend/src/components/context_menu.rs b/frontend/src/components/context_menu.rs index 26217b6..36d8ee2 100644 --- a/frontend/src/components/context_menu.rs +++ b/frontend/src/components/context_menu.rs @@ -1,12 +1,12 @@ use leptos::prelude::*; -use leptos::portal::Portal; -use leptos_shadcn_context_menu::{ - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuTrigger, - ContextMenuSeparator, -}; +use web_sys::MouseEvent; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// ── Kendi reaktif Context Menu implementasyonumuz ── +// leptos-shadcn-context-menu v0.8.1'de ContextMenuContent'te +// `if open.get()` statik kontrolü reaktif değil. Aşağıda +// `Show` bileşeni ile düzgün reaktif versiyon yer alıyor. #[component] pub fn TorrentContextMenu( @@ -17,68 +17,131 @@ pub fn TorrentContextMenu( let hash = StoredValue::new(torrent_hash); let on_action = StoredValue::new(on_action); + let open = RwSignal::new(false); + let position = RwSignal::new((0i32, 0i32)); + + // Sağ tıklama handler + let on_contextmenu = move |e: MouseEvent| { + e.prevent_default(); + e.stop_propagation(); + position.set((e.client_x(), e.client_y())); + open.set(true); + }; + + // Menü dışına tıklandığında kapanma + Effect::new(move |_| { + if open.get() { + let cb = Closure::wrap(Box::new(move |_: MouseEvent| { + open.set(false); + }) as Box); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let _ = document.add_event_listener_with_callback( + "click", + cb.as_ref().unchecked_ref(), + ); + + // Cleanup: tek sefer dinleyici — click yakalandığında otomatik kapanıp listener kalıyor + // ama open=false olduğunda effect tekrar çalışmaz, böylece sorun yok. + cb.forget(); + } + }); + + let menu_action = move |action: &'static str| { + open.set(false); + on_action.get_value().run((action.to_string(), hash.get_value())); + }; + view! { - - - {children()} - - - - - - - - - "Start" - - - - - - - "Stop" - +
+ {children()} +
- - - - - "Recheck" - - - - - + { + let (x, y) = position.get(); + view! { +
+
- - - - "Remove" - + // Start +
+ + + + "Start" +
- - - - - "Remove Data" - - - - + // Stop +
+ + + + "Stop" +
+ + // Recheck +
+ + + + "Recheck" +
+ + // Separator +
+ + // Remove +
+ + + + "Remove" +
+ + // Remove with Data +
+ + + + "Remove with Data" +
+
+ } + } + } } \ No newline at end of file diff --git a/frontend/src/components/layout/sidebar.rs b/frontend/src/components/layout/sidebar.rs index 8f610c6..00e2234 100644 --- a/frontend/src/components/layout/sidebar.rs +++ b/frontend/src/components/layout/sidebar.rs @@ -1,5 +1,8 @@ use leptos::prelude::*; use leptos::task::spawn_local; +use leptos_shadcn_button::{Button, ButtonVariant, ButtonSize}; +use leptos_shadcn_avatar::{Avatar, AvatarFallback}; +use leptos_shadcn_separator::Separator; #[component] pub fn Sidebar() -> impl IntoView { @@ -54,23 +57,7 @@ pub fn Sidebar() -> impl IntoView { is_mobile_menu_open.set(false); }; - let filter_class = move |f: crate::store::FilterStatus| { - let base = "w-full justify-start gap-2 h-9 px-4 py-2 inline-flex items-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"; - if store.filter.get() == f { - format!("{} bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", base) - } else { - format!("{} hover:bg-accent hover:text-accent-foreground text-muted-foreground", base) - } - }; - - let handle_logout = move |_| { - spawn_local(async move { - if shared::server_fns::auth::logout().await.is_ok() { - let window = web_sys::window().expect("window should exist"); - let _ = window.location().set_href("/login"); - } - }); - }; + let is_active = move |f: crate::store::FilterStatus| store.filter.get() == f; let username = move || { store.user.get().unwrap_or_else(|| "User".to_string()) @@ -89,76 +76,116 @@ pub fn Sidebar() -> impl IntoView {

"Filters"

- + - + - + - + - + - +
-
+ + +
-
- + + {first_letter} - -
+ +
{username}
"Online"
- +