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 | |
parent | 4a0c73eebb8dfd6a5543945049175f64b9817c96 (diff) | |
download | wasm-card-3f0892368bcf475fdae39f24ef51b82b3013535f.tar.gz wasm-card-3f0892368bcf475fdae39f24ef51b82b3013535f.tar.bz2 |
try to fix problem with vcard mutable references
-rw-r--r-- | Cargo.toml | 1 | ||||
-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 |
11 files changed, 553 insertions, 199 deletions
@@ -14,6 +14,7 @@ wasm-bindgen = "0.2.68" js-sys = "0.3.45" console_error_panic_hook = "0.1.6" wee_alloc = "0.4.5" +yew-state = "^0.4" printpdf = "0.3.3" base64 = "0.13.0" vcard = "0.4.7" 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, |