diff options
| author | jelemux <jeremias.weber@protonmail.com> | 2021-01-28 17:41:49 +0100 | 
|---|---|---|
| committer | jelemux <jeremias.weber@protonmail.com> | 2021-01-28 17:41:49 +0100 | 
| commit | 3f0892368bcf475fdae39f24ef51b82b3013535f (patch) | |
| tree | 087a0869e618cc37b48e6d462c99bc1b289ddabf /src | |
| parent | 4a0c73eebb8dfd6a5543945049175f64b9817c96 (diff) | |
| download | wasm-card-3f0892368bcf475fdae39f24ef51b82b3013535f.tar.gz wasm-card-3f0892368bcf475fdae39f24ef51b82b3013535f.tar.bz2  | |
try to fix problem with vcard mutable references
Diffstat (limited to 'src')
| -rw-r--r-- | src/view/address.rs | 54 | ||||
| -rw-r--r-- | src/view/main.rs | 437 | ||||
| -rw-r--r-- | src/view/mod.rs | 43 | ||||
| -rw-r--r-- | src/view/name.rs | 61 | ||||
| -rw-r--r-- | src/view/telephone.rs | 54 | ||||
| -rw-r--r-- | src/viewmodel/address.rs | 11 | ||||
| -rw-r--r-- | src/viewmodel/mod.rs | 9 | ||||
| -rw-r--r-- | src/viewmodel/name.rs | 72 | ||||
| -rw-r--r-- | src/viewmodel/telephone.rs | 6 | ||||
| -rw-r--r-- | src/viewmodel/utility.rs | 4 | 
10 files changed, 552 insertions, 199 deletions
diff --git a/src/view/address.rs b/src/view/address.rs index e26de04..b833518 100644 --- a/src/view/address.rs +++ b/src/view/address.rs @@ -1,14 +1,15 @@ +use super::WeakComponentLink;  use yew::prelude::*;  use vcard::properties;  use crate::viewmodel::address::*;  use crate::viewmodel::VCardPropertyInputObject;  use super::VCardPropertyInputComponent; +use crate::viewmodel::Error;  pub struct AddressView { -    link: ComponentLink<Self>, +    props: Props,      value: Address, -    oninput: Callback<Address>, -    errors: Vec<String>, +    error: Option<Error>,  }  pub enum Msg { @@ -21,12 +22,14 @@ pub enum Msg {      UpdateCountry(String),      ToggleWork,      ToggleHome, + +    Generate,  }  #[derive(Clone, PartialEq, Properties)]  pub struct Props { -    pub oninput: Callback<Address>, -    //pub errors: Vec<String>, +    pub generated: Callback<Result<properties::Address,()>>, +    pub weak_link: WeakComponentLink<AddressView>,  }  impl VCardPropertyInputComponent<properties::Address, Address> for AddressView { @@ -36,8 +39,8 @@ impl VCardPropertyInputComponent<properties::Address, Address> for AddressView {      fn get_title(&self) -> String {          "Address".to_string()      } -    fn get_errors(&self) -> Vec<String> { -        self.errors.clone() +    fn get_error(&self) -> Option<Error> { +        self.error.clone()      }  } @@ -45,11 +48,11 @@ impl Component for AddressView {      type Message = Msg;      type Properties = Props;      fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { +        props.weak_link.borrow_mut().replace(link);          Self { -            link, +            props,              value: Address::new(), -            oninput: props.oninput, -            errors: vec![], +            error: None,          }      }      fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { @@ -63,26 +66,39 @@ impl Component for AddressView {              Msg::UpdateCountry(c) => self.value.country = c,              Msg::ToggleWork => self.value.work = !self.value.work,              Msg::ToggleHome => self.value.home = !self.value.home, +            Generate => { +                match self.value.to_vcard_property() { +                    Ok(address) => { +                        self.props.generated.emit(Ok(address)); +                        return false; +                    }, +                    Err(error) => { +                        self.props.generated.emit(Err(())); +                        self.error = Some(error); +                    }, +                }; +            },          }; -        self.oninput.emit(self.value.clone());          true      }      fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool {  -        self.oninput = props.oninput; -        true +        if self.props != props { +            self.props = props; +            true +        } else { +            false +        }      }      fn view(&self) -> yew::virtual_dom::VNode { +        let link = self.props.weak_link.borrow().clone().unwrap(); +          html!{              <div class="box"> -                { -                    self.render_errors() -                } +                { self.render_error() }                  <h3 class="subtitle">{ self.get_title() }</h3> -                { -                    self.get_input_object().render(&self.link) -                } +                { self.get_input_object().render(&link) }              </div>          } diff --git a/src/view/main.rs b/src/view/main.rs index 684db84..ce8c283 100644 --- a/src/view/main.rs +++ b/src/view/main.rs @@ -1,35 +1,47 @@ -use crate::view::telephone::TelephoneView; +use crate::viewmodel::Error; +use crate::view::telephone::{self,TelephoneView};  use std::collections::HashSet; -use super::name::{NameView}; -use super::address::AddressView; +use super::WeakComponentLink; +use super::name::{self,NameView}; +use super::address::{self,AddressView};  use genpdf::Element as _;  use genpdf::{elements, style, fonts};  use qrcodegen::QrCode;  use qrcodegen::QrCodeEcc;  use yew::prelude::*; +use vcard::properties;  use vcard::{VCard, VCardError};  use crate::viewmodel::utility::*; -use crate::viewmodel::name::Name; -use crate::viewmodel::address::Address; -use crate::viewmodel::telephone::Telephone; -use crate::viewmodel::VCardPropertyInputObject;  pub struct MainView {      link: ComponentLink<Self>, -    error: Vec<String>, -    name: Name, -    address: Address, -    telephone: Telephone, +    error: Option<Error>,      download: Option<Download>,      selected_option: DownloadOption, +    vcard: Option<VCard>, + +    name_links: Vec<WeakComponentLink<NameView>>, +    address_links: Vec<WeakComponentLink<AddressView>>, +    telephone_links: Vec<WeakComponentLink<TelephoneView>>, + +    answer_count: usize,  }  pub enum Msg { -    UpdateName(Name), -    UpdateAddress(Address), -    UpdateTelephone(Telephone), -    Generate(DownloadOption), +    AddName, +    AddAddress, +    AddTelephone, + +    ChangeDownloadOption(DownloadOption), + +    Generate, +    GeneratedFormattedName(Result<properties::FormattedName,()>), +    GeneratedName(Result<properties::Name,()>), +    GeneratedAddress(Result<properties::Address,()>), +    GeneratedTelephone(Result<properties::Telephone,()>), +    GenerationComplete, +      Nope,  } @@ -40,71 +52,59 @@ impl Component for MainView {      fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {          MainView {               link,  -            error: vec![],  -            name: Name::new(),  -            address: Address::new(), -            telephone: Telephone::new(), +            error: None,               download: None,  -            selected_option: DownloadOption::VCard  +            selected_option: DownloadOption::VCard, +            vcard: None, + +            name_links: vec![WeakComponentLink::default()], +            address_links: vec![WeakComponentLink::default()], +            telephone_links: vec![WeakComponentLink::default()], +            answer_count: 0,          }      }      fn update(&mut self, msg: Self::Message) -> ShouldRender { -        self.error.clear(); +        let shouldrender; // let the compiler check if it is always set +        self.error = None; +          match msg { -            Msg::UpdateName(value) => { -                self.name = value; -                self.link.send_message(Msg::Generate(self.selected_option)); +            Msg::AddName => { +                self.name_links.push(WeakComponentLink::default()); +                shouldrender = true;              }, -            Msg::UpdateAddress(value) => { -                self.address = value; -                self.link.send_message(Msg::Generate(self.selected_option)); +            Msg::AddAddress => { +                self.address_links.push(WeakComponentLink::default()); +                shouldrender = true;              }, -            Msg::UpdateTelephone(value) => { -                self.telephone = value; -                self.link.send_message(Msg::Generate(self.selected_option)); +            Msg::AddTelephone => { +                self.telephone_links.push(WeakComponentLink::default()); +                shouldrender = true;              }, -            Msg::Generate(option) => { +            Msg::ChangeDownloadOption(option) => {                  self.selected_option = option; +                shouldrender = false; +            }, +            Msg::Generate => { -                let vcard_content = match self.generate_vcard() { -                    Ok(vcard) => Some(vcard.to_string()), -                    Err(VCardError::FormatError(err)) => { -                        self.error.push(err.to_string()); -                        None -                    } -                    Err(VCardError::EmptyFormatName) => { -                        self.error.push(String::from("At least one of the name fields should be filled out.")); -                        None +                if self.selected_option == DownloadOption::VCard || self.selected_option == DownloadOption::QrCode { + +                    for name_link in self.name_links.iter() { +                        let name_link = name_link.borrow().clone().unwrap(); +                        name_link.send_message(name::Msg::Generate);                      } -                }; -                match option { -                    DownloadOption::VCard => { -                        if vcard_content.is_some() { -                            self.download = Some( -                                Download {  -                                    file_name: format!("{}.vcs", self.name.formatted_name()),  -                                    content: vcard_content.unwrap().to_string(),  -                                    mime_type: MimeType::VCard, -                                } -                            ) -                        } +                    for address_link in self.address_links.iter() { +                        let address_link = address_link.borrow().clone().unwrap(); +                        address_link.send_message(address::Msg::Generate);                      } -                    DownloadOption::QrCode => { -                        if vcard_content.is_some() { -                            match QrCode::encode_text(vcard_content.as_ref().unwrap(), QrCodeEcc::Low) { -                                Ok(qr) => self.download = Some( -                                    Download { -                                        file_name: format!("QR-Code VCard {}.svg", self.name.formatted_name()), -                                        content: qr.to_svg_string(4), -                                        mime_type: MimeType::SVG, -                                    } -                                ), -                                Err(_) => self.error.push(String::from("Sorry, VCard is too long!")), -                            }; -                        } + +                    for telephone_link in self.telephone_links.iter() { +                        let telephone_link = telephone_link.borrow().clone().unwrap(); +                        telephone_link.send_message(telephone::Msg::Generate);                      } +                } +                /*                      DownloadOption::PDF => {                          match self.generate_pdf() {                              Ok(pdf) => self.download = Some( @@ -118,13 +118,192 @@ impl Component for MainView {                          }                      }                  } -            } -            Msg::Nope => return false, +                */ + +                shouldrender = true; +            }, +            Msg::GeneratedFormattedName(formatted_name) => { + +                self.answer_count += 1; + +                match formatted_name { +                    Ok(formatted_name) => { +                        match &mut self.vcard { +                            None => { +                                match VCard::from_formatted_name(formatted_name) { +                                    Ok(vcard) => self.vcard = Some(vcard), +                                    Err(VCardError::FormatError(err)) => { +                                        self.error = Some(Error{ +                                            msg: err.to_string(), +                                        }); +                                    }, +                                    Err(VCardError::EmptyFormatName) => { +                                        self.error= Some(Error{ +                                            msg: String::from("At least one of the name fields should be filled out."), +                                        }); +                                    }, +                                }; +                            }, +                            Some(vcard) => { +                                vcard.formatted_names.insert(formatted_name); +                            }, +                        }; +                    }, +                    Err(_) => (), +                }; + +                shouldrender = true; +            }, +            Msg::GeneratedName(name) => { + +                self.answer_count += 1; + +                match name { +                    Ok(name) => { +                        match self.vcard { +                            Some(vcard) => { +                                match vcard.names { +                                    Some(names) => { +                                        names.insert(name); +                                    }, +                                    None => { +                                        let names = { +                                            let mut names = HashSet::new(); +                                            names.insert(name); +                         +                                            names +                                        }; +             +                                        vcard.names = Some(vcard::Set::from_hash_set(names).unwrap()); +                                    } +                                }; +                            }, +                            None => (), +                        }; +                    }, +                    Err(_) => (), +                }; + +                shouldrender = true; + +            }, +            Msg::GeneratedAddress(address) => { + +                self.answer_count += 1; + +                match address { +                    Ok(address) => { +                        match self.vcard { +                            Some(vcard) => { +                                match vcard.addresses { +                                    Some(addresses) => { +                                        addresses.insert(address); +                                    }, +                                    None => { +                                        let addresses = { +                                            let mut addresses = HashSet::new(); +                                            addresses.insert(address); +                         +                                            addresses +                                        }; +         +                                        vcard.addresses = Some(vcard::Set::from_hash_set(addresses).unwrap()); +                                    } +                                }; +                            }, +                            None => (), +                        }; +                    }, +                    Err(_) => (), +                }; + +                shouldrender = true; +            }, +            Msg::GeneratedTelephone(telephone) => { + +                self.answer_count += 1; + +                match telephone { +                    Ok(telephone) => { +                        match self.vcard { +                            Some(vcard) => { +                                match vcard.telephones { +                                    Some(telephones) => { +                                        telephones.insert(telephone); +                                    }, +                                    None => { +                                        let telephones = { +                                            let mut telephones = HashSet::new(); +                                            telephones.insert(telephone); +                                             +                                            telephones +                                        }; +                         +                                        vcard.telephones = Some(vcard::Set::from_hash_set(telephones).unwrap()); +                                    } +                                }; +                            }, +                            None => (), +                        };         +                    }, +                    Err(_) => (), +                } + +                shouldrender = true; +            }, +            Msg::GenerationComplete => { + +                self.answer_count = 0; + +                match self.vcard.clone() { +                    Some(vcard) => { +                        match self.selected_option { +                            DownloadOption::VCard => { +                                self.download = Some( +                                    Download { +                                        file_name: String::from("VCard.vcs"), +                                        content: vcard.to_string(), +                                        mime_type: MimeType::VCard, +                                    } +                                ); +                            }, +                            DownloadOption::QrCode => { +                                match QrCode::encode_text(&vcard.to_string(), QrCodeEcc::Low) { +                                    Ok(qr) => self.download = Some( +                                        Download { +                                            file_name: String::from("QR-Code VCard.svg"), +                                            content: qr.to_svg_string(4), +                                            mime_type: MimeType::SVG, +                                        } +                                    ), +                                    Err(_) => self.error = Some( +                                        Error{ +                                            msg: String::from("Sorry, VCard is too long!"), +                                        } +                                    ), +                                }; +                            }, +                            _ => (), +                        }; +                     +                        self.vcard = None; +                        shouldrender = true; +                    }, +                    None => shouldrender = false, // what TODO here? +                } + +            }, +            Msg::Nope => shouldrender = false,          }; -        if self.error.len() > 0 { + +        if self.answer_count >= self.get_subcomponent_count() { +            self.link.send_message(Msg::GenerationComplete); +        } +        if self.error.is_some() {              self.download = None;          } -        true +         +        shouldrender      }      fn change(&mut self, _props: Self::Properties) -> ShouldRender { @@ -136,9 +315,9 @@ impl Component for MainView {          let download_options = self.link.callback(|e: ChangeData|              match e {                  ChangeData::Select(v) => match v.value().as_str() { -                    "vcard" => Msg::Generate(DownloadOption::VCard), -                    "pdf" => Msg::Generate(DownloadOption::PDF), -                    "qrcode" => Msg::Generate(DownloadOption::QrCode), +                    "vcard" => Msg::ChangeDownloadOption(DownloadOption::VCard), +                    "pdf" => Msg::ChangeDownloadOption(DownloadOption::PDF), +                    "qrcode" => Msg::ChangeDownloadOption(DownloadOption::QrCode),                      _ => Msg::Nope,                  },                  _ => Msg::Nope, @@ -160,15 +339,54 @@ impl Component for MainView {                      <section class="section">                          <div class="container is-max-widescreen"> -                            { self.render_errors() } - -                            <NameView oninput=self.link.callback(|n: Name| Msg::UpdateName(n)) /> - -                            <AddressView oninput=self.link.callback(|a: Address| Msg::UpdateAddress(a)) /> - -                            <TelephoneView oninput=self.link.callback(|t: Telephone| Msg::UpdateTelephone(t)) /> +                            { self.render_error() } + +                            { +                                for self.name_links.iter().map(|link| +                                    html!{ +                                        <NameView   weak_link=link  +                                                    generated_fn=self.link.callback( +                                                        |fmn: Result<properties::FormattedName,()>|  +                                                            Msg::GeneratedFormattedName(fmn) +                                                    ) +                                                    generated_name=self.link.callback( +                                                        |n: Result<properties::Name,()>|  +                                                            Msg::GeneratedName(n) +                                                    ) +                                        /> +                                    } +                                ) +                            } + +                            { +                                for self.address_links.iter().map(|link| +                                    html!{ +                                        <AddressView    weak_link=link +                                                        generated=self.link.callback( +                                                            |a: Result<properties::Address,()>| +                                                                Msg::GeneratedAddress(a) +                                                        ) +                                        /> +                                    } +                                ) +                            } + +                            { +                                for self.telephone_links.iter().map(|link| +                                    html!{ +                                        <TelephoneView  weak_link=link +                                                        generated=self.link.callback( +                                                            |t: Result<properties::Telephone,()>| +                                                                Msg::GeneratedTelephone(t) +                                                        ) +                                        /> +                                    } +                                ) +                            }                              <div class="block level-left"> +                                <button onclick=self.link.callback(|_| Msg::Generate) class="button is-primary level-item" /> +                                  <div class="select level-item">                                      <select id="download_options" onchange=download_options>                                          <option value="vcard">{ "VCard (.vcf)" }</option> @@ -202,17 +420,20 @@ impl Component for MainView {  }  impl MainView { -    fn render_errors(&self) -> Html { +    fn render_error(&self) -> Html {          html!{              <>                  { -                    for self.error.iter().map(|err|  -                        html!{ -                            <div class="notification is-danger is-light"> -                                { err } -                            </div> -                        } -                    )  +                    match &self.error { +                        Some(error) => { +                            html!{ +                                <div class="notification is-danger is-light"> +                                    { error.msg.clone() } +                                </div> +                            } +                        }, +                        None => html!{}, +                    }                  }              </>          } @@ -222,7 +443,7 @@ impl MainView {              let download = self.download.as_ref().unwrap();              html!{ -                <a href=download.as_data_link() download=download.file_name class="button is-primary level-item" > +                <a href=download.as_data_link() download=download.file_name class="button is-success level-item" >                      { "Download" }                  </a>              } @@ -253,41 +474,6 @@ impl MainView {              html!{}          }      } -    fn generate_vcard(&self) -> Result<VCard, VCardError> { -        match VCard::from_formatted_name_str(&self.name.formatted_name()) { -            Ok(vcard) => { -                let mut vcard = vcard; - -                let names = { -                    let mut names = HashSet::new(); -                    names.insert(self.name.to_vcard_property().unwrap()); - -                    names -                }; - -                let addresses = { -                    let mut addresses = HashSet::new(); -                    addresses.insert(self.address.to_vcard_property().unwrap()); - -                    addresses -                }; - -                let telephones = { -                    let mut telephones = HashSet::new(); -                    telephones.insert(self.telephone.to_vcard_property().unwrap()); -                     -                    telephones -                }; - -                vcard.names = Some(vcard::Set::from_hash_set(names).unwrap()); -                vcard.addresses = Some(vcard::Set::from_hash_set(addresses).unwrap()); -                vcard.telephones = Some(vcard::Set::from_hash_set(telephones).unwrap()); - -                Ok(vcard) -            } -            Err(err) => Err(err), -        } -    }      fn generate_pdf(&self) -> Result<String, ()>{          let regular_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Regular.ttf");          let regular_font_data = fonts::FontData::new(regular_bytes.to_vec(), Some(printpdf::BuiltinFont::Helvetica)).expect("font data should be correct"); @@ -336,4 +522,9 @@ impl MainView {              Err(_) => Err(()),          }      } +    fn get_subcomponent_count(&self) -> usize { +        self.name_links.len() +        + self.address_links.len() +        + self.telephone_links.len() +    }  }
\ No newline at end of file diff --git a/src/view/mod.rs b/src/view/mod.rs index b47612a..f208c44 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,4 +1,7 @@  use yew::prelude::*; +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::Rc;  use crate::viewmodel::*;  pub mod main; @@ -12,20 +15,50 @@ pub trait VCardPropertyInputComponent<P, T>: Component  {      fn get_input_object(&self) -> T;      fn get_title(&self) -> String; -    fn get_errors(&self) -> Vec<String>; -    fn render_errors(&self) -> Html { +    fn get_error(&self) -> Option<Error>; +    fn render_error(&self) -> Html {          html!{              <>                  { -                    for self.get_errors().iter().map(|err|  +                    if self.get_error().is_some() {                          html!{                              <div class="notification is-danger is-light"> -                                { err } +                                { self.get_error().unwrap().msg }                              </div>                          } -                    )  +                    } else { +                        html!{} +                    }                  }              </>          }      } +} + +pub struct WeakComponentLink<COMP: Component>(Rc<RefCell<Option<ComponentLink<COMP>>>>); + +impl<COMP: Component> Clone for WeakComponentLink<COMP> { +    fn clone(&self) -> Self { +        Self(Rc::clone(&self.0)) +    } +} + +impl<COMP: Component> Default for WeakComponentLink<COMP> { +    fn default() -> Self { +        Self(Rc::default()) +    } +} + +impl<COMP: Component> Deref for WeakComponentLink<COMP> { +    type Target = Rc<RefCell<Option<ComponentLink<COMP>>>>; + +    fn deref(&self) -> &Self::Target { +        &self.0 +    } +} + +impl<COMP: Component> PartialEq for WeakComponentLink<COMP> { +    fn eq(&self, other: &Self) -> bool { +        Rc::ptr_eq(&self.0, &other.0) +    }  }
\ No newline at end of file diff --git a/src/view/name.rs b/src/view/name.rs index f76fd2e..df3fb15 100644 --- a/src/view/name.rs +++ b/src/view/name.rs @@ -1,3 +1,5 @@ +use crate::viewmodel::Error; +use crate::view::WeakComponentLink;  use yew::prelude::*;  use vcard::properties;  use crate::viewmodel::name::*; @@ -5,10 +7,9 @@ use crate::viewmodel::VCardPropertyInputObject;  use super::VCardPropertyInputComponent;  pub struct NameView { -    link: ComponentLink<Self>, +    props: Props,      value: Name, -    oninput: Callback<Name>, -    errors: Vec<String>, +    error: Option<Error>,  }  pub enum Msg { @@ -17,11 +18,15 @@ pub enum Msg {      UpdateMiddleName(String),      UpdateLastName(String),      UpdateSuffix(String), + +    Generate,  }  #[derive(Clone, PartialEq, Properties)]  pub struct Props { -    pub oninput: Callback<Name>, +    pub generated_name: Callback<Result<properties::Name,()>>, +    pub generated_fn: Callback<Result<properties::FormattedName,()>>, +    pub weak_link: WeakComponentLink<NameView>,  }  impl VCardPropertyInputComponent<properties::Name, Name> for NameView { @@ -31,8 +36,8 @@ impl VCardPropertyInputComponent<properties::Name, Name> for NameView {      fn get_title(&self) -> String {          "Name".to_string()      } -    fn get_errors(&self) -> Vec<String> { -        self.errors.clone() +    fn get_error(&self) -> Option<Error> { +        self.error.clone()      }  } @@ -40,11 +45,11 @@ impl Component for NameView {      type Message = Msg;      type Properties = Props;      fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { +        props.weak_link.borrow_mut().replace(link);          Self { -            link, +            props,              value: Name::new(), -            oninput: props.oninput, -            errors: vec![], +            error: None,          }      }      fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { @@ -54,26 +59,46 @@ impl Component for NameView {              Msg::UpdateMiddleName(m) => self.value.middle_name = m,              Msg::UpdateLastName(l) => self.value.last_name = l,              Msg::UpdateSuffix(s) => self.value.suffix = s, +            Generate => { +                match self.value.formatted_name() { +                    Ok(formatted_name) => self.props.generated_fn.emit(Ok(formatted_name)), +                    Err(error) => { +                        self.props.generated_fn.emit(Err(())); +                        self.error = Some(error); +                    }, +                }; +                match self.value.to_vcard_property() { +                    Ok(name) => { +                        self.props.generated_name.emit(Ok(name)); +                        return false; +                    }, +                    Err(error) => { +                        self.props.generated_name.emit(Err(())); +                        self.error = Some(error); +                    }, +                }; +            },          }; -        self.oninput.emit(self.value.clone());          true      }      fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool {  -        self.oninput = props.oninput; -        true +        if self.props != props { +            self.props = props; +            true +        } else { +            false +        }      }      fn view(&self) -> yew::virtual_dom::VNode { +        let link = self.props.weak_link.borrow().clone().unwrap(); +          html!{              <div class="box"> -                { -                    self.render_errors() -                } +                { self.render_error() }                  <h3 class="subtitle">{ self.get_title() }</h3> -                { -                    self.get_input_object().render(&self.link) -                } +                { self.get_input_object().render(&link) }              </div>          } diff --git a/src/view/telephone.rs b/src/view/telephone.rs index 808ebfa..a47c610 100644 --- a/src/view/telephone.rs +++ b/src/view/telephone.rs @@ -1,3 +1,5 @@ +use crate::view::WeakComponentLink; +use crate::viewmodel::Error;  use yew::prelude::*;  use vcard::properties;  use crate::viewmodel::telephone::*; @@ -5,10 +7,9 @@ use crate::viewmodel::VCardPropertyInputObject;  use super::VCardPropertyInputComponent;  pub struct TelephoneView { -    link: ComponentLink<Self>, +    props: Props,      value: Telephone, -    oninput: Callback<Telephone>, -    errors: Vec<String>, +    error: Option<Error>,  }  pub enum Msg { @@ -23,12 +24,14 @@ pub enum Msg {      ToggleVideo,      TogglePager,      ToggleTextPhone, + +    Generate,  }  #[derive(Clone, PartialEq, Properties)]  pub struct Props { -    pub oninput: Callback<Telephone>, -    //pub errors: Vec<String>, +    pub generated: Callback<Result<properties::Telephone,()>>, +    pub weak_link: WeakComponentLink<TelephoneView>,  }  impl VCardPropertyInputComponent<properties::Telephone, Telephone> for TelephoneView { @@ -38,8 +41,8 @@ impl VCardPropertyInputComponent<properties::Telephone, Telephone> for Telephone      fn get_title(&self) -> String {          "Telephone".to_string()      } -    fn get_errors(&self) -> Vec<String> { -        self.errors.clone() +    fn get_error(&self) -> Option<Error> { +        self.error.clone()      }  } @@ -47,11 +50,11 @@ impl Component for TelephoneView {      type Message = Msg;      type Properties = Props;      fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { +        props.weak_link.borrow_mut().replace(link);          Self { -            link, +            props,              value: Telephone::new(), -            oninput: props.oninput, -            errors: vec![], +            error: None,          }      }      fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { @@ -67,26 +70,39 @@ impl Component for TelephoneView {              Msg::ToggleVideo => self.value.video = !self.value.video,              Msg::TogglePager => self.value.pager = !self.value.pager,              Msg::ToggleTextPhone => self.value.text_phone = !self.value.text_phone, +            Msg::Generate => { +                match self.value.to_vcard_property() { +                    Ok(telephone) => { +                        self.props.generated.emit(Ok(telephone)); +                        return false; +                    }, +                    Err(error) => { +                        self.props.generated.emit(Err(())); +                        self.error = Some(error); +                    }, +                }; +            }          }; -        self.oninput.emit(self.value.clone());          true      }      fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool {  -        self.oninput = props.oninput; -        true +        if self.props != props { +            self.props = props; +            true +        } else { +            false +        }      }      fn view(&self) -> yew::virtual_dom::VNode { +        let link = self.props.weak_link.borrow().clone().unwrap(); +          html!{              <div class="box"> -                { -                    self.render_errors() -                } +                { self.render_error() }                  <h3 class="subtitle">{ self.get_title() }</h3> -                { -                    self.get_input_object().render(&self.link) -                } +                { self.get_input_object().render(&link) }              </div>          } diff --git a/src/viewmodel/address.rs b/src/viewmodel/address.rs index 757236c..b2257bf 100644 --- a/src/viewmodel/address.rs +++ b/src/viewmodel/address.rs @@ -97,7 +97,16 @@ impl VCardPropertyInputObject<properties::Address, AddressView> for Address {              },          ]      } -    fn to_vcard_property(&self) -> Result<properties::Address, VCardPropertyInputError> { +    fn is_empty(&self) -> bool { +        self.post_office_box.is_empty() &&  +        self.extension.is_empty() &&  +        self.street.is_empty() &&  +        self.locality.is_empty() &&  +        self.region.is_empty() &&  +        self.code.is_empty() &&  +        self.country.is_empty() +    } +    fn to_vcard_property(&self) -> Result<properties::Address, Error> { // TODO error handling          let address_value = values::address_value::AddressValue::from_components(              match self.post_office_box.is_empty() {                  true => None, diff --git a/src/viewmodel/mod.rs b/src/viewmodel/mod.rs index 42ad77b..9dce01c 100644 --- a/src/viewmodel/mod.rs +++ b/src/viewmodel/mod.rs @@ -23,12 +23,13 @@ pub trait VCardPropertyInputObject<P: properties::Property, C: VCardPropertyInpu              </div>          }      } -    fn to_vcard_property(&self) -> Result<P, VCardPropertyInputError>; +    fn is_empty(&self) -> bool; +    fn to_vcard_property(&self) -> Result<P, Error>;  } -#[derive(Debug)] -pub struct VCardPropertyInputError { -    msg: String, +#[derive(Debug,Clone,PartialEq)] +pub struct Error { +    pub msg: String,  }  pub enum VCardPropertyInputField { diff --git a/src/viewmodel/name.rs b/src/viewmodel/name.rs index 55c1b4c..e0c3a5b 100644 --- a/src/viewmodel/name.rs +++ b/src/viewmodel/name.rs @@ -61,27 +61,69 @@ impl VCardPropertyInputObject<properties::Name, NameView> for Name {              },          ]      } -    fn to_vcard_property(&self) -> std::result::Result<properties::Name, VCardPropertyInputError> { +    fn is_empty(&self) -> bool { +        self.prefix.is_empty() && +        self.first_name.is_empty() && +        self.middle_name.is_empty() && +        self.last_name.is_empty() && +        self.suffix.is_empty() +    } +    fn to_vcard_property(&self) -> std::result::Result<properties::Name, Error> {          let name_value = values::name_value::NameValue::from_components(              match self.last_name.is_empty() {                  true => None, -                false => Some(text::Component::from_str(&self.last_name).unwrap()), +                false => Some( +                    match text::Component::from_str(&self.last_name) { +                        Ok(last_name) => last_name, +                        Err(_) => return Err(Error{ +                            msg: String::from("Illegal character in last name."), +                        }), +                    } +                ),              },              match self.first_name.is_empty() {                  true => None, -                false => Some(text::Component::from_str(&self.first_name).unwrap()), +                false => Some( +                    match text::Component::from_str(&self.first_name) { +                        Ok(first_name) => first_name, +                        Err(_) => return Err(Error{ +                            msg: String::from("Illegal character in first name."), +                        }), +                    } +                ),              },              match self.middle_name.is_empty() {                  true => None, -                false => Some(text::Component::from_str(&self.middle_name).unwrap()), +                false => Some( +                    match text::Component::from_str(&self.middle_name) { +                        Ok(middle_name) => middle_name, +                        Err(_) => return Err(Error{ +                            msg: String::from("Illegal character in middle name."), +                        }), +                    } +                ),              },              match self.prefix.is_empty() {                  true => None, -                false => Some(text::Component::from_str(&self.prefix).unwrap()), +                false => Some( +                    match text::Component::from_str(&self.prefix) { +                        Ok(prefix) => prefix, +                        Err(_) => return Err(Error{ +                            msg: String::from("Illegal character in prefix."), +                        }), +                    } +                ),              },              match self.suffix.is_empty() {                  true => None, -                false => Some(text::Component::from_str(&self.suffix).unwrap()), +                false => Some( +                    match text::Component::from_str(&self.suffix) { +                        Ok(suffix) => suffix, +                        Err(_) => return Err(Error{ +                            msg: String::from("Illegal character in suffix."), +                        }), +                    } +                ),              },          ); @@ -90,7 +132,7 @@ impl VCardPropertyInputObject<properties::Name, NameView> for Name {  }  impl Name { -    pub fn formatted_name(&self) -> String { +    pub fn formatted_name(&self) -> Result<properties::FormattedName, Error> {          let mut formatted_name = String::new();          if !self.prefix.is_empty() { @@ -113,6 +155,20 @@ impl Name {              formatted_name.push_str(&self.suffix);          } -        formatted_name +        if formatted_name.is_empty() { +            return Err(Error{ +                msg: String::from("Primary name field must not be empty for the formatted name field to be generated."), +            }); +        } + +        let formatted_name = properties::FormattedName::from_text( +            match text::Text::from_string(formatted_name) { +                Ok(formatted_name) => formatted_name, +                Err(_) => return Err(Error{  +                    msg: String::from("Illegal character in formatted name.") // If I see this right, this error should never occur. +                }), +            } +        );  +        Ok(formatted_name)      }  }
\ No newline at end of file diff --git a/src/viewmodel/telephone.rs b/src/viewmodel/telephone.rs index b67b0fb..f90df5d 100644 --- a/src/viewmodel/telephone.rs +++ b/src/viewmodel/telephone.rs @@ -108,7 +108,11 @@ impl VCardPropertyInputObject<properties::Telephone, TelephoneView> for Telephon              },          ]      } -    fn to_vcard_property(&self) -> Result<properties::Telephone, VCardPropertyInputError> { +    fn is_empty(&self) -> bool { +        self.number.is_empty() && +        self.extension.is_empty() +    } +    fn to_vcard_property(&self) -> Result<properties::Telephone, Error> { // TODO error handling          let mut telephone = properties::Telephone::from_telephone_value(              values::telephone_value::TelephoneValue::from_telephone_number_str(                  self.number.clone(),  diff --git a/src/viewmodel/utility.rs b/src/viewmodel/utility.rs index a296c1e..4a82a42 100644 --- a/src/viewmodel/utility.rs +++ b/src/viewmodel/utility.rs @@ -1,3 +1,5 @@ + +  #[derive(Clone)]  pub struct Download {      pub file_name: String, @@ -31,7 +33,7 @@ impl MimeType {      }  } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)]  pub enum DownloadOption {      PDF,      VCard,  | 
