From adf3577f0ea95ed1e0e0fb4a8a43c3e76c3011cb Mon Sep 17 00:00:00 2001 From: Austin Alvarado Date: Fri, 9 Feb 2024 05:31:46 +0000 Subject: [PATCH] commit so i can pull in fixes from master --- app/Cargo.toml | 4 ++ app/queries/get_user_details.graphql | 12 ++++ app/src/components/form/attribute_input.rs | 70 ++++++++++++++++++ app/src/components/form/mod.rs | 1 + app/src/components/user_details.rs | 2 + app/src/components/user_details_form.rs | 84 ++++++++++++++++++++-- app/src/infra/schema.rs | 2 +- 7 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 app/src/components/form/attribute_input.rs diff --git a/app/Cargo.toml b/app/Cargo.toml index 5ce750a..831a816 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -37,12 +37,16 @@ version = "0.3" features = [ "Document", "Element", + "Event", "FileReader", + "FormData", "HtmlDocument", + "HtmlFormElement", "HtmlInputElement", "HtmlOptionElement", "HtmlOptionsCollection", "HtmlSelectElement", + "SubmitEvent", "console", ] diff --git a/app/queries/get_user_details.graphql b/app/queries/get_user_details.graphql index 97370fb..93100cb 100644 --- a/app/queries/get_user_details.graphql +++ b/app/queries/get_user_details.graphql @@ -12,5 +12,17 @@ query GetUserDetails($id: String!) { id displayName } + attributes { + name + value + schema { + name + attributeType + isList + isVisible + isEditable + isHardcoded + } + } } } diff --git a/app/src/components/form/attribute_input.rs b/app/src/components/form/attribute_input.rs new file mode 100644 index 0000000..14279b2 --- /dev/null +++ b/app/src/components/form/attribute_input.rs @@ -0,0 +1,70 @@ +use crate::infra::schema::AttributeType; +use yew::{ + function_component, html, virtual_dom::AttrValue, Callback, InputEvent, NodeRef, Properties, +}; + +/* + +*/ + +#[derive(Properties, PartialEq)] +struct AttributeInputProps { + name: AttrValue, + attribute_type: AttributeType, + #[prop_or(None)] + value: Option, +} + +#[function_component(AttributeInput)] +fn attribute_input(props: &AttributeInputProps) -> Html { + let input_type = match props.attribute_type { + AttributeType::String => "text", + AttributeType::Integer => "number", + AttributeType::DateTime => "datetime-local", + AttributeType::Jpeg => "file", + }; + let accept = match props.attribute_type { + AttributeType::Jpeg => Some("image/jpeg"), + _ => None, + }; + html! { + + } +} + +#[derive(Properties, PartialEq)] +pub struct SingleAttributeInputProps { + pub name: AttrValue, + pub attribute_type: AttributeType, + #[prop_or(None)] + pub value: Option, +} + +#[function_component(SingleAttributeInput)] +pub fn single_attribute_input(props: &SingleAttributeInputProps) -> Html { + html! { +
+ +
+ +
+
+ } +} diff --git a/app/src/components/form/mod.rs b/app/src/components/form/mod.rs index f46bded..a203833 100644 --- a/app/src/components/form/mod.rs +++ b/app/src/components/form/mod.rs @@ -1,3 +1,4 @@ +pub mod attribute_input; pub mod checkbox; pub mod field; pub mod select; diff --git a/app/src/components/user_details.rs b/app/src/components/user_details.rs index f11ac4c..66e36cd 100644 --- a/app/src/components/user_details.rs +++ b/app/src/components/user_details.rs @@ -22,6 +22,8 @@ pub struct GetUserDetails; pub type User = get_user_details::GetUserDetailsUser; pub type Group = get_user_details::GetUserDetailsUserGroups; +pub type Attribute = get_user_details::GetUserDetailsUserAttributes; +pub type AttributeSchema = get_user_details::GetUserDetailsUserAttributesSchema; pub struct UserDetails { common: CommonComponentParts, diff --git a/app/src/components/user_details_form.rs b/app/src/components/user_details_form.rs index 663e78b..31fe380 100644 --- a/app/src/components/user_details_form.rs +++ b/app/src/components/user_details_form.rs @@ -3,18 +3,20 @@ use std::str::FromStr; use crate::{ components::{ form::{field::Field, static_value::StaticValue, submit::Submit}, - user_details::User, + user_details::{AttributeSchema, User}, }, infra::common_component::{CommonComponent, CommonComponentParts}, }; -use anyhow::{bail, Error, Result}; +use anyhow::{anyhow, bail, Error, Ok, Result}; +use gloo_console::log; use gloo_file::{ callbacks::{read_as_bytes, FileReader}, File, }; use graphql_client::GraphQLQuery; +use validator::HasLen; use validator_derive::Validate; -use web_sys::{FileList, HtmlInputElement, InputEvent}; +use web_sys::{FileList, FormData, HtmlFormElement, HtmlInputElement, InputEvent}; use yew::prelude::*; use yew_form_derive::Model; @@ -73,6 +75,7 @@ pub struct UserDetailsForm { /// True if we just successfully updated the user, to display a success message. just_updated: bool, user: User, + form_ref: NodeRef, } pub enum Msg { @@ -150,7 +153,14 @@ impl CommonComponent for UserDetailsForm { } self.reader = None; Ok(false) - } + } // Msg::OnSubmit(e) => { + // e.prevent_default(); + // let form: HtmlFormElement = e.target_unchecked_into(); + // let data = FormData::new_with_form(&form).unwrap(); + // log!(format!("form data{:#?}", data)); + // log!(format!("form data data{:#?}", *data)); + // Ok(true) + // } } } @@ -177,6 +187,7 @@ impl Component for UserDetailsForm { just_updated: false, reader: None, user: ctx.props().user.clone(), + form_ref: NodeRef::default(), } } @@ -278,7 +289,7 @@ impl Component for UserDetailsForm { + onclick={link.callback(|e: MouseEvent| {Msg::SubmitClicked})} /> { if let Some(e) = &self.common.error { @@ -297,6 +308,32 @@ impl Component for UserDetailsForm { } } +type AttributeValue = (String, Vec); + +fn get_values_from_form_data( + schema: Vec, + form: &FormData, +) -> Result> { + schema + .into_iter() + .map(|attr| -> Result { + let val = form + .get_all(attr.name.as_str()) + .iter() + .map(|js_val| js_val.as_string().unwrap()) + .filter(|val| !val.is_empty()) + .collect::>(); + if val.length() > 1 && !attr.is_list { + return Err(anyhow!( + "Multiple values supplied for non-list attribute {}", + attr.name + )); + } + Ok((attr.name.clone(), val)) + }) + .collect() +} + impl UserDetailsForm { fn submit_user_update_form(&mut self, ctx: &Context) -> Result { if !self.form.validate() { @@ -309,7 +346,40 @@ impl UserDetailsForm { { bail!("Image file hasn't finished loading, try again"); } + let form = self.form_ref.cast::().unwrap(); + let form_data = FormData::new_with_form(&form) + .map_err(|e| anyhow!("Failed to get FormData: {:#?}", e.as_string()))?; + let mut all_values = get_values_from_form_data( + self.user + .attributes + .iter() + .map(|attr| attr.schema.clone()) + .filter(|attr| !attr.is_hardcoded) + .filter(|attr| attr.is_editable) + .collect(), + &form_data, + )?; let base_user = &self.user; + let base_attrs = &self.user.attributes; + all_values.retain(|(name, val)| { + let name = name.clone(); + let base_val = base_attrs + .into_iter() + .find(|base_val| base_val.name == name) + .unwrap(); + let new_values = val.clone(); + base_val.value != new_values + }); + let remove_names: Option> = if all_values.is_empty() { + None + } else { + Some(all_values.iter().map(|(name, _)| name.clone()).collect()) + }; + let insert_attrs: Option> = if remove_names.is_none() { + None + } else { + Some(all_values.into_iter().map(|(name, value)| update_user::AttributeValueInput{name, value}).collect()) + }; let mut user_input = update_user::UpdateUserInput { id: self.user.id.clone(), email: None, @@ -317,8 +387,8 @@ impl UserDetailsForm { firstName: None, lastName: None, avatar: None, - removeAttributes: None, - insertAttributes: None, + removeAttributes: remove_names, + insertAttributes: insert_attrs, }; let default_user_input = user_input.clone(); let model = self.form.model(); diff --git a/app/src/infra/schema.rs b/app/src/infra/schema.rs index b55aab6..89749cb 100644 --- a/app/src/infra/schema.rs +++ b/app/src/infra/schema.rs @@ -2,7 +2,7 @@ use anyhow::Result; use std::{fmt::Display, str::FromStr}; use validator::ValidationError; -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum AttributeType { String, Integer,