summaryrefslogtreecommitdiff
path: root/src/view/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/main.rs')
-rw-r--r--src/view/main.rs437
1 files changed, 314 insertions, 123 deletions
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