summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--src/view/address.rs54
-rw-r--r--src/view/main.rs437
-rw-r--r--src/view/mod.rs43
-rw-r--r--src/view/name.rs61
-rw-r--r--src/view/telephone.rs54
-rw-r--r--src/viewmodel/address.rs11
-rw-r--r--src/viewmodel/mod.rs9
-rw-r--r--src/viewmodel/name.rs72
-rw-r--r--src/viewmodel/telephone.rs6
-rw-r--r--src/viewmodel/utility.rs4
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<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,