Compare commits

...

4 Commits

Author SHA1 Message Date
spinline
87ddd3bb93 fix: iOS Dark Mode ve Tema Değişimi Düzeltildi
All checks were successful
Build MIPS Binary / build (push) Successful in 5m17s
- tailwind.config.js: darkMode: 'class' olarak ayarlandı (iOS sistem tercihi yerine .dark class kullanımı için)
- statusbar.rs: Tema değişiminde .dark class ekleme mantığı tüm dark temaları kapsayacak şekilde genişletildi
- index.html: Sayfa yüklenirken .dark class ekleyen inline script güncellendi
- public/tailwind.css: PostCSS ile yeniden derlendi (nesting düzleştirildi + .dark seçiciler eklendi)
2026-02-11 01:00:04 +03:00
spinline
463249982c fix: iOS Safari uyumluluk - CSS nesting düzleştirildi
All checks were successful
Build MIPS Binary / build (push) Successful in 5m16s
- PostCSS tabanlı build'e geçildi (@tailwindcss/postcss + postcss-preset-env)
- CSS native nesting (&) düzleştirilerek eski Safari desteği sağlandı
- iOS 15+ ve Safari 15+ desteği eklendi
2026-02-11 00:54:44 +03:00
spinline
9447a66cc1 feat: loading ekranı shadcn Skeleton ile değiştirildi
All checks were successful
Build MIPS Binary / build (push) Successful in 5m16s
- Yükleniyor... spinner yerine uygulamanın layout'unu simüle eden skeleton UI
- Sidebar, header, tablo satırları ve statusbar skeleton'ları
2026-02-11 00:43:05 +03:00
spinline
45247a020e fix: AddTorrent dialog stili düzeltildi, skeleton crate eklendi
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
- Dialog buggy leptos-shadcn-dialog yerine doğrudan shadcn HTML markup ile yeniden yazıldı
- Backdrop overlay, card panel, X close butonu eklendi
- leptos-shadcn-skeleton dependency eklendi
- Tailwind CSS rebuild edildi
2026-02-11 00:40:39 +03:00
12 changed files with 6722 additions and 2867 deletions

16
Cargo.lock generated
View File

