Compare commits

..

4 Commits

Author SHA1 Message Date
spinline
a08fd9698f refactor: açılır menüler leptos-use::on_click_outside ile modernize edildi, şeffaf backdrop katmanları kaldırıldı
All checks were successful
Build MIPS Binary / build (push) Successful in 5m52s
2026-02-08 20:16:05 +03:00
spinline
7d46dbd437 refactor: tema yönetimi leptos-use::use_local_storage ile reaktif hale getirildi
All checks were successful
Build MIPS Binary / build (push) Successful in 4m28s
2026-02-08 20:02:01 +03:00
spinline
5f107299e3 refactor: long press mantığı leptos-use::use_timeout_fn ile modernize edildi
All checks were successful
Build MIPS Binary / build (push) Successful in 4m29s
2026-02-08 19:53:10 +03:00
spinline
c34133ded1 refactor: manuel Closure ve timer yönetimi Leptos set_timeout ile değiştirildi
All checks were successful
Build MIPS Binary / build (push) Successful in 4m16s
2026-02-08 19:39:38 +03:00
4 changed files with 61 additions and 95 deletions

View File

@@ -54,3 +54,5 @@ tailwind_fuse = "0.3.2"
js-sys = "0.3.85"
base64 = "0.22.1"
serde-wasm-bindgen = "0.6.5"
leptos-use = "0.13"
codee = "0.2"

View File

