From 0660151a8b641fa0a23dde2598132029970f7ae4 Mon Sep 17 00:00:00 2001 From: jelemux Date: Thu, 11 Feb 2021 12:07:22 +0100 Subject: refactoring - reduced code size by about a third --- src/lib.rs | 2 +- src/model/address.rs | 154 ++++++++++++++++++++++++++ src/model/dates.rs | 69 ++++++++++++ src/model/mod.rs | 240 ++++++++++++++++++++++++++++++++++++++++ src/model/name.rs | 149 +++++++++++++++++++++++++ src/model/organizational.rs | 120 ++++++++++++++++++++ src/model/telephone.rs | 146 ++++++++++++++++++++++++ src/model/utility.rs | 45 ++++++++ src/model/vcard.rs | 40 +++++++ src/view/address.rs | 103 ----------------- src/view/dates.rs | 75 ------------- src/view/main.rs | 42 +++---- src/view/mod.rs | 82 +------------- src/view/name.rs | 95 ---------------- src/view/organizational.rs | 84 -------------- src/view/property_group.rs | 81 ++++++++++++++ src/view/telephone.rs | 105 ------------------ src/view/weak_links.rs | 33 ++++++ src/viewmodel/address.rs | 113 ------------------- src/viewmodel/dates.rs | 45 -------- src/viewmodel/mod.rs | 231 -------------------------------------- src/viewmodel/name.rs | 119 -------------------- src/viewmodel/organizational.rs | 88 --------------- src/viewmodel/telephone.rs | 106 ------------------ src/viewmodel/utility.rs | 45 -------- src/viewmodel/vcard.rs | 40 ------- 26 files changed, 1102 insertions(+), 1350 deletions(-) create mode 100644 src/model/address.rs create mode 100644 src/model/dates.rs create mode 100644 src/model/mod.rs create mode 100644 src/model/name.rs create mode 100644 src/model/organizational.rs create mode 100644 src/model/telephone.rs create mode 100644 src/model/utility.rs create mode 100644 src/model/vcard.rs delete mode 100644 src/view/address.rs delete mode 100644 src/view/dates.rs delete mode 100644 src/view/name.rs delete mode 100644 src/view/organizational.rs create mode 100644 src/view/property_group.rs delete mode 100644 src/view/telephone.rs create mode 100644 src/view/weak_links.rs delete mode 100644 src/viewmodel/address.rs delete mode 100644 src/viewmodel/dates.rs delete mode 100644 src/viewmodel/mod.rs delete mode 100644 src/viewmodel/name.rs delete mode 100644 src/viewmodel/organizational.rs delete mode 100644 src/viewmodel/telephone.rs delete mode 100644 src/viewmodel/utility.rs delete mode 100644 src/viewmodel/vcard.rs diff --git a/src/lib.rs b/src/lib.rs index b8e10a5..b5ad040 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,8 @@ use yew::prelude::App; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +pub mod model; pub mod view; -pub mod viewmodel; fn init() { panic::set_hook(Box::new(console_error_panic_hook::hook)); diff --git a/src/model/address.rs b/src/model/address.rs new file mode 100644 index 0000000..2c42a98 --- /dev/null +++ b/src/model/address.rs @@ -0,0 +1,154 @@ +use super::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct Address { + pub post_office_box: String, + pub extension: String, + pub street: String, + pub locality: String, + pub region: String, + pub code: String, + pub country: String, + pub work: bool, + pub home: bool, +} + +#[derive(Clone, PartialEq)] +pub enum AddressMsg { + UpdatePostOfficeBox(String), + UpdateExtension(String), + UpdateStreet(String), + UpdateLocality(String), + UpdateRegion(String), + UpdateCode(String), + UpdateCountry(String), + ToggleWork, + ToggleHome, + + Generate, +} + +impl VCardPropertyInputObject for Address { + fn new() -> Self { + Self { + post_office_box: String::new(), + extension: String::new(), + street: String::new(), + locality: String::new(), + region: String::new(), + code: String::new(), + country: String::new(), + work: false, + home: false, + } + } + fn get_title(&self) -> String { + "Address".to_string() + } + fn get_input_fields( + &self, + link: &ComponentLink>, + ) -> Vec { + let typ = String::from("text"); + vec![ + VCardPropertyInputField::Text { + label: "Post Office Box".to_string(), + id: Some("post_office_box".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| AddressMsg::UpdatePostOfficeBox(e.value)), + value: self.post_office_box.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Extension".to_string(), + id: Some("extension".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| AddressMsg::UpdateExtension(e.value)), + value: self.extension.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Street".to_string(), + id: Some("street".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| AddressMsg::UpdateStreet(e.value)), + value: self.street.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Locality".to_string(), + id: Some("locality".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| AddressMsg::UpdateLocality(e.value)), + value: self.locality.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Region".to_string(), + id: Some("region".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| AddressMsg::UpdateRegion(e.value)), + value: self.region.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Code".to_string(), + id: Some("code".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| AddressMsg::UpdateCode(e.value)), + value: self.code.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Country".to_string(), + id: Some("country".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| AddressMsg::UpdateCountry(e.value)), + value: self.country.clone(), + typ, + }, + VCardPropertyInputField::CheckBox { + label: "Work".to_string(), + id: Some("work".to_string()), + onclick: link.callback(|_: MouseEvent| AddressMsg::ToggleWork), + value: self.work, + }, + VCardPropertyInputField::CheckBox { + label: "Home".to_string(), + id: Some("home".to_string()), + onclick: link.callback(|_: MouseEvent| AddressMsg::ToggleHome), + value: self.home, + }, + ] + } + fn update( + &mut self, + props: InputProps, + msg: as yew::Component>::Message, + ) -> bool { + match msg { + AddressMsg::UpdatePostOfficeBox(b) => self.post_office_box = b, + AddressMsg::UpdateExtension(e) => self.extension = e, + AddressMsg::UpdateStreet(s) => self.street = s, + AddressMsg::UpdateLocality(l) => self.locality = l, + AddressMsg::UpdateRegion(r) => self.region = r, + AddressMsg::UpdateCode(p) => self.code = p, + AddressMsg::UpdateCountry(c) => self.country = c, + AddressMsg::ToggleWork => self.work = !self.work, + AddressMsg::ToggleHome => self.home = !self.home, + AddressMsg::Generate => { + props.generated.emit(self.clone()); + } + }; + true + } + 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() + } +} diff --git a/src/model/dates.rs b/src/model/dates.rs new file mode 100644 index 0000000..192f986 --- /dev/null +++ b/src/model/dates.rs @@ -0,0 +1,69 @@ +use super::*; + +/// Type that represents the vcard `anniversary` and `birthday` properties. +#[derive(Clone, Debug, PartialEq)] +pub struct Dates { + pub anniversary: String, + pub birthday: String, +} + +#[derive(Clone, PartialEq)] +pub enum DatesMsg { + UpdateAnniversary(String), + UpdateBirthday(String), + + Generate, +} + +impl VCardPropertyInputObject for Dates { + fn new() -> Self { + Self { + anniversary: String::new(), + birthday: String::new(), + } + } + fn get_title(&self) -> std::string::String { + "Dates".to_string() + } + fn get_input_fields( + &self, + link: &yew::html::Scope>, + ) -> std::vec::Vec { + let typ = String::from("date"); + vec![ + VCardPropertyInputField::Text { + label: "Anniversary".to_string(), + id: Some("anniversary".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| DatesMsg::UpdateAnniversary(e.value)), + value: self.anniversary.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Birthday".to_string(), + id: Some("birthday".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| DatesMsg::UpdateBirthday(e.value)), + value: self.birthday.clone(), + typ, + }, + ] + } + fn update( + &mut self, + props: InputProps, + msg: as yew::Component>::Message, + ) -> bool { + match msg { + DatesMsg::UpdateAnniversary(a) => self.anniversary = a, + DatesMsg::UpdateBirthday(b) => self.birthday = b, + DatesMsg::Generate => { + props.generated.emit(self.clone()); + } + }; + true + } + fn is_empty(&self) -> bool { + self.anniversary.is_empty() && self.birthday.is_empty() + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..45c91f1 --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,240 @@ +use crate::model::utility::File; +use crate::view::property_group::*; +use wasm_bindgen::closure::Closure; +use wasm_bindgen::JsCast; +use web_sys::FileReader; +use yew::prelude::*; +use yew::services::ConsoleService; + +pub mod address; +pub mod dates; +pub mod name; +pub mod organizational; +pub mod telephone; +pub mod utility; +pub mod vcard; + +/// Trait for types that represent the data of a vcard property used inside of a `VCardPropertyInputComponent`. +pub trait VCardPropertyInputObject: Clone + PartialEq +where + Self: Sized, +{ + /// Function for creating a new (and empty) `VCardPropertyInputObject`. + fn new() -> Self; + /// Getter function for the title of the component + fn get_title(&self) -> String; + /// Converts each field of the `VCardPropertyInputObject` to a VCardPropertyInputField and returns them as a vector. + fn get_input_fields( + &self, + link: &ComponentLink>, + ) -> Vec; + fn update( + &mut self, + props: InputProps, + msg: as yew::Component>::Message, + ) -> bool; + /// Returns a `Html` representation of the `VCardPropertyInputObject`. + fn render(&self, link: &ComponentLink>) -> Html { + html! { +
+ { + for self.get_input_fields(link).iter().map(|field| + field.render() + ) + } +
+ } + } + /// Convenience function for checking if the `VCardPropertyInputObject` is empty. + fn is_empty(&self) -> bool; +} + +/// Type for saving error messages. +/// +/// More of a placeholder for something better later on. +#[derive(Debug, Clone, PartialEq)] +pub struct Error { + pub msg: String, +} + +/// Type that represents the visiual appearance of an input field. +pub enum VCardPropertyInputField { + Text { + label: String, + id: Option, + placeholder: Option, + oninput: Callback, + value: String, + typ: String, + }, + File { + label: String, + name: String, + callback: Callback>, + value: Option, + }, + CheckBox { + label: String, + id: Option, + onclick: Callback, + value: bool, + }, +} + +impl VCardPropertyInputField { + /// Returns a `Html` representation of the `VCardPropertyInputField`. + pub fn render(&self) -> Html { + match self { + Self::Text { + label, + id, + placeholder, + oninput, + value: _, + typ, + } => Self::text_field_input(label, id, placeholder, oninput, typ), + Self::File { + label, + name, + callback, + value, + } => Self::file_field_input(label, name, callback, value), + Self::CheckBox { + label, + id, + onclick, + value, + } => Self::checkbox_field_input(label, id, value, onclick), + } + } + /// Returns an `Html` representation of a text input field with the given parameters. + fn text_field_input( + label: &str, + id: &Option, + placeholder: &Option, + oninput: &Callback, + typ: &str, + ) -> Html { + html! { +
+ +
+ +
+
+ } + } + /// Returns an `Html` representation of a file input field with the given parameters. + fn file_field_input( + label: &str, + name: &str, + callback: &Callback>, + file: &Option, + ) -> Html { + let callback = callback.clone(); + let onchange = Callback::<()>::default(); + let onchange = onchange.reform(move |c: ChangeData| { + if let ChangeData::Files(files) = c { + match files.item(0) { + Some(file) => { + let file_reader = FileReader::new().unwrap(); + match file_reader.read_as_data_url(&file) { + Ok(_) => (), + Err(_) => ConsoleService::warn("Error: Couldn't get file as data url."), + }; + + let callback = callback.clone(); + let onload = Closure::wrap(Box::new(move |event: Event| { + let file_reader: FileReader = + event.target().unwrap().dyn_into().unwrap(); + let data_url: Option = + file_reader.result().unwrap().as_string(); + match data_url { + Some(content) => callback.emit(Some(File { + name: file.name(), + content, + })), + None => { + ConsoleService::warn("Couldn't get data url as string."); + callback.emit(None); + } + }; + }) as Box); + + file_reader.set_onload(Some(onload.as_ref().unchecked_ref())); + onload.forget(); + } + None => callback.emit(None), + } + } else { + callback.emit(None); + } + }); + html! { +
+ +
+ +
+
+ } + } + /// Returns an `Html` representation of a checkbox input field with the given parameters. + fn checkbox_field_input( + label: &str, + id: &Option, + checked: &bool, + onclick: &Callback, + ) -> Html { + html! { +
+ +
+ } + } +} diff --git a/src/model/name.rs b/src/model/name.rs new file mode 100644 index 0000000..915579d --- /dev/null +++ b/src/model/name.rs @@ -0,0 +1,149 @@ +use super::*; + +/// Type that represents a vcard `name` property +/// +/// # Examples +/// ``` +/// # use bcard_wasm_webapp::viewmodel::name::Name; +/// # use crate::bcard_wasm_webapp::viewmodel::VCardPropertyInputObject; +/// let mut name = Name::new(); +/// name.prefix = String::from("Sir"); +/// name.first_name = String::from("Arthur"); +/// name.middle_name = String::from("Charles"); +/// name.last_name = String::from("Clarke"); +/// name.suffix = String::from("CBE FRAS"); +/// +/// assert_eq!(name.generate_fn(), String::from("Sir Arthur Charles Clarke, CBE FRAS")); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct Name { + pub prefix: String, + pub first_name: String, + pub middle_name: String, + pub last_name: String, + pub suffix: String, +} + +#[derive(Clone, PartialEq)] +pub enum NameMsg { + UpdatePrefix(String), + UpdateFirstName(String), + UpdateMiddleName(String), + UpdateLastName(String), + UpdateSuffix(String), + + Generate, +} + +impl VCardPropertyInputObject for Name { + fn new() -> Self { + Self { + prefix: String::new(), + first_name: String::new(), + middle_name: String::new(), + last_name: String::new(), + suffix: String::new(), + } + } + fn get_title(&self) -> String { + "Name".to_string() + } + fn get_input_fields( + &self, + link: &ComponentLink>, + ) -> std::vec::Vec { + let typ = String::from("text"); + vec![ + VCardPropertyInputField::Text { + label: "Prefix".to_string(), + id: Some("prefix".to_string()), + placeholder: Some("Sir".to_string()), + oninput: link.callback(|e: InputData| NameMsg::UpdatePrefix(e.value)), + value: self.prefix.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "First Name".to_string(), + id: Some("first_name".to_string()), + placeholder: Some("Arthur".to_string()), + oninput: link.callback(|e: InputData| NameMsg::UpdateFirstName(e.value)), + value: self.first_name.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Middle Name".to_string(), + id: Some("middle_name".to_string()), + placeholder: Some("Charles".to_string()), + oninput: link.callback(|e: InputData| NameMsg::UpdateMiddleName(e.value)), + value: self.middle_name.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Last Name".to_string(), + id: Some("last_name".to_string()), + placeholder: Some("Clarke".to_string()), + oninput: link.callback(|e: InputData| NameMsg::UpdateLastName(e.value)), + value: self.last_name.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Suffix".to_string(), + id: Some("suffix".to_string()), + placeholder: Some("CBE FRAS".to_string()), + oninput: link.callback(|e: InputData| NameMsg::UpdateSuffix(e.value)), + value: self.suffix.clone(), + typ, + }, + ] + } + fn update( + &mut self, + props: InputProps, + msg: as yew::Component>::Message, + ) -> bool { + match msg { + NameMsg::UpdatePrefix(p) => self.prefix = p, + NameMsg::UpdateFirstName(f) => self.first_name = f, + NameMsg::UpdateMiddleName(m) => self.middle_name = m, + NameMsg::UpdateLastName(l) => self.last_name = l, + NameMsg::UpdateSuffix(s) => self.suffix = s, + NameMsg::Generate => { + props.generated.emit(self.clone()); + } + }; + true + } + 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() + } +} + +impl Name { + pub fn generate_fn(&self) -> String { + let mut full_name = String::new(); + + full_name.push_str(&self.prefix); + if !self.first_name.is_empty() { + full_name.push_str(" "); + full_name.push_str(&self.first_name); + } + if !self.middle_name.is_empty() { + full_name.push_str(" "); + full_name.push_str(&self.middle_name); + } + if !self.last_name.is_empty() { + full_name.push_str(" "); + full_name.push_str(&self.last_name); + } + if !self.suffix.is_empty() { + full_name.push_str(", "); + full_name.push_str(&self.suffix); + } + + full_name + } +} diff --git a/src/model/organizational.rs b/src/model/organizational.rs new file mode 100644 index 0000000..f82f5d7 --- /dev/null +++ b/src/model/organizational.rs @@ -0,0 +1,120 @@ +use super::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct Organizational { + pub org: String, + pub logo: Option, + pub title: String, + pub role: String, + pub member: String, + pub related: String, +} + +#[derive(Clone, PartialEq)] +pub enum OrganizationalMsg { + UpdateOrg(String), + UpdateLogo(Option), + UpdateTitle(String), + UpdateRole(String), + UpdateMember(String), + UpdateRelated(String), + + Generate, +} + +impl VCardPropertyInputObject for Organizational { + fn new() -> Self { + Self { + org: String::new(), + logo: None, + title: String::new(), + role: String::new(), + member: String::new(), + related: String::new(), + } + } + fn get_title(&self) -> std::string::String { + "Organizational".to_string() + } + fn get_input_fields( + &self, + link: &yew::html::Scope>, + ) -> std::vec::Vec { + let typ = String::from("text"); + vec![ + VCardPropertyInputField::Text { + label: "Organisation".to_string(), + id: Some("org".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateOrg(e.value)), + value: self.org.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::File { + // TODO: Add Upload for logo + label: "Logo".to_string(), + name: "logo".to_string(), + callback: link.callback(|file: Option| OrganizationalMsg::UpdateLogo(file)), + value: self.logo.clone(), + }, + VCardPropertyInputField::Text { + label: "Title".to_string(), + id: Some("title".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateTitle(e.value)), + value: self.title.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Role".to_string(), + id: Some("role".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateRole(e.value)), + value: self.role.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Member".to_string(), + id: Some("member".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateMember(e.value)), + value: self.member.clone(), + typ: typ.clone(), + }, + VCardPropertyInputField::Text { + label: "Related".to_string(), + id: Some("related".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateRelated(e.value)), + value: self.related.clone(), + typ: typ, + }, + ] + } + fn update( + &mut self, + props: InputProps, + msg: as yew::Component>::Message, + ) -> bool { + match msg { + OrganizationalMsg::UpdateOrg(o) => self.org = o, + OrganizationalMsg::UpdateLogo(l) => self.logo = l, + OrganizationalMsg::UpdateTitle(t) => self.title = t, + OrganizationalMsg::UpdateRole(r) => self.role = r, + OrganizationalMsg::UpdateMember(m) => self.member = m, + OrganizationalMsg::UpdateRelated(r) => self.related = r, + OrganizationalMsg::Generate => { + props.generated.emit(self.clone()); + } + }; + true + } + fn is_empty(&self) -> bool { + self.org.is_empty() + && self.logo.is_none() + && self.title.is_empty() + && self.role.is_empty() + && self.member.is_empty() + && self.related.is_empty() + } +} diff --git a/src/model/telephone.rs b/src/model/telephone.rs new file mode 100644 index 0000000..946da33 --- /dev/null +++ b/src/model/telephone.rs @@ -0,0 +1,146 @@ +use super::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct Telephone { + pub number: String, + pub work: bool, + pub home: bool, + pub text: bool, + pub voice: bool, + pub fax: bool, + pub cell: bool, + pub video: bool, + pub pager: bool, + pub text_phone: bool, +} + +#[derive(Clone, PartialEq)] +pub enum TelephoneMsg { + UpdateNumber(String), + ToggleWork, + ToggleHome, + ToggleText, + ToggleVoice, + ToggleFax, + ToggleCell, + ToggleVideo, + TogglePager, + ToggleTextPhone, + + Generate, +} + +impl VCardPropertyInputObject for Telephone { + fn new() -> Self { + Self { + number: String::new(), + work: false, + home: false, + text: false, + voice: false, + fax: false, + cell: false, + video: false, + pager: false, + text_phone: false, + } + } + fn get_title(&self) -> String { + "Telephone".to_string() + } + fn get_input_fields( + &self, + link: &ComponentLink>, + ) -> Vec { + let typ = String::from("tel"); + vec![ + VCardPropertyInputField::Text { + label: "Number".to_string(), + id: Some("number".to_string()), + placeholder: None, + oninput: link.callback(|e: InputData| TelephoneMsg::UpdateNumber(e.value)), + value: self.number.clone(), + typ, + }, + VCardPropertyInputField::CheckBox { + label: "Work".to_string(), + id: Some("work".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleWork), + value: self.work, + }, + VCardPropertyInputField::CheckBox { + label: "Home".to_string(), + id: Some("home".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleHome), + value: self.home, + }, + VCardPropertyInputField::CheckBox { + label: "Text".to_string(), + id: Some("text".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleText), + value: self.text, + }, + VCardPropertyInputField::CheckBox { + label: "Voice".to_string(), + id: Some("voice".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleVoice), + value: self.voice, + }, + VCardPropertyInputField::CheckBox { + label: "Fax".to_string(), + id: Some("fax".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleFax), + value: self.fax, + }, + VCardPropertyInputField::CheckBox { + label: "Cell".to_string(), + id: Some("cell".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleCell), + value: self.cell, + }, + VCardPropertyInputField::CheckBox { + label: "Video".to_string(), + id: Some("video".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleVideo), + value: self.video, + }, + VCardPropertyInputField::CheckBox { + label: "Pager".to_string(), + id: Some("pager".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::TogglePager), + value: self.pager, + }, + VCardPropertyInputField::CheckBox { + label: "Text Phone".to_string(), + id: Some("text_phone".to_string()), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleTextPhone), + value: self.text_phone, + }, + ] + } + fn update( + &mut self, + props: InputProps, + msg: as yew::Component>::Message, + ) -> bool { + match msg { + TelephoneMsg::UpdateNumber(n) => self.number = n, + TelephoneMsg::ToggleWork => self.work = !self.work, + TelephoneMsg::ToggleHome => self.home = !self.home, + TelephoneMsg::ToggleText => self.text = !self.text, + TelephoneMsg::ToggleVoice => self.voice = !self.voice, + TelephoneMsg::ToggleFax => self.fax = !self.fax, + TelephoneMsg::ToggleCell => self.cell = !self.cell, + TelephoneMsg::ToggleVideo => self.video = !self.video, + TelephoneMsg::TogglePager => self.pager = !self.pager, + TelephoneMsg::ToggleTextPhone => self.text_phone = !self.text_phone, + TelephoneMsg::Generate => { + props.generated.emit(self.clone()); + } + }; + true + } + fn is_empty(&self) -> bool { + self.number.is_empty() + } +} diff --git a/src/model/utility.rs b/src/model/utility.rs new file mode 100644 index 0000000..617ee45 --- /dev/null +++ b/src/model/utility.rs @@ -0,0 +1,45 @@ +#[derive(Clone)] +pub struct Download { + pub file_name: String, + pub content: String, + pub mime_type: MimeType, +} + +impl Download { + pub fn as_data_link(&self) -> String { + let data = base64::encode(&*self.content); + let uri_component: String = js_sys::encode_uri_component(&data).into(); + + format!("data:{};base64,{}", self.mime_type.as_text(), uri_component) + } +} + +#[derive(Clone, Copy)] +pub enum MimeType { + PDF, + VCard, + SVG, +} + +impl MimeType { + pub fn as_text(&self) -> &str { + match self { + MimeType::PDF => "application/pdf", + MimeType::VCard => "text/vcard", + MimeType::SVG => "image/svg+xml", + } + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum DownloadOption { + PDF, + VCard, + QrCode, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct File { + pub name: String, + pub content: String, +} diff --git a/src/model/vcard.rs b/src/model/vcard.rs new file mode 100644 index 0000000..ee7e28e --- /dev/null +++ b/src/model/vcard.rs @@ -0,0 +1,40 @@ +use crate::model::address::Address; +use crate::model::dates::Dates; +use crate::model::name::Name; +use crate::model::organizational::Organizational; +use crate::model::telephone::Telephone; + +/// Type that represents the data structure of a vcard. +#[derive(Clone, Debug)] +pub struct VCardData { + pub names: Vec, + pub addresses: Vec
, + pub telephones: Vec, + pub datess: Vec, + pub organizationals: Vec, +} + +macro_rules! make_vec_adder_fn { + ( fn $fnname:ident $property:ident => $($arg_name:ident : $arg_type:ty),* ) => { + pub fn $fnname(&mut self, $( $arg_name : $arg_type ),*) { + $(self.$property.push($arg_name);)* + } + }; +} + +impl VCardData { + pub fn new() -> Self { + Self { + names: Vec::new(), + addresses: Vec::new(), + telephones: Vec::new(), + datess: Vec::new(), + organizationals: Vec::new(), + } + } + make_vec_adder_fn!( fn add_name names => name: Name ); + make_vec_adder_fn!( fn add_address addresses => address: Address ); + make_vec_adder_fn!( fn add_telephone telephones => telephone: Telephone ); + make_vec_adder_fn!( fn add_dates datess => dates: Dates ); + make_vec_adder_fn!( fn add_organizational organizationals => organizational: Organizational ); +} diff --git a/src/view/address.rs b/src/view/address.rs deleted file mode 100644 index 57ea7e4..0000000 --- a/src/view/address.rs +++ /dev/null @@ -1,103 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::address::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps; - -/// View Component for a `address` field -/// -/// # Examples -/// -/// ```compile_fail -/// let html = html!{ -/// | -/// Msg::GeneratedAddress(some_address) -/// ) -/// /> -/// }; -/// ``` -#[derive(Clone, PartialEq)] -pub struct AddressView { - props: Props, - value: Address, - error: Option, -} - -pub enum Msg { - UpdatePostOfficeBox(String), - UpdateExtension(String), - UpdateStreet(String), - UpdateLocality(String), - UpdateRegion(String), - UpdateCode(String), - UpdateCountry(String), - ToggleWork, - ToggleHome, - - Generate, -} - -impl VCardPropertyInputComponent
for AddressView { - fn get_input_object(&self) -> Address { - self.value.clone() - } - fn get_title(&self) -> String { - "Address".to_string() - } - fn get_error(&self) -> Option { - self.error.clone() - } -} - -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 { - props, - value: Address::new(), - error: None, - } - } - fn update(&mut self, msg: ::Message) -> bool { - match msg { - Msg::UpdatePostOfficeBox(b) => self.value.post_office_box = b, - Msg::UpdateExtension(e) => self.value.extension = e, - Msg::UpdateStreet(s) => self.value.street = s, - Msg::UpdateLocality(l) => self.value.locality = l, - Msg::UpdateRegion(r) => self.value.region = r, - Msg::UpdateCode(p) => self.value.code = p, - Msg::UpdateCountry(c) => self.value.country = c, - Msg::ToggleWork => self.value.work = !self.value.work, - Msg::ToggleHome => self.value.home = !self.value.home, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: ::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { -
- { self.render_error() } - -

{ self.get_title() }

- - { self.get_input_object().render(&link) } - -
- } - } -} diff --git a/src/view/dates.rs b/src/view/dates.rs deleted file mode 100644 index b2a6dd3..0000000 --- a/src/view/dates.rs +++ /dev/null @@ -1,75 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::dates::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps; - -#[derive(Clone, PartialEq)] -pub struct DatesView { - props: Props, - value: Dates, - error: Option, -} - -pub enum Msg { - UpdateAnniversary(String), - UpdateBirthday(String), - - Generate, -} - -impl VCardPropertyInputComponent for DatesView { - fn get_input_object(&self) -> Dates { - self.value.clone() - } - fn get_title(&self) -> std::string::String { - "Dates".to_string() - } - fn get_error(&self) -> std::option::Option { - self.error.clone() - } -} - -impl Component for DatesView { - type Message = Msg; - type Properties = Props; - fn create(props: ::Properties, link: yew::html::Scope) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Dates::new(), - error: None, - } - } - fn update(&mut self, msg: ::Message) -> bool { - match msg { - Msg::UpdateAnniversary(a) => self.value.anniversary = a, - Msg::UpdateBirthday(b) => self.value.birthday = b, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: ::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { -
- { self.render_error() } - -

