Compare commits
11 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3735d0931 | ||
|
|
55f00729ee | ||
|
|
275f4a91b2 | ||
|
|
025a0c4a57 | ||
|
|
b29f9f3cc2 | ||
|
|
feede5c5b4 | ||
|
|
c1306a32a9 | ||
|
|
ed5fba4b46 | ||
|
|
f149603ac8 | ||
|
|
89ad42f24d | ||
|
|
bec804131b |
@@ -22,12 +22,32 @@ jobs:
|
||||
git fetch --depth=1 origin ${{ gitea.sha }}
|
||||
git checkout FETCH_HEAD
|
||||
|
||||
- name: Cache Cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Cache Node Modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: frontend/node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('frontend/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Build Frontend
|
||||
run: |
|
||||
cd frontend
|
||||
npm install
|
||||
npx @tailwindcss/cli -i input.css -o public/tailwind.css --minify --content './src/**/*.rs'
|
||||
# Trunk'ın optimizasyonunu kapalı (0) tutuyoruz çünkü Cargo.toml'daki opt-level='z' zaten o işi yapıyor.
|
||||
trunk build --release
|
||||
|
||||
- name: Build Backend (MIPS)
|
||||
|
||||
@@ -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-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="copy-file" href="manifest.json" />
|
||||
<link data-trunk rel="copy-file" href="icon-192.png" />
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::components::layout::protected::Protected;
|
||||
use crate::components::ui::skeleton::Skeleton;
|
||||
use crate::components::ui::card::{Card, CardHeader, CardContent};
|
||||
use crate::components::torrent::table::TorrentTable;
|
||||
use crate::components::auth::login::Login;
|
||||
use crate::components::auth::setup::Setup;
|
||||
use leptos::prelude::*;
|
||||
use leptos::task::spawn_local;
|
||||
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::hooks::use_theme_mode::ThemeMode;
|
||||
|
||||
@@ -42,7 +41,6 @@ pub fn App() -> impl IntoView {
|
||||
fn InnerApp() -> impl IntoView {
|
||||
crate::store::provide_torrent_store();
|
||||
let store = use_context::<crate::store::TorrentStore>();
|
||||
let loc = use_location();
|
||||
|
||||
let is_loading = signal(true);
|
||||
let is_authenticated = signal(false);
|
||||
@@ -146,70 +144,43 @@ fn InnerApp() -> impl IntoView {
|
||||
});
|
||||
|
||||
view! {
|
||||
<Show when=move || !is_loading.0.get() fallback=move || {
|
||||
let path = loc.pathname.get();
|
||||
if path == "/login" {
|
||||
// Login Skeleton
|
||||
view! {
|
||||
<div class="flex items-center justify-center min-h-screen bg-muted/40 px-4">
|
||||
<Card class="w-full max-w-sm shadow-lg border-none">
|
||||
<CardHeader class="pb-2 items-center space-y-4">
|
||||
<Skeleton class="w-12 h-12 rounded-xl" />
|
||||
<Skeleton class="h-8 w-32" />
|
||||
<Skeleton class="h-4 w-48" />
|
||||
</CardHeader>
|
||||
<CardContent class="pt-4 space-y-6">
|
||||
<div class="space-y-2">
|
||||
<Skeleton class="h-4 w-24" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Skeleton class="h-4 w-24" />
|
||||
<Skeleton class="h-10 w-full" />
|
||||
</div>
|
||||
<Skeleton class="h-10 w-full rounded-md mt-4" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
}.into_any()
|
||||
} else {
|
||||
// Dashboard Skeleton
|
||||
view! {
|
||||
<div class="flex h-screen bg-background">
|
||||
// Sidebar skeleton
|
||||
<div class="w-56 border-r border-border p-4 space-y-4">
|
||||
<Skeleton class="h-8 w-3/4" />
|
||||
<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-3/5" />
|
||||
<Skeleton class="h-6 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
// Main content skeleton
|
||||
<div class="flex-1 flex flex-col">
|
||||
<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>
|
||||
<Show when=move || !is_loading.0.get() fallback=|| {
|
||||
// Standard 1: Always show Dashboard Skeleton
|
||||
view! {
|
||||
<div class="flex h-screen bg-background text-foreground overflow-hidden">
|
||||
// Sidebar skeleton
|
||||
<div class="w-56 border-r border-border p-4 space-y-4">
|
||||
<Skeleton class="h-8 w-3/4" />
|
||||
<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-3/5" />
|
||||
<Skeleton class="h-6 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
}.into_any()
|
||||
}
|
||||
// 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>
|
||||
}.into_any()
|
||||
}>
|
||||
<Show when=move || is_authenticated.0.get() fallback=|| ()>
|
||||
<Protected>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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};
|
||||
use crate::api;
|
||||
use shared::NotificationLevel;
|
||||
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::checkbox::Checkbox;
|
||||
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::multi_select::*;
|
||||
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;
|
||||
|
||||
const ALL_COLUMNS: [(&str, &str); 8] = [
|
||||
@@ -220,66 +232,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">
|
||||
<Trash2 class="size-4" /> "Toplu Sil..."
|
||||
</div>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle class="text-destructive flex items-center gap-2">
|
||||
<Trash2 class="size-5" />
|
||||
"Toplu Silme Onayı"
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription class="pt-2">
|
||||
{move || format!("Seçili {} adet torrent silinecek. Lütfen silme yöntemini seçin:", selected_count.get())}
|
||||
<div class="mt-4 p-3 bg-muted/50 rounded-md text-xs border border-border italic">
|
||||
"Dikkat: Verilerle birlikte silme işlemi dosyaları diskten de kalıcı olarak kaldıracaktır."
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter class="gap-2 sm:gap-0">
|
||||
<div class="flex flex-col sm:flex-row gap-2 w-full justify-end">
|
||||
<AlertDialogClose class="order-3 sm:order-1">"Vazgeç"</AlertDialogClose>
|
||||
<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::Outline
|
||||
class="order-2 text-foreground"
|
||||
variant=ButtonVariant::Secondary
|
||||
class="w-full sm:w-auto font-medium"
|
||||
on:click=move |_| bulk_action("delete")
|
||||
>
|
||||
"Sadece Listeden Sil"
|
||||
"Sadece Sil"
|
||||
</Button>
|
||||
<Button
|
||||
variant=ButtonVariant::Destructive
|
||||
class="order-1"
|
||||
class="w-full sm:w-auto font-bold"
|
||||
on:click=move |_| bulk_action("delete_with_data")
|
||||
>
|
||||
"Verilerle Birlikte Sil"
|
||||
"Verilerle Sil"
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogBody>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
// Mobile Sort Menu
|
||||
|
||||
@@ -88,25 +88,7 @@ self.addEventListener("fetch", (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special strategy for WASM and Main JS to prevent Preload warnings
|
||||
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.)
|
||||
// Cache-first strategy for static assets (JS, CSS, Images)
|
||||
event.respondWith(
|
||||
caches.match(event.request).then((response) => {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user