From 07aa010d1010a8edf8d1dc9a41616a186dfa81d0 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 4 Jan 2023 19:01:06 +0000 Subject: [PATCH 01/10] Initial implementation of avatar component --- app/src/components/app.rs | 18 +++--- app/src/components/avatar.rs | 117 +++++++++++++++++++++++++++++++++++ app/src/components/mod.rs | 1 + 3 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 app/src/components/avatar.rs diff --git a/app/src/components/app.rs b/app/src/components/app.rs index 7f35c43..dc085e5 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -1,5 +1,6 @@ use crate::{ components::{ + avatar::Avatar, change_password::ChangePasswordForm, create_group::CreateGroupForm, create_user::CreateUserForm, @@ -87,6 +88,7 @@ impl Component for App { self.route_dispatcher .send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login))); } + ConsoleService::log("update app"); true } @@ -247,15 +249,13 @@ impl App { id="dropdownUser" data-bs-toggle="dropdown" aria-expanded="false"> - - - - + { + if let Some((user_id, _)) = &self.user_info { + html! { + + } + } else { html!{} } + } { if let Some((user_id, _)) = &self.user_info { html! { diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs new file mode 100644 index 0000000..9abd39b --- /dev/null +++ b/app/src/components/avatar.rs @@ -0,0 +1,117 @@ +use crate::infra::common_component::{CommonComponent, CommonComponentParts}; +use anyhow::{bail, Result}; +use graphql_client::GraphQLQuery; +use yew::prelude::*; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "queries/get_user_details.graphql", + response_derives = "Debug, Hash, PartialEq, Eq, Clone", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct GetUserDetails; + +pub type User = get_user_details::GetUserDetailsUser; +pub type Group = get_user_details::GetUserDetailsUserGroups; + +pub struct Avatar { + common: CommonComponentParts, + /// The user info. If none, the error is in `error`. If `error` is None, then we haven't + /// received the server response yet. + avatar: Option, +} + +/// State machine describing the possible transitions of the component state. +/// It starts out by fetching the user's details from the backend when loading. +pub enum Msg { + /// Received the user details response, either the user data or an error. + UserDetailsResponse(Result), + Update +} + +#[derive(yew::Properties, Clone, PartialEq, Eq)] +pub struct Props { + pub username: String, + pub width: i32, + pub height: i32, +} + +impl CommonComponent for Avatar { + fn handle_msg(&mut self, msg: ::Message) -> Result { + match msg { + Msg::UserDetailsResponse(response) => match response { + Ok(user) => self.avatar = user.user.avatar, + Err(e) => { + self.avatar = None; + bail!("Error getting user details: {}", e); + } + }, + Msg::Update => self.get_user_details(), + } + Ok(true) + } + + fn mut_common(&mut self) -> &mut CommonComponentParts { + &mut self.common + } +} + +impl Avatar { + fn get_user_details(&mut self) { + if self.common.username.len() > 0 { + self.common.call_graphql::( + get_user_details::Variables { + id: self.common.username.clone(), + }, + Msg::UserDetailsResponse, + "Error trying to fetch user details", + ) + } + } +} + +impl Component for Avatar { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + let mut avatar = Self { + common: CommonComponentParts::::create(props, link), + avatar: None, + }; + avatar.get_user_details(); + avatar + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + CommonComponentParts::::update(self, msg) + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + self.common.change(props) + } + + fn view(&self) -> Html { + match &self.avatar { + Some(avatar) => html! { + Avatar + }, + None => html! { + + + + + }, + } + } +} diff --git a/app/src/components/mod.rs b/app/src/components/mod.rs index f78dcf9..9dbe562 100644 --- a/app/src/components/mod.rs +++ b/app/src/components/mod.rs @@ -1,6 +1,7 @@ pub mod add_group_member; pub mod add_user_to_group; pub mod app; +pub mod avatar; pub mod change_password; pub mod create_group; pub mod create_user; From ed91e19b3e730b84448122b5dc660d4afbfdb245 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 4 Jan 2023 22:51:36 +0000 Subject: [PATCH 02/10] new query for avatar --- app/queries/get_user_avatar.graphql | 5 +++++ app/src/components/avatar.rs | 25 ++++++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 app/queries/get_user_avatar.graphql diff --git a/app/queries/get_user_avatar.graphql b/app/queries/get_user_avatar.graphql new file mode 100644 index 0000000..587a9f9 --- /dev/null +++ b/app/queries/get_user_avatar.graphql @@ -0,0 +1,5 @@ +query GetUserAvatar($id: String!) { + user(userId: $id) { + avatar + } +} diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs index 9abd39b..4046e6c 100644 --- a/app/src/components/avatar.rs +++ b/app/src/components/avatar.rs @@ -6,14 +6,13 @@ use yew::prelude::*; #[derive(GraphQLQuery)] #[graphql( schema_path = "../schema.graphql", - query_path = "queries/get_user_details.graphql", + query_path = "queries/get_user_avatar.graphql", response_derives = "Debug, Hash, PartialEq, Eq, Clone", custom_scalars_module = "crate::infra::graphql" )] -pub struct GetUserDetails; +pub struct GetUserAvatar; -pub type User = get_user_details::GetUserDetailsUser; -pub type Group = get_user_details::GetUserDetailsUserGroups; +pub type User = get_user_avatar::GetUserAvatarUser; pub struct Avatar { common: CommonComponentParts, @@ -26,7 +25,7 @@ pub struct Avatar { /// It starts out by fetching the user's details from the backend when loading. pub enum Msg { /// Received the user details response, either the user data or an error. - UserDetailsResponse(Result), + UserAvatarResponse(Result), Update } @@ -40,14 +39,14 @@ pub struct Props { impl CommonComponent for Avatar { fn handle_msg(&mut self, msg: ::Message) -> Result { match msg { - Msg::UserDetailsResponse(response) => match response { + Msg::UserAvatarResponse(response) => match response { Ok(user) => self.avatar = user.user.avatar, Err(e) => { self.avatar = None; bail!("Error getting user details: {}", e); } }, - Msg::Update => self.get_user_details(), + Msg::Update => self.get_user_avatar(), } Ok(true) } @@ -58,14 +57,14 @@ impl CommonComponent for Avatar { } impl Avatar { - fn get_user_details(&mut self) { + fn get_user_avatar(&mut self) { if self.common.username.len() > 0 { - self.common.call_graphql::( - get_user_details::Variables { + self.common.call_graphql::( + get_user_avatar::Variables { id: self.common.username.clone(), }, - Msg::UserDetailsResponse, - "Error trying to fetch user details", + Msg::UserAvatarResponse, + "Error trying to fetch user avatar", ) } } @@ -80,7 +79,7 @@ impl Component for Avatar { common: CommonComponentParts::::create(props, link), avatar: None, }; - avatar.get_user_details(); + avatar.get_user_avatar(); avatar } From b49e3404f85cb8d00b87e9bfff69ecb5c09ac6e8 Mon Sep 17 00:00:00 2001 From: Austin Date: Fri, 17 Mar 2023 15:52:21 +0000 Subject: [PATCH 03/10] Tweaks --- app/src/components/app.rs | 1 - app/src/components/avatar.rs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/components/app.rs b/app/src/components/app.rs index dc085e5..3bbef44 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -88,7 +88,6 @@ impl Component for App { self.route_dispatcher .send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login))); } - ConsoleService::log("update app"); true } diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs index 4046e6c..50ccee0 100644 --- a/app/src/components/avatar.rs +++ b/app/src/components/avatar.rs @@ -7,13 +7,11 @@ use yew::prelude::*; #[graphql( schema_path = "../schema.graphql", query_path = "queries/get_user_avatar.graphql", - response_derives = "Debug, Hash, PartialEq, Eq, Clone", + response_derives = "Debug", custom_scalars_module = "crate::infra::graphql" )] pub struct GetUserAvatar; -pub type User = get_user_avatar::GetUserAvatarUser; - pub struct Avatar { common: CommonComponentParts, /// The user info. If none, the error is in `error`. If `error` is None, then we haven't From 590825fe73fa8a25c27efaf45cdcfa3a73723f07 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 22 Mar 2023 20:08:43 +0000 Subject: [PATCH 04/10] Updates --- Cargo.lock | 26 +++++++++++ app/Cargo.toml | 1 + app/src/components/app.rs | 10 +---- app/src/components/avatar.rs | 58 ++++++++++++------------- app/src/components/avatar_event_bus.rs | 47 ++++++++++++++++++++ app/src/components/mod.rs | 1 + app/src/components/user_details_form.rs | 5 +++ 7 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 app/src/components/avatar_event_bus.rs diff --git a/Cargo.lock b/Cargo.lock index 4fbc339..b8c8327 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,6 +352,12 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + [[package]] name = "arrayref" version = "0.3.6" @@ -2434,6 +2440,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "yew", + "yew-agent", "yew-router", "yew_form", "yew_form_derive", @@ -4798,6 +4805,25 @@ dependencies = [ "yew-macro", ] +[[package]] +name = "yew-agent" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616700dc3851945658c44ba4477ede6b77c795462fbbb9b0ad9a8b6273a3ca77" +dependencies = [ + "anymap2", + "bincode", + "gloo-console", + "gloo-utils", + "js-sys", + "serde", + "slab", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew", +] + [[package]] name = "yew-macro" version = "0.19.3" diff --git a/app/Cargo.toml b/app/Cargo.toml index cc489be..3dcd0ab 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -24,6 +24,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "*" yew = "0.19.3" yew-router = "0.16" +yew-agent = "0.1.0" # Needed because of https://github.com/tkaitchuck/aHash/issues/95 indexmap = "=1.6.2" diff --git a/app/src/components/app.rs b/app/src/components/app.rs index 7cd1ee7..6292896 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -350,15 +350,7 @@ impl App { id="dropdownUser" data-bs-toggle="dropdown" aria-expanded="false"> - - - - + {user_id} diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs index 50ccee0..947fc3c 100644 --- a/app/src/components/avatar.rs +++ b/app/src/components/avatar.rs @@ -1,7 +1,10 @@ -use crate::infra::common_component::{CommonComponent, CommonComponentParts}; +use crate::{ + components::avatar_event_bus::AvatarEventBus, + infra::common_component::{CommonComponent, CommonComponentParts}}; use anyhow::{bail, Result}; use graphql_client::GraphQLQuery; -use yew::prelude::*; +use yew::{prelude::*}; +use yew_agent::{Bridge, Bridged}; #[derive(GraphQLQuery)] #[graphql( @@ -16,7 +19,8 @@ pub struct Avatar { common: CommonComponentParts, /// The user info. If none, the error is in `error`. If `error` is None, then we haven't /// received the server response yet. - avatar: Option, + avatar: Option, + _producer: Box>, } /// State machine describing the possible transitions of the component state. @@ -35,16 +39,16 @@ pub struct Props { } impl CommonComponent for Avatar { - fn handle_msg(&mut self, msg: ::Message) -> Result { + fn handle_msg(&mut self, ctx: &Context, msg: ::Message) -> Result { match msg { Msg::UserAvatarResponse(response) => match response { Ok(user) => self.avatar = user.user.avatar, Err(e) => { self.avatar = None; - bail!("Error getting user details: {}", e); + bail!("Error getting user avatar: {}", e); } }, - Msg::Update => self.get_user_avatar(), + Msg::Update => self.get_user_avatar(ctx), } Ok(true) } @@ -55,16 +59,15 @@ impl CommonComponent for Avatar { } impl Avatar { - fn get_user_avatar(&mut self) { - if self.common.username.len() > 0 { - self.common.call_graphql::( - get_user_avatar::Variables { - id: self.common.username.clone(), - }, - Msg::UserAvatarResponse, - "Error trying to fetch user avatar", - ) - } + fn get_user_avatar(&mut self, ctx: &Context) { + self.common.call_graphql::( + ctx, + get_user_avatar::Variables { + id: ctx.props().username.clone(), + }, + Msg::UserAvatarResponse, + "Error trying to fetch user avatar", + ) } } @@ -72,36 +75,33 @@ impl Component for Avatar { type Message = Msg; type Properties = Props; - fn create(props: Self::Properties, link: ComponentLink) -> Self { + fn create(ctx: &Context) -> Self { let mut avatar = Self { - common: CommonComponentParts::::create(props, link), + common: CommonComponentParts::::create(), avatar: None, + _producer: AvatarEventBus::bridge(ctx.link().callback(|_| Msg::Update)), }; - avatar.get_user_avatar(); + avatar.get_user_avatar(ctx); avatar } - fn update(&mut self, msg: Self::Message) -> ShouldRender { - CommonComponentParts::::update(self, msg) + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + CommonComponentParts::::update(self, ctx, msg) } - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.common.change(props) - } - - fn view(&self) -> Html { + fn view(&self, ctx: &Context) -> Html { match &self.avatar { Some(avatar) => html! { Avatar }, None => html! { diff --git a/app/src/components/avatar_event_bus.rs b/app/src/components/avatar_event_bus.rs new file mode 100644 index 0000000..2a1ea81 --- /dev/null +++ b/app/src/components/avatar_event_bus.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use yew_agent::{Agent, AgentLink, Context, HandlerId}; + +#[derive(Serialize, Deserialize, Debug)] +pub enum Request { + Update, +} + +pub struct AvatarEventBus { + link: AgentLink, + subscribers: HashSet, +} + +impl Agent for AvatarEventBus { + type Reach = Context; + type Message = (); + type Input = Request; + type Output = (); + + fn create(link: AgentLink) -> Self { + Self { + link, + subscribers: HashSet::new(), + } + } + + fn update(&mut self, _msg: Self::Message) {} + + fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) { + match msg { + Request::Update => { + for sub in self.subscribers.iter() { + self.link.respond(*sub, ()); + } + } + } + } + + fn connected(&mut self, id: HandlerId) { + self.subscribers.insert(id); + } + + fn disconnected(&mut self, id: HandlerId) { + self.subscribers.remove(&id); + } +} \ No newline at end of file diff --git a/app/src/components/mod.rs b/app/src/components/mod.rs index 9dbe562..c406b64 100644 --- a/app/src/components/mod.rs +++ b/app/src/components/mod.rs @@ -2,6 +2,7 @@ pub mod add_group_member; pub mod add_user_to_group; pub mod app; pub mod avatar; +pub mod avatar_event_bus; pub mod change_password; pub mod create_group; pub mod create_user; diff --git a/app/src/components/user_details_form.rs b/app/src/components/user_details_form.rs index 3ad9530..6ff366b 100644 --- a/app/src/components/user_details_form.rs +++ b/app/src/components/user_details_form.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use crate::{ components::user_details::User, + components::avatar_event_bus::{AvatarEventBus, Request}, infra::common_component::{CommonComponent, CommonComponentParts}, }; use anyhow::{bail, Error, Result}; @@ -14,6 +15,7 @@ use validator_derive::Validate; use web_sys::{FileList, HtmlInputElement, InputEvent}; use yew::prelude::*; use yew_form_derive::Model; +use yew_agent::{Dispatched, Dispatcher}; #[derive(Default)] struct JsFile { @@ -72,6 +74,7 @@ pub struct UserDetailsForm { /// True if we just successfully updated the user, to display a success message. just_updated: bool, user: User, + avatar_event_bus: Dispatcher, } pub enum Msg { @@ -163,6 +166,7 @@ impl Component for UserDetailsForm { just_updated: false, reader: None, user: ctx.props().user.clone(), + avatar_event_bus: AvatarEventBus::dispatcher(), } } @@ -402,6 +406,7 @@ impl UserDetailsForm { self.user.avatar = Some(avatar); } self.just_updated = true; + self.avatar_event_bus.send(Request::Update); Ok(true) } From 641f08e2697477d4db546e9559fd81634c16df50 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 22 Mar 2023 20:12:24 +0000 Subject: [PATCH 05/10] remove dead code --- app/src/components/app.rs | 43 --------------------------------------- 1 file changed, 43 deletions(-) diff --git a/app/src/components/app.rs b/app/src/components/app.rs index 6292896..3262774 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -289,49 +289,6 @@ impl App { } } else { html!{} } } - - /**/ { self.view_user_menu(ctx) } From d509dc82a0de96f5c6f703a29b117ac6b9074b0e Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 23 Mar 2023 19:29:46 +0000 Subject: [PATCH 06/10] Review feedback plus avatar rounding --- app/src/components/avatar.rs | 45 ++++++++++++++++++------- app/src/components/avatar_event_bus.rs | 18 +++++++--- app/src/components/user_details_form.rs | 13 +++++-- app/static/style.css | 4 +++ 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs index 947fc3c..9689798 100644 --- a/app/src/components/avatar.rs +++ b/app/src/components/avatar.rs @@ -1,9 +1,11 @@ use crate::{ - components::avatar_event_bus::AvatarEventBus, - infra::common_component::{CommonComponent, CommonComponentParts}}; + components::avatar_event_bus::{AvatarEventBus, Response}, + infra::common_component::{CommonComponent, CommonComponentParts}, +}; use anyhow::{bail, Result}; use graphql_client::GraphQLQuery; -use yew::{prelude::*}; +use serde::{Deserialize, Serialize}; +use yew::prelude::*; use yew_agent::{Bridge, Bridged}; #[derive(GraphQLQuery)] @@ -15,11 +17,18 @@ use yew_agent::{Bridge, Bridged}; )] pub struct GetUserAvatar; +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AvatarData(String); + +impl AvatarData { + pub fn new(data: String) -> Self { + AvatarData(data) + } +} + pub struct Avatar { common: CommonComponentParts, - /// The user info. If none, the error is in `error`. If `error` is None, then we haven't - /// received the server response yet. - avatar: Option, + avatar: Option, _producer: Box>, } @@ -28,7 +37,7 @@ pub struct Avatar { pub enum Msg { /// Received the user details response, either the user data or an error. UserAvatarResponse(Result), - Update + Update(Response), } #[derive(yew::Properties, Clone, PartialEq, Eq)] @@ -39,16 +48,26 @@ pub struct Props { } impl CommonComponent for Avatar { - fn handle_msg(&mut self, ctx: &Context, msg: ::Message) -> Result { + fn handle_msg( + &mut self, + ctx: &Context, + msg: ::Message, + ) -> Result { match msg { Msg::UserAvatarResponse(response) => match response { - Ok(user) => self.avatar = user.user.avatar, + Ok(user) => self.avatar = user.user.avatar.map(AvatarData::new), Err(e) => { self.avatar = None; bail!("Error getting user avatar: {}", e); } }, - Msg::Update => self.get_user_avatar(ctx), + Msg::Update(Response::Update((username, avatar))) => { + if username == ctx.props().username { + self.avatar = avatar; + return Ok(true); + } + return Ok(false); + } } Ok(true) } @@ -79,7 +98,7 @@ impl Component for Avatar { let mut avatar = Self { common: CommonComponentParts::::create(), avatar: None, - _producer: AvatarEventBus::bridge(ctx.link().callback(|_| Msg::Update)), + _producer: AvatarEventBus::bridge(ctx.link().callback(Msg::Update)), }; avatar.get_user_avatar(ctx); avatar @@ -93,8 +112,8 @@ impl Component for Avatar { match &self.avatar { Some(avatar) => html! { Avatar }, diff --git a/app/src/components/avatar_event_bus.rs b/app/src/components/avatar_event_bus.rs index 2a1ea81..5b62e66 100644 --- a/app/src/components/avatar_event_bus.rs +++ b/app/src/components/avatar_event_bus.rs @@ -2,9 +2,16 @@ use serde::{Deserialize, Serialize}; use std::collections::HashSet; use yew_agent::{Agent, AgentLink, Context, HandlerId}; +use super::avatar::AvatarData; + #[derive(Serialize, Deserialize, Debug)] pub enum Request { - Update, + Update((String, Option)), +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Response { + Update((String, Option)), } pub struct AvatarEventBus { @@ -16,7 +23,7 @@ impl Agent for AvatarEventBus { type Reach = Context; type Message = (); type Input = Request; - type Output = (); + type Output = Response; fn create(link: AgentLink) -> Self { Self { @@ -29,9 +36,10 @@ impl Agent for AvatarEventBus { fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) { match msg { - Request::Update => { + Request::Update((username, avatar)) => { for sub in self.subscribers.iter() { - self.link.respond(*sub, ()); + self.link + .respond(*sub, Response::Update((username.clone(), avatar.clone()))); } } } @@ -44,4 +52,4 @@ impl Agent for AvatarEventBus { fn disconnected(&mut self, id: HandlerId) { self.subscribers.remove(&id); } -} \ No newline at end of file +} diff --git a/app/src/components/user_details_form.rs b/app/src/components/user_details_form.rs index 6ff366b..6872dc1 100644 --- a/app/src/components/user_details_form.rs +++ b/app/src/components/user_details_form.rs @@ -1,8 +1,9 @@ use std::str::FromStr; use crate::{ - components::user_details::User, + components::avatar::AvatarData, components::avatar_event_bus::{AvatarEventBus, Request}, + components::user_details::User, infra::common_component::{CommonComponent, CommonComponentParts}, }; use anyhow::{bail, Error, Result}; @@ -14,8 +15,8 @@ use graphql_client::GraphQLQuery; use validator_derive::Validate; use web_sys::{FileList, HtmlInputElement, InputEvent}; use yew::prelude::*; -use yew_form_derive::Model; use yew_agent::{Dispatched, Dispatcher}; +use yew_form_derive::Model; #[derive(Default)] struct JsFile { @@ -406,7 +407,13 @@ impl UserDetailsForm { self.user.avatar = Some(avatar); } self.just_updated = true; - self.avatar_event_bus.send(Request::Update); + self.avatar_event_bus.send(Request::Update(( + self.user.id.clone(), + self.user + .avatar + .as_ref() + .map(|data| AvatarData::new(data.clone())), + ))); Ok(true) } diff --git a/app/static/style.css b/app/static/style.css index be2db44..26d24a8 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -29,4 +29,8 @@ html.dark .nav-link { .nav-link { color: #212529 +} + +.avatar { + border-radius: 50%; } \ No newline at end of file From 7b27538c167f8fa6d6d70d8d752bcb8f063f9b6c Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 23 Mar 2023 19:46:02 +0000 Subject: [PATCH 07/10] rename component --- app/src/components/app.rs | 4 ++-- app/src/components/avatar.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/components/app.rs b/app/src/components/app.rs index 3262774..5d5ad0b 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -1,6 +1,6 @@ use crate::{ components::{ - avatar::Avatar, + avatar::ShowAvatar, change_password::ChangePasswordForm, create_group::CreateGroupForm, create_user::CreateUserForm, @@ -307,7 +307,7 @@ impl App { id="dropdownUser" data-bs-toggle="dropdown" aria-expanded="false"> - + {user_id} diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs index 9689798..d652bbe 100644 --- a/app/src/components/avatar.rs +++ b/app/src/components/avatar.rs @@ -26,7 +26,7 @@ impl AvatarData { } } -pub struct Avatar { +pub struct ShowAvatar { common: CommonComponentParts, avatar: Option, _producer: Box>, @@ -47,7 +47,7 @@ pub struct Props { pub height: i32, } -impl CommonComponent for Avatar { +impl CommonComponent for ShowAvatar { fn handle_msg( &mut self, ctx: &Context, @@ -77,7 +77,7 @@ impl CommonComponent for Avatar { } } -impl Avatar { +impl ShowAvatar { fn get_user_avatar(&mut self, ctx: &Context) { self.common.call_graphql::( ctx, @@ -90,7 +90,7 @@ impl Avatar { } } -impl Component for Avatar { +impl Component for ShowAvatar { type Message = Msg; type Properties = Props; From e2fd9686a802797500fb19fc21f3e9af45cb53a4 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 30 Mar 2023 19:44:50 +0000 Subject: [PATCH 08/10] Refactor to context and reducer --- Cargo.lock | 210 ++++++++++++++++++++---- app/Cargo.toml | 2 +- app/queries/get_user_avatar.graphql | 1 + app/src/components/app.rs | 16 +- app/src/components/avatar.rs | 148 +++-------------- app/src/components/avatar_cache.rs | 150 +++++++++++++++++ app/src/components/avatar_event_bus.rs | 55 ------- app/src/components/mod.rs | 2 +- app/src/components/user_details_form.rs | 23 ++- 9 files changed, 384 insertions(+), 223 deletions(-) create mode 100644 app/src/components/avatar_cache.rs delete mode 100644 app/src/components/avatar_event_bus.rs diff --git a/Cargo.lock b/Cargo.lock index 49e6872..40934b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1532,6 +1532,25 @@ dependencies = [ "gloo-utils", ] +[[package]] +name = "gloo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20" +dependencies = [ + "gloo-console", + "gloo-dialogs", + "gloo-events", + "gloo-file", + "gloo-history", + "gloo-net", + "gloo-render", + "gloo-storage", + "gloo-timers", + "gloo-utils", + "gloo-worker 0.2.1", +] + [[package]] name = "gloo-console" version = "0.2.3" @@ -1578,6 +1597,22 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-history" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd451019e0b7a2b8a7a7b23e74916601abf1135c54664e57ff71dcc26dfcdeb7" +dependencies = [ + "gloo-events", + "gloo-utils", + "serde", + "serde-wasm-bindgen 0.4.5", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-net" version = "0.2.6" @@ -1648,6 +1683,40 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-worker" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09110b5555bcafe508cee0fb94308af9aac7a85f980d3c88b270d117c6c6911d" +dependencies = [ + "anymap2", + "bincode", + "gloo-console", + "gloo-utils", + "js-sys", + "serde", + "slab", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode", + "gloo-console", + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "graphql-introspection-query" version = "0.2.0" @@ -2044,6 +2113,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "implicit-clone" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7" +dependencies = [ + "indexmap", +] + [[package]] name = "indexmap" version = "1.6.2" @@ -2410,7 +2488,7 @@ dependencies = [ "tracing-forest", "tracing-log", "tracing-subscriber", - "uuid 0.8.2", + "uuid 1.3.0", "webpki-roots", ] @@ -2439,7 +2517,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "yew", + "yew 0.19.3", "yew-agent", "yew-router", "yew_form", @@ -2537,12 +2615,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - [[package]] name = "memchr" version = "2.5.0" @@ -3003,6 +3075,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + [[package]] name = "pkcs1" version = "0.3.3" @@ -3067,6 +3150,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3113,6 +3206,23 @@ dependencies = [ "yansi", ] +[[package]] +name = "prokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" +dependencies = [ + "futures", + "gloo 0.8.0", + "num_cpus", + "once_cell", + "pin-project", + "pinned", + "tokio", + "tokio-stream", + "wasm-bindgen-futures", +] + [[package]] name = "quote" version = "1.0.23" @@ -3645,6 +3755,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_bytes" version = "0.11.9" @@ -4411,9 +4532,6 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "md5", -] [[package]] name = "uuid" @@ -4422,6 +4540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ "getrandom 0.2.8", + "md-5", ] [[package]] @@ -4775,7 +4894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a1ccb53e57d3f7d847338cf5758befa811cabe207df07f543c06f502f9998cd" dependencies = [ "console_error_panic_hook", - "gloo", + "gloo 0.4.2", "gloo-utils", "indexmap", "js-sys", @@ -4784,26 +4903,42 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "yew-macro", + "yew-macro 0.19.3", +] + +[[package]] +name = "yew" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dbecfe44343b70cc2932c3eb445425969ae21754a8ab3a0966981c1cf7af1cc" +dependencies = [ + "console_error_panic_hook", + "futures", + "gloo 0.8.0", + "implicit-clone", + "indexmap", + "js-sys", + "prokio", + "rustversion", + "serde", + "slab", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro 0.20.0", ] [[package]] name = "yew-agent" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616700dc3851945658c44ba4477ede6b77c795462fbbb9b0ad9a8b6273a3ca77" +checksum = "b06f7c5ed97fff22816bb00d3d82ebc0fc1119d7bbb9e07e62c0d2853f51920a" dependencies = [ - "anymap2", - "bincode", - "gloo-console", - "gloo-utils", - "js-sys", - "serde", - "slab", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yew", + "gloo-worker 0.1.2", + "yew 0.20.0", ] [[package]] @@ -4820,23 +4955,38 @@ dependencies = [ "syn", ] +[[package]] +name = "yew-macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64c253c1d401f1ea868ca9988db63958cfa15a69f739101f338d6f05eea8301" +dependencies = [ + "boolinator", + "once_cell", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "yew-router" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155804f6f3aa309f596d5c3fa14486a94e7756f1edd7634569949e401d5099f2" dependencies = [ - "gloo", + "gloo 0.4.2", "gloo-utils", "js-sys", "route-recognizer", "serde", - "serde-wasm-bindgen", + "serde-wasm-bindgen 0.3.1", "serde_urlencoded", "thiserror", "wasm-bindgen", "web-sys", - "yew", + "yew 0.19.3", "yew-router-macro", ] @@ -4861,7 +5011,7 @@ dependencies = [ "validator_derive", "wasm-bindgen", "web-sys", - "yew", + "yew 0.19.3", ] [[package]] diff --git a/app/Cargo.toml b/app/Cargo.toml index 7f570be..844924c 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -24,7 +24,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "*" yew = "0.19.3" yew-router = "0.16" -yew-agent = "0.1.0" +yew-agent = "0.2.0" # Needed because of https://github.com/tkaitchuck/aHash/issues/95 indexmap = "=1.6.2" diff --git a/app/queries/get_user_avatar.graphql b/app/queries/get_user_avatar.graphql index 587a9f9..28d2bd8 100644 --- a/app/queries/get_user_avatar.graphql +++ b/app/queries/get_user_avatar.graphql @@ -1,5 +1,6 @@ query GetUserAvatar($id: String!) { user(userId: $id) { + id avatar } } diff --git a/app/src/components/app.rs b/app/src/components/app.rs index 5d5ad0b..7f40690 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -1,6 +1,7 @@ use crate::{ components::{ avatar::ShowAvatar, + avatar_cache::*, change_password::ChangePasswordForm, create_group::CreateGroupForm, create_user::CreateUserForm, @@ -137,8 +138,19 @@ impl Component for App { let link = ctx.link().clone(); let is_admin = self.is_admin(); let password_reset_enabled = self.password_reset_enabled; + let mode = self + .user_info + .clone() + .map(|(username, is_admin)| { + if is_admin { + CacheMode::AllUsers + } else { + CacheMode::SingleUser(username) + } + }) + .unwrap_or(CacheMode::None); html! { -
+ {self.view_banner(ctx)}
@@ -150,7 +162,7 @@ impl Component for App {
{self.view_footer()}
-
+ } } } diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs index d652bbe..f8ee8c5 100644 --- a/app/src/components/avatar.rs +++ b/app/src/components/avatar.rs @@ -1,133 +1,37 @@ use crate::{ - components::avatar_event_bus::{AvatarEventBus, Response}, - infra::common_component::{CommonComponent, CommonComponentParts}, + components::avatar_cache::{AvatarCacheContext}, }; -use anyhow::{bail, Result}; -use graphql_client::GraphQLQuery; -use serde::{Deserialize, Serialize}; -use yew::prelude::*; -use yew_agent::{Bridge, Bridged}; +use yew::{function_component, prelude::*}; -#[derive(GraphQLQuery)] -#[graphql( - schema_path = "../schema.graphql", - query_path = "queries/get_user_avatar.graphql", - response_derives = "Debug", - custom_scalars_module = "crate::infra::graphql" -)] -pub struct GetUserAvatar; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AvatarData(String); - -impl AvatarData { - pub fn new(data: String) -> Self { - AvatarData(data) - } -} - -pub struct ShowAvatar { - common: CommonComponentParts, - avatar: Option, - _producer: Box>, -} - -/// State machine describing the possible transitions of the component state. -/// It starts out by fetching the user's details from the backend when loading. -pub enum Msg { - /// Received the user details response, either the user data or an error. - UserAvatarResponse(Result), - Update(Response), -} - -#[derive(yew::Properties, Clone, PartialEq, Eq)] +#[derive(Properties, PartialEq)] pub struct Props { pub username: String, pub width: i32, pub height: i32, } -impl CommonComponent for ShowAvatar { - fn handle_msg( - &mut self, - ctx: &Context, - msg: ::Message, - ) -> Result { - match msg { - Msg::UserAvatarResponse(response) => match response { - Ok(user) => self.avatar = user.user.avatar.map(AvatarData::new), - Err(e) => { - self.avatar = None; - bail!("Error getting user avatar: {}", e); - } - }, - Msg::Update(Response::Update((username, avatar))) => { - if username == ctx.props().username { - self.avatar = avatar; - return Ok(true); - } - return Ok(false); - } - } - Ok(true) - } - - fn mut_common(&mut self) -> &mut CommonComponentParts { - &mut self.common - } -} - -impl ShowAvatar { - fn get_user_avatar(&mut self, ctx: &Context) { - self.common.call_graphql::( - ctx, - get_user_avatar::Variables { - id: ctx.props().username.clone(), - }, - Msg::UserAvatarResponse, - "Error trying to fetch user avatar", - ) - } -} - -impl Component for ShowAvatar { - type Message = Msg; - type Properties = Props; - - fn create(ctx: &Context) -> Self { - let mut avatar = Self { - common: CommonComponentParts::::create(), - avatar: None, - _producer: AvatarEventBus::bridge(ctx.link().callback(Msg::Update)), - }; - avatar.get_user_avatar(ctx); - avatar - } - - fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { - CommonComponentParts::::update(self, ctx, msg) - } - - fn view(&self, ctx: &Context) -> Html { - match &self.avatar { - Some(avatar) => html! { - Avatar - }, - None => html! { - - - - - }, - } +#[function_component(ShowAvatar)] +pub fn show_avatar(props: &Props) -> Html { + let cache = use_context::().expect("no ctx found"); + let avatar = cache.avatars.get(&props.username).map(|val| val.clone()).unwrap_or(None); + match avatar { + Some(avatar) => html! { + Avatar + }, + None => html! { + + + + + }, } } diff --git a/app/src/components/avatar_cache.rs b/app/src/components/avatar_cache.rs new file mode 100644 index 0000000..a11e6ac --- /dev/null +++ b/app/src/components/avatar_cache.rs @@ -0,0 +1,150 @@ +use crate::infra::api::HostService; +use anyhow::Result; +use gloo_console::error; +use graphql_client::GraphQLQuery; +use std::{collections::HashMap, rc::Rc}; +use yew::prelude::*; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "queries/get_user_avatar.graphql", + response_derives = "Debug", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct GetUserAvatar; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "queries/list_users.graphql", + response_derives = "Debug,Clone,PartialEq,Eq,Hash", + variables_derives = "Clone", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct ListUserNames; + +#[derive(Debug, PartialEq, Clone)] +pub enum CacheAction { + Clear, + AddAvatar((String, Option)), +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct AvatarCache { + pub avatars: HashMap>, +} + +impl Reducible for AvatarCache { + type Action = CacheAction; + + fn reduce(self: Rc, action: Self::Action) -> Rc { + match action { + CacheAction::AddAvatar((username, avatar)) => { + let mut avatars = self.avatars.clone(); + avatars.insert(username, avatar); + AvatarCache { avatars }.into() + } + CacheAction::Clear => AvatarCache { + avatars: HashMap::new(), + } + .into(), + } + } +} + +pub type AvatarCacheContext = UseReducerHandle; + +#[derive(Debug, PartialEq, Clone)] +pub enum CacheMode { + AllUsers, + SingleUser(String), + None, +} + +#[derive(Properties, Debug, PartialEq)] +pub struct AvatarCacheProviderProps { + #[prop_or_default] + pub children: Children, + + pub mode: CacheMode, +} + +#[function_component(AvatarCacheProvider)] +pub fn avatar_cache_provider(props: &AvatarCacheProviderProps) -> Html { + let cache = use_reducer(|| AvatarCache { + avatars: HashMap::new(), + }); + { + let cache = cache.clone(); + let mode = props.mode.clone(); + use_effect_with_deps( + move |mode| { + match mode { + CacheMode::None => { + cache.dispatch(CacheAction::Clear) + } + CacheMode::AllUsers => { + let cache = cache.clone(); + wasm_bindgen_futures::spawn_local(async move { + let result = fetch_all_avatars(cache).await; + if let Err(e) = result { + error!(&format!("Could not fetch all avatars: {e:#}")) + } + }); + } + CacheMode::SingleUser(username) => { + let cache = cache.clone(); + let username = username.clone(); + wasm_bindgen_futures::spawn_local(async move { + let result = HostService::graphql_query::( + get_user_avatar::Variables { id: username }, + "Error trying to fetch user avatar", + ) + .await; + if let Ok(response) = result { + cache.dispatch(CacheAction::AddAvatar(( + response.user.id, + response.user.avatar, + ))) + } + }); + } + }; + move || cache.dispatch(CacheAction::Clear) + }, + mode, + ) + } + + html! { + context={cache}> + {props.children.clone()} + > + } +} + +async fn fetch_all_avatars(cache: UseReducerHandle) -> Result<()> { + let response = HostService::graphql_query::( + list_user_names::Variables { filters: None }, + "Error trying to fetch user list", + ) + .await?; + for user in &response.users { + let result = HostService::graphql_query::( + get_user_avatar::Variables { + id: user.id.clone(), + }, + "Error trying to fetch user avatar", + ) + .await; + if let Ok(response) = result { + cache.dispatch(CacheAction::AddAvatar(( + response.user.id, + response.user.avatar, + ))); + } + } + + return Ok(()); +} diff --git a/app/src/components/avatar_event_bus.rs b/app/src/components/avatar_event_bus.rs deleted file mode 100644 index 5b62e66..0000000 --- a/app/src/components/avatar_event_bus.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; -use yew_agent::{Agent, AgentLink, Context, HandlerId}; - -use super::avatar::AvatarData; - -#[derive(Serialize, Deserialize, Debug)] -pub enum Request { - Update((String, Option)), -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum Response { - Update((String, Option)), -} - -pub struct AvatarEventBus { - link: AgentLink, - subscribers: HashSet, -} - -impl Agent for AvatarEventBus { - type Reach = Context; - type Message = (); - type Input = Request; - type Output = Response; - - fn create(link: AgentLink) -> Self { - Self { - link, - subscribers: HashSet::new(), - } - } - - fn update(&mut self, _msg: Self::Message) {} - - fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) { - match msg { - Request::Update((username, avatar)) => { - for sub in self.subscribers.iter() { - self.link - .respond(*sub, Response::Update((username.clone(), avatar.clone()))); - } - } - } - } - - fn connected(&mut self, id: HandlerId) { - self.subscribers.insert(id); - } - - fn disconnected(&mut self, id: HandlerId) { - self.subscribers.remove(&id); - } -} diff --git a/app/src/components/mod.rs b/app/src/components/mod.rs index c406b64..6690e04 100644 --- a/app/src/components/mod.rs +++ b/app/src/components/mod.rs @@ -2,7 +2,7 @@ pub mod add_group_member; pub mod add_user_to_group; pub mod app; pub mod avatar; -pub mod avatar_event_bus; +pub mod avatar_cache; pub mod change_password; pub mod create_group; pub mod create_user; diff --git a/app/src/components/user_details_form.rs b/app/src/components/user_details_form.rs index 6872dc1..e657376 100644 --- a/app/src/components/user_details_form.rs +++ b/app/src/components/user_details_form.rs @@ -1,8 +1,7 @@ use std::str::FromStr; use crate::{ - components::avatar::AvatarData, - components::avatar_event_bus::{AvatarEventBus, Request}, + components::avatar_cache::*, components::user_details::User, infra::common_component::{CommonComponent, CommonComponentParts}, }; @@ -15,7 +14,7 @@ use graphql_client::GraphQLQuery; use validator_derive::Validate; use web_sys::{FileList, HtmlInputElement, InputEvent}; use yew::prelude::*; -use yew_agent::{Dispatched, Dispatcher}; +use yew::context::ContextHandle; use yew_form_derive::Model; #[derive(Default)] @@ -75,7 +74,8 @@ pub struct UserDetailsForm { /// True if we just successfully updated the user, to display a success message. just_updated: bool, user: User, - avatar_event_bus: Dispatcher, + avatar_cache: AvatarCacheContext, + _context_handle: ContextHandle, } pub enum Msg { @@ -160,6 +160,10 @@ impl Component for UserDetailsForm { first_name: ctx.props().user.first_name.clone(), last_name: ctx.props().user.last_name.clone(), }; + let (avatar_cache, _context_handle) = ctx + .link() + .context(Callback::noop()) + .expect("No Message Context Provided"); Self { common: CommonComponentParts::::create(), form: yew_form::Form::new(model), @@ -167,7 +171,8 @@ impl Component for UserDetailsForm { just_updated: false, reader: None, user: ctx.props().user.clone(), - avatar_event_bus: AvatarEventBus::dispatcher(), + avatar_cache, + _context_handle, } } @@ -407,13 +412,7 @@ impl UserDetailsForm { self.user.avatar = Some(avatar); } self.just_updated = true; - self.avatar_event_bus.send(Request::Update(( - self.user.id.clone(), - self.user - .avatar - .as_ref() - .map(|data| AvatarData::new(data.clone())), - ))); + self.avatar_cache.dispatch(CacheAction::AddAvatar((self.user.id.clone(), self.user.avatar.clone()))); Ok(true) } From d868bbf6511a74417dbfade78996681edba1b4b1 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 30 Mar 2023 19:50:26 +0000 Subject: [PATCH 09/10] Cleanup --- app/src/components/avatar.rs | 10 ++++++---- app/src/components/avatar_cache.rs | 14 ++++++-------- app/src/components/user_details_form.rs | 7 +++++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/src/components/avatar.rs b/app/src/components/avatar.rs index f8ee8c5..c5ea1ab 100644 --- a/app/src/components/avatar.rs +++ b/app/src/components/avatar.rs @@ -1,6 +1,4 @@ -use crate::{ - components::avatar_cache::{AvatarCacheContext}, -}; +use crate::components::avatar_cache::AvatarCacheContext; use yew::{function_component, prelude::*}; #[derive(Properties, PartialEq)] @@ -13,7 +11,11 @@ pub struct Props { #[function_component(ShowAvatar)] pub fn show_avatar(props: &Props) -> Html { let cache = use_context::().expect("no ctx found"); - let avatar = cache.avatars.get(&props.username).map(|val| val.clone()).unwrap_or(None); + let avatar = cache + .avatars + .get(&props.username) + .map(|val| val.clone()) + .unwrap_or(None); match avatar { Some(avatar) => html! { )), } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Clone)] pub struct AvatarCache { pub avatars: HashMap>, } @@ -55,14 +55,14 @@ impl Reducible for AvatarCache { pub type AvatarCacheContext = UseReducerHandle; -#[derive(Debug, PartialEq, Clone)] +#[derive(PartialEq, Clone)] pub enum CacheMode { AllUsers, SingleUser(String), None, } -#[derive(Properties, Debug, PartialEq)] +#[derive(Properties, PartialEq)] pub struct AvatarCacheProviderProps { #[prop_or_default] pub children: Children, @@ -81,9 +81,7 @@ pub fn avatar_cache_provider(props: &AvatarCacheProviderProps) -> Html { use_effect_with_deps( move |mode| { match mode { - CacheMode::None => { - cache.dispatch(CacheAction::Clear) - } + CacheMode::None => cache.dispatch(CacheAction::Clear), CacheMode::AllUsers => { let cache = cache.clone(); wasm_bindgen_futures::spawn_local(async move { diff --git a/app/src/components/user_details_form.rs b/app/src/components/user_details_form.rs index e657376..b231026 100644 --- a/app/src/components/user_details_form.rs +++ b/app/src/components/user_details_form.rs @@ -13,8 +13,8 @@ use gloo_file::{ use graphql_client::GraphQLQuery; use validator_derive::Validate; use web_sys::{FileList, HtmlInputElement, InputEvent}; -use yew::prelude::*; use yew::context::ContextHandle; +use yew::prelude::*; use yew_form_derive::Model; #[derive(Default)] @@ -412,7 +412,10 @@ impl UserDetailsForm { self.user.avatar = Some(avatar); } self.just_updated = true; - self.avatar_cache.dispatch(CacheAction::AddAvatar((self.user.id.clone(), self.user.avatar.clone()))); + self.avatar_cache.dispatch(CacheAction::AddAvatar(( + self.user.id.clone(), + self.user.avatar.clone(), + ))); Ok(true) } From b8de723156e536af0c531bffa2db58679b5bc8d5 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 30 Mar 2023 19:51:12 +0000 Subject: [PATCH 10/10] Remove yew-agent --- Cargo.lock | 198 ++----------------------------------------------- app/Cargo.toml | 1 - 2 files changed, 7 insertions(+), 192 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40934b5..b25d09f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,12 +352,6 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" -[[package]] -name = "anymap2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" - [[package]] name = "arrayref" version = "0.3.6" @@ -1532,25 +1526,6 @@ dependencies = [ "gloo-utils", ] -[[package]] -name = "gloo" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20" -dependencies = [ - "gloo-console", - "gloo-dialogs", - "gloo-events", - "gloo-file", - "gloo-history", - "gloo-net", - "gloo-render", - "gloo-storage", - "gloo-timers", - "gloo-utils", - "gloo-worker 0.2.1", -] - [[package]] name = "gloo-console" version = "0.2.3" @@ -1597,22 +1572,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-history" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd451019e0b7a2b8a7a7b23e74916601abf1135c54664e57ff71dcc26dfcdeb7" -dependencies = [ - "gloo-events", - "gloo-utils", - "serde", - "serde-wasm-bindgen 0.4.5", - "serde_urlencoded", - "thiserror", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "gloo-net" version = "0.2.6" @@ -1683,40 +1642,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-worker" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09110b5555bcafe508cee0fb94308af9aac7a85f980d3c88b270d117c6c6911d" -dependencies = [ - "anymap2", - "bincode", - "gloo-console", - "gloo-utils", - "js-sys", - "serde", - "slab", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-worker" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" -dependencies = [ - "anymap2", - "bincode", - "gloo-console", - "gloo-utils", - "js-sys", - "serde", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "graphql-introspection-query" version = "0.2.0" @@ -2113,15 +2038,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "implicit-clone" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7" -dependencies = [ - "indexmap", -] - [[package]] name = "indexmap" version = "1.6.2" @@ -2517,8 +2433,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "yew 0.19.3", - "yew-agent", + "yew", "yew-router", "yew_form", "yew_form_derive", @@ -3075,17 +2990,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pinned" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" -dependencies = [ - "futures", - "rustversion", - "thiserror", -] - [[package]] name = "pkcs1" version = "0.3.3" @@ -3150,16 +3054,6 @@ dependencies = [ "termtree", ] -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3206,23 +3100,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prokio" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" -dependencies = [ - "futures", - "gloo 0.8.0", - "num_cpus", - "once_cell", - "pin-project", - "pinned", - "tokio", - "tokio-stream", - "wasm-bindgen-futures", -] - [[package]] name = "quote" version = "1.0.23" @@ -3755,17 +3632,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_bytes" version = "0.11.9" @@ -4894,7 +4760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a1ccb53e57d3f7d847338cf5758befa811cabe207df07f543c06f502f9998cd" dependencies = [ "console_error_panic_hook", - "gloo 0.4.2", + "gloo", "gloo-utils", "indexmap", "js-sys", @@ -4903,42 +4769,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "yew-macro 0.19.3", -] - -[[package]] -name = "yew" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbecfe44343b70cc2932c3eb445425969ae21754a8ab3a0966981c1cf7af1cc" -dependencies = [ - "console_error_panic_hook", - "futures", - "gloo 0.8.0", - "implicit-clone", - "indexmap", - "js-sys", - "prokio", - "rustversion", - "serde", - "slab", - "thiserror", - "tokio", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yew-macro 0.20.0", -] - -[[package]] -name = "yew-agent" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06f7c5ed97fff22816bb00d3d82ebc0fc1119d7bbb9e07e62c0d2853f51920a" -dependencies = [ - "gloo-worker 0.1.2", - "yew 0.20.0", + "yew-macro", ] [[package]] @@ -4955,38 +4786,23 @@ dependencies = [ "syn", ] -[[package]] -name = "yew-macro" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64c253c1d401f1ea868ca9988db63958cfa15a69f739101f338d6f05eea8301" -dependencies = [ - "boolinator", - "once_cell", - "prettyplease", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "yew-router" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155804f6f3aa309f596d5c3fa14486a94e7756f1edd7634569949e401d5099f2" dependencies = [ - "gloo 0.4.2", + "gloo", "gloo-utils", "js-sys", "route-recognizer", "serde", - "serde-wasm-bindgen 0.3.1", + "serde-wasm-bindgen", "serde_urlencoded", "thiserror", "wasm-bindgen", "web-sys", - "yew 0.19.3", + "yew", "yew-router-macro", ] @@ -5011,7 +4827,7 @@ dependencies = [ "validator_derive", "wasm-bindgen", "web-sys", - "yew 0.19.3", + "yew", ] [[package]] diff --git a/app/Cargo.toml b/app/Cargo.toml index 844924c..e2a7fed 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -24,7 +24,6 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "*" yew = "0.19.3" yew-router = "0.16" -yew-agent = "0.2.0" # Needed because of https://github.com/tkaitchuck/aHash/issues/95 indexmap = "=1.6.2"