@@ -1,4 +1,5 @@
use leptos::*;
use leptos_use::on_click_outside;
#[component]
pub fn ContextMenu(
@@ -8,6 +9,10 @@ pub fn ContextMenu(
on_close: Callback<()>,
on_action: Callback<(String, String)>, // (Action, Hash)
) -> impl IntoView {
let container_ref = create_node_ref::<html::Div>();
let _ = on_click_outside(container_ref, move |_| on_close.call(()));
let handle_action = move |action: &str| {
let hash = torrent_hash.clone();
let action_str = action.to_string();
@@ -22,16 +27,8 @@ pub fn ContextMenu(
}
view! {
// Backdrop to catch clicks outside
<div
class="fixed inset-0 z-[99] cursor-default"
role="button"
tabindex="-1"
on:click=move |_| on_close.call(())
on:contextmenu=move |e| e.prevent_default()
></div>
<div
node_ref=container_ref
class="fixed z-[100] min-w-[200px] animate-in fade-in zoom-in-95 duration-100"
style=format!("left: {}px; top: {}px", position.0, position.1)
on:contextmenu=move |e| e.prevent_default()

View File

@@ -1,4 +1,7 @@
use leptos::*;
use leptos_use::on_click_outside;
use leptos_use::storage::use_local_storage;
use codee::string::FromToStringCodec;
use shared::GlobalLimitRequest;
fn format_bytes(bytes: i64) -> String {
@@ -26,34 +29,19 @@ pub fn StatusBar() -> impl IntoView {
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
let stats = store.global_stats;
let initial_theme = if let Some(win) = web_sys::window() {
if let Some(doc) = win.document() {
doc.document_element()
.and_then(|el| el.get_attribute("data-theme"))
.unwrap_or_else(|| "dark".to_string())
} else {
"dark".to_string()
// Use leptos-use for reactive localStorage management
let (current_theme, set_current_theme, _) = use_local_storage::<String, FromToStringCodec>("vibetorrent_theme");
// Initialize with default if empty
if current_theme.get_untracked().is_empty() {
set_current_theme.set("dark".to_string());
}
} else {
"dark".to_string()
};
let (current_theme, set_current_theme) = create_signal(initial_theme);
// Automatically sync theme to document attribute
create_effect(move |_| {
if let Some(win) = web_sys::window() {
if let Some(storage) = win.local_storage().ok().flatten() {
if let Ok(Some(stored_theme)) = storage.get_item("vibetorrent_theme") {
let theme = stored_theme.to_lowercase();
set_current_theme.set(theme.clone());
if let Some(doc) = win.document() {
let _ = doc
.document_element()
.unwrap()
.set_attribute("data-theme", &theme);
}
}
}
let theme = current_theme.get().to_lowercase();
if let Some(doc) = document().document_element() {
let _ = doc.set_attribute("data-theme", &theme);
}
});
@@ -127,19 +115,20 @@ pub fn StatusBar() -> impl IntoView {
set_active_dropdown.set(0);
};
view! {
// Transparent overlay to close dropdowns when clicking outside
<Show when=move || active_dropdown.get() != 0>
<div
class="fixed inset-0 z-[98] cursor-default"
on:pointerdown=move |_| close_all()
></div>
</Show>
// Refs for click outside detection
let down_ref = create_node_ref::<html::Div>();
let up_ref = create_node_ref::<html::Div>();
let theme_ref = create_node_ref::<html::Div>();
let _ = on_click_outside(down_ref, move |_| if active_dropdown.get_untracked() == 1 { close_all() });
let _ = on_click_outside(up_ref, move |_| if active_dropdown.get_untracked() == 2 { close_all() });
let _ = on_click_outside(theme_ref, move |_| if active_dropdown.get_untracked() == 3 { close_all() });
view! {
<div class="fixed bottom-0 left-0 right-0 h-8 min-h-8 bg-base-200 border-t border-base-300 flex items-center px-4 text-xs gap-4 text-base-content/70 z-[99]">
// --- DOWNLOAD SPEED DROPDOWN ---
<div class="relative">
<div class="relative" node_ref=down_ref>
<div
class="flex items-center gap-2 cursor-pointer hover:text-primary transition-colors select-none"
title="Global Download Speed - Click to Set Limit"
@@ -192,7 +181,7 @@ pub fn StatusBar() -> impl IntoView {
</div>
// --- UPLOAD SPEED DROPDOWN ---
<div class="relative">
<div class="relative" node_ref=up_ref>
<div
class="flex items-center gap-2 cursor-pointer hover:text-primary transition-colors select-none"
title="Global Upload Speed - Click to Set Limit"
@@ -245,7 +234,7 @@ pub fn StatusBar() -> impl IntoView {
</div>
<div class="ml-auto flex items-center gap-4">
<div class="relative">
<div class="relative" node_ref=theme_ref>
<div
class="btn btn-ghost btn-xs btn-square"
title="Change Theme"
@@ -275,14 +264,6 @@ pub fn StatusBar() -> impl IntoView {
on:pointerdown=move |e| {
e.stop_propagation();
set_current_theme.set(theme.to_string());
if let Some(win) = web_sys::window() {
if let Some(doc) = win.document() {
let _ = doc.document_element().unwrap().set_attribute("data-theme", theme);
}
if let Some(storage) = win.local_storage().ok().flatten() {
let _ = storage.set_item("vibetorrent_theme", theme);
}
}
close_all();
}
>

View File

@@ -1,6 +1,5 @@
use leptos::*;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use leptos_use::use_timeout_fn;
use crate::store::{get_action_messages, show_toast_with_signal};
use shared::NotificationLevel;
@@ -423,58 +422,45 @@ pub fn TorrentTable() -> impl IntoView {
let _t_hash = t.hash.clone();
let t_hash_click = t.hash.clone();
let (timer_id, set_timer_id) = create_signal(Option::<i32>::None);
let t_hash_long = t.hash.clone();
let clear_timer = move || {
if let Some(id) = timer_id.get_untracked() {
window().clear_timeout_with_handle(id);
set_timer_id.set(None);
}
};
let handle_touchstart = {
let t_hash = t_hash_long.clone();
move |e: web_sys::TouchEvent| {
clear_timer();
if let Some(touch) = e.touches().get(0) {
let x = touch.client_x();
let y = touch.client_y();
let hash = t_hash.clone();
let closure = Closure::wrap(Box::new(move || {
set_menu_position.set((x, y));
set_selected_hash.set(Some(hash.clone()));
let leptos_use::UseTimeoutFnReturn { start, stop, .. } = use_timeout_fn(
move |pos: (i32, i32)| {
set_menu_position.set(pos);
set_selected_hash.set(Some(t_hash_long.clone()));
set_menu_visible.set(true);
// Haptic feedback (iOS Safari doesn't support vibrate)
// Haptic feedback
let navigator = window().navigator();
if js_sys::Reflect::has(&navigator, &wasm_bindgen::JsValue::from_str("vibrate")).unwrap_or(false) {
if let Ok(vibrate) = js_sys::Reflect::get(&navigator, &"vibrate".into()) {
if vibrate.is_function() {
let _ = navigator.vibrate_with_duration(50);
}
}) as Box<dyn Fn()>);
}
},
600.0,
);
let id = window()
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
600
)
.unwrap_or(0);
closure.forget();
set_timer_id.set(Some(id));
let handle_touchstart = {
let start = start.clone();
move |e: web_sys::TouchEvent| {
if let Some(touch) = e.touches().get(0) {
start((touch.client_x(), touch.client_y()));
}
}
};
let handle_touchmove = move |_| {
clear_timer();
let handle_touchmove = {
let stop = stop.clone();
move |_| stop()
};
let handle_touchend = move |_| {
clear_timer();
let handle_touchend = {
let stop = stop.clone();
move |_| stop()
};
let handle_touchcancel = move |_| stop();
view! {
<div
class=move || {
@@ -492,7 +478,7 @@ pub fn TorrentTable() -> impl IntoView {
on:touchstart=handle_touchstart
on:touchmove=handle_touchmove
on:touchend=handle_touchend
on:touchcancel=handle_touchend
on:touchcancel=handle_touchcancel
>
<div class="card-body gap-3">
<div class="flex justify-between items-start gap-2">