refactor(frontend): rewrite with Thaw UI components and inline modal
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
pub mod modal;
|
||||
pub mod context_menu;
|
||||
pub mod ui;
|
||||
pub mod toolbar;
|
||||
pub mod sidebar;
|
||||
pub mod status_bar;
|
||||
pub mod torrent_table;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ pub fn Modal(
|
||||
children: Children,
|
||||
#[prop(into)] on_confirm: Callback<()>,
|
||||
#[prop(into)] on_cancel: Callback<()>,
|
||||
#[prop(into)] visible: Signal<bool>,
|
||||
#[prop(into)] is_open: MaybeSignal<bool>,
|
||||
#[prop(into, default = "Confirm".to_string())] confirm_text: String,
|
||||
#[prop(into, default = "Cancel".to_string())] cancel_text: String,
|
||||
#[prop(into, default = false)] is_danger: bool,
|
||||
@@ -20,7 +20,7 @@ pub fn Modal(
|
||||
let cancel_text = store_value(cancel_text);
|
||||
|
||||
view! {
|
||||
<Show when=move || visible.get() fallback=|| ()>
|
||||
<Show when=move || is_open.get() fallback=|| ()>
|
||||
<div class="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-end md:items-center justify-center z-[200] animate-in fade-in duration-200 sm:p-4">
|
||||
<div class="bg-card p-6 rounded-t-2xl md:rounded-lg w-full max-w-sm shadow-xl border border-border ring-0 transform transition-all animate-in slide-in-from-bottom-10 md:slide-in-from-bottom-0 md:zoom-in-95">
|
||||
<h3 class="text-lg font-semibold text-card-foreground mb-4">{title.get_value()}</h3>
|
||||
|
||||
52
frontend/src/components/sidebar.rs
Normal file
52
frontend/src/components/sidebar.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use leptos::*;
|
||||
use thaw::*;
|
||||
use shared::TorrentStatus;
|
||||
|
||||
#[component]
|
||||
pub fn Sidebar(
|
||||
#[prop(into)] active_filter: Signal<Option<TorrentStatus>>,
|
||||
#[prop(into)] on_filter_change: Callback<Option<TorrentStatus>>,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<div class="w-64 border-r border-border bg-card/30 flex flex-col">
|
||||
<div class="p-4 font-bold text-lg">"Groups"</div>
|
||||
<div class="flex-1 overflow-y-auto p-2 space-y-1">
|
||||
<Button
|
||||
variant=if active_filter.get().is_none() { ButtonVariant::Primary } else { ButtonVariant::Text }
|
||||
class="w-full justify-start text-left"
|
||||
on_click=move |_| on_filter_change.call(None)
|
||||
>
|
||||
"All"
|
||||
</Button>
|
||||
<Button
|
||||
variant=if active_filter.get() == Some(TorrentStatus::Downloading) { ButtonVariant::Primary } else { ButtonVariant::Text }
|
||||
class="w-full justify-start text-left"
|
||||
on_click=move |_| on_filter_change.call(Some(TorrentStatus::Downloading))
|
||||
>
|
||||
"Downloading"
|
||||
</Button>
|
||||
<Button
|
||||
variant=if active_filter.get() == Some(TorrentStatus::Seeding) { ButtonVariant::Primary } else { ButtonVariant::Text }
|
||||
class="w-full justify-start text-left"
|
||||
on_click=move |_| on_filter_change.call(Some(TorrentStatus::Seeding))
|
||||
>
|
||||
"Seeding"
|
||||
</Button>
|
||||
<Button
|
||||
variant=if active_filter.get() == Some(TorrentStatus::Paused) { ButtonVariant::Primary } else { ButtonVariant::Text }
|
||||
class="w-full justify-start text-left"
|
||||
on_click=move |_| on_filter_change.call(Some(TorrentStatus::Paused))
|
||||
>
|
||||
"Paused"
|
||||
</Button>
|
||||
<Button
|
||||
variant=if active_filter.get() == Some(TorrentStatus::Error) { ButtonVariant::Primary } else { ButtonVariant::Text }
|
||||
class="w-full justify-start text-left"
|
||||
on_click=move |_| on_filter_change.call(Some(TorrentStatus::Error))
|
||||
>
|
||||
"Errors"
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
20
frontend/src/components/status_bar.rs
Normal file
20
frontend/src/components/status_bar.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use leptos::*;
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn StatusBar() -> impl IntoView {
|
||||
view! {
|
||||
<div class="h-8 border-t border-border bg-card/30 flex items-center px-4 text-xs space-x-4">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="i-mdi-arrow-down text-green-500"></span>
|
||||
"0 KB/s"
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="i-mdi-arrow-up text-blue-500"></span>
|
||||
"0 KB/s"
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div>"Free Space: 700 GB"</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
34
frontend/src/components/toolbar.rs
Normal file
34
frontend/src/components/toolbar.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use leptos::*;
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn Toolbar(
|
||||
#[prop(into)] on_add: Callback<()>,
|
||||
#[prop(into)] on_start: Callback<()>,
|
||||
#[prop(into)] on_pause: Callback<()>,
|
||||
#[prop(into)] on_delete: Callback<()>,
|
||||
#[prop(into)] on_settings: Callback<()>,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<div class="flex items-center gap-2 p-2 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<Button variant=ButtonVariant::Text on_click=move |_| on_add.call(())>
|
||||
<span class="i-mdi-plus mr-2"/> "Add"
|
||||
</Button>
|
||||
<div class="h-4 w-px bg-border mx-2"></div>
|
||||
<Button variant=ButtonVariant::Text on_click=move |_| on_start.call(())>
|
||||
<span class="i-mdi-play mr-2"/> "Start"
|
||||
</Button>
|
||||
<Button variant=ButtonVariant::Text on_click=move |_| on_pause.call(())>
|
||||
<span class="i-mdi-pause mr-2"/> "Pause"
|
||||
</Button>
|
||||
<Button variant=ButtonVariant::Text color=ButtonColor::Error on_click=move |_| on_delete.call(())>
|
||||
<span class="i-mdi-delete mr-2"/> "Delete"
|
||||
</Button>
|
||||
<div class="flex-1"></div>
|
||||
<Input placeholder="Filter..." class="w-48" />
|
||||
<Button variant=ButtonVariant::Text on_click=move |_| on_settings.call(())>
|
||||
<span class="i-mdi-cog mr-2"/> "Settings"
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
51
frontend/src/components/torrent_table.rs
Normal file
51
frontend/src/components/torrent_table.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use leptos::*;
|
||||
use thaw::*;
|
||||
use shared::Torrent;
|
||||
|
||||
#[component]
|
||||
pub fn TorrentTable(
|
||||
#[prop(into)] torrents: Signal<Vec<Torrent>>
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<div class="flex-1 overflow-auto bg-background">
|
||||
<table class="w-full text-left text-xs">
|
||||
<thead class="bg-muted/50 border-b border-border text-muted-foreground font-medium sticky top-0 bg-background z-10">
|
||||
<tr>
|
||||
<th class="px-2 py-1.5 font-medium">"Name"</th>
|
||||
<th class="px-2 py-1.5 font-medium w-20 text-right">"Size"</th>
|
||||
<th class="px-2 py-1.5 font-medium w-24">"Progress"</th>
|
||||
<th class="px-2 py-1.5 font-medium w-20 text-center">"Status"</th>
|
||||
<th class="px-2 py-1.5 font-medium w-20 text-right">"Down"</th>
|
||||
<th class="px-2 py-1.5 font-medium w-20 text-right">"Up"</th>
|
||||
<th class="px-2 py-1.5 font-medium w-20 text-right">"ETA"</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-border">
|
||||
<For
|
||||
each=move || torrents.get()
|
||||
key=|t| t.hash.clone()
|
||||
children=move |torrent| {
|
||||
view! {
|
||||
<tr class="hover:bg-muted/50 group transition-colors">
|
||||
<td class="px-2 py-1.5 truncate max-w-[200px]">{torrent.name}</td>
|
||||
<td class="px-2 py-1.5 text-right whitespace-nowrap text-muted-foreground">{torrent.size}</td>
|
||||
<td class="px-2 py-1.5">
|
||||
<Progress percentage=torrent.percent_complete as f32 />
|
||||
</td>
|
||||
<td class="px-2 py-1.5 text-center">
|
||||
<span class="text-[10px] px-1.5 py-0.5 rounded-full border border-border bg-background">
|
||||
{format!("{:?}", torrent.status)}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-1.5 text-right whitespace-nowrap text-blue-500">{torrent.down_rate}</td>
|
||||
<td class="px-2 py-1.5 text-right whitespace-nowrap text-green-500">{torrent.up_rate}</td>
|
||||
<td class="px-2 py-1.5 text-right whitespace-nowrap text-muted-foreground">{torrent.eta}</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
use leptos::*;
|
||||
use crate::utils::cn;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub enum ButtonVariant {
|
||||
#[default]
|
||||
Default,
|
||||
Destructive,
|
||||
Outline,
|
||||
Secondary,
|
||||
Ghost,
|
||||
Link,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub enum ButtonSize {
|
||||
#[default]
|
||||
Default,
|
||||
Sm,
|
||||
Lg,
|
||||
Icon,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Button(
|
||||
#[prop(into, optional)] variant: ButtonVariant,
|
||||
#[prop(into, optional)] size: ButtonSize,
|
||||
#[prop(into, optional)] class: MaybeSignal<String>,
|
||||
#[prop(into, optional)] on_click: Option<Callback<web_sys::MouseEvent>>,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
let variant_classes = match variant {
|
||||
ButtonVariant::Default => "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
ButtonVariant::Destructive => "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
ButtonVariant::Outline => "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
ButtonVariant::Secondary => "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ButtonVariant::Ghost => "hover:bg-accent hover:text-accent-foreground",
|
||||
ButtonVariant::Link => "text-primary underline-offset-4 hover:underline",
|
||||
};
|
||||
|
||||
let size_classes = match size {
|
||||
ButtonSize::Default => "h-10 px-4 py-2",
|
||||
ButtonSize::Sm => "h-9 rounded-md px-3",
|
||||
ButtonSize::Lg => "h-11 rounded-md px-8",
|
||||
ButtonSize::Icon => "h-10 w-10",
|
||||
};
|
||||
|
||||
let base_classes = "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
||||
|
||||
view! {
|
||||
<button
|
||||
class=move || cn(format!("{} {} {} {}", base_classes, variant_classes, size_classes, class.get()))
|
||||
on:click=move |e| {
|
||||
if let Some(cb) = on_click {
|
||||
cb.call(e);
|
||||
}
|
||||
}
|
||||
>
|
||||
{children()}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod button;
|
||||
Reference in New Issue
Block a user