rewrite components such that data always flows downwards

This commit is contained in:
2026-04-05 17:03:19 -04:00
parent 44425723c6
commit ea9ad2f8a7
6 changed files with 95 additions and 79 deletions
+26 -27
View File
@@ -9,18 +9,22 @@ use crate::{
}; };
#[derive(PartialEq, Clone, Props)] #[derive(PartialEq, Clone, Props)]
pub struct MediaGridProps pub struct MediaGridProps {
{
pub query: Signal<String>, pub query: Signal<String>,
pub max_page: Signal<i32>, pub max_page: Signal<i32>,
pub total_items: Signal<i32>, pub total_items: Signal<i32>,
pub page: Signal<i32>, pub page: Signal<i32>,
pub page_size: Signal<i32>, pub page_size: Signal<i32>,
pub on_page_loaded: Option<EventHandler<PaginationInfo>>,
}
pub struct PaginationInfo {
pub total_pages: i32,
pub total_items: i32,
} }
#[component] #[component]
pub fn MediaGrid(mut props: MediaGridProps) -> Element pub fn MediaGrid(props: MediaGridProps) -> Element {
{
let mut error_display = use_signal(|| { let mut error_display = use_signal(|| {
rsx! {} rsx! {}
}); });
@@ -34,32 +38,29 @@ pub fn MediaGrid(mut props: MediaGridProps) -> Element
query: Some(props.query.cloned()), query: Some(props.query.cloned()),
}; };
let result = client.list_media(request).await; let result = client.list_media(request).await;
if let Ok(items) = result if let Ok(items) = result {
{
let res = items.into_inner(); let res = items.into_inner();
return Ok(res); return Ok(res);
} } else {
else
{
let err = result.err().unwrap(); let err = result.err().unwrap();
let message = err.message(); let message = err.message();
return Err(format!("Failed to load results: {message}")); return Err(format!("Failed to load results: {message}"));
} }
})); }));
use_effect(move || match media_result() use_effect(move || match media_result() {
{ Some(value) => match value {
Some(value) => match value Ok(result) => {
{ if let Some(pagination) = result.pagination {
Ok(result) =>
{
if let Some(pagination) = result.pagination
{
let total_pages = pagination.total_pages; let total_pages = pagination.total_pages;
let total_items = pagination.total_items; let total_items = pagination.total_items;
props.max_page.set(total_pages.max(1)); if let Some(handler) = props.on_page_loaded {
props.total_items.set(total_items.max(1)); handler.call(PaginationInfo {
total_pages,
total_items,
});
}
} }
items.set(Some(result.items)); items.set(Some(result.items));
error_display.set(rsx! {}); error_display.set(rsx! {});
@@ -70,8 +71,7 @@ pub fn MediaGrid(mut props: MediaGridProps) -> Element
} }
}), }),
}, },
_ => _ => {}
{}
}); });
rsx! { rsx! {
@@ -87,8 +87,7 @@ pub fn MediaGrid(mut props: MediaGridProps) -> Element
} }
#[component] #[component]
fn PlaceholderGrid(count: usize) -> Element fn PlaceholderGrid(count: usize) -> Element {
{
rsx! { rsx! {
div{ div{
class: "mediaGrid", class: "mediaGrid",
@@ -100,12 +99,12 @@ fn PlaceholderGrid(count: usize) -> Element
} }
#[component] #[component]
fn MediaList(items: Vec<MediaModel>) -> Element fn MediaList(items: Vec<MediaModel>) -> Element {
{
rsx! { rsx! {
{items.iter().map(|itm| rsx!{ {items.iter().enumerate().map(|(index, itm)| rsx!{
MediaItem { MediaItem {
item: itm.clone() item: itm.clone(),
index
} }
})} })}
} }
+16 -11
View File
@@ -1,5 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_primitives::context_menu::{ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger}; use dioxus_primitives::context_menu::{
ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger,
};
use tonic::{Response, Status}; use tonic::{Response, Status};
use web_sys::window; use web_sys::window;
@@ -11,23 +13,28 @@ use crate::{
}, },
}; };
pub struct MediaClassChangeEvent {
pub index: usize,
pub class: String,
}
#[derive(PartialEq, Clone, Props)] #[derive(PartialEq, Clone, Props)]
pub struct MediaItemProps pub struct MediaItemProps {
{
pub item: MediaModel, pub item: MediaModel,
pub index: usize,
pub on_class_changed: Option<EventHandler<MediaClassChangeEvent>>,
pub on_deleted: Option<EventHandler<usize>>,
} }
#[component] #[component]
pub fn MediaItem(props: MediaItemProps) -> Element pub fn MediaItem(props: MediaItemProps) -> Element {
{
let item = props.item; let 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 class = item.class; let class = item.class;
let mut class_signal = use_signal(|| match class let mut class_signal = use_signal(|| match class {
{
1 => "blur", 1 => "blur",
2 => "secret", 2 => "secret",
_ => "", _ => "",
@@ -174,8 +181,7 @@ pub fn MediaItem(props: MediaItemProps) -> Element
} }
#[component] #[component]
pub fn MediaItemPlaceHolder() -> Element pub fn MediaItemPlaceHolder() -> Element {
{
return rsx! { return rsx! {
div { class: "mediaItem placeholder", div { class: "mediaItem placeholder",
img { }, img { },
@@ -190,8 +196,7 @@ pub fn MediaItemPlaceHolder() -> Element
}; };
} }
async fn set_class(id: String, class: i32) -> Result<Response<()>, Status> async fn set_class(id: String, class: i32) -> Result<Response<()>, Status> {
{
let mut client = get_rpc_client(); let mut client = get_rpc_client();
return client return client
.set_media_class(SetMediaClassRequest { .set_media_class(SetMediaClassRequest {
+15 -12
View File
@@ -2,8 +2,12 @@ use dioxus::prelude::*;
use web_sys::window; use web_sys::window;
#[component] #[component]
pub fn Pagination(page: Signal<i32>, max_page: Signal<i32>, item_count: Signal<i32>) -> Element pub fn Pagination(
{ page: Signal<i32>,
max_page: Signal<i32>,
item_count: Signal<i32>,
on_page_change: EventHandler<i32>,
) -> Element {
let cur_page_val = page.cloned(); let cur_page_val = page.cloned();
let max_page_val = max_page.cloned(); let max_page_val = max_page.cloned();
let item_count_val = item_count.cloned(); let item_count_val = item_count.cloned();
@@ -12,16 +16,16 @@ pub fn Pagination(page: Signal<i32>, max_page: Signal<i32>, item_count: Signal<i
class: "pagination", class: "pagination",
a { a {
onclick: move|_| { onclick: move|_| {
page.set(1); on_page_change.call(1);
on_page_change(); scroll_document();
}, },
"First" "First"
} }
a { a {
onclick: move|_| { onclick: move|_| {
let p = (cur_page_val - 1).max(1); let p = (cur_page_val - 1).max(1);
page.set(p); on_page_change.call(p);
on_page_change(); scroll_document();
}, },
"Prev" "Prev"
} }
@@ -29,15 +33,15 @@ pub fn Pagination(page: Signal<i32>, max_page: Signal<i32>, item_count: Signal<i
a { a {
onclick: move|_| { onclick: move|_| {
let p = (cur_page_val + 1).min(max_page_val); let p = (cur_page_val + 1).min(max_page_val);
page.set(p); on_page_change.call(p);
on_page_change(); scroll_document();
}, },
"Next" "Next"
} }
a { a {
onclick: move|_| { onclick: move|_| {
page.set(max_page_val); on_page_change.call(max_page_val);
on_page_change(); scroll_document();
}, },
"Last" "Last"
} }
@@ -45,8 +49,7 @@ pub fn Pagination(page: Signal<i32>, max_page: Signal<i32>, item_count: Signal<i
} }
} }
fn on_page_change() fn scroll_document() {
{
let window = window().expect("Failed to get window"); let window = window().expect("Failed to get window");
let document = window.document().expect("Failed to get document"); let document = window.document().expect("Failed to get document");
document document
+6 -2
View File
@@ -1,14 +1,18 @@
use dioxus::prelude::*; use dioxus::prelude::*;
#[component] #[component]
pub fn Search(query: Signal<String>, page: Signal<i32>) -> Element { pub fn Search(query: String, oninput: Option<EventHandler<String>>) -> Element {
rsx! { rsx! {
div { class: "searchBar", div { class: "searchBar",
input { input {
r#type: "search", r#type: "search",
placeholder: "Search Files", placeholder: "Search Files",
value: query, value: query,
oninput: move |event| {query.set(event.value()); page.set(1);}, oninput: move |event| {
if let Some(handler) = oninput {
handler.call(event.value());
}
},
} }
} }
} }
+25 -10
View File
@@ -1,4 +1,4 @@
use crate::components::{MediaGrid, Pagination, Search}; use crate::components::{MediaGrid, Pagination, PaginationInfo, Search};
use dioxus::prelude::*; use dioxus::prelude::*;
// #[component] // #[component]
@@ -19,19 +19,34 @@ use dioxus::prelude::*;
// } // }
#[component] #[component]
pub fn Home(page: Option<i32>, q: Option<String>) -> Element pub fn Home(page: Option<i32>, q: Option<String>) -> Element {
{ let mut query = use_signal(|| q.unwrap_or("".to_string()));
let query = use_signal(|| q.unwrap_or("".to_string())); let mut page = use_signal(|| page.unwrap_or(1));
let page = use_signal(|| page.unwrap_or(1));
let page_size = use_signal::<i32>(|| 100); let page_size = use_signal::<i32>(|| 100);
let max_page = use_signal(|| 1 as i32); let mut max_page = use_signal(|| 1 as i32);
let item_count = use_signal(|| 0 as i32); let mut item_count = use_signal(|| 0 as i32);
rsx! { rsx! {
div { div {
class: "stickyTop", class: "stickyTop",
Search { query, page }, Search {
Pagination { page, max_page, item_count }, query: query(),
oninput: move |q| {
query.set(q);
page.set(1);
}
},
Pagination {
page, max_page, item_count,
on_page_change: move |p|{
page.set(p);
}
},
}
MediaGrid { query: query, page: page, max_page, total_items: item_count, page_size,
on_page_loaded: move |p: PaginationInfo| {
max_page.set(p.total_pages);
item_count.set(p.total_items);
}
} }
MediaGrid { query: query, page: page, max_page, total_items: item_count, page_size }
} }
} }
+7 -17
View File
@@ -1,6 +1,4 @@
use crate::HOST; use crate::HOST;
use crate::components::radio_group::{RadioGroup, RadioItem};
use crate::rpc::aoba::SetMediaClassRequest;
use crate::rpc::{ use crate::rpc::{
aoba::{Id, MediaModel}, aoba::{Id, MediaModel},
get_rpc_client, get_rpc_client,
@@ -8,26 +6,20 @@ use crate::rpc::{
use dioxus::prelude::*; use dioxus::prelude::*;
#[component] #[component]
pub fn Media(id: String) -> Element pub fn Media(id: String) -> Element {
{
let media_result = use_resource(use_reactive!(|(id)| async move { let media_result = use_resource(use_reactive!(|(id)| async move {
let mut client = get_rpc_client(); let mut client = get_rpc_client();
let result = client.get_media(Id { value: id.clone() }).await; let result = client.get_media(Id { value: id.clone() }).await;
if let Ok(item) = result if let Ok(item) = result {
{
let res = item.into_inner(); let res = item.into_inner();
return res.value; return res.value;
} } else {
else
{
return None; return None;
} }
})); }));
return match media_result.cloned().unwrap_or(None) return match media_result.cloned().unwrap_or(None) {
{ Some(media) => {
Some(media) =>
{
return rsx! {MediaPage{media: media}}; return rsx! {MediaPage{media: media}};
} }
None => rsx! {"Not Found"}, None => rsx! {"Not Found"},
@@ -35,12 +27,10 @@ pub fn Media(id: String) -> Element
} }
#[component] #[component]
fn MediaPage(media: MediaModel) -> Element fn MediaPage(media: MediaModel) -> Element {
{
let url = media.thumb_url; let url = media.thumb_url;
let id = media.id.expect("Media has no id").value.clone(); let id = media.id.expect("Media has no id").value.clone();
let cur_class = use_signal(|| match media.class let cur_class = use_signal(|| match media.class {
{
0 => "Standard", 0 => "Standard",
1 => "NSFW", 1 => "NSFW",
2 => "Secret", 2 => "Secret",