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,