Compare commits

..

5 Commits

Author SHA1 Message Date
spinline
c8139f9338 chore: comprehensive cleanup of unused imports, dead code and compiler warnings
All checks were successful
Build MIPS Binary / build (push) Successful in 1m50s
2026-02-12 23:43:33 +03:00
spinline
a3735d0931 fix: resolve compilation errors related to JsCast and AlertDialogTrigger attributes
All checks were successful
Build MIPS Binary / build (push) Successful in 1m50s
2026-02-12 23:39:17 +03:00
spinline
55f00729ee fix: relocate AlertDialog outside of DropdownMenu to ensure proper centering
Some checks failed
Build MIPS Binary / build (push) Failing after 30s
2026-02-12 23:37:36 +03:00
spinline
275f4a91b2 fix: change href to src in Trunk script tag to resolve build error
All checks were successful
Build MIPS Binary / build (push) Successful in 1m52s
2026-02-12 23:34:06 +03:00
spinline
025a0c4a57 fix: use script tag for Trunk rust asset to resolve preload warnings
Some checks failed
Build MIPS Binary / build (push) Failing after 25s
2026-02-12 23:32:37 +03:00
10 changed files with 86 additions and 151 deletions

View File

@@ -20,7 +20,7 @@
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" /> <link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
<!-- Trunk Assets --> <!-- Trunk Assets -->
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="0" data-preload="false" /> <script data-trunk rel="rust" src="Cargo.toml" data-wasm-opt="0" data-preload="false"></script>
<link data-trunk rel="css" href="public/tailwind.css" /> <link data-trunk rel="css" href="public/tailwind.css" />
<link data-trunk rel="copy-file" href="manifest.json" /> <link data-trunk rel="copy-file" href="manifest.json" />
<link data-trunk rel="copy-file" href="icon-192.png" /> <link data-trunk rel="copy-file" href="icon-192.png" />

View File

@@ -1,5 +1,3 @@
use leptos::prelude::*;
pub fn use_random_id_for(prefix: &str) -> String { pub fn use_random_id_for(prefix: &str) -> String {
format!("{}_{}", prefix, js_sys::Math::random().to_string().replace(".", "")) format!("{}_{}", prefix, js_sys::Math::random().to_string().replace(".", ""))
} }

View File

