ContextMenu Renderer Finalized
This commit is contained in:
@@ -176,6 +176,11 @@ form {
|
|||||||
grid-template-columns: $size 1fr auto;
|
grid-template-columns: $size 1fr auto;
|
||||||
height: $size;
|
height: $size;
|
||||||
transition: border 0.1s ease-out;
|
transition: border 0.1s ease-out;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2,26 +2,27 @@ use core::str;
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub mod props {
|
mod props {
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Props)]
|
#[derive(PartialEq, Clone, Props)]
|
||||||
pub struct ContextMenu {
|
pub struct ContextMenu {
|
||||||
pub top: f64,
|
pub top: f64,
|
||||||
pub left: f64,
|
pub left: f64,
|
||||||
pub items: Option<Vec<ContextMenuItem>>,
|
pub items: Element,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Props, Default)]
|
#[derive(PartialEq, Clone, Props, Default)]
|
||||||
pub struct ContextMenuItem {
|
pub struct ContextMenuItem {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sub_items: Option<Vec<ContextMenuItem>>,
|
pub sub_items: Option<Element>,
|
||||||
|
pub onclick: Option<EventHandler<MouseEvent>>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct ContextMenuRenderer {
|
pub struct ContextMenuRenderer {
|
||||||
pub menu: Signal<Option<props::ContextMenu>>,
|
pub menu: Signal<Option<Element>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextMenuRenderer {
|
impl ContextMenuRenderer {
|
||||||
@@ -32,11 +33,7 @@ impl ContextMenuRenderer {
|
|||||||
pub fn render(&self) -> Element {
|
pub fn render(&self) -> Element {
|
||||||
if let Some(menu) = self.menu.cloned() {
|
if let Some(menu) = self.menu.cloned() {
|
||||||
rsx! {
|
rsx! {
|
||||||
ContextMenu{
|
{menu}
|
||||||
items: menu.items,
|
|
||||||
left: menu.left,
|
|
||||||
top: menu.top
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rsx! {}
|
rsx! {}
|
||||||
@@ -53,43 +50,40 @@ pub fn ContextMenuRoot() -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ContextMenu(props: props::ContextMenu) -> Element {
|
pub fn ContextMenu(props: props::ContextMenu) -> Element {
|
||||||
let menu_items = if let Some(items) = props.items {
|
|
||||||
rsx! {
|
|
||||||
ItemList { items }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rsx! {}
|
|
||||||
};
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "contextMenu",
|
class: "contextMenu",
|
||||||
style: "left: {props.left}px; top: {props.top}px;",
|
style: "left: {props.left}px; top: {props.top}px;",
|
||||||
{menu_items}
|
ItemList { items: props.items }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ItemList(items: Vec<props::ContextMenuItem>) -> Element {
|
fn ItemList(items: Element) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div{
|
div{
|
||||||
class: "itemList",
|
class: "itemList",
|
||||||
{items.iter().map(|e| rsx!{
|
{items}
|
||||||
ContextMenuItem{
|
|
||||||
name: e.name.clone(),
|
|
||||||
sub_items: e.sub_items.clone()
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ContextMenuItem(props: props::ContextMenuItem) -> Element {
|
pub fn ContextMenuItem(props: props::ContextMenuItem) -> Element {
|
||||||
|
let mut renderer = use_context::<ContextMenuRenderer>();
|
||||||
|
if let Some(_sub) = props.sub_items {
|
||||||
|
todo!("Sub Menu");
|
||||||
|
}
|
||||||
rsx! {
|
rsx! {
|
||||||
div{
|
div{
|
||||||
|
onclick: move |e|{
|
||||||
|
if let Some(handler) = props.onclick{
|
||||||
|
handler.call(e);
|
||||||
|
}
|
||||||
|
renderer.close();
|
||||||
|
},
|
||||||
class: "contextItem",
|
class: "contextItem",
|
||||||
div {
|
div {
|
||||||
class: "icon"
|
class: "icon"
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ use dioxus::prelude::*;
|
|||||||
use tonic::IntoRequest;
|
use tonic::IntoRequest;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{
|
components::MediaItem,
|
||||||
ContextMenuRenderer, MediaItem,
|
|
||||||
props::{ContextMenu, ContextMenuItem},
|
|
||||||
},
|
|
||||||
rpc::{aoba::PageFilter, get_rpc_client},
|
rpc::{aoba::PageFilter, get_rpc_client},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,41 +46,16 @@ pub fn MediaGrid(props: MediaGridProps) -> Element {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut ct_renderer = use_context::<ContextMenuRenderer>();
|
|
||||||
|
|
||||||
let oncontext = move |event: Event<MouseData>| {
|
|
||||||
event.prevent_default();
|
|
||||||
let data = event.data();
|
|
||||||
let pos = data.coordinates().client();
|
|
||||||
let left = pos.x;
|
|
||||||
let top = pos.y;
|
|
||||||
ct_renderer.menu.set(Some(ContextMenu {
|
|
||||||
left: left,
|
|
||||||
top: top,
|
|
||||||
items: Some(vec![
|
|
||||||
ContextMenuItem {
|
|
||||||
name: "Details".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
ContextMenuItem {
|
|
||||||
name: "Download".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
ContextMenuItem {
|
|
||||||
name: "Delete".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
match media_result.cloned() {
|
match media_result.cloned() {
|
||||||
Some(value) => match value {
|
Some(value) => match value {
|
||||||
Ok(result) => rsx! {
|
Ok(result) => rsx! {
|
||||||
div {
|
div {
|
||||||
class: "mediaGrid",
|
class: "mediaGrid",
|
||||||
|
// oncontextmenu: oncontext,
|
||||||
{result.items.iter().map(|itm| rsx!{
|
{result.items.iter().map(|itm| rsx!{
|
||||||
MediaItem { item: Some(itm.clone()), oncontextmenu: oncontext }
|
MediaItem {
|
||||||
|
item: itm.clone()
|
||||||
|
}
|
||||||
})},
|
})},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -100,7 +72,7 @@ pub fn MediaGrid(props: MediaGridProps) -> Element {
|
|||||||
div{
|
div{
|
||||||
class: "mediaGrid",
|
class: "mediaGrid",
|
||||||
{(0..50).map(|_| rsx!{
|
{(0..50).map(|_| rsx!{
|
||||||
MediaItem {}
|
MediaItem { }
|
||||||
})}
|
})}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,32 +1,72 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use web_sys::window;
|
||||||
|
|
||||||
use crate::{HOST, rpc::aoba::MediaModel};
|
use crate::{
|
||||||
|
HOST,
|
||||||
|
components::{ContextMenu, ContextMenuItem, ContextMenuRenderer},
|
||||||
|
rpc::aoba::MediaModel,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Props)]
|
#[derive(PartialEq, Clone, Props)]
|
||||||
pub struct MediaItemProps {
|
pub struct MediaItemProps {
|
||||||
pub item: Option<MediaModel>,
|
pub item: Option<MediaModel>,
|
||||||
pub oncontextmenu: Option<EventHandler<Event<MouseData>>>,
|
// pub oncontextmenu: Option<EventHandler<Event<MouseData>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn MediaItem(props: MediaItemProps) -> Element {
|
pub fn MediaItem(props: MediaItemProps) -> Element {
|
||||||
|
let mut ct_renderer = use_context::<ContextMenuRenderer>();
|
||||||
|
|
||||||
if let Some(item) = props.item {
|
if let Some(item) = props.item {
|
||||||
let mtype = item.media_type().as_str_name();
|
let mtype = item.media_type().as_str_name();
|
||||||
let filename = item.file_name;
|
let filename = item.file_name;
|
||||||
let id = item.id.unwrap().value;
|
let id = item.id.unwrap().value;
|
||||||
let thumb = item.thumb_url;
|
let thumb = item.thumb_url;
|
||||||
let url = item.media_url;
|
let url = item.media_url;
|
||||||
|
let download = format!("{HOST}{url}");
|
||||||
|
|
||||||
|
let oncontext = move |event: Event<MouseData>| {
|
||||||
|
println!("ContextMenu");
|
||||||
|
event.prevent_default();
|
||||||
|
event.stop_propagation();
|
||||||
|
let data = event.data();
|
||||||
|
if data.modifiers().ctrl() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pos = data.coordinates().client();
|
||||||
|
let left = pos.x;
|
||||||
|
let top = pos.y;
|
||||||
|
let download = download.clone();
|
||||||
|
|
||||||
|
let menu: Element = rsx! {
|
||||||
|
ContextMenu {
|
||||||
|
left: left,
|
||||||
|
top: top,
|
||||||
|
items: rsx! {
|
||||||
|
ContextMenuItem {
|
||||||
|
name: "Details",
|
||||||
|
},
|
||||||
|
ContextMenuItem {
|
||||||
|
name: "Download",
|
||||||
|
onclick: move |_|{
|
||||||
|
_ = window().unwrap().open_with_url_and_target(&download, "_blank");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ContextMenuItem {
|
||||||
|
name: "Delete",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ct_renderer.menu.set(Some(menu));
|
||||||
|
};
|
||||||
|
|
||||||
return rsx! {
|
return rsx! {
|
||||||
a {
|
a {
|
||||||
class: "mediaItem",
|
class: "mediaItem",
|
||||||
href: "{HOST}/{url}",
|
href: "{HOST}{url}",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
oncontextmenu: move |e| {
|
oncontextmenu: oncontext,
|
||||||
if let Some(handler) = props.oncontextmenu{
|
|
||||||
handler.call(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"data-id" : id,
|
"data-id" : id,
|
||||||
img { src: "{HOST}{thumb}" }
|
img { src: "{HOST}{thumb}" }
|
||||||
span { class: "info",
|
span { class: "info",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Route,
|
Route,
|
||||||
components::{ContextMenuRoot, Navbar},
|
components::{ContextMenuRenderer, ContextMenuRoot, Navbar},
|
||||||
contexts::AuthContext,
|
contexts::AuthContext,
|
||||||
views::Login,
|
views::Login,
|
||||||
};
|
};
|
||||||
@@ -13,13 +13,24 @@ pub fn MainLayout() -> Element {
|
|||||||
|
|
||||||
if auth_context.jwt.cloned().is_none() {
|
if auth_context.jwt.cloned().is_none() {
|
||||||
return rsx! {
|
return rsx! {
|
||||||
Login {}
|
Login { }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut ct_renderer = use_context::<ContextMenuRenderer>();
|
||||||
|
|
||||||
return rsx! {
|
return rsx! {
|
||||||
ContextMenuRoot { }
|
ContextMenuRoot { }
|
||||||
Navbar {}
|
Navbar { }
|
||||||
div { id: "content", Outlet::<Route> {} }
|
div {
|
||||||
|
id: "content",
|
||||||
|
onclick: move |_| {
|
||||||
|
ct_renderer.close();
|
||||||
|
},
|
||||||
|
oncontextmenu: move |_| {
|
||||||
|
ct_renderer.close();
|
||||||
|
},
|
||||||
|
Outlet::<Route> { }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ fn main() {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn App() -> Element {
|
fn App() -> Element {
|
||||||
let _auth_state = use_context_provider(|| AuthContext::new());
|
use_context_provider(|| AuthContext::new());
|
||||||
let _renderer = use_context_provider(|| ContextMenuRenderer::default());
|
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" }
|
||||||
|
|||||||
Reference in New Issue
Block a user