Compare commits
4 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9447a66cc1 | ||
|
|
45247a020e | ||
|
|
77b77c7775 | ||
|
|
8ef3008cb8 |
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@config "./tailwind.config.js";
|
@config "./tailwind.config.js";
|
||||||
|
@source "../src/**/*.rs";
|
||||||
|
@source "/Users/bilal/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/leptos-shadcn-*/src/**/*.rs";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
/* Shadcn Colors */
|
/* Shadcn Colors */
|
||||||
@@ -41,14 +43,17 @@
|
|||||||
from {
|
from {
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
height: var(--radix-accordion-content-height);
|
height: var(--radix-accordion-content-height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes accordion-up {
|
@keyframes accordion-up {
|
||||||
from {
|
from {
|
||||||
height: var(--radix-accordion-content-height);
|
height: var(--radix-accordion-content-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
@@ -123,17 +128,35 @@
|
|||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure Shadcn Utilities are always available */
|
/* Ensure Shadcn Utilities are always available */
|
||||||
.bg-popover { background-color: hsl(var(--popover)); }
|
.bg-popover {
|
||||||
.text-popover-foreground { color: hsl(var(--popover-foreground)); }
|
background-color: hsl(var(--popover));
|
||||||
.border-border { border-color: hsl(var(--border)); }
|
}
|
||||||
.shadow-md { box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); }
|
|
||||||
.z-50 { z-index: 50; }
|
.text-popover-foreground {
|
||||||
.z-100 { z-index: 100; }
|
color: hsl(var(--popover-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-border {
|
||||||
|
border-color: hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow-md {
|
||||||
|
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-50 {
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-100 {
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix for iOS click/blur events */
|
/* Fix for iOS click/blur events */
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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()>
|
||||||
|
|||||||
@@ -63,24 +63,35 @@ pub fn TorrentContextMenu(
|
|||||||
|
|
||||||
<Show when=move || open.get()>
|
<Show when=move || open.get()>
|
||||||
{
|
{
|
||||||
let (x, y) = position.get();
|
let (x, y) = position.get();
|
||||||
view! {
|
// Menü yaklaşık boyutları
|
||||||
<div
|
let menu_width = 200;
|
||||||
class="fixed inset-0 z-[99]"
|
let menu_height = 220;
|
||||||
on:click=move |e: MouseEvent| {
|
let window = web_sys::window().unwrap();
|
||||||
e.stop_propagation();
|
let vw = window.inner_width().unwrap().as_f64().unwrap() as i32;
|
||||||
open.set(false);
|
let vh = window.inner_height().unwrap().as_f64().unwrap() as i32;
|
||||||
}
|
// Sağa taşarsa sola aç, alta taşarsa yukarı aç
|
||||||
on:contextmenu=move |e: MouseEvent| {
|
let final_x = if x + menu_width > vw { x - menu_width } else { x };
|
||||||
e.prevent_default();
|
let final_y = if y + menu_height > vh { y - menu_height } else { y };
|
||||||
e.stop_propagation();
|
let final_x = final_x.max(0);
|
||||||
open.set(false);
|
let final_y = final_y.max(0);
|
||||||
}
|
view! {
|
||||||
/>
|
<div
|
||||||
<div
|
class="fixed inset-0 z-[99]"
|
||||||
class="fixed z-[100] min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95"
|
on:click=move |e: MouseEvent| {
|
||||||
style=format!("left: {}px; top: {}px;", x, y)
|
e.stop_propagation();
|
||||||
on:click=move |e: MouseEvent| e.stop_propagation()
|
open.set(false);
|
||||||
|
}
|
||||||
|
on:contextmenu=move |e: MouseEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
e.stop_propagation();
|
||||||
|
open.set(false);
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="fixed z-[100] min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95"
|
||||||
|
style=format!("left: {}px; top: {}px;", final_x, final_y)
|
||||||
|
on:click=move |e: MouseEvent| e.stop_propagation()
|
||||||
>
|
>
|
||||||
// Start
|
// Start
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -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,58 +50,76 @@ pub fn AddTorrentDialog(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let handle_backdrop = {
|
||||||
|
let on_close = on_close.clone();
|
||||||
|
move |e: web_sys::MouseEvent| {
|
||||||
|
e.stop_propagation();
|
||||||
|
on_close.run(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Dialog
|
// Backdrop overlay
|
||||||
open=Signal::derive(move || is_open.0.get())
|
<div
|
||||||
on_open_change=Callback::new(move |open: bool| {
|
class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
|
||||||
is_open.1.set(open);
|
on:click=handle_backdrop
|
||||||
if !open {
|
/>
|
||||||
on_close.run(());
|
// 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">
|
||||||
<DialogContent class="sm:max-w-[425px]">
|
<h2 class="text-lg font-semibold leading-none tracking-tight">"Add Torrent"</h2>
|
||||||
<DialogHeader>
|
<p class="text-sm text-muted-foreground">"Enter a Magnet link or a .torrent file URL."</p>
|
||||||
<DialogTitle>"Add Torrent"</DialogTitle>
|
</div>
|
||||||
<DialogDescription>"Enter a Magnet link or a .torrent file URL."</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<form on:submit=handle_submit class="space-y-4">
|
<form on:submit=handle_submit class="space-y-4">
|
||||||
<Input
|
<Input
|
||||||
input_type="text"
|
input_type="text"
|
||||||
placeholder="magnet:?xt=urn:btih:..."
|
placeholder="magnet:?xt=urn:btih:..."
|
||||||
value=MaybeProp::derive(move || Some(uri.0.get()))
|
value=MaybeProp::derive(move || Some(uri.0.get()))
|
||||||
on_change=Callback::new(move |val: String| uri.1.set(val))
|
on_change=Callback::new(move |val: String| uri.1.set(val))
|
||||||
disabled=Signal::derive(move || is_loading.0.get())
|
disabled=Signal::derive(move || is_loading.0.get())
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{move || error_msg.0.get().map(|msg| view! {
|
{move || error_msg.0.get().map(|msg| view! {
|
||||||
<Alert variant=AlertVariant::Destructive>
|
<Alert variant=AlertVariant::Destructive>
|
||||||
<AlertDescription>{msg}</AlertDescription>
|
<AlertDescription>{msg}</AlertDescription>
|
||||||
</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(());
|
})
|
||||||
|
>
|
||||||
|
"Cancel"
|
||||||
|
</Button>
|
||||||
|
<Button disabled=Signal::derive(move || is_loading.0.get())>
|
||||||
|
{move || if is_loading.0.get() {
|
||||||
|
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 {
|
||||||
"Cancel"
|
leptos::either::Either::Right(view! { "Add" })
|
||||||
</Button>
|
}}
|
||||||
<Button disabled=Signal::derive(move || is_loading.0.get())>
|
</Button>
|
||||||
{move || if is_loading.0.get() {
|
</div>
|
||||||
leptos::either::Either::Left(view! { <span class="loading loading-spinner"></span> "Adding..." })
|
</form>
|
||||||
} else {
|
|
||||||
leptos::either::Either::Right(view! { "Add" })
|
// Close button (X)
|
||||||
}}
|
<button
|
||||||
</Button>
|
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none"
|
||||||
</DialogFooter>
|
on:click=move |_| on_close.run(())
|
||||||
</form>
|
>
|
||||||
</DialogContent>
|
<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">
|
||||||
</Dialog>
|
<path d="M18 6 6 18"></path>
|
||||||
|
<path d="m6 6 12 12"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">"Close"</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const os = require("os");
|
||||||
|
|
||||||
|
// Cargo registry'deki leptos-shadcn crate'lerini Tailwind'e taratmak için
|
||||||
|
const cargoRegistry = path.join(
|
||||||
|
os.homedir(),
|
||||||
|
".cargo/registry/src/*/leptos-shadcn-*/src/**/*.rs"
|
||||||
|
);
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./index.html", "./src/**/*.{rs,html}"],
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{rs,html}",
|
||||||
|
cargoRegistry,
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|||||||
Reference in New Issue
Block a user