From 3f0892368bcf475fdae39f24ef51b82b3013535f Mon Sep 17 00:00:00 2001 From: jelemux Date: Thu, 28 Jan 2021 17:41:49 +0100 Subject: try to fix problem with vcard mutable references --- Cargo.toml | 1 + src/view/address.rs | 54 ++++-- src/view/main.rs | 437 ++++++++++++++++++++++++++++++++------------- src/view/mod.rs | 43 ++++- src/view/name.rs | 61 +++++-- src/view/telephone.rs | 54 ++++-- src/viewmodel/address.rs | 11 +- src/viewmodel/mod.rs | 9 +- src/viewmodel/name.rs | 72 +++++++- src/viewmodel/telephone.rs | 6 +- src/viewmodel/utility.rs | 4 +- 11 files changed, 553 insertions(+), 199 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 346d7c1..0228c75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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, + props: Props, value: Address, - oninput: Callback
, - errors: Vec, + error: Option, } 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
, - //pub errors: Vec, + pub generated: Callback>, + pub weak_link: WeakComponentLink, } impl VCardPropertyInputComponent for AddressView { @@ -36,8 +39,8 @@ impl VCardPropertyInputComponent for AddressView { fn get_title(&self) -> String { "Address".to_string() } - fn get_errors(&self) -> Vec { - self.errors.clone() + fn get_error(&self) -> Option { + self.error.clone() } } @@ -45,11 +48,11 @@ impl Component for AddressView { type Message = Msg; type Properties = Props; fn create(props: ::Properties, link: yew::html::Scope) -> 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: ::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: ::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!{
- { - self.render_errors() - } + { self.render_error() }

{ self.get_title() }

- { - self.get_input_object().render(&self.link) - } + { self.get_input_object().render(&link) }
} 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, - error: Vec, - name: Name, - address: Address, - telephone: Telephone, + error: Option, download: Option, selected_option: DownloadOption, + vcard: Option, + + name_links: Vec>, + address_links: Vec>, + telephone_links: Vec>, + + answer_count: usize, } pub enum Msg { - UpdateName(Name), - UpdateAddress(Address), - UpdateTelephone(Telephone), - Generate(DownloadOption), + AddName, + AddAddress, + AddTelephone, + + ChangeDownloadOption(DownloadOption), + + Generate, + GeneratedFormattedName(Result), + GeneratedName(Result), + GeneratedAddress(Result), + GeneratedTelephone(Result), + GenerationComplete, + Nope, } @@ -40,71 +52,59 @@ impl Component for MainView { fn create(_props: Self::Properties, link: ComponentLink) -> 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 {
- { self.render_errors() } - - - - - - + { self.render_error() } + + { + for self.name_links.iter().map(|link| + html!{ + | + Msg::GeneratedFormattedName(fmn) + ) + generated_name=self.link.callback( + |n: Result| + Msg::GeneratedName(n) + ) + /> + } + ) + } + + { + for self.address_links.iter().map(|link| + html!{ + | + Msg::GeneratedAddress(a) + ) + /> + } + ) + } + + { + for self.telephone_links.iter().map(|link| + html!{ + | + Msg::GeneratedTelephone(t) + ) + /> + } + ) + }
+