diff --git a/Cargo.lock b/Cargo.lock index 6984743..c3d7414 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1770,6 +1770,7 @@ dependencies = [ "yew-router", "yew_form", "yew_form_derive", + "yewtil", ] [[package]] @@ -3537,6 +3538,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0" dependencies = [ "cfg-if 1.0.0", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -3757,6 +3760,19 @@ dependencies = [ "yew_form", ] +[[package]] +name = "yewtil" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543663ac49cd613df079282a1d8bdbdebdad6e02bac229f870fd4237b5d9aaa" +dependencies = [ + "log", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew", +] + [[package]] name = "zeroize" version = "1.4.1" diff --git a/app/Cargo.toml b/app/Cargo.toml index cc03fb1..5e61f0e 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -18,6 +18,7 @@ validator = "*" validator_derive = "*" wasm-bindgen = "0.2" yew = "0.18" +yewtil = "*" yew-router = "0.15" yew_form = "0.1.8" yew_form_derive = "*" diff --git a/app/src/components/add_user_to_group.rs b/app/src/components/add_user_to_group.rs index b805cc2..bfbae08 100644 --- a/app/src/components/add_user_to_group.rs +++ b/app/src/components/add_user_to_group.rs @@ -1,7 +1,7 @@ use crate::{ components::{ select::{Select, SelectOption, SelectOptionProps}, - user_details::{Group, User}, + user_details::Group, }, infra::api::HostService, }; @@ -12,6 +12,7 @@ use yew::{ prelude::*, services::{fetch::FetchTask, ConsoleService}, }; +use yewtil::NeqAssign; #[derive(GraphQLQuery)] #[graphql( @@ -63,7 +64,8 @@ pub enum Msg { #[derive(yew::Properties, Clone, PartialEq)] pub struct Props { - pub user: User, + pub username: String, + pub groups: Vec, pub on_user_added_to_group: Callback, pub on_error: Callback, } @@ -89,7 +91,7 @@ impl AddUserToGroupComponent { }; self._task = HostService::graphql_query::( add_user_to_group::Variables { - user: self.props.user.id.clone(), + user: self.props.username.clone(), group: group_id, }, self.link.callback(Msg::AddGroupResponse), @@ -133,7 +135,7 @@ impl AddUserToGroupComponent { } fn get_selectable_group_list(&self, group_list: &[Group]) -> Vec { - let user_groups = self.props.user.groups.iter().collect::>(); + let user_groups = self.props.groups.iter().collect::>(); group_list .iter() .filter(|g| !user_groups.contains(g)) @@ -168,12 +170,7 @@ impl Component for AddUserToGroupComponent { } fn change(&mut self, props: Self::Properties) -> ShouldRender { - if props.user.groups != self.props.user.groups { - self.props.user = props.user; - true - } else { - false - } + self.props.neq_assign(props) } fn view(&self) -> Html { diff --git a/app/src/components/mod.rs b/app/src/components/mod.rs index d8c8778..77f0244 100644 --- a/app/src/components/mod.rs +++ b/app/src/components/mod.rs @@ -4,6 +4,7 @@ pub mod change_password; pub mod create_user; pub mod login; pub mod logout; +pub mod remove_user_from_group; pub mod router; pub mod select; pub mod user_details; diff --git a/app/src/components/remove_user_from_group.rs b/app/src/components/remove_user_from_group.rs new file mode 100644 index 0000000..765de9c --- /dev/null +++ b/app/src/components/remove_user_from_group.rs @@ -0,0 +1,111 @@ +use crate::{components::user_details::Group, infra::api::HostService}; +use anyhow::{Error, Result}; +use graphql_client::GraphQLQuery; +use yew::{ + prelude::*, + services::{fetch::FetchTask, ConsoleService}, +}; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "queries/remove_user_from_group.graphql", + response_derives = "Debug", + variables_derives = "Clone", + custom_scalars_module = "crate::infra::graphql" +)] +pub struct RemoveUserFromGroup; + +pub struct RemoveUserFromGroupComponent { + link: ComponentLink, + props: Props, + // Used to keep the request alive long enough. + _task: Option, +} + +#[derive(yew::Properties, Clone, PartialEq)] +pub struct Props { + pub username: String, + pub group: Group, + pub is_admin: bool, + pub on_user_removed_from_group: Callback, + pub on_error: Callback, +} + +pub enum Msg { + SubmitRemoveGroup, + RemoveGroupResponse(Result), +} + +impl RemoveUserFromGroupComponent { + fn submit_remove_group(&mut self) -> Result { + let group = self.props.group.id; + self._task = HostService::graphql_query::( + remove_user_from_group::Variables { + user: self.props.username.clone(), + group, + }, + self.link.callback(Msg::RemoveGroupResponse), + "Error trying to initiate removing the user from a group", + ) + .map_err(|e| { + ConsoleService::log(&e.to_string()); + e + }) + .ok(); + Ok(true) + } + + fn handle_msg(&mut self, msg: ::Message) -> Result { + match msg { + Msg::SubmitRemoveGroup => return self.submit_remove_group(), + Msg::RemoveGroupResponse(response) => { + response?; + self.props + .on_user_removed_from_group + .emit(self.props.group.clone()); + } + } + Ok(true) + } +} + +impl Component for RemoveUserFromGroupComponent { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + Self { + link, + props, + _task: None, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match self.handle_msg(msg) { + Err(e) => { + self.props.on_error.emit(e); + true + } + Ok(b) => b, + } + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + let group = &self.props.group; + html! { + <> + {&group.display_name} + { if self.props.is_admin { html! { + + }} else { html!{} } + } + + } + } +} diff --git a/app/src/components/select.rs b/app/src/components/select.rs index 851e58a..e78581e 100644 --- a/app/src/components/select.rs +++ b/app/src/components/select.rs @@ -1,8 +1,10 @@ use yew::{html::ChangeData, prelude::*}; +use yewtil::NeqAssign; pub struct Select { link: ComponentLink, props: SelectProps, + node_ref: NodeRef, } #[derive(yew::Properties, Clone, PartialEq, Debug)] @@ -26,49 +28,48 @@ impl Select { .nth(nth as usize) .map(|child| child.props) } + + fn send_selection_update(&self) { + let select_node = self.node_ref.cast::().unwrap(); + self.props + .on_selection_change + .emit(self.get_nth_child_props(select_node.selected_index())) + } } impl Component for Select { type Message = SelectMsg; type Properties = SelectProps; fn create(props: Self::Properties, link: ComponentLink) -> Self { - let res = Self { link, props }; - res.props - .on_selection_change - .emit(res.get_nth_child_props(0)); - res + Self { + link, + props, + node_ref: NodeRef::default(), + } } + + fn rendered(&mut self, _first_render: bool) { + self.send_selection_update(); + } + fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - SelectMsg::OnSelectChange(data) => match data { - ChangeData::Select(e) => { - self.props - .on_selection_change - .emit(self.get_nth_child_props(e.selected_index())); - } - _ => unreachable!(), - }, + let SelectMsg::OnSelectChange(data) = msg; + match data { + ChangeData::Select(_) => self.send_selection_update(), + _ => unreachable!(), } false } + fn change(&mut self, props: Self::Properties) -> ShouldRender { - if self.props.children.len() != props.children.len() { - let was_empty = self.props.children.is_empty(); - self.props = props; - if self.props.children.is_empty() || was_empty { - self.props - .on_selection_change - .emit(self.get_nth_child_props(0)); - } - true - } else { - false - } + self.props.children.neq_assign(props.children) } + fn view(&self) -> Html { html! { } @@ -79,7 +80,7 @@ pub struct SelectOption { props: SelectOptionProps, } -#[derive(yew::Properties, Clone, PartialEq)] +#[derive(yew::Properties, Clone, PartialEq, Debug)] pub struct SelectOptionProps { pub value: String, pub text: String, @@ -88,20 +89,19 @@ pub struct SelectOptionProps { impl Component for SelectOption { type Message = (); type Properties = SelectOptionProps; + fn create(props: Self::Properties, _: ComponentLink) -> Self { Self { props } } + fn update(&mut self, _: Self::Message) -> ShouldRender { false } + fn change(&mut self, props: Self::Properties) -> ShouldRender { - if self.props != props { - self.props = props; - true - } else { - false - } + self.props.neq_assign(props) } + fn view(&self) -> Html { html! {