Compare commits
4 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3792e78e0 | ||
|
|
384165a958 | ||
|
|
7169e44f4e | ||
|
|
51fb85c2d8 |
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1, 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>
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body style="cursor: pointer;">
|
||||||
<div
|
<div
|
||||||
id="app-loading"
|
id="app-loading"
|
||||||
style="
|
style="
|
||||||
@@ -142,6 +142,19 @@
|
|||||||
body.app-loaded #app-loading {
|
body.app-loaded #app-loading {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* iOS Safari Click Fixes */
|
||||||
|
body {
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -174,6 +187,17 @@
|
|||||||
|
|
||||||
<!-- Service Worker Registration & PWA Setup -->
|
<!-- Service Worker Registration & PWA Setup -->
|
||||||
<script>
|
<script>
|
||||||
|
// Global Dropdown Closer for iOS/Mobile
|
||||||
|
document.addEventListener('click', function(event) {
|
||||||
|
const details = document.querySelectorAll('details[open]');
|
||||||
|
details.forEach(detail => {
|
||||||
|
// Eğer tıklanan yer bu details'in içinde değilse kapat
|
||||||
|
if (!detail.contains(event.target)) {
|
||||||
|
detail.removeAttribute('open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, true); // Use capture phase for better mobile support
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ pub fn StatusBar() -> impl IntoView {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Refs for click outside detection
|
// Refs for click outside detection (Handled globally via JS in index.html for better iOS support)
|
||||||
let down_details_ref = create_node_ref::<html::Details>();
|
let down_details_ref = create_node_ref::<html::Details>();
|
||||||
let up_details_ref = create_node_ref::<html::Details>();
|
let up_details_ref = create_node_ref::<html::Details>();
|
||||||
let theme_details_ref = create_node_ref::<html::Details>();
|
let theme_details_ref = create_node_ref::<html::Details>();
|
||||||
@@ -109,16 +109,12 @@ pub fn StatusBar() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = on_click_outside(down_details_ref, move |_| close_details(down_details_ref));
|
|
||||||
let _ = on_click_outside(up_details_ref, move |_| close_details(up_details_ref));
|
|
||||||
let _ = on_click_outside(theme_details_ref, move |_| close_details(theme_details_ref));
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="fixed bottom-0 left-0 right-0 h-8 min-h-8 bg-base-200 border-t border-base-300 flex items-center px-4 text-xs gap-4 text-base-content/70 z-[99]">
|
<div class="fixed bottom-0 left-0 right-0 h-8 min-h-8 bg-base-200 border-t border-base-300 flex items-center px-4 text-xs gap-4 text-base-content/70 z-[99] cursor-pointer">
|
||||||
|
|
||||||
// --- DOWNLOAD SPEED DROPDOWN ---
|
// --- DOWNLOAD SPEED DROPDOWN ---
|
||||||
<details class="dropdown dropdown-top" node_ref=down_details_ref>
|
<details class="dropdown dropdown-top" node_ref=down_details_ref>
|
||||||
<summary class="flex items-center gap-2 cursor-pointer hover:text-primary transition-colors select-none list-none marker:hidden">
|
<summary class="flex items-center gap-2 cursor-pointer hover:text-primary transition-colors select-none list-none [&::-webkit-details-marker]:hidden outline-none">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -160,7 +156,7 @@ pub fn StatusBar() -> impl IntoView {
|
|||||||
|
|
||||||
// --- UPLOAD SPEED DROPDOWN ---
|
// --- UPLOAD SPEED DROPDOWN ---
|
||||||
<details class="dropdown dropdown-top" node_ref=up_details_ref>
|
<details class="dropdown dropdown-top" node_ref=up_details_ref>
|
||||||
<summary class="flex items-center gap-2 cursor-pointer hover:text-primary transition-colors select-none list-none marker:hidden">
|
<summary class="flex items-center gap-2 cursor-pointer hover:text-primary transition-colors select-none list-none [&::-webkit-details-marker]:hidden outline-none">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5L12 3m0 0l7.5 7.5M12 3v18" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5L12 3m0 0l7.5 7.5M12 3v18" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -202,7 +198,7 @@ pub fn StatusBar() -> impl IntoView {
|
|||||||
|
|
||||||
<div class="ml-auto flex items-center gap-4">
|
<div class="ml-auto flex items-center gap-4">
|
||||||
<details class="dropdown dropdown-top dropdown-end" node_ref=theme_details_ref>
|
<details class="dropdown dropdown-top dropdown-end" node_ref=theme_details_ref>
|
||||||
<summary class="btn btn-ghost btn-xs btn-square">
|
<summary class="btn btn-ghost btn-xs btn-square cursor-pointer outline-none list-none [&::-webkit-details-marker]:hidden">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -164,10 +164,13 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Signal-based sort dropdown for mobile
|
// Refs for click outside detection
|
||||||
let (sort_open, set_sort_open) = create_signal(false);
|
let sort_details_ref = create_node_ref::<html::Details>();
|
||||||
let sort_menu_ref = create_node_ref::<html::Div>();
|
let _ = on_click_outside(sort_details_ref, move |_| {
|
||||||
let _ = on_click_outside(sort_menu_ref, move |_| set_sort_open.set(false));
|
if let Some(el) = sort_details_ref.get_untracked() {
|
||||||
|
el.set_open(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let sort_arrow = move |col: SortColumn| {
|
let sort_arrow = move |col: SortColumn| {
|
||||||
if sort_col.get() == col {
|
if sort_col.get() == col {
|
||||||
@@ -342,28 +345,18 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:hidden flex flex-col h-full bg-base-200 relative">
|
<div class="md:hidden flex flex-col h-full bg-base-200 relative cursor-pointer">
|
||||||
<div class="px-3 py-2 border-b border-base-200 flex justify-between items-center bg-base-100/95 backdrop-blur z-10 shrink-0">
|
<div class="px-3 py-2 border-b border-base-200 flex justify-between items-center bg-base-100/95 backdrop-blur z-10 shrink-0 cursor-default">
|
||||||
<span class="text-xs font-bold opacity-50 uppercase tracking-wider">"Torrents"</span>
|
<span class="text-xs font-bold opacity-50 uppercase tracking-wider">"Torrents"</span>
|
||||||
|
|
||||||
<div class="relative" node_ref=sort_menu_ref>
|
<details class="dropdown dropdown-end" node_ref=sort_details_ref>
|
||||||
<div
|
<summary class="btn btn-ghost btn-xs gap-1 opacity-70 font-normal list-none [&::-webkit-details-marker]:hidden cursor-pointer">
|
||||||
role="button"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 pointer-events-none">
|
||||||
class="btn btn-ghost btn-xs gap-1 opacity-70 font-normal"
|
|
||||||
on:click=move |_| {
|
|
||||||
let cur = sort_open.get_untracked();
|
|
||||||
set_sort_open.set(!cur);
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5" />
|
||||||
</svg>
|
</svg>
|
||||||
"Sort"
|
<span class="pointer-events-none">"Sort"</span>
|
||||||
</div>
|
</summary>
|
||||||
<ul
|
<ul class="dropdown-content z-[100] menu p-2 shadow bg-base-100 rounded-box w-48 mt-1 border border-base-200 text-xs cursor-default">
|
||||||
class="absolute top-full right-0 z-[100] menu p-2 shadow bg-base-100 rounded-box w-48 mt-1 border border-base-200 text-xs"
|
|
||||||
style=move || if sort_open.get() { "display: block" } else { "display: none" }
|
|
||||||
>
|
|
||||||
<li class="menu-title px-2 py-1 opacity-50 text-[10px] uppercase font-bold">"Sort By"</li>
|
<li class="menu-title px-2 py-1 opacity-50 text-[10px] uppercase font-bold">"Sort By"</li>
|
||||||
{
|
{
|
||||||
let columns = vec![
|
let columns = vec![
|
||||||
@@ -388,7 +381,9 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
class=move || if is_active() { "bg-primary/10 text-primary font-bold flex justify-between" } else { "flex justify-between" }
|
class=move || if is_active() { "bg-primary/10 text-primary font-bold flex justify-between" } else { "flex justify-between" }
|
||||||
on:click=move |_| {
|
on:click=move |_| {
|
||||||
handle_sort(col);
|
handle_sort(col);
|
||||||
set_sort_open.set(false);
|
if let Some(el) = sort_details_ref.get_untracked() {
|
||||||
|
el.set_open(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@@ -406,10 +401,10 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
}).collect::<Vec<_>>()
|
}).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-y-auto p-3 pb-20 flex-1 grid grid-cols-1 content-start gap-3"> {move || filtered_torrents().into_iter().map(|t| {
|
<div class="overflow-y-auto p-3 pb-20 flex-1 grid grid-cols-1 content-start gap-3 cursor-pointer"> {move || filtered_torrents().into_iter().map(|t| {
|
||||||
let progress_class = if t.percent_complete >= 100.0 { "progress-success" } else { "progress-primary" };
|
let progress_class = if t.percent_complete >= 100.0 { "progress-success" } else { "progress-primary" };
|
||||||
let status_str = format!("{:?}", t.status);
|
let status_str = format!("{:?}", t.status);
|
||||||
let status_badge_class = match t.status {
|
let status_badge_class = match t.status {
|
||||||
@@ -464,7 +459,7 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
view! {
|
view! {
|
||||||
<div
|
<div
|
||||||
class=move || {
|
class=move || {
|
||||||
"card card-compact bg-base-100 shadow-sm border border-base-200 transition-transform active:scale-[0.99] select-none"
|
"card card-compact bg-base-100 shadow-sm border border-base-200 transition-transform active:scale-[0.99] select-none cursor-pointer"
|
||||||
}
|
}
|
||||||
style="user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;"
|
style="user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;"
|
||||||
on:contextmenu={
|
on:contextmenu={
|
||||||
|
|||||||
Reference in New Issue
Block a user