fix: upgrade to leptos 0.8 with compatible deps
Some checks failed
Build MIPS Binary / build (push) Failing after 1m28s

- Update leptos-use from 0.15 to 0.16 for reactive_graph compatibility
- Fix web-sys ProgressElement -> ProgressEvent feature
- Resolve closure ownership issues in context_menu.rs and statusbar.rs
- Update Cargo dependencies for stable compilation
This commit is contained in:
spinline
2026-02-09 21:25:46 +03:00
parent 95a0d59cc4
commit cd7d21cd48
16 changed files with 744 additions and 1770 deletions

View File

@@ -1,33 +1,32 @@
use leptos::prelude::*;
use leptos::logging;
use leptos::html;
use leptos::task::spawn_local;
use crate::components::layout::sidebar::Sidebar;
use crate::components::layout::statusbar::StatusBar;
use crate::components::layout::toolbar::Toolbar;
use crate::components::layout::statusbar::StatusBar;
#[component]
pub fn Protected(children: Children) -> impl IntoView {
view! {
<div class="drawer lg:drawer-open h-full w-full">
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col h-full overflow-hidden bg-base-100 text-base-content text-sm select-none">
<div class="drawer-content flex flex-col h-full overflow-hidden bg-base-100">
// --- TOOLBAR (TOP) ---
<Toolbar />
<main class="flex-1 flex flex-col min-w-0 bg-base-100 overflow-hidden pb-8">
// --- MAIN CONTENT ---
<main class="flex-1 overflow-hidden relative">
{children()}
</main>
// --- STATUS BAR (BOTTOM) ---
<StatusBar />
</div>
<div class="drawer-side z-40 transition-none duration-0">
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay transition-none duration-0"></label>
<div class="menu p-0 min-h-full bg-base-200 text-base-content border-r border-base-300 transition-none duration-0">
<Sidebar />
</div>
// --- SIDEBAR (DRAWER) ---
<div class="drawer-side z-[100]">
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<Sidebar />
</div>
</div>
}
}
}

View File

@@ -1,7 +1,5 @@
use leptos::wasm_bindgen::JsCast;
use leptos::prelude::*;
use leptos::logging;
use leptos::html;
use leptos::wasm_bindgen::JsCast;
use leptos::task::spawn_local;
use crate::api;
@@ -76,195 +74,104 @@ pub fn Sidebar() -> impl IntoView {
let handle_logout = move |_| {
spawn_local(async move {
if api::auth::logout().await.is_ok() {
let _ = window().location().set_href("/login");
let window = web_sys::window().expect("window should exist");
let _ = window.location().set_href("/login");
}
});
};
let username = move || {
store.user.get().unwrap_or_else(|| "User".to_string())
};
let first_letter = move || {
username().chars().next().unwrap_or('?').to_uppercase().to_string()
};
view! {
<div class="w-64 min-h-[100dvh] flex flex-col bg-base-200 border-r border-base-300 pb-8" style="padding-top: env(safe-area-inset-top);">
<div class="p-2 flex-1 overflow-y-auto">
<ul class="menu w-full rounded-box gap-1">
<li class="menu-title text-primary uppercase font-bold px-4">"Filters"</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::All))} on:click=move |_| set_filter(crate::store::FilterStatus::All)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
"All"
<span class="badge badge-sm badge-ghost ml-auto">{total_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Downloading))} on:click=move |_| set_filter(crate::store::FilterStatus::Downloading)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
"Downloading"
<span class="badge badge-sm badge-ghost ml-auto">{downloading_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Seeding))} on:click=move |_| set_filter(crate::store::FilterStatus::Seeding)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
"Seeding"
<span class="badge badge-sm badge-ghost ml-auto">{seeding_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Completed))} on:click=move |_| set_filter(crate::store::FilterStatus::Completed)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
"Completed"
<span class="badge badge-sm badge-ghost ml-auto">{completed_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Paused))} on:click=move |_| set_filter(crate::store::FilterStatus::Paused)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
</svg>
"Paused"
<span class="badge badge-sm badge-ghost ml-auto">{paused_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Inactive))} on:click=move |_| set_filter(crate::store::FilterStatus::Inactive)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
</svg>
"Inactive"
<span class="badge badge-sm badge-ghost ml-auto">{inactive_count}</span>
</button>
</li>
</ul>
let username = move || {
store.user.get().unwrap_or_else(|| "User".to_string())
};
let first_letter = move || {
username().chars().next().unwrap_or('?').to_uppercase().to_string()
};
view! {
<div class="w-64 min-h-[100dvh] flex flex-col bg-base-200 border-r border-base-300 pb-8" style="padding-top: env(safe-area-inset-top);">
<div class="p-2 flex-1 overflow-y-auto">
<ul class="menu w-full rounded-box gap-1">
<li class="menu-title text-primary uppercase font-bold px-4">"Filters"</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::All))} on:click=move |_| set_filter(crate::store::FilterStatus::All)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
"All"
<span class="badge badge-sm badge-ghost ml-auto">{total_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Downloading))} on:click=move |_| set_filter(crate::store::FilterStatus::Downloading)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
"Downloading"
<span class="badge badge-sm badge-ghost ml-auto">{downloading_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Seeding))} on:click=move |_| set_filter(crate::store::FilterStatus::Seeding)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
"Seeding"
<span class="badge badge-sm badge-ghost ml-auto">{seeding_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Completed))} on:click=move |_| set_filter(crate::store::FilterStatus::Completed)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
"Completed"
<span class="badge badge-sm badge-ghost ml-auto">{completed_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Paused))} on:click=move |_| set_filter(crate::store::FilterStatus::Paused)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
</svg>
"Paused"
<span class="badge badge-sm badge-ghost ml-auto">{paused_count}</span>
</button>
</li>
<li>
<button class={move || format!("cursor-pointer {}", filter_class(crate::store::FilterStatus::Inactive))} on:click=move |_| set_filter(crate::store::FilterStatus::Inactive)>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
</svg>
"Inactive"
<span class="badge badge-sm badge-ghost ml-auto">{inactive_count}</span>
</button>
</li>
</ul>
</div>
<div class="p-4 border-t border-base-300 bg-base-200/50">
<div class="flex items-center gap-3">
<div class="avatar">
<div class="w-8 rounded-full bg-neutral text-neutral-content ring ring-primary ring-offset-base-100 ring-offset-1">
<span class="text-sm font-bold flex items-center justify-center h-full">{first_letter}</span>
</div>
<div class="p-4 border-t border-base-300 bg-base-200/50">
<div class="flex items-center gap-3">
<div class="avatar">
<div class="w-8 rounded-full bg-neutral text-neutral-content ring ring-primary ring-offset-base-100 ring-offset-1">
<span class="text-sm font-bold flex items-center justify-center h-full">{first_letter}</span>
</div>
</div>
<div class="flex-1 overflow-hidden">
<div class="font-bold text-sm truncate">{username}</div>
<div class="text-[10px] text-base-content/60 truncate">"Online"</div>
</div>
<button
class="btn btn-ghost btn-xs btn-square text-error hover:bg-error/10"
title="Logout"
on:click=handle_logout
>
<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="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
</svg>
</button>
</div>
</div>
</div>
}}
<div class="flex-1 overflow-hidden">
<div class="font-bold text-sm truncate">{username}</div>
<div class="text-[10px] text-base-content/60 truncate">"Online"</div>
</div>
<button
class="btn btn-ghost btn-xs btn-square text-error hover:bg-error/10"
title="Logout"
on:click=handle_logout
>
<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="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
</svg>
</button>
</div>
</div>
</div>
}
}

