From 44959589f8c3615706e609e1fbe76a89dfffa0e0 Mon Sep 17 00:00:00 2001 From: Amatsugu Date: Sun, 29 Mar 2026 20:16:09 -0400 Subject: [PATCH] async solve, now to fix list updating --- AobaClient/assets/style/dx-components.scss | 1 + AobaClient/assets/style/main.scss | 4 +- AobaClient/src/components/media_grid.rs | 125 +++++---- AobaClient/src/components/media_item.rs | 307 +++++++++++---------- AobaClient/src/components/navbar.rs | 17 +- AobaClient/src/route.rs | 8 +- AobaClient/src/views/home.rs | 41 +-- AobaClient/src/views/media.rs | 6 +- 8 files changed, 264 insertions(+), 245 deletions(-) diff --git a/AobaClient/assets/style/dx-components.scss b/AobaClient/assets/style/dx-components.scss index 26b6ffd..995693c 100644 --- a/AobaClient/assets/style/dx-components.scss +++ b/AobaClient/assets/style/dx-components.scss @@ -7,4 +7,5 @@ div[role="menu"] { width: auto; outline: none; width: max-content; + z-index: 10; } diff --git a/AobaClient/assets/style/main.scss b/AobaClient/assets/style/main.scss index 338da79..d14b404 100644 --- a/AobaClient/assets/style/main.scss +++ b/AobaClient/assets/style/main.scss @@ -113,11 +113,11 @@ $mediaItemSize: 300px; &.placeholder { } - &.nsfw img { + &.blur img { filter: blur(20px); transition: filter 0.25s ease-out; } - &.nsfw:hover img { + &.blur:hover img { filter: blur(0px); } } diff --git a/AobaClient/src/components/media_grid.rs b/AobaClient/src/components/media_grid.rs index 4281a55..bd0516f 100644 --- a/AobaClient/src/components/media_grid.rs +++ b/AobaClient/src/components/media_grid.rs @@ -1,90 +1,93 @@ use dioxus::prelude::*; -use tonic::IntoRequest; use crate::{ - components::MediaItem, - rpc::{aoba::PageFilter, get_rpc_client}, + components::{MediaItem, MediaItemPlaceHolder}, + rpc::{ + aoba::{MediaModel, PageFilter}, + get_rpc_client, + }, }; #[derive(PartialEq, Clone, Props)] -pub struct MediaGridProps { - pub query: Option, +pub struct MediaGridProps +{ + pub query: Signal, pub max_page: Signal, pub total_items: Signal, - #[props(default = Some(1))] - pub page: Option, - #[props(default = Some(100))] - pub page_size: Option, -} - -impl IntoRequest for MediaGridProps { - fn into_request(self) -> tonic::Request { - let f: PageFilter = self.into(); - f.into_request() - } -} - -impl Into for MediaGridProps { - fn into(self) -> PageFilter { - PageFilter { - page: self.page, - page_size: self.page_size, - query: self.query, - } - } + pub page: Signal, + pub page_size: Signal, } #[component] -pub fn MediaGrid(mut props: MediaGridProps) -> Element { +pub fn MediaGrid(mut props: MediaGridProps) -> Element +{ let media_result = use_resource(use_reactive!(|(props)| async move { let mut client = get_rpc_client(); - let result = client.list_media(props.into_request()).await; - if let Ok(items) = result { + let request = PageFilter { + page_size: Some(props.page_size.cloned()), + page: Some(props.page.cloned()), + query: Some(props.query.cloned()), + }; + let result = client.list_media(request).await; + if let Ok(items) = result + { let res = items.into_inner(); + return Ok(res); - } else { + } + else + { let err = result.err().unwrap(); let message = err.message(); return Err(format!("Failed to load results: {message}")); } })); - match media_result.cloned() { - Some(value) => match value { - Ok(result) => { - let pagination = result.pagination.unwrap(); - let total_pages = pagination.total_pages; - let total_items = pagination.total_items; - props.max_page.set(total_pages.max(1)); - props.total_items.set(total_items.max(1)); - return rsx! { - div { - class: "mediaGrid", - // oncontextmenu: oncontext, - {result.items.iter().map(|itm| rsx!{ - MediaItem { - item: itm.clone() - } - })}, - } - }; - } - Err(msg) => rsx! { - div { - class: "mediaGrid", - div { - "Failed to load results: {msg}" - } - } - }, - }, - None => rsx! { + let mut media_grid_display = use_signal(|| { + rsx! { div{ class: "mediaGrid", {(0..50).map(|_| rsx!{ - MediaItem { } + MediaItemPlaceHolder { } })} } + } + }); + + use_memo(move || match media_result() + { + Some(value) => match value + { + Ok(result) => + { + if let Some(pagination) = result.pagination + { + let total_pages = pagination.total_pages; + let total_items = pagination.total_items; + props.max_page.set(total_pages.max(1)); + props.total_items.set(total_items.max(1)); + } + media_grid_display.set(rsx! { + {result.items.iter().map(|itm| rsx!{ + MediaItem { + item: itm.clone() + } + })}, + }); + } + Err(msg) => media_grid_display.set(rsx! { + div{ + "Failed to load results: {msg}" + } + }), }, + _ => (), + }); + + rsx! { + div { + class: "mediaGrid", + {media_grid_display} + } } } diff --git a/AobaClient/src/components/media_item.rs b/AobaClient/src/components/media_item.rs index b1adc08..da7effa 100644 --- a/AobaClient/src/components/media_item.rs +++ b/AobaClient/src/components/media_item.rs @@ -5,9 +5,8 @@ use web_sys::window; use crate::{ HOST, - route::Route, rpc::{ - aoba::{Id, MediaClass, MediaModel, SetMediaClassRequest}, + aoba::{Id, MediaModel, SetMediaClassRequest}, get_rpc_client, }, }; @@ -15,170 +14,180 @@ use crate::{ #[derive(PartialEq, Clone, Props)] pub struct MediaItemProps { - pub item: Option, - // pub oncontextmenu: Option>>, + pub item: MediaModel, } #[component] pub fn MediaItem(props: MediaItemProps) -> Element { - let mut class_signal = use_signal(|| ""); - if let Some(item) = props.item + let item = props.item; + let mtype = item.media_type().as_str_name(); + let filename = item.file_name; + let id = item.id.unwrap().value; + let thumb = item.thumb_url; + let class = item.class; + let mut class_signal = use_signal(|| match class { - let mtype = item.media_type().as_str_name(); - let filename = item.file_name; - let id = item.id.unwrap().value; - let thumb = item.thumb_url; - let class = item.class; - let url = item.media_url; - let download = format!("{HOST}{url}"); + 1 => "blur", + 2 => "secret", + _ => "", + }); + let url = item.media_url; + let download = format!("{HOST}{url}"); - match class - { - 1 => class_signal.set("nsfw"), - 2 => class_signal.set("secret"), - _ => class_signal.set(""), - }; + // class_signal.set(match class + // { + // 1 => "blur", + // 2 => "secret", + // _ => "", + // }); - return rsx! { - ContextMenu{ - ContextMenuTrigger{ - a { - class: "mediaItem {class_signal()}", - href: "{HOST}{url}", - target: "_blank", - "data-id" : id.clone(), - img { src: "{HOST}{thumb}" } - span { class: "info", - span { class: "name", "{filename}" } - span { class: "details", - span { "{mtype}" } - span { "{item.view_count}" } - } + return rsx! { + ContextMenu{ + ContextMenuTrigger{ + a { + class: "mediaItem {class_signal()}", + href: "{HOST}{url}", + target: "_blank", + "data-id" : id.clone(), + img { src: "{HOST}{thumb}" } + span { class: "info", + span { class: "name", "{filename}" } + span { class: "details", + span { "{mtype}" } + span { "{item.view_count}" } } - }, + } }, - ContextMenuContent{ - ContextMenuItem { - index: 0 as usize, - value: id.clone(), - on_select: move |id: String|{ - window().expect("Failed to get window") - .location().set_href(&format!("/media/{}", id)) - .expect("Failed to open Url"); - }, - div{ - class: "contextItem", - div{ - class: "label", - "Details" - } - } + }, + ContextMenuContent{ + ContextMenuItem { + index: 0 as usize, + value: id.clone(), + on_select: move |id: String|{ + window().expect("Failed to get window") + .location().set_href(&format!("/media/{}", id)) + .expect("Failed to open Url"); }, - ContextMenuItem { - index: 1 as usize, - value: "{download}", - on_select: move |url: String|{ - window().expect("Failed to get window").open_with_url_and_target(&url, "_blank").expect("Failed to open url"); - }, + div{ + class: "contextItem", div{ - class: "contextItem", - div{ - class: "label", - "Download" - } - } - }, - { - if class != 0 { - rsx!{ContextMenuItem { - index: 2 as usize, - value: "{id}", - on_select: async |id: String|{ - _ = set_class(id, 0).await; - }, - div{ - class: "contextItem", - div{ - class: "label", - "Mark Standard" - } - } - }} - }else{ - rsx!{} + class: "label", + "Details {class_signal()}" } } - { - if class != 1 { - rsx!{ContextMenuItem { - index: 2 as usize, - value: "{id}", - on_select: async |id: String|{ - _ = set_class(id, 1).await; - }, - div{ - class: "contextItem", - div{ - class: "label", - "Mark NSFW" - } - } - }} - }else{ - rsx!{} - } - } - { - if class != 1 { - rsx!{ContextMenuItem { - index: 2 as usize, - value: "{id}", - on_select: async |id: String|{ - _ = set_class(id, 2).await; - }, - div{ - class: "contextItem", - div{ - class: "label", - "Mark Secret" - } - } - }} - }else{ - rsx!{} - } - } - ContextMenuItem { - index: 2 as usize, - value: "", - div{ - class: "contextItem", - div{ - class: "label", - "Delete" - } - } + }, + ContextMenuItem { + index: 1 as usize, + value: "{download}", + on_select: move |url: String|{ + window().expect("Failed to get window").open_with_url_and_target(&url, "_blank").expect("Failed to open url"); }, + div{ + class: "contextItem", + div{ + class: "label", + "Download" + } + } + }, + { + if class_signal() != "" { + rsx!{ContextMenuItem { + index: 2 as usize, + value: "{id}", + on_select: move |id: String|{ + spawn(async move { + if let Ok(_) = set_class(id, 0).await{ + class_signal.set(""); + } + }); + }, + div{ + class: "contextItem", + div{ + class: "label", + "Mark Standard" + } + } + }} + }else{rsx!{}} + } + { + if class_signal() != "blur" { + rsx!{ContextMenuItem { + index: 2 as usize, + value: "{id}", + on_select: move |id: String|{ + spawn(async move { + if let Ok(_) = set_class(id, 1).await{ + class_signal.set("blur"); + } + }); + }, + div{ + class: "contextItem", + div{ + class: "label", + "Mark blur" + } + } + }} + }else{rsx!{}} + } + { + if class_signal() != "secret" { + rsx!{ContextMenuItem { + index: 2 as usize, + value: "{id}", + on_select: move |id: String|{ + spawn(async move { + if let Ok(_) = set_class(id, 2).await{ + class_signal.set("secret"); + } + }); + }, + div{ + class: "contextItem", + div{ + class: "label", + "Mark Secret" + } + } + }} + }else{rsx!{}} + } + ContextMenuItem { + index: 2 as usize, + value: "", + div{ + class: "contextItem", + div{ + class: "label", + "Delete" + } + } + }, + } + } + }; +} + +#[component] +pub fn MediaItemPlaceHolder() -> Element +{ + return rsx! { + div { class: "mediaItem placeholder", + img { }, + span { class: "info", + span { class: "name" } + span { class: "details", + span { } + span { } } } - }; - } - else - { - return rsx! { - div { class: "mediaItem placeholder", - img { }, - span { class: "info", - span { class: "name" } - span { class: "details", - span { } - span { } - } - } - } - }; - } + } + }; } async fn set_class(id: String, class: i32) -> Result, Status> diff --git a/AobaClient/src/components/navbar.rs b/AobaClient/src/components/navbar.rs index 90855fa..3b9262c 100644 --- a/AobaClient/src/components/navbar.rs +++ b/AobaClient/src/components/navbar.rs @@ -6,7 +6,8 @@ const NAV_CSS: Asset = asset!("/assets/style/nav.scss"); const NAV_ICON: Asset = asset!("/assets/favicon.ico"); #[component] -pub fn Navbar() -> Element { +pub fn Navbar() -> Element +{ rsx! { document::Link { rel: "stylesheet", href: NAV_CSS } nav { @@ -19,17 +20,19 @@ pub fn Navbar() -> Element { } #[component] -pub fn MainNaviagation() -> Element { +pub fn MainNaviagation() -> Element +{ rsx! { div { class: "mainNav", - Link { class: "navItem", to: Route::Home { }, "Home" } + Link { class: "navItem", to: Route::Home { page: None, q: None }, "Home" } Link { class: "navItem", to: Route::Settings {}, "Settings" } } } } #[component] -pub fn Branding() -> Element { +pub fn Branding() -> Element +{ rsx! { div { class: "branding", img { src: NAV_ICON, alt: "Aoba" } @@ -38,14 +41,16 @@ pub fn Branding() -> Element { } #[component] -pub fn Widgets() -> Element { +pub fn Widgets() -> Element +{ rsx! { div { class: "widgets" } } } #[component] -pub fn Utils() -> Element { +pub fn Utils() -> Element +{ let mut auth_context = use_context::(); let version = APP_VERSION; rsx! { diff --git a/AobaClient/src/route.rs b/AobaClient/src/route.rs index a24009f..6fbb6b5 100644 --- a/AobaClient/src/route.rs +++ b/AobaClient/src/route.rs @@ -1,6 +1,6 @@ use crate::{ layouts::MainLayout, - views::{Home, HomePaged, Media, Settings}, + views::{Home, Media, Settings}, }; use dioxus::prelude::*; @@ -9,10 +9,10 @@ use dioxus::prelude::*; pub enum Route { #[layout(MainLayout)] - #[route("/")] - Home { }, #[route("/?:page&:q")] - HomePaged { page: i32, q: String }, + Home { page: Option, q: Option }, + // #[route("/")] + // Home { }, #[route("/media/:id")] Media { id: String }, #[route("/settings")] diff --git a/AobaClient/src/views/home.rs b/AobaClient/src/views/home.rs index e0932b0..adf21c6 100644 --- a/AobaClient/src/views/home.rs +++ b/AobaClient/src/views/home.rs @@ -1,28 +1,29 @@ use crate::components::{MediaGrid, Pagination, Search}; use dioxus::prelude::*; -#[component] -pub fn Home() -> Element -{ - let query = use_signal(|| "".to_string()); - let page = use_signal(|| 1 as i32); - let max_page = use_signal(|| 1 as i32); - let item_count = use_signal(|| 0 as i32); - rsx! { - div { - class: "stickyTop", - Search { query, page }, - Pagination { page, max_page, item_count }, - } - MediaGrid { query: query.cloned(), page: page.cloned(), max_page, total_items: item_count } - } -} +// #[component] +// pub fn Home() -> Element +// { +// let query = use_signal(|| "".to_string()); +// let page = use_signal(|| 1 as i32); +// let max_page = use_signal(|| 1 as i32); +// let item_count = use_signal(|| 0 as i32); +// rsx! { +// div { +// class: "stickyTop", +// Search { query, page }, +// Pagination { page, max_page, item_count }, +// } +// MediaGrid { query: query.cloned(), page: page.cloned(), max_page, total_items: item_count } +// } +// } #[component] -pub fn HomePaged(page: i32, q: String) -> Element +pub fn Home(page: Option, q: Option) -> Element { - let query = use_signal(|| q); - let page = use_signal(|| page); + let query = use_signal(|| q.unwrap_or("".to_string())); + let page = use_signal(|| page.unwrap_or(1)); + let page_size = use_signal::(|| 100); let max_page = use_signal(|| 1 as i32); let item_count = use_signal(|| 0 as i32); rsx! { @@ -31,6 +32,6 @@ pub fn HomePaged(page: i32, q: String) -> Element Search { query, page }, Pagination { page, max_page, item_count }, } - MediaGrid { query: query.cloned(), page: page.cloned(), max_page, total_items: item_count } + MediaGrid { query: query, page: page, max_page, total_items: item_count, page_size } } } diff --git a/AobaClient/src/views/media.rs b/AobaClient/src/views/media.rs index ed1a9f4..d7aaa61 100644 --- a/AobaClient/src/views/media.rs +++ b/AobaClient/src/views/media.rs @@ -39,16 +39,16 @@ fn MediaPage(media: MediaModel) -> Element { let url = media.thumb_url; let id = media.id.expect("Media has no id").value.clone(); - let cur_class = match media.class + let cur_class = use_signal(|| match media.class { 0 => "Standard", 1 => "NSFW", 2 => "Secret", _ => "Unkown", - }; + }); rsx! { img { src: "{HOST}{url}", } - label { "Media Class: {cur_class}" } + label { "Media Class: {cur_class()}" } } }