diff --git a/frontend/index.html b/frontend/index.html
index 9d78b43..88844fa 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -26,10 +26,48 @@
if (t === "Light") t = "light";
if (t === "Dark" || t === "Midnight") t = "dark";
- document.documentElement.setAttribute("data-theme", t.toLowerCase());
+ var theme = t.toLowerCase();
+ document.documentElement.setAttribute("data-theme", theme);
if (!localTheme) {
localStorage.setItem("vibetorrent_theme", "Dark");
}
+
+ var meta = document.querySelector('meta[name="theme-color"]');
+ if (meta) {
+ var colorMap = {
+ "light": "#ffffff",
+ "cupcake": "#faf7f5",
+ "bumblebee": "#ffffff",
+ "emerald": "#ffffff",
+ "corporate": "#ffffff",
+ "synthwave": "#2d1b69",
+ "retro": "#ece3ca",
+ "cyberpunk": "#ffee00",
+ "valentine": "#f0d6e8",
+ "halloween": "#212121",
+ "garden": "#e9e7e7",
+ "forest": "#171212",
+ "aqua": "#345da7",
+ "lofi": "#ffffff",
+ "pastel": "#ffffff",
+ "fantasy": "#ffffff",
+ "wireframe": "#ffffff",
+ "black": "#000000",
+ "luxury": "#09090b",
+ "dracula": "#282a36",
+ "cmyk": "#ffffff",
+ "autumn": "#8C0327",
+ "business": "#202020",
+ "acid": "#fafafa",
+ "lemonade": "#F1F8E8",
+ "night": "#0f1729",
+ "coffee": "#20161f",
+ "winter": "#ffffff",
+ "dark": "#1d232a"
+ };
+ var color = colorMap[theme] || "#1d232a";
+ meta.setAttribute("content", color);
+ }
})();
diff --git a/frontend/manifest.json b/frontend/manifest.json
index 95064c0..9fad16c 100644
--- a/frontend/manifest.json
+++ b/frontend/manifest.json
@@ -3,8 +3,8 @@
"short_name": "VibeTorrent",
"start_url": "/",
"display": "standalone",
- "background_color": "#111827",
- "theme_color": "#000000",
+ "background_color": "#1d232a",
+ "theme_color": "#1d232a",
"orientation": "any",
"icons": [
{
diff --git a/frontend/public/tailwind.css b/frontend/public/tailwind.css
index d50bfae..f6be0df 100644
--- a/frontend/public/tailwind.css
+++ b/frontend/public/tailwind.css
@@ -1211,6 +1211,9 @@
.z-\[1\] {
z-index: 1;
}
+ .z-\[99\] {
+ z-index: 99;
+ }
.z-\[100\] {
z-index: 100;
}
@@ -1673,6 +1676,9 @@
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
+ .cursor-default {
+ cursor: default;
+ }
.cursor-pointer {
cursor: pointer;
}
@@ -1685,6 +1691,9 @@
.flex-col {
flex-direction: column;
}
+ .content-start {
+ align-content: flex-start;
+ }
.items-center {
align-items: center;
}
diff --git a/frontend/src/components/context_menu.rs b/frontend/src/components/context_menu.rs
index d24c16f..9018495 100644
--- a/frontend/src/components/context_menu.rs
+++ b/frontend/src/components/context_menu.rs
@@ -1,41 +1,3 @@
-use leptos::*;
-use leptos::html::Div;
-use wasm_bindgen::JsCast;
-
-pub fn use_click_outside(
- target: NodeRef
,
- callback: impl Fn() + Clone + 'static,
-) {
- create_effect(move |_| {
- if let Some(_) = target.get() {
- let handle_click = {
- let callback = callback.clone();
- let target = target.clone();
- move |ev: web_sys::MouseEvent| {
- if let Some(el) = target.get() {
- let ev_target = ev.target().unwrap().unchecked_into::
();
- let el_node = el.unchecked_ref::();
- if !el_node.contains(Some(&ev_target)) {
- callback();
- }
- }
- }
- };
-
- let window = web_sys::window().unwrap();
- let closure = wasm_bindgen::closure::Closure::::new(handle_click);
- let _ = window.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref());
-
- // Cleanup
- on_cleanup(move || {
- let window = web_sys::window().unwrap();
- let _ = window.remove_event_listener_with_callback("click", closure.as_ref().unchecked_ref());
- });
- }
- });
-}
-
-
#[component]
pub fn ContextMenu(
position: (i32, i32),
@@ -53,21 +15,19 @@ pub fn ContextMenu(
on_close.call(()); // Close menu AFTER
};
- let target = create_node_ref::();
-
- use_click_outside(target, move || {
- if visible {
- on_close.call(());
- }
- });
-
if !visible {
return view! {}.into_view();
}
view! {
+ // Backdrop to catch clicks outside
+
+
impl IntoView {
on:click=move |_| {
let doc = web_sys::window().unwrap().document().unwrap();
let _ = doc.document_element().unwrap().set_attribute("data-theme", theme);
+
+ // Update theme-color meta tag to match new theme
+ if let Some(meta) = doc.query_selector("meta[name='theme-color']").unwrap() {
+ let window = web_sys::window().unwrap();
+ // Force a style recalc by reading a property or just wait for next tick?
+ // Usually get_computed_style forces it.
+ if let Ok(Some(style)) = window.get_computed_style(&doc.body().unwrap()) {
+ if let Ok(color) = style.get_property_value("background-color") {
+ let _ = meta.set_attribute("content", &color);
+ }
+ }
+ }
}
>
{theme}
diff --git a/frontend/src/components/torrent/table.rs b/frontend/src/components/torrent/table.rs
index 2cedf26..fbe21ac 100644
--- a/frontend/src/components/torrent/table.rs
+++ b/frontend/src/components/torrent/table.rs
@@ -247,7 +247,7 @@ pub fn TorrentTable() -> impl IntoView {
-
+
{move || filtered_torrents().into_iter().map(|t| {
let progress_class = if t.percent_complete >= 100.0 { "progress-success" } else { "progress-primary" };
let status_str = format!("{:?}", t.status);
@@ -271,11 +271,12 @@ pub fn TorrentTable() -> impl IntoView {
class=move || {
let base = "card card-compact bg-base-100 shadow-sm border border-base-200 transition-transform active:scale-[0.99]";
if is_selected_fn() {
- format!("{} ring-2 ring-primary", base)
+ format!("{} ring-2 ring-primary select-none", base)
} else {
- base.to_string()
+ format!("{} select-none", base)
}
}
+ style="user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;"
on:contextmenu={
let t_hash = t_hash_ctx.clone();
move |e: web_sys::MouseEvent| handle_context_menu(e, t_hash.clone())