Compare commits

...

8 Commits

Author SHA1 Message Date
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
spinline
b29f9f3cc2 fix: align AlertDialog structure with project standards using AlertDialogBody
All checks were successful
Build MIPS Binary / build (push) Successful in 1m52s
2026-02-12 23:30:42 +03:00
spinline
feede5c5b4 fix: resolve compilation type error and cleanup unused imports in app.rs
All checks were successful
Build MIPS Binary / build (push) Successful in 1m50s
2026-02-12 23:26:45 +03:00
spinline
c1306a32a9 fix: use data-preload='false' and revert SW strategy to resolve browser warnings
Some checks failed
Build MIPS Binary / build (push) Failing after 44s
2026-02-12 23:23:40 +03:00
spinline
ed5fba4b46 fix: further refine alert dialog styling and button layout
Some checks failed
Build MIPS Binary / build (push) Failing after 34s
2026-02-12 23:22:07 +03:00
4 changed files with 147 additions and 145 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-no-preload /> <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,13 +1,12 @@
use crate::components::layout::protected::Protected; use crate::components::layout::protected::Protected;
use crate::components::ui::skeleton::Skeleton; use crate::components::ui::skeleton::Skeleton;
use crate::components::ui::card::{Card, CardHeader, CardContent};
use crate::components::torrent::table::TorrentTable; use crate::components::torrent::table::TorrentTable;
use crate::components::auth::login::Login; use crate::components::auth::login::Login;
use crate::components::auth::setup::Setup; use crate::components::auth::setup::Setup;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::task::spawn_local; use leptos::task::spawn_local;
use leptos_router::components::{Router, Routes, Route}; use leptos_router::components::{Router, Routes, Route};
use leptos_router::hooks::{use_navigate, use_location}; use leptos_router::hooks::use_navigate;
use crate::components::ui::toast::Toaster; use crate::components::ui::toast::Toaster;
use crate::components::hooks::use_theme_mode::ThemeMode; use crate::components::hooks::use_theme_mode::ThemeMode;
@@ -42,7 +41,6 @@ pub fn App() -> impl IntoView {
fn InnerApp() -> impl IntoView { fn InnerApp() -> impl IntoView {
crate::store::provide_torrent_store(); crate::store::provide_torrent_store();
let store = use_context::<crate::store::TorrentStore>(); let store = use_context::<crate::store::TorrentStore>();
let loc = use_location();
let is_loading = signal(true); let is_loading = signal(true);
let is_authenticated = signal(false); let is_authenticated = signal(false);
@@ -131,71 +129,71 @@ fn InnerApp() -> impl IntoView {
view! { <Setup /> } view! { <Setup /> }
} /> } />
<Route path=leptos_router::path!("/") view=move || { <Route path=leptos_router::path!("/") view=move || {
let navigate = use_navigate(); let navigate = use_navigate();
Effect::new(move |_| { Effect::new(move |_| {
if !is_loading.0.get() { if !is_loading.0.get() {
if needs_setup.0.get() { if needs_setup.0.get() {
log::info!("Setup not completed, redirecting to setup"); log::info!("Setup not completed, redirecting to setup");
navigate("/setup", Default::default()); navigate("/setup", Default::default());
} else if !is_authenticated.0.get() { } else if !is_authenticated.0.get() {
log::info!("Not authenticated, redirecting to login"); log::info!("Not authenticated, redirecting to login");
navigate("/login", Default::default()); navigate("/login", Default::default());
}
} }
}); }
});
view! {
<Show when=move || !is_loading.0.get() fallback=|| { view! {
// Standard 1: Always show Dashboard Skeleton <Show when=move || !is_loading.0.get() fallback=|| {
view! { // Standard 1: Always show Dashboard Skeleton
<div class="flex h-screen bg-background text-foreground overflow-hidden"> view! {
// Sidebar skeleton <div class="flex h-screen bg-background text-foreground overflow-hidden">
<div class="w-56 border-r border-border p-4 space-y-4"> // Sidebar skeleton
<Skeleton class="h-8 w-3/4" /> <div class="w-56 border-r border-border p-4 space-y-4">
<div class="space-y-2"> <Skeleton class="h-8 w-3/4" />
<Skeleton class="h-6 w-full" /> <div class="space-y-2">
<Skeleton class="h-6 w-full" /> <Skeleton class="h-6 w-full" />
<Skeleton class="h-6 w-4/5" /> <Skeleton class="h-6 w-full" />
<Skeleton class="h-6 w-full" /> <Skeleton class="h-6 w-4/5" />
<Skeleton class="h-6 w-3/5" /> <Skeleton class="h-6 w-full" />
<Skeleton class="h-6 w-full" /> <Skeleton class="h-6 w-3/5" />
</div> <Skeleton class="h-6 w-full" />
</div>
// Main content skeleton
<div class="flex-1 flex flex-col min-w-0">
<div class="border-b border-border p-4 flex items-center gap-4">
<Skeleton class="h-8 w-48" />
<Skeleton class="h-8 w-64" />
<div class="ml-auto"><Skeleton class="h-8 w-24" /></div>
</div>
<div class="flex-1 p-4 space-y-3">
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-3/4" />
</div>
<div class="border-t border-border p-3">
<Skeleton class="h-5 w-96" />
</div>
</div> </div>
</div> </div>
}.into_any() // Main content skeleton
}> <div class="flex-1 flex flex-col min-w-0">
<Show when=move || is_authenticated.0.get() fallback=|| ()> <div class="border-b border-border p-4 flex items-center gap-4">
<Protected> <Skeleton class="h-8 w-48" />
<div class="flex flex-col h-full overflow-hidden"> <Skeleton class="h-8 w-64" />
<div class="flex-1 overflow-hidden"> <div class="ml-auto"><Skeleton class="h-8 w-24" /></div>
<TorrentTable />
</div>
</div> </div>
</Protected> <div class="flex-1 p-4 space-y-3">
</Show> <Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-3/4" />
</div>
<div class="border-t border-border p-3">
<Skeleton class="h-5 w-96" />
</div>
</div>
</div>
}.into_any()
}>
<Show when=move || is_authenticated.0.get() fallback=|| ()>
<Protected>
<div class="flex flex-col h-full overflow-hidden">
<div class="flex-1 overflow-hidden">
<TorrentTable />
</div>
</div>
</Protected>
</Show> </Show>
}.into_any() </Show>
}/> }.into_any()
}/>
<Route path=leptos_router::path!("/settings") view=move || { <Route path=leptos_router::path!("/settings") view=move || {
let authenticated = is_authenticated.0.get(); let authenticated = is_authenticated.0.get();

View File

@@ -1,11 +1,13 @@
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};
use crate::api; use crate::api;
use shared::NotificationLevel; use shared::NotificationLevel;
use crate::components::context_menu::TorrentContextMenu; use crate::components::context_menu::TorrentContextMenu;
use crate::components::ui::card::{Card, CardHeader, CardTitle, CardContent as CardBody};
use crate::components::ui::data_table::*; use crate::components::ui::data_table::*;
use crate::components::ui::checkbox::Checkbox; use crate::components::ui::checkbox::Checkbox;
use crate::components::ui::badge::{Badge, BadgeVariant}; use crate::components::ui::badge::{Badge, BadgeVariant};
@@ -14,7 +16,17 @@ use crate::components::ui::empty::*;
use crate::components::ui::input::Input; use crate::components::ui::input::Input;
use crate::components::ui::multi_select::*; use crate::components::ui::multi_select::*;
use crate::components::ui::dropdown_menu::*; use crate::components::ui::dropdown_menu::*;
use crate::components::ui::alert_dialog::*; use crate::components::ui::alert_dialog::{
AlertDialog,
AlertDialogBody,
AlertDialogClose,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
};
use tailwind_fuse::tw_merge; use tailwind_fuse::tw_merge;
const ALL_COLUMNS: [(&str, &str); 8] = [ const ALL_COLUMNS: [(&str, &str); 8] = [
@@ -220,68 +232,78 @@ 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()>
<DropdownMenu> <div class="flex items-center gap-2">
<DropdownMenuTrigger class="w-[140px] h-9 gap-2"> <DropdownMenu>
<Ellipsis class="size-4" /> <DropdownMenuTrigger class="w-[140px] h-9 gap-2">
{move || format!("Toplu İşlem ({})", selected_count.get())} <Ellipsis class="size-4" />
</DropdownMenuTrigger> {move || format!("Toplu İşlem ({})", selected_count.get())}
<DropdownMenuContent class="w-48"> </DropdownMenuTrigger>
<DropdownMenuLabel>"Seçili Torrentler"</DropdownMenuLabel> <DropdownMenuContent class="w-48">
<DropdownMenuGroup class="mt-2"> <DropdownMenuLabel>"Seçili Torrentler"</DropdownMenuLabel>
<DropdownMenuItem on:click=move |_| bulk_action("start")> <DropdownMenuGroup class="mt-2">
<Play class="mr-2 size-4" /> "Başlat" <DropdownMenuItem on:click=move |_| bulk_action("start")>
</DropdownMenuItem> <Play class="mr-2 size-4" /> "Başlat"
<DropdownMenuItem on:click=move |_| bulk_action("stop")> </DropdownMenuItem>
<Square class="mr-2 size-4" /> "Durdur" <DropdownMenuItem on:click=move |_| bulk_action("stop")>
</DropdownMenuItem> <Square class="mr-2 size-4" /> "Durdur"
</DropdownMenuItem>
<div class="my-1 h-px bg-border" />
<div class="my-1 h-px bg-border" />
<AlertDialog>
<AlertDialogTrigger class="w-full text-left"> // Trigger the hidden AlertDialog from this menu item
<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"> <DropdownMenuItem class="text-destructive focus:bg-destructive/10 cursor-pointer" on:click=move |_| {
<Trash2 class="size-4" /> "Toplu Sil..." if let Some(trigger) = document().get_element_by_id("bulk-delete-trigger") {
</div> let _ = trigger.dyn_into::<web_sys::HtmlElement>().map(|el: web_sys::HtmlElement| el.click());
</AlertDialogTrigger> }
<AlertDialogContent> }>
<AlertDialogHeader> <Trash2 class="mr-2 size-4" /> "Toplu Sil..."
<AlertDialogTitle class="text-destructive flex items-center gap-2"> </DropdownMenuItem>
<Trash2 class="size-5" /> </DropdownMenuGroup>
"Toplu Silme Onayı" </DropdownMenuContent>
</AlertDialogTitle> </DropdownMenu>
<AlertDialogDescription class="pt-2">
{move || format!("Seçili {} adet torrent silinecek. Lütfen silme yöntemini seçin:", selected_count.get())} // Hidden AlertDialog moved outside the DropdownMenuContent to ensure proper centering
<div class="mt-4 p-3 bg-muted/50 rounded-md text-xs border border-border italic"> <AlertDialog>
"Dikkat: Verilerle birlikte silme işlemi dosyaları diskten de kalıcı olarak kaldıracaktır." <AlertDialogTrigger attr:id="bulk-delete-trigger" class="hidden">""</AlertDialogTrigger>
</div> <AlertDialogContent class="sm:max-w-[425px]">
</AlertDialogDescription> <AlertDialogBody>
</AlertDialogHeader> <AlertDialogHeader class="space-y-3">
<AlertDialogFooter> <AlertDialogTitle class="text-destructive flex items-center gap-2 text-xl">
<div class="flex flex-col-reverse sm:flex-row gap-2 w-full sm:justify-end mt-4 sm:mt-0"> <Trash2 class="size-6" />
<AlertDialogClose class="w-full sm:w-auto">"Vazgeç"</AlertDialogClose> "Toplu Silme Onayı"
<div class="flex flex-col sm:flex-row gap-2"> </AlertDialogTitle>
<Button <AlertDialogDescription class="text-sm leading-relaxed text-left">
variant=ButtonVariant::Outline {move || format!("Seçili {} adet torrent silinecek. Lütfen silme yöntemini seçin:", selected_count.get())}
class="w-full sm:w-auto text-foreground" <div class="mt-4 p-4 bg-destructive/5 rounded-lg border border-destructive/10 text-xs text-destructive/80 font-medium">
on:click=move |_| bulk_action("delete") "⚠️ Dikkat: Verilerle birlikte silme işlemi dosyaları diskten de kalıcı olarak kaldıracaktır."
>
"Sadece Listeden Sil"
</Button>
<Button
variant=ButtonVariant::Destructive
class="w-full sm:w-auto"
on:click=move |_| bulk_action("delete_with_data")
>
"Verilerle Birlikte Sil"
</Button>
</div>
</div> </div>
</AlertDialogFooter> </AlertDialogDescription>
</AlertDialogContent> </AlertDialogHeader>
</AlertDialog> <AlertDialogFooter class="mt-6">
</DropdownMenuGroup> <div class="flex flex-col-reverse sm:flex-row gap-3 w-full sm:justify-end">
</DropdownMenuContent> <AlertDialogClose class="sm:flex-1 md:flex-none">"Vazgeç"</AlertDialogClose>
</DropdownMenu> <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>
</div>
</Show> </Show>
// Mobile Sort Menu // Mobile Sort Menu

View File

@@ -88,25 +88,7 @@ self.addEventListener("fetch", (event) => {
return; return;
} }
// Special strategy for WASM and Main JS to prevent Preload warnings // Cache-first strategy for static assets (JS, CSS, Images)
if (url.pathname.endsWith(".wasm") || (url.pathname.endsWith(".js") && url.pathname.includes("frontend-"))) {
event.respondWith(
fetch(event.request)
.then((response) => {
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
})
.catch(() => {
return caches.match(event.request);
}),
);
return;
}
// Cache-first strategy for other static assets (CSS, Images, etc.)
event.respondWith( event.respondWith(
caches.match(event.request).then((response) => { caches.match(event.request).then((response) => {
return ( return (