diff options
-rw-r--r-- | Readme.md | 3 | ||||
-rw-r--r-- | src/view/dates.rs | 78 | ||||
-rw-r--r-- | src/view/main.rs | 54 | ||||
-rw-r--r-- | src/view/mod.rs | 1 | ||||
-rw-r--r-- | src/viewmodel/address.rs | 8 | ||||
-rw-r--r-- | src/viewmodel/dates.rs | 43 | ||||
-rw-r--r-- | src/viewmodel/mod.rs | 11 | ||||
-rw-r--r-- | src/viewmodel/name.rs | 6 | ||||
-rw-r--r-- | src/viewmodel/telephone.rs | 2 | ||||
-rw-r--r-- | src/viewmodel/vcard.rs | 4 |
10 files changed, 205 insertions, 5 deletions
@@ -13,7 +13,7 @@ Supports generating vCards (.vcf), print-ready PDF business cards and QR Codes. ## 🎮 Features -* Supported properties as of yet: name, address, telephone +* Supported properties as of yet: name, address, telephone, birthday, anniversary * Output and Download as vCard (.vcf) * Output and Download as QR Code * Generates a dummy PDF @@ -24,6 +24,7 @@ Supports generating vCards (.vcf), print-ready PDF business cards and QR Codes. * Button for adding more of one type of property (Support is already there) * Put generation and download both in download button * Generate PDF +* Replace more code with macros to make it even more unreadable 😉 ## 🚲 Usage diff --git a/src/view/dates.rs b/src/view/dates.rs new file mode 100644 index 0000000..1c16680 --- /dev/null +++ b/src/view/dates.rs @@ -0,0 +1,78 @@ +use yew::prelude::*; +use yewtil::NeqAssign; +use crate::viewmodel::Error; +use crate::view::WeakComponentLink; +use crate::viewmodel::dates::*; +use crate::viewmodel::VCardPropertyInputObject; +use super::VCardPropertyInputComponent; + +pub struct DatesView { + props: Props, + value: Dates, + error: Option<Error>, +} + +pub enum Msg { + UpdateAnniversary(String), + UpdateBirthday(String), + + Generate, +} + +#[derive(Clone, PartialEq, Properties)] +pub struct Props { + pub generated: Callback<Dates>, + pub weak_link: WeakComponentLink<DatesView>, +} + +impl VCardPropertyInputComponent<Dates> 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<Error> { + self.error.clone() + } +} + +impl Component for DatesView { + 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 { + props, + value: Dates::new(), + error: None, + } + } + fn update(&mut self, msg: <Self as yew::Component>::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: <Self as yew::Component>::Properties) -> bool { + self.props.neq_assign(props) + } + fn view(&self) -> yew::virtual_dom::VNode { + let link = self.props.weak_link.borrow().clone().unwrap(); + + html!{ + <div class="box"> + { self.render_error() } + + <h3 class="subtitle">{ self.get_title() }</h3> + + { self.get_input_object().render(&link) } + + </div> + } + } +}
\ No newline at end of file diff --git a/src/view/main.rs b/src/view/main.rs index 83acc3f..0d2cddb 100644 --- a/src/view/main.rs +++ b/src/view/main.rs @@ -1,3 +1,4 @@ +use crate::viewmodel::dates::Dates; use yew::services::ConsoleService; use crate::viewmodel::vcard::VCardData; use crate::viewmodel::Error; @@ -6,6 +7,7 @@ use crate::viewmodel::VCardPropertyInputObject; use super::WeakComponentLink; use super::name::{self,NameView}; use super::address::{self,AddressView}; +use super::dates::{self,DatesView}; use genpdf::Element as _; use genpdf::{elements, style, fonts}; use qrcodegen::QrCode; @@ -32,6 +34,7 @@ pub struct MainView { name_links: Vec<WeakComponentLink<NameView>>, address_links: Vec<WeakComponentLink<AddressView>>, telephone_links: Vec<WeakComponentLink<TelephoneView>>, + dates_links: Vec<WeakComponentLink<DatesView>>, answer_count: usize, } @@ -40,6 +43,7 @@ pub enum Msg { AddName, AddAddress, AddTelephone, + AddDates, ChangeDownloadOption(DownloadOption), @@ -47,6 +51,7 @@ pub enum Msg { GeneratedName(Name), GeneratedAddress(Address), GeneratedTelephone(Telephone), + GeneratedDates(Dates), GenerationComplete, Nope, @@ -67,6 +72,7 @@ impl Component for MainView { name_links: vec![WeakComponentLink::default()], address_links: vec![WeakComponentLink::default()], telephone_links: vec![WeakComponentLink::default()], + dates_links: vec![WeakComponentLink::default()], answer_count: 0, } } @@ -88,6 +94,10 @@ impl Component for MainView { self.telephone_links.push(WeakComponentLink::default()); shouldrender = true; }, + Msg::AddDates => { + self.dates_links.push(WeakComponentLink::default()); + shouldrender = true; + }, Msg::ChangeDownloadOption(option) => { self.selected_option = option; shouldrender = false; @@ -110,6 +120,11 @@ impl Component for MainView { let telephone_link = telephone_link.borrow().clone().unwrap(); telephone_link.send_message(telephone::Msg::Generate); } + + for dates_link in self.dates_links.iter() { + let dates_link = dates_link.borrow().clone().unwrap(); + dates_link.send_message(dates::Msg::Generate) + } } /* DownloadOption::PDF => { @@ -163,6 +178,17 @@ impl Component for MainView { shouldrender = true; }, + Msg::GeneratedDates(dates) => { + + self.answer_count += 1; + + match self.vcard_data.get_mut() { + Some(vcard_data) => vcard_data.add_dates(dates), + None => ConsoleService::info("Error in GeneratedDates: Couldn't get mutable borrow of VCardData"), + }; + + shouldrender = true; + }, Msg::GenerationComplete => { self.answer_count = 0; @@ -305,6 +331,20 @@ impl Component for MainView { ); } } + + for dates in vcard_data.datess { + + if !dates.anniversary.is_empty() { + builder = builder.with_anniversary(dates.anniversary); + } + + if !dates.birthday.is_empty() { + builder = builder.with_bday( + parameters!(), + dates.birthday + ); + } + } let rev = Local::now(); @@ -445,6 +485,19 @@ impl Component for MainView { ) } + { + for self.dates_links.iter().map(|link| + html!{ + <DatesView weak_link=link + generated=self.link.callback( + |d: Dates| + Msg::GeneratedDates(d) + ) + /> + } + ) + } + <div class="block level-left"> <button onclick=self.link.callback(|_| Msg::Generate) class="button is-primary level-item">{ "Generate" }</button> @@ -587,5 +640,6 @@ impl MainView { self.name_links.len() + self.address_links.len() + self.telephone_links.len() + + self.dates_links.len() } }
\ No newline at end of file diff --git a/src/view/mod.rs b/src/view/mod.rs index d97c6e4..65ef06d 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -8,6 +8,7 @@ pub mod main; pub mod name; pub mod address; pub mod telephone; +pub mod dates; /// Trait for types that represent an input component for a vcard property. pub trait VCardPropertyInputComponent<T: VCardPropertyInputObject<Self>>: Component { diff --git a/src/viewmodel/address.rs b/src/viewmodel/address.rs index 6fd3173..26e168a 100644 --- a/src/viewmodel/address.rs +++ b/src/viewmodel/address.rs @@ -29,6 +29,7 @@ impl VCardPropertyInputObject<AddressView> for Address { } } fn get_input_fields(&self, link: &ComponentLink<AddressView>) -> Vec<VCardPropertyInputField> { + let typ = String::from("text"); vec![ VCardPropertyInputField::Text{ label: "Post Office Box".to_string(), @@ -36,6 +37,7 @@ impl VCardPropertyInputObject<AddressView> for Address { 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(), @@ -43,6 +45,7 @@ impl VCardPropertyInputObject<AddressView> for Address { placeholder: None, oninput: link.callback(|e: InputData| Msg::UpdateExtension(e.value)), value: self.extension.clone(), + typ: typ.clone(), }, VCardPropertyInputField::Text{ label: "Street".to_string(), @@ -50,6 +53,7 @@ impl VCardPropertyInputObject<AddressView> for Address { placeholder: None, oninput: link.callback(|e: InputData| Msg::UpdateStreet(e.value)), value: self.street.clone(), + typ: typ.clone(), }, VCardPropertyInputField::Text{ label: "Locality".to_string(), @@ -57,6 +61,7 @@ impl VCardPropertyInputObject<AddressView> for Address { placeholder: None, oninput: link.callback(|e: InputData| Msg::UpdateLocality(e.value)), value: self.locality.clone(), + typ: typ.clone(), }, VCardPropertyInputField::Text{ label: "Region".to_string(), @@ -64,6 +69,7 @@ impl VCardPropertyInputObject<AddressView> for Address { placeholder: None, oninput: link.callback(|e: InputData| Msg::UpdateRegion(e.value)), value: self.region.clone(), + typ: typ.clone(), }, VCardPropertyInputField::Text{ label: "Code".to_string(), @@ -71,6 +77,7 @@ impl VCardPropertyInputObject<AddressView> for Address { placeholder: None, oninput: link.callback(|e: InputData| Msg::UpdateCode(e.value)), value: self.code.clone(), + typ: typ.clone(), }, VCardPropertyInputField::Text{ label: "Country".to_string(), @@ -78,6 +85,7 @@ impl VCardPropertyInputObject<AddressView> for Address { placeholder: None, oninput: link.callback(|e: InputData| Msg::UpdateCountry(e.value)), value: self.country.clone(), + typ, }, VCardPropertyInputField::CheckBox{ label: "Work".to_string(), diff --git a/src/viewmodel/dates.rs b/src/viewmodel/dates.rs new file mode 100644 index 0000000..6c7fa37 --- /dev/null +++ b/src/viewmodel/dates.rs @@ -0,0 +1,43 @@ +use crate::view::dates::*; +use super::*; + +/// Type that represents the vcard `anniversary` and `birthday` properties. +#[derive(Clone, Debug)] +pub struct Dates { + pub anniversary: String, + pub birthday: String, +} + +impl VCardPropertyInputObject<DatesView> for Dates { + fn new() -> Self { + Self { + anniversary: String::new(), + birthday: String::new(), + } + } + fn get_input_fields(&self, link: &yew::html::Scope<DatesView>) -> std::vec::Vec<VCardPropertyInputField> { + 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() + } +}
\ No newline at end of file diff --git a/src/viewmodel/mod.rs b/src/viewmodel/mod.rs index d5d0de3..edd1a8e 100644 --- a/src/viewmodel/mod.rs +++ b/src/viewmodel/mod.rs @@ -2,10 +2,11 @@ use yew::prelude::*; use crate::view::VCardPropertyInputComponent; pub mod vcard; +pub mod utility; pub mod address; pub mod name; pub mod telephone; -pub mod utility; +pub mod dates; /// Trait for types that represent the data of a vcard property used inside of a `VCardPropertyInputComponent`. @@ -48,6 +49,7 @@ pub enum VCardPropertyInputField { placeholder: Option<String>, oninput: Callback<InputData>, value: String, + typ: String }, CheckBox { label: String, @@ -67,7 +69,8 @@ impl VCardPropertyInputField { placeholder, oninput, value: _, - } => Self::text_field_input(label, id, placeholder, oninput), + typ, + } => Self::text_field_input(label, id, placeholder, oninput, typ), Self::CheckBox { label, id, @@ -77,7 +80,7 @@ impl VCardPropertyInputField { } } /// Returns an `Html` representation of a text input field with the given parameters. - fn text_field_input(label: &str, id: &Option<String>, placeholder: &Option<String>, oninput: &Callback<InputData>) -> Html { + fn text_field_input(label: &str, id: &Option<String>, placeholder: &Option<String>, oninput: &Callback<InputData>, typ: &str) -> Html { html!{ <div class="field column is-one-fifth-widescreen @@ -87,7 +90,7 @@ impl VCardPropertyInputField { <label class="label">{ label }</label> <div class="control"> <input id=id.as_ref().unwrap_or(&"".to_string()) - type="text" + type=typ placeholder=placeholder.as_ref().unwrap_or(&"".to_string()) oninput=oninput /> diff --git a/src/viewmodel/name.rs b/src/viewmodel/name.rs index b6ded60..aa6747c 100644 --- a/src/viewmodel/name.rs +++ b/src/viewmodel/name.rs @@ -36,6 +36,7 @@ impl VCardPropertyInputObject<NameView> for Name { } } fn get_input_fields(&self, link: &ComponentLink<NameView>) -> std::vec::Vec<VCardPropertyInputField> { + let typ = String::from("text"); vec![ VCardPropertyInputField::Text{ label: "Prefix".to_string(), @@ -43,6 +44,7 @@ impl VCardPropertyInputObject<NameView> for Name { 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(), @@ -50,6 +52,7 @@ impl VCardPropertyInputObject<NameView> for Name { 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(), @@ -57,6 +60,7 @@ impl VCardPropertyInputObject<NameView> for Name { 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(), @@ -64,6 +68,7 @@ impl VCardPropertyInputObject<NameView> for Name { 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(), @@ -71,6 +76,7 @@ impl VCardPropertyInputObject<NameView> for Name { placeholder: Some("CBE FRAS".to_string()), oninput: link.callback(|e: InputData| Msg::UpdateSuffix(e.value)), value: self.suffix.clone(), + typ, }, ] } diff --git a/src/viewmodel/telephone.rs b/src/viewmodel/telephone.rs index 6c930a2..774b63d 100644 --- a/src/viewmodel/telephone.rs +++ b/src/viewmodel/telephone.rs @@ -31,6 +31,7 @@ impl VCardPropertyInputObject<TelephoneView> for Telephone { } } fn get_input_fields(&self, link: &ComponentLink<TelephoneView>) -> Vec<VCardPropertyInputField> { + let typ = String::from("tel"); vec![ VCardPropertyInputField::Text{ label: "Number".to_string(), @@ -38,6 +39,7 @@ impl VCardPropertyInputObject<TelephoneView> for Telephone { placeholder: None, oninput: link.callback(|e: InputData| Msg::UpdateNumber(e.value)), value: self.number.clone(), + typ, }, VCardPropertyInputField::CheckBox{ label: "Work".to_string(), diff --git a/src/viewmodel/vcard.rs b/src/viewmodel/vcard.rs index 2b81fd8..565b1ff 100644 --- a/src/viewmodel/vcard.rs +++ b/src/viewmodel/vcard.rs @@ -1,3 +1,4 @@ +use crate::viewmodel::dates::Dates; use crate::viewmodel::telephone::Telephone; use crate::viewmodel::address::Address; use crate::viewmodel::name::Name; @@ -8,6 +9,7 @@ pub struct VCardData { pub names: Vec<Name>, pub addresses: Vec<Address>, pub telephones: Vec<Telephone>, + pub datess: Vec<Dates>, } macro_rules! make_vec_adder_fn { @@ -24,9 +26,11 @@ impl VCardData { names: Vec::new(), addresses: Vec::new(), telephones: Vec::new(), + datess: 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 ); }
\ No newline at end of file |