Compare commits
2 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e1356eae | ||
|
|
98b1f059c7 |
@@ -6,7 +6,7 @@ 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 leptos_router::hooks::{use_navigate, use_location};
|
||||||
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;
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ 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);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use icons::PanelLeft;
|
use icons::{PanelLeft, Plus};
|
||||||
use crate::components::torrent::add_torrent::AddTorrentDialog;
|
use crate::components::torrent::add_torrent::AddTorrentDialogContent;
|
||||||
use crate::components::ui::button::{Button, ButtonVariant, ButtonSize};
|
use crate::components::ui::button::{ButtonVariant, ButtonSize};
|
||||||
use crate::components::ui::sheet::{Sheet, SheetContent, SheetTrigger, SheetDirection};
|
use crate::components::ui::sheet::{Sheet, SheetContent, SheetTrigger, SheetDirection};
|
||||||
|
use crate::components::ui::dialog::{Dialog, DialogContent, DialogTrigger};
|
||||||
use crate::components::layout::sidebar::Sidebar;
|
use crate::components::layout::sidebar::Sidebar;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Toolbar() -> impl IntoView {
|
pub fn Toolbar() -> impl IntoView {
|
||||||
let show_add_modal = signal(false);
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="flex min-h-14 h-auto items-center border-b border-border bg-background px-4" style="padding-top: env(safe-area-inset-top);">
|
<div class="flex min-h-14 h-auto items-center border-b border-border bg-background px-4" style="padding-top: env(safe-area-inset-top);">
|
||||||
// Sol kısım: Menü butonu (Mobil) + Add Torrent
|
// Sol kısım: Menü butonu (Mobil) + Add Torrent
|
||||||
@@ -33,25 +32,24 @@ pub fn Toolbar() -> impl IntoView {
|
|||||||
</Sheet>
|
</Sheet>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Dialog>
|
||||||
on:click=move |_| show_add_modal.1.set(true)
|
<DialogTrigger
|
||||||
class="gap-2"
|
variant=ButtonVariant::Default
|
||||||
>
|
class="gap-2"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 md:w-5 md:h-5">
|
>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
<Plus class="w-4 h-4 md:w-5 md:h-5" />
|
||||||
</svg>
|
<span class="hidden sm:inline">"Add Torrent"</span>
|
||||||
<span class="hidden sm:inline">"Add Torrent"</span>
|
<span class="sm:hidden">"Add"</span>
|
||||||
<span class="sm:hidden">"Add"</span>
|
</DialogTrigger>
|
||||||
</Button>
|
<DialogContent id="add-torrent-dialog" class="sm:max-w-[425px]">
|
||||||
|
<AddTorrentDialogContent />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Sağ kısım boş
|
// Sağ kısım boş
|
||||||
<div class="flex flex-1 items-center justify-end gap-2">
|
<div class="flex flex-1 items-center justify-end gap-2">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when=move || show_add_modal.0.get()>
|
|
||||||
<AddTorrentDialog on_close=Callback::new(move |()| show_add_modal.1.set(false)) />
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos::task::spawn_local;
|
use leptos::task::spawn_local;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
use crate::components::ui::input::{Input, InputType};
|
use crate::components::ui::input::{Input, InputType};
|
||||||
use crate::store::TorrentStore;
|
|
||||||
use crate::api;
|
use crate::api;
|
||||||
|
use crate::components::ui::button::Button;
|
||||||
use crate::components::ui::button::{Button, ButtonVariant};
|
use crate::components::ui::dialog::{
|
||||||
|
DialogBody, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose
|
||||||
|
};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn AddTorrentDialog(
|
pub fn AddTorrentDialogContent() -> impl IntoView {
|
||||||
on_close: Callback<()>,
|
|
||||||
) -> impl IntoView {
|
|
||||||
let _store = use_context::<TorrentStore>().expect("TorrentStore not provided");
|
|
||||||
|
|
||||||
let uri = RwSignal::new(String::new());
|
let uri = RwSignal::new(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);
|
||||||
@@ -21,20 +19,30 @@ pub fn AddTorrentDialog(
|
|||||||
let uri_val = uri.get();
|
let uri_val = uri.get();
|
||||||
|
|
||||||
if uri_val.is_empty() {
|
if uri_val.is_empty() {
|
||||||
error_msg.1.set(Some("Please enter a Magnet URI or URL".to_string()));
|
error_msg.1.set(Some("Lütfen bir Magnet URI veya URL girin".to_string()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_loading.1.set(true);
|
is_loading.1.set(true);
|
||||||
error_msg.1.set(None);
|
error_msg.1.set(None);
|
||||||
|
|
||||||
let on_close = on_close.clone();
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
match api::torrent::add(&uri_val).await {
|
match api::torrent::add(&uri_val).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!("Torrent added successfully");
|
log::info!("Torrent added successfully");
|
||||||
crate::store::toast_success("Torrent başarıyla eklendi");
|
crate::store::toast_success("Torrent başarıyla eklendi");
|
||||||
on_close.run(());
|
|
||||||
|
// Programmatically close the dialog by triggering the close button
|
||||||
|
if let Some(doc) = web_sys::window().and_then(|w| w.document()) {
|
||||||
|
if let Some(el) = doc.get_element_by_id("add-torrent-dialog") {
|
||||||
|
if let Some(close_btn) = el.query_selector("[data-dialog-close]").ok().flatten() {
|
||||||
|
let _ = close_btn.dyn_into::<web_sys::HtmlElement>().map(|btn| btn.click());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uri.set(String::new());
|
||||||
|
is_loading.1.set(false);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to add torrent: {:?}", e);
|
log::error!("Failed to add torrent: {:?}", e);
|
||||||
@@ -45,29 +53,16 @@ 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! {
|
||||||
// Backdrop overlay
|
<DialogBody>
|
||||||
<div
|
<DialogHeader>
|
||||||
class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
|
<DialogTitle>"Add Torrent"</DialogTitle>
|
||||||
on:click=handle_backdrop
|
<DialogDescription>
|
||||||
/>
|
"Enter a Magnet link or a .torrent file URL."
|
||||||
// Dialog panel
|
</DialogDescription>
|
||||||
<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]">
|
</DialogHeader>
|
||||||
// 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 pt-4">
|
||||||
<Input
|
<Input
|
||||||
r#type=InputType::Text
|
r#type=InputType::Text
|
||||||
placeholder="magnet:?xt=urn:btih:..."
|
placeholder="magnet:?xt=urn:btih:..."
|
||||||
@@ -81,14 +76,10 @@ pub fn AddTorrentDialog(
|
|||||||
</div>
|
</div>
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
|
<DialogFooter class="pt-2">
|
||||||
<Button
|
<DialogClose>
|
||||||
variant=ButtonVariant::Ghost
|
|
||||||
attr:r#type="button"
|
|
||||||
on:click=move |_| on_close.run(())
|
|
||||||
>
|
|
||||||
"Cancel"
|
"Cancel"
|
||||||
</Button>
|
</DialogClose>
|
||||||
<Button
|
<Button
|
||||||
attr:r#type="submit"
|
attr:r#type="submit"
|
||||||
attr:disabled=move || is_loading.0.get()
|
attr:disabled=move || is_loading.0.get()
|
||||||
@@ -102,21 +93,8 @@ pub fn AddTorrentDialog(
|
|||||||
leptos::either::Either::Right(view! { "Add" })
|
leptos::either::Either::Right(view! { "Add" })
|
||||||
}}
|
}}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
|
</DialogBody>
|
||||||
// Close button (X)
|
|
||||||
<Button
|
|
||||||
variant=ButtonVariant::Ghost
|
|
||||||
class="absolute right-2 top-2 size-8 p-0 opacity-70 hover:opacity-100"
|
|
||||||
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>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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};
|
||||||
|
|||||||
@@ -72,13 +72,13 @@ pub fn DialogTrigger(
|
|||||||
pub fn DialogContent(
|
pub fn DialogContent(
|
||||||
children: Children,
|
children: Children,
|
||||||
#[prop(optional, into)] class: String,
|
#[prop(optional, into)] class: String,
|
||||||
|
#[prop(optional, into)] id: Option<String>,
|
||||||
#[prop(into, optional)] hide_close_button: Option<bool>,
|
#[prop(into, optional)] hide_close_button: Option<bool>,
|
||||||
#[prop(default = true)] close_on_backdrop_click: bool,
|
#[prop(default = true)] close_on_backdrop_click: bool,
|
||||||
#[prop(default = "Dialog")] data_name_prefix: &'static str,
|
#[prop(default = "Dialog")] data_name_prefix: &'static str,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let ctx = expect_context::<DialogContext>();
|
let ctx = expect_context::<DialogContext>();
|
||||||
let merged_class = tw_merge!(
|
let merged_class = tw_merge!(
|
||||||
// "flex flex-col gap-4", // TODO 🐛 Bug when I try to have this.. Using DialogBody instead.
|
|
||||||
"relative bg-background border rounded-2xl shadow-lg p-6 w-full max-w-[calc(100%-2rem)] max-h-[85vh] fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-100 transition-all duration-200 data-[state=closed]:opacity-0 data-[state=closed]:scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100",
|
"relative bg-background border rounded-2xl shadow-lg p-6 w-full max-w-[calc(100%-2rem)] max-h-[85vh] fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-100 transition-all duration-200 data-[state=closed]:opacity-0 data-[state=closed]:scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100",
|
||||||
class
|
class
|
||||||
);
|
);
|
||||||
@@ -88,10 +88,14 @@ pub fn DialogContent(
|
|||||||
|
|
||||||
let target_id_clone = ctx.target_id.clone();
|
let target_id_clone = ctx.target_id.clone();
|
||||||
let backdrop_id = format!("{}_backdrop", ctx.target_id);
|
let backdrop_id = format!("{}_backdrop", ctx.target_id);
|
||||||
let target_id_for_script = ctx.target_id.clone();
|
|
||||||
let backdrop_id_for_script = backdrop_id.clone();
|
let backdrop_id_for_script = backdrop_id.clone();
|
||||||
let backdrop_behavior = if close_on_backdrop_click { "auto" } else { "manual" };
|
let backdrop_behavior = if close_on_backdrop_click { "auto" } else { "manual" };
|
||||||
|
|
||||||
|
// Use provided id or fallback to random target_id
|
||||||
|
let final_id = id.unwrap_or_else(|| ctx.target_id.clone());
|
||||||
|
let final_id_for_script = final_id.clone();
|
||||||
|
let trigger_id_for_script = ctx.target_id.clone();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<script src="/lock_scroll.js"></script>
|
<script src="/lock_scroll.js"></script>
|
||||||
|
|
||||||
@@ -105,7 +109,7 @@ pub fn DialogContent(
|
|||||||
<div
|
<div
|
||||||
data-name=content_data_name
|
data-name=content_data_name
|
||||||
class=merged_class
|
class=merged_class
|
||||||
id=ctx.target_id
|
id=final_id
|
||||||
data-target="target__dialog"
|
data-target="target__dialog"
|
||||||
data-state="closed"
|
data-state="closed"
|
||||||
data-backdrop=backdrop_behavior
|
data-backdrop=backdrop_behavior
|
||||||
@@ -147,9 +151,7 @@ pub fn DialogContent(
|
|||||||
dialog.setAttribute('data-initialized', 'true');
|
dialog.setAttribute('data-initialized', 'true');
|
||||||
|
|
||||||
const openDialog = () => {{
|
const openDialog = () => {{
|
||||||
// Lock scrolling
|
if (window.ScrollLock) window.ScrollLock.lock();
|
||||||
window.ScrollLock.lock();
|
|
||||||
|
|
||||||
dialog.setAttribute('data-state', 'open');
|
dialog.setAttribute('data-state', 'open');
|
||||||
backdrop.setAttribute('data-state', 'open');
|
backdrop.setAttribute('data-state', 'open');
|
||||||
dialog.style.pointerEvents = 'auto';
|
dialog.style.pointerEvents = 'auto';
|
||||||
@@ -161,28 +163,18 @@ pub fn DialogContent(
|
|||||||
backdrop.setAttribute('data-state', 'closed');
|
backdrop.setAttribute('data-state', 'closed');
|
||||||
dialog.style.pointerEvents = 'none';
|
dialog.style.pointerEvents = 'none';
|
||||||
backdrop.style.pointerEvents = 'none';
|
backdrop.style.pointerEvents = 'none';
|
||||||
|
|
||||||
// Unlock scrolling after animation
|
|
||||||
window.ScrollLock.unlock(200);
|
window.ScrollLock.unlock(200);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Open dialog when trigger is clicked
|
|
||||||
trigger.addEventListener('click', openDialog);
|
trigger.addEventListener('click', openDialog);
|
||||||
|
dialog.querySelectorAll('[data-dialog-close]').forEach(btn => {{
|
||||||
// Close buttons
|
|
||||||
const closeButtons = dialog.querySelectorAll('[data-dialog-close]');
|
|
||||||
closeButtons.forEach(btn => {{
|
|
||||||
btn.addEventListener('click', closeDialog);
|
btn.addEventListener('click', closeDialog);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Close on backdrop click (if data-backdrop="auto")
|
|
||||||
backdrop.addEventListener('click', () => {{
|
backdrop.addEventListener('click', () => {{
|
||||||
if (dialog.getAttribute('data-backdrop') === 'auto') {{
|
if (dialog.getAttribute('data-backdrop') === 'auto') {{
|
||||||
closeDialog();
|
closeDialog();
|
||||||
}}
|
}}
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Handle ESC key to close
|
|
||||||
document.addEventListener('keydown', (e) => {{
|
document.addEventListener('keydown', (e) => {{
|
||||||
if (e.key === 'Escape' && dialog.getAttribute('data-state') === 'open') {{
|
if (e.key === 'Escape' && dialog.getAttribute('data-state') === 'open') {{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -190,17 +182,12 @@ pub fn DialogContent(
|
|||||||
}}
|
}}
|
||||||
}});
|
}});
|
||||||
}};
|
}};
|
||||||
|
setupDialog();
|
||||||
if (document.readyState === 'loading') {{
|
|
||||||
document.addEventListener('DOMContentLoaded', setupDialog);
|
|
||||||
}} else {{
|
|
||||||
setupDialog();
|
|
||||||
}}
|
|
||||||
}})();
|
}})();
|
||||||
"#,
|
"#,
|
||||||
target_id_for_script,
|
final_id_for_script,
|
||||||
backdrop_id_for_script,
|
backdrop_id_for_script,
|
||||||
target_id_for_script,
|
trigger_id_for_script,
|
||||||
)}
|
)}
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user