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) }