@@ -8,26 +8,15 @@ pub struct ThemeMode {
const LOCALSTORAGE_KEY: &str = "darkmode"; const LOCALSTORAGE_KEY: &str = "darkmode";
/// Hook to access the dark mode context
///
/// Returns the ThemeMode instance from context for easy access
pub fn use_theme_mode() -> ThemeMode { pub fn use_theme_mode() -> ThemeMode {
expect_context::<ThemeMode>() expect_context::<ThemeMode>()
} }
/* ========================================================== */
/* ✨ FUNCTIONS ✨ */
/* ========================================================== */
impl ThemeMode { impl ThemeMode {
#[must_use]
/// Initializes a new ThemeMode instance.
pub fn init() -> Self { pub fn init() -> Self {
let theme_mode = Self { state: RwSignal::new(false) }; let theme_mode = Self { state: RwSignal::new(false) };
provide_context(theme_mode); provide_context(theme_mode);
// Use Effect to handle browser-only initialization
Effect::new(move |_| { Effect::new(move |_| {
let initial = Self::get_storage_state().unwrap_or(Self::prefers_dark_mode()); let initial = Self::get_storage_state().unwrap_or(Self::prefers_dark_mode());
theme_mode.state.set(initial); theme_mode.state.set(initial);
@@ -43,45 +32,14 @@ impl ThemeMode {
}); });
} }
pub fn set_dark(&self) {
self.set(true);
}
pub fn set_light(&self) {
self.set(false);
}
/// - `dark`: Set to `true` for dark mode, and `false` for light mode.
pub fn set(&self, dark: bool) {
self.state.set(dark);
Self::set_storage_state(dark);
}
#[must_use]
pub fn get(&self) -> bool { pub fn get(&self) -> bool {
self.state.get() self.state.get()
} }
#[must_use]
pub fn is_dark(&self) -> bool {
self.state.get()
}
#[must_use]
pub fn is_light(&self) -> bool {
!self.state.get()
}
/* ========================================================== */
/* ✨ FUNCTIONS ✨ */
/* ========================================================== */
/// Retrieves the local storage object, if available.
fn get_storage() -> Option<Storage> { fn get_storage() -> Option<Storage> {
window().local_storage().ok().flatten() window().local_storage().ok().flatten()
} }
/// Retrieves the dark mode state from local storage, if available.
fn get_storage_state() -> Option<bool> { fn get_storage_state() -> Option<bool> {
Self::get_storage() Self::get_storage()
.and_then(|storage| storage.get(LOCALSTORAGE_KEY).ok()) .and_then(|storage| storage.get(LOCALSTORAGE_KEY).ok())
@@ -89,7 +47,6 @@ impl ThemeMode {
.and_then(|entry| entry.parse::<bool>().ok()) .and_then(|entry| entry.parse::<bool>().ok())
} }
/// Checks whether the user's system prefers dark mode based on media queries.
fn prefers_dark_mode() -> bool { fn prefers_dark_mode() -> bool {
window() window()
.match_media("(prefers-color-scheme: dark)") .match_media("(prefers-color-scheme: dark)")
@@ -99,10 +56,9 @@ impl ThemeMode {
.unwrap_or_default() .unwrap_or_default()
} }
/// Stores the dark mode state in local storage.
fn set_storage_state(state: bool) { fn set_storage_state(state: bool) {
if let Some(storage) = Self::get_storage() { if let Some(storage) = Self::get_storage() {
storage.set(LOCALSTORAGE_KEY, state.to_string().as_str()).ok(); let _ = storage.set(LOCALSTORAGE_KEY, state.to_string().as_str());
} }
} }
} }

View File

@@ -1,5 +1,6 @@
use leptos::prelude::*; use leptos::prelude::*;
use leptos::task::spawn_local; use leptos::task::spawn_local;
use wasm_bindgen::JsCast;
use std::collections::HashSet; use std::collections::HashSet;
use icons::{ArrowUpDown, Inbox, Settings2, Play, Square, Trash2, Ellipsis, ArrowUp, ArrowDown, Check, ListFilter}; use icons::{ArrowUpDown, Inbox, Settings2, Play, Square, Trash2, Ellipsis, ArrowUp, ArrowDown, Check, ListFilter};
use crate::store::{get_action_messages, show_toast}; use crate::store::{get_action_messages, show_toast};
@@ -230,6 +231,7 @@ pub fn TorrentTable() -> impl IntoView {
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Show when=move || has_selection.get()> <Show when=move || has_selection.get()>
<div class="flex items-center gap-2">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger class="w-[140px] h-9 gap-2"> <DropdownMenuTrigger class="w-[140px] h-9 gap-2">
<Ellipsis class="size-4" /> <Ellipsis class="size-4" />
@@ -247,12 +249,21 @@ pub fn TorrentTable() -> impl IntoView {
<div class="my-1 h-px bg-border" /> <div class="my-1 h-px bg-border" />
// Trigger the hidden AlertDialog from this menu item
<DropdownMenuItem class="text-destructive focus:bg-destructive/10 cursor-pointer" on:click=move |_| {
if let Some(trigger) = document().get_element_by_id("bulk-delete-trigger") {
let _ = trigger.dyn_into::<web_sys::HtmlElement>().map(|el: web_sys::HtmlElement| el.click());
}
}>
<Trash2 class="mr-2 size-4" /> "Toplu Sil..."
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
// Hidden AlertDialog moved outside the DropdownMenuContent to ensure proper centering
<AlertDialog> <AlertDialog>
<AlertDialogTrigger class="w-full text-left"> <AlertDialogTrigger attr:id="bulk-delete-trigger" class="hidden">""</AlertDialogTrigger>
<div class="inline-flex gap-2 items-center w-full rounded-sm px-2 py-1.5 text-sm transition-colors text-destructive hover:bg-destructive/10 focus:bg-destructive/10 cursor-pointer">
<Trash2 class="size-4" /> "Toplu Sil..."
</div>
</AlertDialogTrigger>
<AlertDialogContent class="sm:max-w-[425px]"> <AlertDialogContent class="sm:max-w-[425px]">
<AlertDialogBody> <AlertDialogBody>
<AlertDialogHeader class="space-y-3"> <AlertDialogHeader class="space-y-3">
@@ -260,7 +271,7 @@ pub fn TorrentTable() -> impl IntoView {
<Trash2 class="size-6" /> <Trash2 class="size-6" />
"Toplu Silme Onayı" "Toplu Silme Onayı"
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription class="text-sm leading-relaxed"> <AlertDialogDescription class="text-sm leading-relaxed text-left">
{move || format!("Seçili {} adet torrent silinecek. Lütfen silme yöntemini seçin:", selected_count.get())} {move || format!("Seçili {} adet torrent silinecek. Lütfen silme yöntemini seçin:", selected_count.get())}
<div class="mt-4 p-4 bg-destructive/5 rounded-lg border border-destructive/10 text-xs text-destructive/80 font-medium"> <div class="mt-4 p-4 bg-destructive/5 rounded-lg border border-destructive/10 text-xs text-destructive/80 font-medium">
"⚠️ Dikkat: Verilerle birlikte silme işlemi dosyaları diskten de kalıcı olarak kaldıracaktır." "⚠️ Dikkat: Verilerle birlikte silme işlemi dosyaları diskten de kalıcı olarak kaldıracaktır."
@@ -291,9 +302,7 @@ pub fn TorrentTable() -> impl IntoView {
</AlertDialogBody> </AlertDialogBody>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
</DropdownMenuGroup> </div>
</DropdownMenuContent>
</DropdownMenu>
</Show> </Show>
// Mobile Sort Menu // Mobile Sort Menu

View File

@@ -6,7 +6,6 @@ pub enum BadgeVariant {
#[default] #[default]
Default, Default,
Secondary, Secondary,
Outline,
Destructive, Destructive,
Success, Success,
Warning, Warning,
@@ -22,7 +21,6 @@ pub fn Badge(
let variant_classes = match variant { let variant_classes = match variant {
BadgeVariant::Default => "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", BadgeVariant::Default => "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
BadgeVariant::Secondary => "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", BadgeVariant::Secondary => "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
BadgeVariant::Outline => "text-foreground",
BadgeVariant::Destructive => "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", BadgeVariant::Destructive => "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
BadgeVariant::Success => "border-transparent bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20", BadgeVariant::Success => "border-transparent bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20",
BadgeVariant::Warning => "border-transparent bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/20", BadgeVariant::Warning => "border-transparent bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/20",

View File

@@ -209,7 +209,7 @@ pub fn ContextMenuTrigger(
class=trigger_class class=trigger_class
data-name="ContextMenuTrigger" data-name="ContextMenuTrigger"
data-context-trigger=ctx.target_id data-context-trigger=ctx.target_id
on:contextmenu=move |e: web_sys::MouseEvent| { on:contextmenu=move |_e: web_sys::MouseEvent| {
if let Some(cb) = on_open { if let Some(cb) = on_open {
cb.run(()); cb.run(());
} }

View File

@@ -1,6 +1,6 @@
// * Reuse @table.rs // * Reuse @table.rs
pub use crate::components::ui::table::{ pub use crate::components::ui::table::{
Table as DataTable, TableBody as DataTableBody, TableCaption as DataTableCaption, TableCell as DataTableCell, Table as DataTable, TableBody as DataTableBody, TableCell as DataTableCell,
TableFooter as DataTableFooter, TableHead as DataTableHead, TableHeader as DataTableHeader, TableHead as DataTableHead, TableHeader as DataTableHeader,
TableRow as DataTableRow, TableWrapper as DataTableWrapper, TableRow as DataTableRow, TableWrapper as DataTableWrapper,
}; };

View File

@@ -94,8 +94,6 @@ pub fn DropdownMenuAction(
#[prop(optional, into)] class: String, #[prop(optional, into)] class: String,
#[prop(optional, into)] href: Option<String>, #[prop(optional, into)] href: Option<String>,
) -> impl IntoView { ) -> impl IntoView {
let _ctx = expect_context::<DropdownMenuContext>();
let class = tw_merge!( let class = tw_merge!(
"inline-flex gap-2 items-center w-full text-sm text-left transition-colors duration-200 focus:outline-none focus-visible:outline-none text-popover-foreground [&_svg:not([class*='size-'])]:size-4 hover:bg-accent hover:text-accent-foreground", "inline-flex gap-2 items-center w-full text-sm text-left transition-colors duration-200 focus:outline-none focus-visible:outline-none text-popover-foreground [&_svg:not([class*='size-'])]:size-4 hover:bg-accent hover:text-accent-foreground",
class class
@@ -175,17 +173,15 @@ pub enum DropdownMenuAlign {
#[derive(Clone)] #[derive(Clone)]
struct DropdownMenuContext { struct DropdownMenuContext {
target_id: String, target_id: String,
align: DropdownMenuAlign,
} }
#[component] #[component]
pub fn DropdownMenu( pub fn DropdownMenu(
children: Children, children: Children,
#[prop(default = DropdownMenuAlign::default())] align: DropdownMenuAlign,
) -> impl IntoView { ) -> impl IntoView {
let dropdown_target_id = use_random_id_for("dropdown"); let dropdown_target_id = use_random_id_for("dropdown");
let ctx = DropdownMenuContext { target_id: dropdown_target_id.clone(), align }; let ctx = DropdownMenuContext { target_id: dropdown_target_id.clone() };
view! { view! {
<Provider value=ctx> <Provider value=ctx>
@@ -252,12 +248,13 @@ pub enum DropdownMenuPosition {
pub fn DropdownMenuContent( pub fn DropdownMenuContent(
children: Children, children: Children,
#[prop(optional, into)] class: String, #[prop(optional, into)] class: String,
#[prop(default = DropdownMenuAlign::default())] align: DropdownMenuAlign,
#[prop(default = DropdownMenuPosition::default())] position: DropdownMenuPosition, #[prop(default = DropdownMenuPosition::default())] position: DropdownMenuPosition,
) -> impl IntoView { ) -> impl IntoView {
let ctx = expect_context::<DropdownMenuContext>(); let ctx = expect_context::<DropdownMenuContext>();
let base_classes = "z-50 p-1 rounded-md border bg-card shadow-md h-fit fixed transition-all duration-200 data-[state=closed]:opacity-0 data-[state=closed]:scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100"; let base_classes = "z-50 p-1 rounded-md border bg-card shadow-md h-fit fixed transition-all duration-200 data-[state=closed]:opacity-0 data-[state=closed]:scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100";
let width_class = match ctx.align { let width_class = match align {
DropdownMenuAlign::Center => "min-w-full", DropdownMenuAlign::Center => "min-w-full",
_ => "w-[180px]", _ => "w-[180px]",
}; };
@@ -265,7 +262,7 @@ pub fn DropdownMenuContent(
let class = tw_merge!(width_class, base_classes, class); let class = tw_merge!(width_class, base_classes, class);
let target_id_for_script = ctx.target_id.clone(); let target_id_for_script = ctx.target_id.clone();
let align_for_script = match ctx.align { let align_for_script = match align {
DropdownMenuAlign::Start => "start", DropdownMenuAlign::Start => "start",
DropdownMenuAlign::StartOuter => "start-outer", DropdownMenuAlign::StartOuter => "start-outer",
DropdownMenuAlign::End => "end", DropdownMenuAlign::End => "end",
@@ -442,26 +439,6 @@ pub fn DropdownMenuContent(
trigger.addEventListener('click', (e) => {{ trigger.addEventListener('click', (e) => {{
e.stopPropagation(); e.stopPropagation();
// Check if any other dropdown is open
const allDropdowns = document.querySelectorAll('[data-target=\"target__dropdown\"]');
let otherDropdownOpen = false;
allDropdowns.forEach(dd => {{
if (dd !== dropdown && dd.getAttribute('data-state') === 'open') {{
otherDropdownOpen = true;
dd.setAttribute('data-state', 'closed');
dd.style.pointerEvents = 'none';
// Unlock scroll
if (window.ScrollLock) {{
window.ScrollLock.unlock(200);
}}
}}
}});
// If another dropdown was open, just close it and don't open this one
if (otherDropdownOpen) {{
return;
}}
// Normal toggle behavior // Normal toggle behavior
if (isOpen) {{ if (isOpen) {{
closeDropdown(); closeDropdown();

View File

@@ -9,7 +9,7 @@ use crate::components::hooks::use_can_scroll_vertical::use_can_scroll_vertical;
use crate::components::hooks::use_random::use_random_id_for; use crate::components::hooks::use_random::use_random_id_for;
// * Reuse @select.rs // * Reuse @select.rs
pub use crate::components::ui::select::{ pub use crate::components::ui::select::{
SelectGroup as MultiSelectGroup, SelectItem as MultiSelectItem, SelectLabel as MultiSelectLabel, SelectGroup as MultiSelectGroup, SelectItem as MultiSelectItem,
}; };
#[derive(Clone, Copy, PartialEq, Eq, Default)] #[derive(Clone, Copy, PartialEq, Eq, Default)]

View File

@@ -31,9 +31,6 @@ pub struct SheetContext {
/* ✨ FUNCTIONS ✨ */ /* ✨ FUNCTIONS ✨ */
/* ========================================================== */ /* ========================================================== */
pub type SheetVariant = ButtonVariant;
pub type SheetSize = ButtonSize;
#[component] #[component]
pub fn Sheet(children: Children, #[prop(optional, into)] class: String) -> impl IntoView { pub fn Sheet(children: Children, #[prop(optional, into)] class: String) -> impl IntoView {
let sheet_target_id = use_random_id_for("sheet"); let sheet_target_id = use_random_id_for("sheet");
@@ -203,7 +200,7 @@ pub fn SheetContent(
target_id_for_script, target_id_for_script,
)} )}
</script> </script>
}.into_any() }
} }
/* ========================================================== */ /* ========================================================== */