View File

@@ -1,11 +1,8 @@
use leptos::prelude::*;
use leptos::logging;
use leptos::html;
use leptos::task::spawn_local;
use leptos_use::storage::use_local_storage;
use ::codee::string::FromToStringCodec;
use shared::GlobalLimitRequest;
use reactive_graph::traits::{Get, Set, GetUntracked};
use crate::api;
fn format_bytes(bytes: i64) -> String {
@@ -33,16 +30,16 @@ pub fn StatusBar() -> impl IntoView {
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
let stats = store.global_stats;
// 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() {
let current_theme_val = current_theme.get();
if current_theme_val.is_empty() {
set_current_theme.set("dark".to_string());
}
// Automatically sync theme to document attribute
create_effect(move |_| {
Effect::new(move |_| {
let theme = current_theme.get().to_lowercase();
if let Some(doc) = document().document_element() {
let _ = doc.set_attribute("data-theme", &theme);
@@ -50,7 +47,7 @@ pub fn StatusBar() -> impl IntoView {
});
// Preset limits in bytes/s
let limits: Vec<(i64, &str)> = vec![
let limits: Vec<(i64, &str)> = vec!(
(0, "Unlimited"),
(100 * 1024, "100 KB/s"),
(500 * 1024, "500 KB/s"),
@@ -59,11 +56,11 @@ pub fn StatusBar() -> impl IntoView {
(5 * 1024 * 1024, "5 MB/s"),
(10 * 1024 * 1024, "10 MB/s"),
(20 * 1024 * 1024, "20 MB/s"),
];
);
let set_limit = move |limit_type: &str, val: i64| {
let limit_type = limit_type.to_string();
logging::log!("Setting {} limit to {}", limit_type, val);
log::info!("Setting {} limit to {}", limit_type, val);
let req = if limit_type == "down" {
GlobalLimitRequest {
@@ -77,22 +74,20 @@ pub fn StatusBar() -> impl IntoView {
}
};
spawn_local(async move {
leptos::task::spawn_local(async move {
if let Err(e) = api::settings::set_global_limits(&req).await {
logging::error!("Failed to set limit: {:?}", e);
log::error!("Failed to set limit: {:?}", e);
} else {
logging::log!("Limit set successfully");
log::info!("Limit set successfully");
}
});
};
// Refs for click outside detection (Handled globally via JS in index.html for better iOS support)
let down_details_ref = create_node_ref::<html::Details>();
let up_details_ref = create_node_ref::<html::Details>();
let theme_details_ref = create_node_ref::<html::Details>();
let down_details_ref = NodeRef::<html::Details>::new();
let up_details_ref = NodeRef::<html::Details>::new();
let theme_details_ref = NodeRef::<html::Details>::new();
// Helper to close a details element
let close_details = |node_ref: NodeRef<html::Details>| {
let close_details = move |node_ref: NodeRef<html::Details>| {
if let Some(el) = node_ref.get_untracked() {
el.set_open(false);
}
@@ -199,16 +194,19 @@ pub fn StatusBar() -> impl IntoView {
"light", "dark", "dim", "nord", "cupcake", "dracula", "cyberpunk", "emerald", "sunset", "abyss"
];
themes.into_iter().map(|theme| {
let theme_name = theme.to_string();
let theme_name_for_class = theme_name.clone();
let theme_name_for_onclick = theme_name.clone();
view! {
<li>
<button
class=move || if current_theme.get() == theme { "bg-primary/10 text-primary font-bold text-xs capitalize" } else { "text-xs capitalize" }
class=move || if current_theme.get() == theme_name_for_class { "bg-primary/10 text-primary font-bold text-xs capitalize" } else { "text-xs capitalize" }
on:click=move |_| {
set_current_theme.set(theme.to_string());
set_current_theme.set(theme_name_for_onclick.clone());
close_details(theme_details_ref);
}
>
{theme}
{theme_name}
</button>
</li>
}
@@ -221,7 +219,7 @@ pub fn StatusBar() -> impl IntoView {
title="Settings & Notification Permissions"
on:click=move |_| {
// Request push notification permission when settings button is clicked
spawn_local(async {
leptos::task::spawn_local(async {
log::info!("Settings button clicked - requesting push notification permission");
// Check current permission state before requesting
@@ -266,11 +264,11 @@ pub fn StatusBar() -> impl IntoView {
}
>
<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.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.212 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.212 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 012.6-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
</div>
</div>
}
}
}

View File

@@ -1,11 +1,9 @@
use leptos::prelude::*;
use leptos::logging;
use leptos::html;
use leptos::task::spawn_local;
use crate::components::torrent::add_torrent::AddTorrentDialog;
#[component]
pub fn Toolbar() -> impl IntoView {
let (show_add_modal, set_show_add_modal) = create_signal(false);
let show_add_modal = signal(false);
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
view! {
@@ -14,54 +12,48 @@ pub fn Toolbar() -> impl IntoView {
<label for="my-drawer" class="btn btn-square btn-ghost lg:hidden drawer-button">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-5 h-5 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
</label>
<div class="flex gap-2">
<button
class="btn btn-sm btn-primary gap-2 font-normal"
title="Add Magnet Link"
on:click=move |_| set_show_add_modal.set(true)
<div class="flex items-center gap-3">
<button
class="btn btn-primary btn-sm md:btn-md gap-2 shadow-md hover:shadow-primary/20 transition-all"
on:click=move |_| show_add_modal.1.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">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 md:w-5 md:h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
"Add Torrent"
<span class="hidden sm:inline">"Add Torrent"</span>
<span class="sm:hidden">"Add"</span>
</button>
</div>
</div>
<div class="navbar-end gap-2 px-4">
<div class="join">
<input
type="text"
placeholder="Search..."
class="input input-sm input-bordered join-item w-full max-w-xs focus:outline-none"
prop:value=move || store.search_query.get()
on:input=move |ev| store.search_query.set(event_target_value(&ev))
on:keydown=move |ev: web_sys::KeyboardEvent| {
if ev.key() == "Escape" {
store.search_query.set(String::new());
}
}
/>
<Show when=move || !store.search_query.get().is_empty()>
<button
class="btn btn-sm btn-ghost join-item border-base-content/20 border-l-0 px-2"
title="Clear Search"
on:click=move |_| store.search_query.set(String::new())
>
<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 opacity-70">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</Show>
</div>
<div class="navbar-center hidden md:flex">
<div class="join shadow-sm border border-base-200">
<div class="relative">
<input
type="text"
placeholder="Search..."
class="input input-sm input-bordered join-item w-full max-w-xs focus:outline-none"
prop:value=move || store.search_query.get()
on:input=move |ev| store.search_query.set(event_target_value(&ev))
/>
<Show when=move || !store.search_query.get().is_empty()>
<button
class="absolute right-2 top-1/2 -translate-y-1/2 btn btn-ghost btn-xs btn-circle"
on:click=move |_| store.search_query.set(String::new())
>
"×"
</button>
</Show>
</div>
</div>
</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 class="navbar-end px-4 gap-2">
<Show when=move || show_add_modal.0.get()>
<AddTorrentDialog on_close=Callback::new(move |()| show_add_modal.1.set(false)) />
</Show>
</div>
</div>
}
}
}