Compare commits
3 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e339ca1c49 | ||
|
|
a08fd9698f | ||
|
|
7d46dbd437 |
@@ -55,3 +55,4 @@ js-sys = "0.3.85"
|
||||
base64 = "0.22.1"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
leptos-use = "0.13"
|
||||
codee = "0.2"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,26 +115,24 @@ 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"
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
toggle(1);
|
||||
}
|
||||
on:click=move |_| toggle(1)
|
||||
>
|
||||
<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="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3" />
|
||||
@@ -173,8 +159,7 @@ pub fn StatusBar() -> impl IntoView {
|
||||
<li>
|
||||
<button
|
||||
class=move || if is_active() { "bg-primary/10 text-primary font-bold text-xs flex justify-between" } else { "text-xs flex justify-between" }
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
on:click=move |_| {
|
||||
set_limit("down", val);
|
||||
close_all();
|
||||
}
|
||||
@@ -192,14 +177,11 @@ 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"
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
toggle(2);
|
||||
}
|
||||
on:click=move |_| toggle(2)
|
||||
>
|
||||
<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="M4.5 10.5L12 3m0 0l7.5 7.5M12 3v18" />
|
||||
@@ -226,8 +208,7 @@ pub fn StatusBar() -> impl IntoView {
|
||||
<li>
|
||||
<button
|
||||
class=move || if is_active() { "bg-primary/10 text-primary font-bold text-xs flex justify-between" } else { "text-xs flex justify-between" }
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
on:click=move |_| {
|
||||
set_limit("up", val);
|
||||
close_all();
|
||||
}
|
||||
@@ -245,14 +226,11 @@ 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"
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
toggle(3);
|
||||
}
|
||||
on:click=move |_| toggle(3)
|
||||
>
|
||||
<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="M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42" />
|
||||
@@ -272,17 +250,8 @@ pub fn StatusBar() -> impl IntoView {
|
||||
<li>
|
||||
<button
|
||||
class=move || if current_theme.get() == theme { "bg-primary/10 text-primary font-bold text-xs capitalize" } else { "text-xs capitalize" }
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
on:click=move |_| {
|
||||
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();
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use leptos::*;
|
||||
use leptos_use::use_timeout_fn;
|
||||
use leptos_use::{on_click_outside, use_timeout_fn};
|
||||
use crate::store::{get_action_messages, show_toast_with_signal};
|
||||
use shared::NotificationLevel;
|
||||
|
||||
@@ -166,6 +166,8 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
|
||||
// Signal-based sort dropdown for mobile
|
||||
let (sort_open, set_sort_open) = create_signal(false);
|
||||
let sort_menu_ref = create_node_ref::<html::Div>();
|
||||
let _ = on_click_outside(sort_menu_ref, move |_| set_sort_open.set(false));
|
||||
|
||||
let sort_arrow = move |col: SortColumn| {
|
||||
if sort_col.get() == col {
|
||||
@@ -344,12 +346,11 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
<div class="px-3 py-2 border-b border-base-200 flex justify-between items-center bg-base-100/95 backdrop-blur z-10 shrink-0">
|
||||
<span class="text-xs font-bold opacity-50 uppercase tracking-wider">"Torrents"</span>
|
||||
|
||||
<div class="relative">
|
||||
<div class="relative" node_ref=sort_menu_ref>
|
||||
<div
|
||||
role="button"
|
||||
class="btn btn-ghost btn-xs gap-1 opacity-70 font-normal"
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
on:click=move |_| {
|
||||
let cur = sort_open.get_untracked();
|
||||
set_sort_open.set(!cur);
|
||||
}
|
||||
@@ -385,8 +386,7 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
<button
|
||||
type="button"
|
||||
class=move || if is_active() { "bg-primary/10 text-primary font-bold flex justify-between" } else { "flex justify-between" }
|
||||
on:pointerdown=move |e| {
|
||||
e.stop_propagation();
|
||||
on:click=move |_| {
|
||||
handle_sort(col);
|
||||
set_sort_open.set(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user