WIP ContextMenu Renderer

This commit is contained in:
2025-08-09 17:14:28 -04:00
parent 364b23e62a
commit 6df6de098b
5 changed files with 94 additions and 46 deletions

View File

@@ -161,6 +161,7 @@ form {
.contextMenu { .contextMenu {
backdrop-filter: blur(10px) brightness(0.5) grayscale(1); backdrop-filter: blur(10px) brightness(0.5) grayscale(1);
position: absolute; position: absolute;
z-index: 100;
.itemList { .itemList {
display: flex; display: flex;

View File

@@ -2,15 +2,58 @@ use core::str;
use dioxus::prelude::*; use dioxus::prelude::*;
#[derive(PartialEq, Clone, Props)] pub mod props {
pub struct ContextMenuProps { use dioxus::prelude::*;
#[derive(PartialEq, Clone, Props)]
pub struct ContextMenu {
pub top: f64, pub top: f64,
pub left: f64, pub left: f64,
pub items: Option<Vec<Element>>, pub items: Option<Vec<ContextMenuItem>>,
}
#[derive(PartialEq, Clone, Props, Default)]
pub struct ContextMenuItem {
pub name: String,
pub sub_items: Option<Vec<ContextMenuItem>>,
}
}
#[derive(Clone, Copy, Default)]
pub struct ContextMenuRenderer {
pub menu: Signal<Option<props::ContextMenu>>,
}
impl ContextMenuRenderer {
pub fn close(&mut self) {
self.menu.set(None);
}
pub fn render(&self) -> Element {
if let Some(menu) = self.menu.cloned() {
rsx! {
ContextMenu{
items: menu.items,
left: menu.left,
top: menu.top
}
}
} else {
rsx! {}
}
}
} }
#[component] #[component]
pub fn ContextMenu(props: ContextMenuProps) -> Element { pub fn ContextMenuRoot() -> Element {
let renderer = use_context::<ContextMenuRenderer>();
rsx! {
{renderer.render()}
}
}
#[component]
fn ContextMenu(props: props::ContextMenu) -> Element {
let menu_items = if let Some(items) = props.items { let menu_items = if let Some(items) = props.items {
rsx! { rsx! {
ItemList { items } ItemList { items }
@@ -29,23 +72,22 @@ pub fn ContextMenu(props: ContextMenuProps) -> Element {
} }
#[component] #[component]
fn ItemList(items: Vec<Element>) -> Element { fn ItemList(items: Vec<props::ContextMenuItem>) -> Element {
rsx! { rsx! {
div{ div{
class: "itemList", class: "itemList",
{items.iter().map(|e| rsx!{{e}}) } {items.iter().map(|e| rsx!{
ContextMenuItem{
name: e.name.clone(),
sub_items: e.sub_items.clone()
}
})}
} }
} }
} }
#[derive(PartialEq, Clone, Props)]
pub struct ContextMenuItemProps {
pub name: String,
pub sub_items: Option<Vec<Element>>,
}
#[component] #[component]
pub fn ContextMenuItem(props: ContextMenuItemProps) -> Element { fn ContextMenuItem(props: props::ContextMenuItem) -> Element {
rsx! { rsx! {
div{ div{
class: "contextItem", class: "contextItem",

View File

@@ -2,7 +2,10 @@ use dioxus::prelude::*;
use tonic::IntoRequest; use tonic::IntoRequest;
use crate::{ use crate::{
components::{ContextMenu, ContextMenuItem, MediaItem}, components::{
ContextMenuRenderer, MediaItem,
props::{ContextMenu, ContextMenuItem},
},
rpc::{aoba::PageFilter, get_rpc_client}, rpc::{aoba::PageFilter, get_rpc_client},
}; };
@@ -46,36 +49,32 @@ pub fn MediaGrid(props: MediaGridProps) -> Element {
} }
})); }));
let mut context_menu: Signal<Element> = use_signal(|| rsx! {}); let mut ct_renderer = use_context::<ContextMenuRenderer>();
let oncontext = move |event: Event<MouseData>| { let oncontext = move |event: Event<MouseData>| {
event.prevent_default(); event.prevent_default();
let data = event.data(); let data = event.data();
let pos = data.coordinates().client(); let pos = data.coordinates().client();
let left = pos.x; let left = pos.x;
let top = pos.y; let top = pos.y;
context_menu.set(rsx! { ct_renderer.menu.set(Some(ContextMenu {
ContextMenu{
left: left, left: left,
top: top, top: top,
items: vec![ items: Some(vec![
rsx!{ ContextMenuItem {
ContextMenuItem{ name: "Details".to_string(),
name: "Details" ..Default::default()
}
}, },
rsx!{ ContextMenuItem {
ContextMenuItem{ name: "Download".to_string(),
name: "Download" ..Default::default()
}
}, },
rsx!{ ContextMenuItem {
ContextMenuItem{ name: "Delete".to_string(),
name: "Delete" ..Default::default()
}
}, },
] ]),
} }));
});
}; };
match media_result.cloned() { match media_result.cloned() {
@@ -83,14 +82,10 @@ pub fn MediaGrid(props: MediaGridProps) -> Element {
Ok(result) => rsx! { Ok(result) => rsx! {
div { div {
class: "mediaGrid", class: "mediaGrid",
onclick: move |_e| {
context_menu.set(rsx!{});
},
{result.items.iter().map(|itm| rsx!{ {result.items.iter().map(|itm| rsx!{
MediaItem { item: Some(itm.clone()), oncontextmenu: oncontext } MediaItem { item: Some(itm.clone()), oncontextmenu: oncontext }
})}, })},
} }
{context_menu.cloned()}
}, },
Err(msg) => rsx! { Err(msg) => rsx! {
div { div {

View File

@@ -1,6 +1,11 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::{Route, components::Navbar, contexts::AuthContext, views::Login}; use crate::{
Route,
components::{ContextMenuRoot, Navbar},
contexts::AuthContext,
views::Login,
};
#[component] #[component]
pub fn MainLayout() -> Element { pub fn MainLayout() -> Element {
@@ -13,6 +18,7 @@ pub fn MainLayout() -> Element {
} }
return rsx! { return rsx! {
ContextMenuRoot { }
Navbar {} Navbar {}
div { id: "content", Outlet::<Route> {} } div { id: "content", Outlet::<Route> {} }
}; };

View File

@@ -11,6 +11,8 @@ use contexts::AuthContext;
use dioxus::prelude::*; use dioxus::prelude::*;
use route::Route; use route::Route;
use crate::components::ContextMenuRenderer;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub const HOST: &'static str = "http://localhost:8081"; pub const HOST: &'static str = "http://localhost:8081";
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@@ -31,6 +33,7 @@ fn main() {
#[component] #[component]
fn App() -> Element { fn App() -> Element {
let _auth_state = use_context_provider(|| AuthContext::new()); let _auth_state = use_context_provider(|| AuthContext::new());
let _renderer = use_context_provider(|| ContextMenuRenderer::default());
rsx! { rsx! {
document::Link { rel: "icon", href: FAVICON } document::Link { rel: "icon", href: FAVICON }
document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" } document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" }
@@ -41,6 +44,7 @@ fn App() -> Element {
rel: "stylesheet", rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap", href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap",
} }
Router::<Route> {}
Router::<Route> { }
} }
} }