@@ -1274,6 +1274,7 @@ dependencies = [
"leptos-shadcn-scroll-area", "leptos-shadcn-scroll-area",
"leptos-shadcn-separator", "leptos-shadcn-separator",
"leptos-shadcn-sheet", "leptos-shadcn-sheet",
"leptos-shadcn-skeleton",
"leptos-shadcn-tabs", "leptos-shadcn-tabs",
"leptos-shadcn-toast", "leptos-shadcn-toast",
"leptos-shadcn-tooltip", "leptos-shadcn-tooltip",
@@ -2390,6 +2391,21 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "leptos-shadcn-skeleton"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c14b6bd0f2fe191e3e114a34cee889fc983546ad488e76e76511e3d75ea3f86"
dependencies = [
"leptos",
"leptos-node-ref",
"leptos-shadcn-signal-management",
"leptos-struct-component",
"leptos-style",
"tailwind_fuse",
"web-sys",
]
[[package]] [[package]]
name = "leptos-shadcn-tabs" name = "leptos-shadcn-tabs"
version = "0.8.1" version = "0.8.1"

1
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -52,3 +52,4 @@ leptos-shadcn-alert = "0.8"
leptos-shadcn-toast = "0.8" leptos-shadcn-toast = "0.8"
leptos-shadcn-dropdown-menu = "0.8" leptos-shadcn-dropdown-menu = "0.8"
leptos-shadcn-tooltip = "0.8" leptos-shadcn-tooltip = "0.8"
leptos-shadcn-skeleton = "0.8"

View File

@@ -1,11 +1,10 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta <meta name="viewport"
name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<title>VibeTorrent</title> <title>VibeTorrent</title>
<!-- PWA & Mobile Capable --> <!-- PWA & Mobile Capable -->
@@ -37,6 +36,13 @@
var theme = t.toLowerCase(); var theme = t.toLowerCase();
document.documentElement.setAttribute("data-theme", theme); document.documentElement.setAttribute("data-theme", theme);
// Shadcn dark mode CSS değişkenleri .dark class ile çalışıyor
var darkThemes = ["dark", "black", "night", "coffee", "luxury", "business", "dracula", "halloween", "forest", "synthwave", "dim", "nord", "sunset", "cyberpunk", "abyss"];
if (darkThemes.indexOf(theme) !== -1) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
if (!localTheme) { if (!localTheme) {
localStorage.setItem("vibetorrent_theme", "dark"); localStorage.setItem("vibetorrent_theme", "dark");
} }
@@ -82,20 +88,15 @@
</head> </head>
<body style="cursor: pointer;"> <body style="cursor: pointer;">
<div <div id="app-loading" style="
id="app-loading"
style="
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh;
font-family: sans-serif; font-family: sans-serif;
" ">
> <div id="app-loading-spinner" style="
<div
id="app-loading-spinner"
style="
width: 40px; width: 40px;
height: 40px; height: 40px;
border: 3px solid currentColor; border: 3px solid currentColor;
@@ -103,21 +104,15 @@
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
opacity: 0.5; opacity: 0.5;
" "></div>
></div> <div id="app-loading-error" style="display: none; text-align: center; margin-top: 20px; padding: 0 20px">
<div
id="app-loading-error"
style="display: none; text-align: center; margin-top: 20px; padding: 0 20px"
>
<p style="color: #ef4444; font-weight: bold; margin-bottom: 8px"> <p style="color: #ef4444; font-weight: bold; margin-bottom: 8px">
Uygulama yüklenemedi Uygulama yüklenemedi
</p> </p>
<p style="font-size: 14px; opacity: 0.7"> <p style="font-size: 14px; opacity: 0.7">
Bağlantınız yavaş olabilir veya bir sistem hatası oluşmuş olabilir. Bağlantınız yavaş olabilir veya bir sistem hatası oluşmuş olabilir.
</p> </p>
<button <button onclick="location.reload()" style="
onclick="location.reload()"
style="
margin-top: 16px; margin-top: 16px;
padding: 8px 16px; padding: 8px 16px;
background: #3b82f6; background: #3b82f6;
@@ -126,8 +121,7 @@
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
" ">
>
Sayfayı Yenile Sayfayı Yenile
</button> </button>
</div> </div>
@@ -152,6 +146,7 @@
summary { summary {
list-style: none; list-style: none;
} }
summary::-webkit-details-marker { summary::-webkit-details-marker {
display: none; display: none;
} }
@@ -226,4 +221,5 @@
} }
</script> </script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,11 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.18",
"autoprefixer": "^10.4.23", "autoprefixer": "^10.4.23",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"postcss-preset-env": "^11.1.3",
"tailwindcss": "^4.1.18" "tailwindcss": "^4.1.18"
}, },
"dependencies": { "dependencies": {

View File

@@ -0,0 +1,15 @@
module.exports = {
plugins: {
"@tailwindcss/postcss": {},
"postcss-preset-env": {
features: {
"nesting-rules": true,
},
browsers: [
"last 2 versions",
"iOS >= 15",
"Safari >= 15",
],
},
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ 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 leptos_router::hooks::use_navigate;
use leptos_shadcn_skeleton::Skeleton;
#[component] #[component]
pub fn App() -> impl IntoView { pub fn App() -> impl IntoView {
@@ -116,10 +117,40 @@ pub fn App() -> impl IntoView {
view! { view! {
<Show when=move || !is_loading.0.get() fallback=|| view! { <Show when=move || !is_loading.0.get() fallback=|| view! {
<div class="flex items-center justify-center h-screen bg-background"> <div class="flex h-screen bg-background">
<div class="flex flex-col items-center gap-4"> // Sidebar skeleton
<div class="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div> <div class="w-56 border-r border-border p-4 space-y-4">
<p class="text-sm text-muted-foreground">"Yükleniyor..."</p> <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">
// Header skeleton
<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>
// Table skeleton rows
<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>
// Status bar skeleton
<div class="border-t border-border p-3">
<Skeleton class="h-5 w-96" />
</div>
</div> </div>
</div> </div>
}.into_any()> }.into_any()>

View File

@@ -44,7 +44,7 @@ pub fn StatusBar() -> impl IntoView {
if let Some(doc) = document().document_element() { if let Some(doc) = document().document_element() {
let _ = doc.set_attribute("data-theme", &theme); let _ = doc.set_attribute("data-theme", &theme);
// Also set class for Shadcn dark mode support // Also set class for Shadcn dark mode support
if theme == "dark" || theme == "dracula" || theme == "dim" || theme == "abyss" { if theme == "dark" || theme == "dracula" || theme == "dim" || theme == "abyss" || theme == "sunset" || theme == "cyberpunk" || theme == "nord" || theme == "business" || theme == "night" || theme == "black" || theme == "luxury" || theme == "coffee" || theme == "forest" || theme == "halloween" || theme == "synthwave" {
let _ = doc.class_list().add_1("dark"); let _ = doc.class_list().add_1("dark");
} else { } else {
let _ = doc.class_list().remove_1("dark"); let _ = doc.class_list().remove_1("dark");

View File

@@ -1,6 +1,5 @@
use leptos::prelude::*; use leptos::prelude::*;
use leptos::task::spawn_local; use leptos::task::spawn_local;
use leptos_shadcn_dialog::{Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter};
use leptos_shadcn_input::Input; use leptos_shadcn_input::Input;
use leptos_shadcn_button::{Button, ButtonVariant}; use leptos_shadcn_button::{Button, ButtonVariant};
use leptos_shadcn_alert::{Alert, AlertDescription, AlertVariant}; use leptos_shadcn_alert::{Alert, AlertDescription, AlertVariant};
@@ -17,7 +16,6 @@ pub fn AddTorrentDialog(
let uri = signal(String::new()); let uri = signal(String::new());
let is_loading = signal(false); let is_loading = signal(false);
let error_msg = signal(Option::<String>::None); let error_msg = signal(Option::<String>::None);
let is_open = signal(true);
let handle_submit = move |ev: web_sys::SubmitEvent| { let handle_submit = move |ev: web_sys::SubmitEvent| {
ev.prevent_default(); ev.prevent_default();
@@ -41,7 +39,6 @@ pub fn AddTorrentDialog(
shared::NotificationLevel::Success, shared::NotificationLevel::Success,
"Torrent başarıyla eklendi" "Torrent başarıyla eklendi"
); );
is_open.1.set(false);
on_close.run(()); on_close.run(());
} }
Err(e) => { Err(e) => {
@@ -53,22 +50,27 @@ pub fn AddTorrentDialog(
}); });
}; };
let handle_backdrop = {
view! { let on_close = on_close.clone();
<Dialog move |e: web_sys::MouseEvent| {
open=Signal::derive(move || is_open.0.get()) e.stop_propagation();
on_open_change=Callback::new(move |open: bool| {
is_open.1.set(open);
if !open {
on_close.run(()); on_close.run(());
} }
}) };
>
<DialogContent class="sm:max-w-[425px]"> view! {
<DialogHeader> // Backdrop overlay
<DialogTitle>"Add Torrent"</DialogTitle> <div
<DialogDescription>"Enter a Magnet link or a .torrent file URL."</DialogDescription> class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
</DialogHeader> on:click=handle_backdrop
/>
// Dialog panel
<div class="fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-card p-6 shadow-lg rounded-lg sm:max-w-[425px]">
// Header
<div class="flex flex-col space-y-1.5 text-center sm:text-left">
<h2 class="text-lg font-semibold leading-none tracking-tight">"Add Torrent"</h2>
<p class="text-sm text-muted-foreground">"Enter a Magnet link or a .torrent file URL."</p>
</div>
<form on:submit=handle_submit class="space-y-4"> <form on:submit=handle_submit class="space-y-4">
<Input <Input
@@ -85,11 +87,10 @@ pub fn AddTorrentDialog(
</Alert> </Alert>
})} })}
<DialogFooter> <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
<Button <Button
variant=ButtonVariant::Ghost variant=ButtonVariant::Ghost
on_click=Callback::new(move |()| { on_click=Callback::new(move |()| {
is_open.1.set(false);
on_close.run(()); on_close.run(());
}) })
> >
@@ -97,14 +98,28 @@ pub fn AddTorrentDialog(
</Button> </Button>
<Button disabled=Signal::derive(move || is_loading.0.get())> <Button disabled=Signal::derive(move || is_loading.0.get())>
{move || if is_loading.0.get() { {move || if is_loading.0.get() {
leptos::either::Either::Left(view! { <span class="loading loading-spinner"></span> "Adding..." }) leptos::either::Either::Left(view! {
<span class="animate-spin mr-2 h-4 w-4 border-2 border-current border-t-transparent rounded-full"></span>
"Adding..."
})
} else { } else {
leptos::either::Either::Right(view! { "Add" }) leptos::either::Either::Right(view! { "Add" })
}} }}
</Button> </Button>
</DialogFooter> </div>
</form> </form>
</DialogContent>
</Dialog> // Close button (X)
<button
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none"
on:click=move |_| on_close.run(())
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
<span class="sr-only">"Close"</span>
</button>
</div>
} }
} }

View File

@@ -9,6 +9,7 @@ const cargoRegistry = path.join(
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: "class",
content: [ content: [
"./index.html", "./index.html",
"./src/**/*.{rs,html}", "./src/**/*.{rs,html}",