{ self.get_title() }

- - { self.get_input_object().render(&link) } - -
- } - } -} diff --git a/src/view/main.rs b/src/view/main.rs index 196afa0..d35fb59 100644 --- a/src/view/main.rs +++ b/src/view/main.rs @@ -1,18 +1,14 @@ -use super::address::{self, AddressView}; -use super::dates::{self, DatesView}; -use super::name::{self, NameView}; -use super::WeakComponentLink; -use crate::view::organizational::{self, OrganizationalView}; -use crate::view::telephone::{self, TelephoneView}; -use crate::viewmodel::address::Address; -use crate::viewmodel::dates::Dates; -use crate::viewmodel::name::Name; -use crate::viewmodel::organizational::Organizational; -use crate::viewmodel::telephone::Telephone; -use crate::viewmodel::utility::*; -use crate::viewmodel::vcard::VCardData; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; +use crate::view::property_group::PropertyGroupInputComponent; +use crate::view::weak_links::WeakComponentLink; +use crate::model::address::*; +use crate::model::dates::*; +use crate::model::name::*; +use crate::model::organizational::*; +use crate::model::telephone::*; +use crate::model::utility::*; +use crate::model::vcard::VCardData; +use crate::model::Error; +use crate::model::VCardPropertyInputObject; use boolinator::Boolinator; use chrono::prelude::*; use genpdf::Element as _; @@ -25,6 +21,12 @@ use yew::prelude::*; use yew::services::ConsoleService; use yewtil::ptr::Mrc; +type NameView = PropertyGroupInputComponent; +type AddressView = PropertyGroupInputComponent; +type DatesView = PropertyGroupInputComponent; +type OrganizationalView = PropertyGroupInputComponent; +type TelephoneView = PropertyGroupInputComponent; + pub struct MainView { link: ComponentLink, error: Option, @@ -117,27 +119,27 @@ impl Component for MainView { { for name_link in self.name_links.iter() { let name_link = name_link.borrow().clone().unwrap(); - name_link.send_message(name::Msg::Generate); + name_link.send_message(NameMsg::Generate); } for address_link in self.address_links.iter() { let address_link = address_link.borrow().clone().unwrap(); - address_link.send_message(address::Msg::Generate); + address_link.send_message(AddressMsg::Generate); } for telephone_link in self.telephone_links.iter() { let telephone_link = telephone_link.borrow().clone().unwrap(); - telephone_link.send_message(telephone::Msg::Generate); + telephone_link.send_message(TelephoneMsg::Generate); } for dates_link in self.dates_links.iter() { let dates_link = dates_link.borrow().clone().unwrap(); - dates_link.send_message(dates::Msg::Generate) + dates_link.send_message(DatesMsg::Generate) } for organizational_links in self.organizational_links.iter() { let organizational_link = organizational_links.borrow().clone().unwrap(); - organizational_link.send_message(organizational::Msg::Generate) + organizational_link.send_message(OrganizationalMsg::Generate) } } /* diff --git a/src/view/mod.rs b/src/view/mod.rs index cbff0fd..691f19a 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,81 +1,3 @@ -use crate::viewmodel::*; -use std::cell::RefCell; -use std::ops::Deref; -use std::rc::Rc; -use yew::prelude::*; - -pub mod address; -pub mod dates; pub mod main; -pub mod name; -pub mod organizational; -pub mod telephone; - -#[derive(Clone, PartialEq, Properties)] -pub struct InputProps -where - O: VCardPropertyInputObject + Clone, - C: VCardPropertyInputComponent + Clone, -{ - pub generated: Callback, - pub weak_link: WeakComponentLink, -} - -/// Trait for types that represent an input component for a vcard property. -pub trait VCardPropertyInputComponent>: - Component + Clone + PartialEq -{ - /// Returns the object containing the input data. - fn get_input_object(&self) -> T; - /// Getter function for the title of the component - fn get_title(&self) -> String; - /// Getter function for an eventual error. - fn get_error(&self) -> Option; - /// Returns the error as `Html` - fn render_error(&self) -> Html { - html! { - <> - { - if self.get_error().is_some() { - html!{ -
- { self.get_error().unwrap().msg } -
- } - } else { - html!{} - } - } - - } - } -} - -/// Weak link; Useful for being able to have a list of subcomponents. -pub struct WeakComponentLink(Rc>>>); - -impl Clone for WeakComponentLink { - fn clone(&self) -> Self { - Self(Rc::clone(&self.0)) - } -} - -impl Default for WeakComponentLink { - fn default() -> Self { - Self(Rc::default()) - } -} - -impl Deref for WeakComponentLink { - type Target = Rc>>>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PartialEq for WeakComponentLink { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) - } -} +pub mod property_group; +pub mod weak_links; diff --git a/src/view/name.rs b/src/view/name.rs deleted file mode 100644 index 4b7089b..0000000 --- a/src/view/name.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::name::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps; - -/// View Component for a `name` field -/// -/// # Examples -/// -/// ```compile_fail -/// let html = html!{ -/// | -/// Msg::GeneratedName(some_name) -/// ) -/// /> -/// }; -/// ``` -#[derive(Clone, PartialEq)] -pub struct NameView { - props: Props, - value: Name, - error: Option, -} - -pub enum Msg { - UpdatePrefix(String), - UpdateFirstName(String), - UpdateMiddleName(String), - UpdateLastName(String), - UpdateSuffix(String), - - Generate, -} - -impl VCardPropertyInputComponent for NameView { - fn get_input_object(&self) -> Name { - self.value.clone() - } - fn get_title(&self) -> String { - "Name".to_string() - } - fn get_error(&self) -> Option { - self.error.clone() - } -} - -impl Component for NameView { - type Message = Msg; - type Properties = Props; - fn create(props: ::Properties, link: yew::html::Scope) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Name::new(), - error: None, - } - } - fn update(&mut self, msg: ::Message) -> bool { - match msg { - Msg::UpdatePrefix(p) => self.value.prefix = p, - Msg::UpdateFirstName(f) => self.value.first_name = f, - Msg::UpdateMiddleName(m) => self.value.middle_name = m, - Msg::UpdateLastName(l) => self.value.last_name = l, - Msg::UpdateSuffix(s) => self.value.suffix = s, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: ::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { -
- { self.render_error() } - -

{ self.get_title() }

- - { self.get_input_object().render(&link) } - -
- } - } -} diff --git a/src/view/organizational.rs b/src/view/organizational.rs deleted file mode 100644 index 7f2ca69..0000000 --- a/src/view/organizational.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::organizational::*; -use crate::viewmodel::utility::File; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps; - -#[derive(Clone, PartialEq)] -pub struct OrganizationalView { - props: Props, - value: Organizational, - error: Option, -} - -pub enum Msg { - UpdateOrg(String), - UpdateLogo(Option), - UpdateTitle(String), - UpdateRole(String), - UpdateMember(String), - UpdateRelated(String), - - Generate, -} - -impl VCardPropertyInputComponent for OrganizationalView { - fn get_input_object(&self) -> Organizational { - self.value.clone() - } - fn get_title(&self) -> std::string::String { - "Organizational".to_string() - } - fn get_error(&self) -> std::option::Option { - self.error.clone() - } -} - -impl Component for OrganizationalView { - type Message = Msg; - type Properties = Props; - fn create(props: ::Properties, link: yew::html::Scope) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Organizational::new(), - error: None, - } - } - fn update(&mut self, msg: ::Message) -> bool { - match msg { - Msg::UpdateOrg(o) => self.value.org = o, - Msg::UpdateLogo(l) => self.value.logo = l, - Msg::UpdateTitle(t) => self.value.title = t, - Msg::UpdateRole(r) => self.value.role = r, - Msg::UpdateMember(m) => self.value.member = m, - Msg::UpdateRelated(r) => self.value.related = r, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: ::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { -
- { self.render_error() } - -

{ self.get_title() }

- - { self.get_input_object().render(&link) } - -
- } - } -} diff --git a/src/view/property_group.rs b/src/view/property_group.rs new file mode 100644 index 0000000..9c86e80 --- /dev/null +++ b/src/view/property_group.rs @@ -0,0 +1,81 @@ +use crate::model::*; +use crate::view::weak_links::WeakComponentLink; +use yew::prelude::*; +use yewtil::NeqAssign; + +#[derive(Clone, PartialEq, Properties)] +pub struct InputProps< + O: 'static + VCardPropertyInputObject + Clone, + M: 'static + PartialEq + Clone, +> { + pub generated: Callback, + pub weak_link: WeakComponentLink>, +} + +#[derive(Clone, PartialEq)] +pub struct PropertyGroupInputComponent< + O: 'static + VCardPropertyInputObject, + M: 'static + PartialEq + Clone, +> { + pub props: InputProps, + pub value: O, + pub error: Option, +} + +impl, M: 'static + PartialEq + Clone> Component + for PropertyGroupInputComponent +{ + type Message = M; + type Properties = InputProps; + fn create(props: ::Properties, link: yew::html::Scope) -> Self { + props.weak_link.borrow_mut().replace(link); + Self { + props, + value: O::new(), + error: None, + } + } + fn update(&mut self, msg: ::Message) -> bool { + self.value.update(self.props.clone(), msg) + } + fn change(&mut self, props: ::Properties) -> bool { + self.props.neq_assign(props) + } + fn view(&self) -> yew::virtual_dom::VNode { + let link = self.props.weak_link.borrow().clone().unwrap(); + + html! { +
+ { self.render_error() } + +

{ self.value.get_title() }

+ + { self.value.render(&link) } + +
+ } + } +} + +impl, M: 'static + PartialEq + Clone> + PropertyGroupInputComponent +{ + /// Returns the error as `Html` + fn render_error(&self) -> Html { + html! { + <> + { + if self.error.is_some() { + html!{ +
+ { self.error.clone().unwrap().msg } +
+ } + } else { + html!{} + } + } + + } + } +} diff --git a/src/view/telephone.rs b/src/view/telephone.rs deleted file mode 100644 index 68389ba..0000000 --- a/src/view/telephone.rs +++ /dev/null @@ -1,105 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::telephone::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps; - -/// View Component for a `telephone` field -/// -/// # Examples -/// -/// ```compile_fail -/// let html = html!{ -/// | -/// Msg::GeneratedTelephone(some_telephone) -/// ) -/// /> -/// }; -/// ``` -#[derive(Clone, PartialEq)] -pub struct TelephoneView { - props: Props, - value: Telephone, - error: Option, -} - -pub enum Msg { - UpdateNumber(String), - ToggleWork, - ToggleHome, - ToggleText, - ToggleVoice, - ToggleFax, - ToggleCell, - ToggleVideo, - TogglePager, - ToggleTextPhone, - - Generate, -} - -impl VCardPropertyInputComponent for TelephoneView { - fn get_input_object(&self) -> Telephone { - self.value.clone() - } - fn get_title(&self) -> String { - "Telephone".to_string() - } - fn get_error(&self) -> Option { - self.error.clone() - } -} - -impl Component for TelephoneView { - type Message = Msg; - type Properties = Props; - fn create(props: ::Properties, link: yew::html::Scope) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Telephone::new(), - error: None, - } - } - fn update(&mut self, msg: ::Message) -> bool { - match msg { - Msg::UpdateNumber(n) => self.value.number = n, - Msg::ToggleWork => self.value.work = !self.value.work, - Msg::ToggleHome => self.value.home = !self.value.home, - Msg::ToggleText => self.value.text = !self.value.text, - Msg::ToggleVoice => self.value.voice = !self.value.voice, - Msg::ToggleFax => self.value.fax = !self.value.fax, - Msg::ToggleCell => self.value.cell = !self.value.cell, - 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 => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: ::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { -
- { self.render_error() } - -

{ self.get_title() }

- - { self.get_input_object().render(&link) } - -
- } - } -} diff --git a/src/view/weak_links.rs b/src/view/weak_links.rs new file mode 100644 index 0000000..689c3c4 --- /dev/null +++ b/src/view/weak_links.rs @@ -0,0 +1,33 @@ +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::Rc; +use yew::prelude::*; + +/// Weak link; Useful for being able to have a list of subcomponents. +pub struct WeakComponentLink(Rc>>>); + +impl Clone for WeakComponentLink { + fn clone(&self) -> Self { + Self(Rc::clone(&self.0)) + } +} + +impl Default for WeakComponentLink { + fn default() -> Self { + Self(Rc::default()) + } +} + +impl Deref for WeakComponentLink { + type Target = Rc>>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq for WeakComponentLink { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} diff --git a/src/viewmodel/address.rs b/src/viewmodel/address.rs deleted file mode 100644 index 9542675..0000000 --- a/src/viewmodel/address.rs +++ /dev/null @@ -1,113 +0,0 @@ -use super::*; -use crate::view::address::*; - -#[derive(Clone, Debug, PartialEq)] -pub struct Address { - pub post_office_box: String, - pub extension: String, - pub street: String, - pub locality: String, - pub region: String, - pub code: String, - pub country: String, - pub work: bool, - pub home: bool, -} - -impl VCardPropertyInputObject for Address { - fn new() -> Self { - Self { - post_office_box: String::new(), - extension: String::new(), - street: String::new(), - locality: String::new(), - region: String::new(), - code: String::new(), - country: String::new(), - work: false, - home: false, - } - } - fn get_input_fields(&self, link: &ComponentLink) -> Vec { - let typ = String::from("text"); - vec![ - VCardPropertyInputField::Text { - label: "Post Office Box".to_string(), - id: Some("post_office_box".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdatePostOfficeBox(e.value)), - value: self.post_office_box.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Extension".to_string(), - id: Some("extension".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateExtension(e.value)), - value: self.extension.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Street".to_string(), - id: Some("street".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateStreet(e.value)), - value: self.street.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Locality".to_string(), - id: Some("locality".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateLocality(e.value)), - value: self.locality.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Region".to_string(), - id: Some("region".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateRegion(e.value)), - value: self.region.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Code".to_string(), - id: Some("code".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateCode(e.value)), - value: self.code.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Country".to_string(), - id: Some("country".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateCountry(e.value)), - value: self.country.clone(), - typ, - }, - VCardPropertyInputField::CheckBox { - label: "Work".to_string(), - id: Some("work".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleWork), - value: self.work, - }, - VCardPropertyInputField::CheckBox { - label: "Home".to_string(), - id: Some("home".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleHome), - value: self.home, - }, - ] - } - 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() - } -} diff --git a/src/viewmodel/dates.rs b/src/viewmodel/dates.rs deleted file mode 100644 index 7d8d394..0000000 --- a/src/viewmodel/dates.rs +++ /dev/null @@ -1,45 +0,0 @@ -use super::*; -use crate::view::dates::*; - -/// Type that represents the vcard `anniversary` and `birthday` properties. -#[derive(Clone, Debug, PartialEq)] -pub struct Dates { - pub anniversary: String, - pub birthday: String, -} - -impl VCardPropertyInputObject for Dates { - fn new() -> Self { - Self { - anniversary: String::new(), - birthday: String::new(), - } - } - fn get_input_fields( - &self, - link: &yew::html::Scope, - ) -> std::vec::Vec { - let typ = String::from("date"); - vec![ - VCardPropertyInputField::Text { - label: "Anniversary".to_string(), - id: Some("anniversary".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateAnniversary(e.value)), - value: self.anniversary.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Birthday".to_string(), - id: Some("birthday".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateBirthday(e.value)), - value: self.birthday.clone(), - typ, - }, - ] - } - fn is_empty(&self) -> bool { - self.anniversary.is_empty() && self.birthday.is_empty() - } -} diff --git a/src/viewmodel/mod.rs b/src/viewmodel/mod.rs deleted file mode 100644 index 0385c61..0000000 --- a/src/viewmodel/mod.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::view::VCardPropertyInputComponent; -use crate::viewmodel::utility::File; -use wasm_bindgen::closure::Closure; -use wasm_bindgen::JsCast; -use web_sys::FileReader; -use yew::prelude::*; -use yew::services::ConsoleService; - -pub mod address; -pub mod dates; -pub mod name; -pub mod organizational; -pub mod telephone; -pub mod utility; -pub mod vcard; - -/// Trait for types that represent the data of a vcard property used inside of a `VCardPropertyInputComponent`. -pub trait VCardPropertyInputObject>: - Clone + PartialEq -where - Self: Sized, -{ - /// Function for creating a new (and empty) `VCardPropertyInputObject`. - fn new() -> Self; - /// Converts each field of the `VCardPropertyInputObject` to a VCardPropertyInputField and returns them as a vector. - fn get_input_fields(&self, link: &ComponentLink) -> Vec; - /// Returns a `Html` representation of the `VCardPropertyInputObject`. - fn render(&self, link: &ComponentLink) -> Html { - html! { -
- { - for self.get_input_fields(link).iter().map(|field| - field.render() - ) - } -
- } - } - /// Convenience function for checking if the `VCardPropertyInputObject` is empty. - fn is_empty(&self) -> bool; -} - -/// Type for saving error messages. -/// -/// More of a placeholder for something better later on. -#[derive(Debug, Clone, PartialEq)] -pub struct Error { - pub msg: String, -} - -/// Type that represents the visiual appearance of an input field. -pub enum VCardPropertyInputField { - Text { - label: String, - id: Option, - placeholder: Option, - oninput: Callback, - value: String, - typ: String, - }, - File { - label: String, - name: String, - callback: Callback>, - value: Option, - }, - CheckBox { - label: String, - id: Option, - onclick: Callback, - value: bool, - }, -} - -impl VCardPropertyInputField { - /// Returns a `Html` representation of the `VCardPropertyInputField`. - pub fn render(&self) -> Html { - match self { - Self::Text { - label, - id, - placeholder, - oninput, - value: _, - typ, - } => Self::text_field_input(label, id, placeholder, oninput, typ), - Self::File { - label, - name, - callback, - value, - } => Self::file_field_input(label, name, callback, value), - Self::CheckBox { - label, - id, - onclick, - value, - } => Self::checkbox_field_input(label, id, value, onclick), - } - } - /// Returns an `Html` representation of a text input field with the given parameters. - fn text_field_input( - label: &str, - id: &Option, - placeholder: &Option, - oninput: &Callback, - typ: &str, - ) -> Html { - html! { -
- -
- -
-
- } - } - /// Returns an `Html` representation of a file input field with the given parameters. - fn file_field_input( - label: &str, - name: &str, - callback: &Callback>, - file: &Option, - ) -> Html { - let callback = callback.clone(); - let onchange = Callback::<()>::default(); - let onchange = onchange.reform(move |c: ChangeData| { - if let ChangeData::Files(files) = c { - match files.item(0) { - Some(file) => { - let file_reader = FileReader::new().unwrap(); - match file_reader.read_as_data_url(&file) { - Ok(_) => (), - Err(_) => ConsoleService::warn("Error: Couldn't get file as data url."), - }; - - let callback = callback.clone(); - let onload = Closure::wrap(Box::new(move |event: Event| { - let file_reader: FileReader = - event.target().unwrap().dyn_into().unwrap(); - let data_url: Option = - file_reader.result().unwrap().as_string(); - match data_url { - Some(content) => callback.emit(Some(File { - name: file.name(), - content, - })), - None => { - ConsoleService::warn("Couldn't get data url as string."); - callback.emit(None); - } - }; - }) as Box); - - file_reader.set_onload(Some(onload.as_ref().unchecked_ref())); - onload.forget(); - } - None => callback.emit(None), - } - } else { - callback.emit(None); - } - }); - html! { -
- -
- -
-
- } - } - /// Returns an `Html` representation of a checkbox input field with the given parameters. - fn checkbox_field_input( - label: &str, - id: &Option, - checked: &bool, - onclick: &Callback, - ) -> Html { - html! { -
- -
- } - } -} diff --git a/src/viewmodel/name.rs b/src/viewmodel/name.rs deleted file mode 100644 index de0aa81..0000000 --- a/src/viewmodel/name.rs +++ /dev/null @@ -1,119 +0,0 @@ -use super::*; -use crate::view::name::*; - -/// Type that represents a vcard `name` property -/// -/// # Examples -/// ``` -/// # use bcard_wasm_webapp::viewmodel::name::Name; -/// # use crate::bcard_wasm_webapp::viewmodel::VCardPropertyInputObject; -/// let mut name = Name::new(); -/// name.prefix = String::from("Sir"); -/// name.first_name = String::from("Arthur"); -/// name.middle_name = String::from("Charles"); -/// name.last_name = String::from("Clarke"); -/// name.suffix = String::from("CBE FRAS"); -/// -/// assert_eq!(name.generate_fn(), String::from("Sir Arthur Charles Clarke, CBE FRAS")); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub struct Name { - pub prefix: String, - pub first_name: String, - pub middle_name: String, - pub last_name: String, - pub suffix: String, -} - -impl VCardPropertyInputObject for Name { - fn new() -> Self { - Self { - prefix: String::new(), - first_name: String::new(), - middle_name: String::new(), - last_name: String::new(), - suffix: String::new(), - } - } - fn get_input_fields( - &self, - link: &ComponentLink, - ) -> std::vec::Vec { - let typ = String::from("text"); - vec![ - VCardPropertyInputField::Text { - label: "Prefix".to_string(), - id: Some("prefix".to_string()), - placeholder: Some("Sir".to_string()), - oninput: link.callback(|e: InputData| Msg::UpdatePrefix(e.value)), - value: self.prefix.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "First Name".to_string(), - id: Some("first_name".to_string()), - placeholder: Some("Arthur".to_string()), - oninput: link.callback(|e: InputData| Msg::UpdateFirstName(e.value)), - value: self.first_name.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Middle Name".to_string(), - id: Some("middle_name".to_string()), - placeholder: Some("Charles".to_string()), - oninput: link.callback(|e: InputData| Msg::UpdateMiddleName(e.value)), - value: self.middle_name.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Last Name".to_string(), - id: Some("last_name".to_string()), - placeholder: Some("Clarke".to_string()), - oninput: link.callback(|e: InputData| Msg::UpdateLastName(e.value)), - value: self.last_name.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Suffix".to_string(), - id: Some("suffix".to_string()), - placeholder: Some("CBE FRAS".to_string()), - oninput: link.callback(|e: InputData| Msg::UpdateSuffix(e.value)), - value: self.suffix.clone(), - typ, - }, - ] - } - 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() - } -} - -impl Name { - pub fn generate_fn(&self) -> String { - let mut full_name = String::new(); - - full_name.push_str(&self.prefix); - if !self.first_name.is_empty() { - full_name.push_str(" "); - full_name.push_str(&self.first_name); - } - if !self.middle_name.is_empty() { - full_name.push_str(" "); - full_name.push_str(&self.middle_name); - } - if !self.last_name.is_empty() { - full_name.push_str(" "); - full_name.push_str(&self.last_name); - } - if !self.suffix.is_empty() { - full_name.push_str(", "); - full_name.push_str(&self.suffix); - } - - full_name - } -} diff --git a/src/viewmodel/organizational.rs b/src/viewmodel/organizational.rs deleted file mode 100644 index 72b19d2..0000000 --- a/src/viewmodel/organizational.rs +++ /dev/null @@ -1,88 +0,0 @@ -use super::*; -use crate::view::organizational::*; - -#[derive(Clone, Debug, PartialEq)] -pub struct Organizational { - pub org: String, - pub logo: Option, - pub title: String, - pub role: String, - pub member: String, - pub related: String, -} - -impl VCardPropertyInputObject for Organizational { - fn new() -> Self { - Self { - org: String::new(), - logo: None, - title: String::new(), - role: String::new(), - member: String::new(), - related: String::new(), - } - } - fn get_input_fields( - &self, - link: &yew::html::Scope, - ) -> std::vec::Vec { - let typ = String::from("text"); - vec![ - VCardPropertyInputField::Text { - label: "Organisation".to_string(), - id: Some("org".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateOrg(e.value)), - value: self.org.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::File { - // TODO: Add Upload for logo - label: "Logo".to_string(), - name: "logo".to_string(), - callback: link.callback(|file: Option| Msg::UpdateLogo(file)), - value: self.logo.clone(), - }, - VCardPropertyInputField::Text { - label: "Title".to_string(), - id: Some("title".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateTitle(e.value)), - value: self.title.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Role".to_string(), - id: Some("role".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateRole(e.value)), - value: self.role.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Member".to_string(), - id: Some("member".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateMember(e.value)), - value: self.member.clone(), - typ: typ.clone(), - }, - VCardPropertyInputField::Text { - label: "Related".to_string(), - id: Some("related".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateRelated(e.value)), - value: self.related.clone(), - typ: typ, - }, - ] - } - fn is_empty(&self) -> bool { - self.org.is_empty() - && self.logo.is_none() - && self.title.is_empty() - && self.role.is_empty() - && self.member.is_empty() - && self.related.is_empty() - } -} diff --git a/src/viewmodel/telephone.rs b/src/viewmodel/telephone.rs deleted file mode 100644 index 44b938f..0000000 --- a/src/viewmodel/telephone.rs +++ /dev/null @@ -1,106 +0,0 @@ -use super::*; -use crate::view::telephone::*; - -#[derive(Clone, Debug, PartialEq)] -pub struct Telephone { - pub number: String, - pub work: bool, - pub home: bool, - pub text: bool, - pub voice: bool, - pub fax: bool, - pub cell: bool, - pub video: bool, - pub pager: bool, - pub text_phone: bool, -} - -impl VCardPropertyInputObject for Telephone { - fn new() -> Self { - Self { - number: String::new(), - work: false, - home: false, - text: false, - voice: false, - fax: false, - cell: false, - video: false, - pager: false, - text_phone: false, - } - } - fn get_input_fields( - &self, - link: &ComponentLink, - ) -> Vec { - let typ = String::from("tel"); - vec![ - VCardPropertyInputField::Text { - label: "Number".to_string(), - id: Some("number".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateNumber(e.value)), - value: self.number.clone(), - typ, - }, - VCardPropertyInputField::CheckBox { - label: "Work".to_string(), - id: Some("work".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleWork), - value: self.work, - }, - VCardPropertyInputField::CheckBox { - label: "Home".to_string(), - id: Some("home".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleHome), - value: self.home, - }, - VCardPropertyInputField::CheckBox { - label: "Text".to_string(), - id: Some("text".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleText), - value: self.text, - }, - VCardPropertyInputField::CheckBox { - label: "Voice".to_string(), - id: Some("voice".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleVoice), - value: self.voice, - }, - VCardPropertyInputField::CheckBox { - label: "Fax".to_string(), - id: Some("fax".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleFax), - value: self.fax, - }, - VCardPropertyInputField::CheckBox { - label: "Cell".to_string(), - id: Some("cell".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleCell), - value: self.cell, - }, - VCardPropertyInputField::CheckBox { - label: "Video".to_string(), - id: Some("video".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleVideo), - value: self.video, - }, - VCardPropertyInputField::CheckBox { - label: "Pager".to_string(), - id: Some("pager".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::TogglePager), - value: self.pager, - }, - VCardPropertyInputField::CheckBox { - label: "Text Phone".to_string(), - id: Some("text_phone".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleTextPhone), - value: self.text_phone, - }, - ] - } - fn is_empty(&self) -> bool { - self.number.is_empty() - } -} diff --git a/src/viewmodel/utility.rs b/src/viewmodel/utility.rs deleted file mode 100644 index 617ee45..0000000 --- a/src/viewmodel/utility.rs +++ /dev/null @@ -1,45 +0,0 @@ -#[derive(Clone)] -pub struct Download { - pub file_name: String, - pub content: String, - pub mime_type: MimeType, -} - -impl Download { - pub fn as_data_link(&self) -> String { - let data = base64::encode(&*self.content); - let uri_component: String = js_sys::encode_uri_component(&data).into(); - - format!("data:{};base64,{}", self.mime_type.as_text(), uri_component) - } -} - -#[derive(Clone, Copy)] -pub enum MimeType { - PDF, - VCard, - SVG, -} - -impl MimeType { - pub fn as_text(&self) -> &str { - match self { - MimeType::PDF => "application/pdf", - MimeType::VCard => "text/vcard", - MimeType::SVG => "image/svg+xml", - } - } -} - -#[derive(Clone, Copy, PartialEq)] -pub enum DownloadOption { - PDF, - VCard, - QrCode, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct File { - pub name: String, - pub content: String, -} diff --git a/src/viewmodel/vcard.rs b/src/viewmodel/vcard.rs deleted file mode 100644 index 18ce43a..0000000 --- a/src/viewmodel/vcard.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::viewmodel::address::Address; -use crate::viewmodel::dates::Dates; -use crate::viewmodel::name::Name; -use crate::viewmodel::organizational::Organizational; -use crate::viewmodel::telephone::Telephone; - -/// Type that represents the data structure of a vcard. -#[derive(Clone, Debug)] -pub struct VCardData { - pub names: Vec, - pub addresses: Vec
, - pub telephones: Vec, - pub datess: Vec, - pub organizationals: Vec, -} - -macro_rules! make_vec_adder_fn { - ( fn $fnname:ident $property:ident => $($arg_name:ident : $arg_type:ty),* ) => { - pub fn $fnname(&mut self, $( $arg_name : $arg_type ),*) { - $(self.$property.push($arg_name);)* - } - }; -} - -impl VCardData { - pub fn new() -> Self { - Self { - names: Vec::new(), - addresses: Vec::new(), - telephones: Vec::new(), - datess: Vec::new(), - organizationals: Vec::new(), - } - } - make_vec_adder_fn!( fn add_name names => name: Name ); - make_vec_adder_fn!( fn add_address addresses => address: Address ); - make_vec_adder_fn!( fn add_telephone telephones => telephone: Telephone ); - make_vec_adder_fn!( fn add_dates datess => dates: Dates ); - make_vec_adder_fn!( fn add_organizational organizationals => organizational: Organizational ); -} -- cgit v1.2.3