This commit is contained in:
Austin 2023-03-22 20:08:43 +00:00
parent 970af4f01a
commit 590825fe73
7 changed files with 110 additions and 38 deletions

26
Cargo.lock generated
View File

@ -352,6 +352,12 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "anymap2"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.6" version = "0.3.6"
@ -2434,6 +2440,7 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"yew", "yew",
"yew-agent",
"yew-router", "yew-router",
"yew_form", "yew_form",
"yew_form_derive", "yew_form_derive",
@ -4798,6 +4805,25 @@ dependencies = [
"yew-macro", "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]] [[package]]
name = "yew-macro" name = "yew-macro"
version = "0.19.3" version = "0.19.3"

View File

@ -24,6 +24,7 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = "*" wasm-bindgen-futures = "*"
yew = "0.19.3" yew = "0.19.3"
yew-router = "0.16" yew-router = "0.16"
yew-agent = "0.1.0"
# Needed because of https://github.com/tkaitchuck/aHash/issues/95 # Needed because of https://github.com/tkaitchuck/aHash/issues/95
indexmap = "=1.6.2" indexmap = "=1.6.2"

View File

@ -350,15 +350,7 @@ impl App {
id="dropdownUser" id="dropdownUser"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false"> aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" <Avatar username={user_id.clone()} width=32 height=32 />
width="32"
height="32"
fill="currentColor"
class="bi bi-person-circle"
viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
</svg>
<span class="ms-2"> <span class="ms-2">
{user_id} {user_id}
</span> </span>

View File

@ -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 anyhow::{bail, Result};
use graphql_client::GraphQLQuery; use graphql_client::GraphQLQuery;
use yew::prelude::*; use yew::{prelude::*};
use yew_agent::{Bridge, Bridged};
#[derive(GraphQLQuery)] #[derive(GraphQLQuery)]
#[graphql( #[graphql(
@ -17,6 +20,7 @@ pub struct Avatar {
/// The user info. If none, the error is in `error`. If `error` is None, then we haven't /// The user info. If none, the error is in `error`. If `error` is None, then we haven't
/// received the server response yet. /// received the server response yet.
avatar: Option<String>, avatar: Option<String>,
_producer: Box<dyn Bridge<AvatarEventBus>>,
} }
/// State machine describing the possible transitions of the component state. /// State machine describing the possible transitions of the component state.
@ -35,16 +39,16 @@ pub struct Props {
} }
impl CommonComponent<Avatar> for Avatar { impl CommonComponent<Avatar> for Avatar {
fn handle_msg(&mut self, msg: <Self as Component>::Message) -> Result<bool> { fn handle_msg(&mut self, ctx: &Context<Self>, msg: <Self as Component>::Message) -> Result<bool> {
match msg { match msg {
Msg::UserAvatarResponse(response) => match response { Msg::UserAvatarResponse(response) => match response {
Ok(user) => self.avatar = user.user.avatar, Ok(user) => self.avatar = user.user.avatar,
Err(e) => { Err(e) => {
self.avatar = None; 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) Ok(true)
} }
@ -55,16 +59,15 @@ impl CommonComponent<Avatar> for Avatar {
} }
impl Avatar { impl Avatar {
fn get_user_avatar(&mut self) { fn get_user_avatar(&mut self, ctx: &Context<Self>) {
if self.common.username.len() > 0 { self.common.call_graphql::<GetUserAvatar, _>(
self.common.call_graphql::<GetUserAvatar, _>( ctx,
get_user_avatar::Variables { get_user_avatar::Variables {
id: self.common.username.clone(), id: ctx.props().username.clone(),
}, },
Msg::UserAvatarResponse, Msg::UserAvatarResponse,
"Error trying to fetch user avatar", "Error trying to fetch user avatar",
) )
}
} }
} }
@ -72,36 +75,33 @@ impl Component for Avatar {
type Message = Msg; type Message = Msg;
type Properties = Props; type Properties = Props;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
let mut avatar = Self { let mut avatar = Self {
common: CommonComponentParts::<Self>::create(props, link), common: CommonComponentParts::<Self>::create(),
avatar: None, avatar: None,
_producer: AvatarEventBus::bridge(ctx.link().callback(|_| Msg::Update)),
}; };
avatar.get_user_avatar(); avatar.get_user_avatar(ctx);
avatar avatar
} }
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
CommonComponentParts::<Self>::update(self, msg) CommonComponentParts::<Self>::update(self, ctx, msg)
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn view(&self, ctx: &Context<Self>) -> Html {
self.common.change(props)
}
fn view(&self) -> Html {
match &self.avatar { match &self.avatar {
Some(avatar) => html! { Some(avatar) => html! {
<img <img
id="avatarDisplay" id="avatarDisplay"
src={format!("data:image/jpeg;base64, {}", avatar)} src={format!("data:image/jpeg;base64, {}", avatar)}
style={format!("max-height:{}px;max-width:{}px;height:auto;width:auto;", self.common.props.height,self.common.props.width)} style={format!("max-height:{}px;max-width:{}px;height:auto;width:auto;", ctx.props().height,ctx.props().width)}
alt="Avatar" /> alt="Avatar" />
}, },
None => html! { None => html! {
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
width={self.common.props.width.to_string()} width={ctx.props().width.to_string()}
height={self.common.props.height.to_string()} height={ctx.props().height.to_string()}
fill="currentColor" fill="currentColor"
class="bi bi-person-circle" class="bi bi-person-circle"
viewBox="0 0 16 16"> viewBox="0 0 16 16">

View File

@ -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<AvatarEventBus>,
subscribers: HashSet<HandlerId>,
}
impl Agent for AvatarEventBus {
type Reach = Context<Self>;
type Message = ();
type Input = Request;
type Output = ();
fn create(link: AgentLink<Self>) -> 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);
}
}

View File

@ -2,6 +2,7 @@ pub mod add_group_member;
pub mod add_user_to_group; pub mod add_user_to_group;
pub mod app; pub mod app;
pub mod avatar; pub mod avatar;
pub mod avatar_event_bus;
pub mod change_password; pub mod change_password;
pub mod create_group; pub mod create_group;
pub mod create_user; pub mod create_user;

View File

@ -2,6 +2,7 @@ use std::str::FromStr;
use crate::{ use crate::{
components::user_details::User, components::user_details::User,
components::avatar_event_bus::{AvatarEventBus, Request},
infra::common_component::{CommonComponent, CommonComponentParts}, infra::common_component::{CommonComponent, CommonComponentParts},
}; };
use anyhow::{bail, Error, Result}; use anyhow::{bail, Error, Result};
@ -14,6 +15,7 @@ use validator_derive::Validate;
use web_sys::{FileList, HtmlInputElement, InputEvent}; use web_sys::{FileList, HtmlInputElement, InputEvent};
use yew::prelude::*; use yew::prelude::*;
use yew_form_derive::Model; use yew_form_derive::Model;
use yew_agent::{Dispatched, Dispatcher};
#[derive(Default)] #[derive(Default)]
struct JsFile { struct JsFile {
@ -72,6 +74,7 @@ pub struct UserDetailsForm {
/// True if we just successfully updated the user, to display a success message. /// True if we just successfully updated the user, to display a success message.
just_updated: bool, just_updated: bool,
user: User, user: User,
avatar_event_bus: Dispatcher<AvatarEventBus>,
} }
pub enum Msg { pub enum Msg {
@ -163,6 +166,7 @@ impl Component for UserDetailsForm {
just_updated: false, just_updated: false,
reader: None, reader: None,
user: ctx.props().user.clone(), user: ctx.props().user.clone(),
avatar_event_bus: AvatarEventBus::dispatcher(),
} }
} }
@ -402,6 +406,7 @@ impl UserDetailsForm {
self.user.avatar = Some(avatar); self.user.avatar = Some(avatar);
} }
self.just_updated = true; self.just_updated = true;
self.avatar_event_bus.send(Request::Update);
Ok(true) Ok(true)
} }