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" />
<!-- 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="copy-file" href="manifest.json" />
<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 {
format!("{}_{}", prefix, js_sys::Math::random().to_string().replace(".", ""))
}

View File

@@ -8,26 +8,15 @@ pub struct ThemeMode {
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 {
expect_context::<ThemeMode>()
}
/* ========================================================== */
/* ✨ FUNCTIONS ✨ */
/* ========================================================== */
impl ThemeMode {
#[must_use]
/// Initializes a new ThemeMode instance.
pub fn init() -> Self {
let theme_mode = Self { state: RwSignal::new(false) };
provide_context(theme_mode);
// Use Effect to handle browser-only initialization
Effect::new(move |_| {
let initial = Self::get_storage_state().unwrap_or(Self::prefers_dark_mode());
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 {
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> {
window().local_storage().ok().flatten()
}
/// Retrieves the dark mode state from local storage, if available.
fn get_storage_state() -> Option<bool> {
Self::get_storage()
.and_then(|storage| storage.get(LOCALSTORAGE_KEY).ok())
@@ -89,7 +47,6 @@ impl ThemeMode {
.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 {
window()
.match_media("(prefers-color-scheme: dark)")
@@ -99,10 +56,9 @@ impl ThemeMode {
.unwrap_or_default()
}
/// Stores the dark mode state in local storage.
fn set_storage_state(state: bool) {
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::task::spawn_local;
use wasm_bindgen::JsCast;
use std::collections::HashSet;
use icons::{ArrowUpDown, Inbox, Settings2, Play, Square, Trash2, Ellipsis, ArrowUp, ArrowDown, Check, ListFilter};
use crate::store::{get_action_messages, show_toast};
@@ -230,70 +231,78 @@ pub fn TorrentTable() -> impl IntoView {
<div class="flex items-center gap-2">
<Show when=move || has_selection.get()>
<DropdownMenu>
<DropdownMenuTrigger class="w-[140px] h-9 gap-2">
<Ellipsis class="size-4" />
{move || format!("Toplu İşlem ({})", selected_count.get())}
</DropdownMenuTrigger>
<DropdownMenuContent class="w-48">
<DropdownMenuLabel>"Seçili Torrentler"</DropdownMenuLabel>
<DropdownMenuGroup class="mt-2">
<DropdownMenuItem on:click=move |_| bulk_action("start")>
<Play class="mr-2 size-4" /> "Başlat"
</DropdownMenuItem>
<DropdownMenuItem on:click=move |_| bulk_action("stop")>
<Square class="mr-2 size-4" /> "Durdur"
</DropdownMenuItem>
<div class="my-1 h-px bg-border" />
<AlertDialog>
<AlertDialogTrigger class="w-full text-left">
<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 class="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger class="w-[140px] h-9 gap-2">
<Ellipsis class="size-4" />
{move || format!("Toplu İşlem ({})", selected_count.get())}
</DropdownMenuTrigger>
<DropdownMenuContent class="w-48">
<DropdownMenuLabel>"Seçili Torrentler"</DropdownMenuLabel>
<DropdownMenuGroup class="mt-2">
<DropdownMenuItem on:click=move |_| bulk_action("start")>
<Play class="mr-2 size-4" /> "Başlat"
</DropdownMenuItem>
<DropdownMenuItem on:click=move |_| bulk_action("stop")>
<Square class="mr-2 size-4" /> "Durdur"
</DropdownMenuItem>
<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>
<AlertDialogTrigger attr:id="bulk-delete-trigger" class="hidden">""</AlertDialogTrigger>
<AlertDialogContent class="sm:max-w-[425px]">
<AlertDialogBody>
<AlertDialogHeader class="space-y-3">
<AlertDialogTitle class="text-destructive flex items-center gap-2 text-xl">
<Trash2 class="size-6" />
"Toplu Silme Onayı"
</AlertDialogTitle>
<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())}
<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."
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter class="mt-6">
<div class="flex flex-col-reverse sm:flex-row gap-3 w-full sm:justify-end">
<AlertDialogClose class="sm:flex-1 md:flex-none">"Vazgeç"</AlertDialogClose>
<div class="flex flex-col sm:flex-row gap-2">
<Button
variant=ButtonVariant::Secondary
class="w-full sm:w-auto font-medium"
on:click=move |_| bulk_action("delete")
>
"Sadece Sil"
</Button>
<Button
variant=ButtonVariant::Destructive
class="w-full sm:w-auto font-bold"
on:click=move |_| bulk_action("delete_with_data")
>
"Verilerle Sil"
</Button>
</div>
</div>
</AlertDialogTrigger>
<AlertDialogContent class="sm:max-w-[425px]">
<AlertDialogBody>
<AlertDialogHeader class="space-y-3">
<AlertDialogTitle class="text-destructive flex items-center gap-2 text-xl">
<Trash2 class="size-6" />
"Toplu Silme Onayı"
</AlertDialogTitle>
<AlertDialogDescription class="text-sm leading-relaxed">
{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">
"⚠️ Dikkat: Verilerle birlikte silme işlemi dosyaları diskten de kalıcı olarak kaldıracaktır."
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter class="mt-6">
<div class="flex flex-col-reverse sm:flex-row gap-3 w-full sm:justify-end">
<AlertDialogClose class="sm:flex-1 md:flex-none">"Vazgeç"</AlertDialogClose>
<div class="flex flex-col sm:flex-row gap-2">
<Button
variant=ButtonVariant::Secondary
class="w-full sm:w-auto font-medium"
on:click=move |_| bulk_action("delete")
>
"Sadece Sil"
</Button>
<Button
variant=ButtonVariant::Destructive
class="w-full sm:w-auto font-bold"
on:click=move |_| bulk_action("delete_with_data")
>
"Verilerle Sil"
</Button>
</div>
</div>
</AlertDialogFooter>
</AlertDialogBody>
</AlertDialogContent>
</AlertDialog>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</AlertDialogFooter>
</AlertDialogBody>
</AlertDialogContent>
</AlertDialog>
</div>
</Show>
// Mobile Sort Menu

View File

@@ -6,7 +6,6 @@ pub enum BadgeVariant {
#[default]
Default,
Secondary,
Outline,
Destructive,
Success,
Warning,
@@ -22,7 +21,6 @@ pub fn Badge(
let variant_classes = match variant {
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::Outline => "text-foreground",
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::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
data-name="ContextMenuTrigger"
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 {
cb.run(());
}

View File

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

View File

@@ -94,8 +94,6 @@ pub fn DropdownMenuAction(
#[prop(optional, into)] class: String,
#[prop(optional, into)] href: Option<String>,
) -> impl IntoView {
let _ctx = expect_context::<DropdownMenuContext>();
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",
class
@@ -175,17 +173,15 @@ pub enum DropdownMenuAlign {
#[derive(Clone)]
struct DropdownMenuContext {
target_id: String,
align: DropdownMenuAlign,
}
#[component]
pub fn DropdownMenu(
children: Children,
#[prop(default = DropdownMenuAlign::default())] align: DropdownMenuAlign,
) -> impl IntoView {
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! {
<Provider value=ctx>
@@ -252,12 +248,13 @@ pub enum DropdownMenuPosition {
pub fn DropdownMenuContent(
children: Children,
#[prop(optional, into)] class: String,
#[prop(default = DropdownMenuAlign::default())] align: DropdownMenuAlign,
#[prop(default = DropdownMenuPosition::default())] position: DropdownMenuPosition,
) -> impl IntoView {
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 width_class = match ctx.align {
let width_class = match align {
DropdownMenuAlign::Center => "min-w-full",
_ => "w-[180px]",
};
@@ -265,7 +262,7 @@ pub fn DropdownMenuContent(
let class = tw_merge!(width_class, base_classes, class);
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::StartOuter => "start-outer",
DropdownMenuAlign::End => "end",
@@ -442,26 +439,6 @@ pub fn DropdownMenuContent(
trigger.addEventListener('click', (e) => {{
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
if (isOpen) {{
closeDropdown();
@@ -533,4 +510,4 @@ pub fn DropdownMenuSubItem(children: Children, #[prop(optional, into)] class: St
{children()}
</li>
}
}
}

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;
// * Reuse @select.rs
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)]

View File

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