diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 3 | ||||
| -rw-r--r-- | src/util.rs | 39 | ||||
| -rw-r--r-- | src/view/address.rs | 181 | ||||
| -rw-r--r-- | src/view/input_objects/address.rs | 151 | ||||
| -rw-r--r-- | src/view/input_objects/mod.rs | 106 | ||||
| -rw-r--r-- | src/view/input_objects/name.rs | 118 | ||||
| -rw-r--r-- | src/view/input_objects/telephone.rs | 162 | ||||
| -rw-r--r-- | src/view/input_objects/utility.rs | 39 | ||||
| -rw-r--r-- | src/view/main.rs | 335 | ||||
| -rw-r--r-- | src/view/mod.rs | 388 | ||||
| -rw-r--r-- | src/view/name.rs | 138 | ||||
| -rw-r--r-- | src/view/telephone.rs | 137 | 
12 files changed, 1007 insertions, 790 deletions
| @@ -4,14 +4,13 @@ extern crate console_error_panic_hook;  use wasm_bindgen::prelude::*;  use yew::prelude::App;  use std::panic; -use view::MainView; +use view::main::MainView;  // Use `wee_alloc` as the global allocator.  #[global_allocator]  static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;  mod view; -mod util;  fn init() {      panic::set_hook(Box::new(console_error_panic_hook::hook)); diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 3d8f231..0000000 --- a/src/util.rs +++ /dev/null @@ -1,39 +0,0 @@ -use yew::prelude::*; - -pub fn text_field_input(label: &str, id: &str, placeholder: Option<&str>, oninput: Callback<InputData>) -> Html { -    html!{ -        <div class="field column  -                    is-one-fifth-widescreen -                    is-one-quarter-desktop -                    is-one-third-tablet -                    is-half-mobile" > -            <label class="label">{ label }</label> -            <div class="control"> -                <input  id=id -                        type="text"  -                        placeholder=placeholder.unwrap_or("") -                        oninput=oninput -                /> -            </div> -        </div> -    } -} - -pub fn checkbox_field_input(label: &str, id: &str, checked: bool, onclick: Callback<MouseEvent>) -> Html { -    html!{ -        <div class="field column  -                    is-one-fifth-widescreen  -                    is-one-quarter-desktop  -                    is-one-third-tablet  -                    is-half-mobile" > -            <label class="checkbox"> -                <input  id=id -                        type="checkbox"  -                        checked=checked -                        onclick=onclick -                /> -                { label } -            </label> -        </div> -    } -}
\ No newline at end of file diff --git a/src/view/address.rs b/src/view/address.rs index a30ba85..4d3eae7 100644 --- a/src/view/address.rs +++ b/src/view/address.rs @@ -1,108 +1,14 @@  use yew::prelude::*;  use vcard::properties; -use vcard::parameters; -use vcard::values::{self, text}; -use std::collections::HashSet; - -use crate::util; - -#[derive(Clone)] -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, -    address_type: AddressType, -} - -impl Address { -    pub fn new_with_type(address_type: AddressType) -> 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(), -            address_type -        } -    } -    pub fn to_vcard_value(&self) -> properties::Address { -        let address_value = values::address_value::AddressValue::from_components( -            match self.post_office_box.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.post_office_box).unwrap()), -            }, -            match self.extension.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.extension).unwrap()), -            }, -            match self.street.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.street).unwrap()), -            }, -            match self.locality.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.locality).unwrap()), -            }, -            match self.region.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.region).unwrap()), -            }, -            match self.code.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.code).unwrap()), -            }, -            match self.country.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.country).unwrap()), -            }, -        ); - -        let mut address = properties::Address::from_address_value(address_value); - -        let type_values = { -            let mut type_values = HashSet::new(); - -            type_values.insert( -                match self.address_type { -                    AddressType::Home => values::type_value::TypeValue::Home, -                    AddressType::Work => values::type_value::TypeValue::Work, -                } -            ); - -            vcard::Set::from_hash_set(type_values).unwrap() -        }; - -        address.typ = Some(parameters::typ::Type::from_type_values(type_values)); - -        address -    } -} - -#[derive(Clone, Copy, PartialEq)] -pub enum AddressType { -    Home, -    Work, -} - -impl AddressType { -    pub fn to_str(&self) -> &str { -        match self { -            AddressType::Home => "Home", -            AddressType::Work => "Work", -        } -    } -} +use super::input_objects::address::*; +use super::input_objects::VCardPropertyInputObject; +use super::VCardPropertyInputComponent;  pub struct AddressView {      link: ComponentLink<Self>,      value: Address,      oninput: Callback<Address>, +    errors: Vec<String>,  }  pub enum Msg { @@ -113,23 +19,37 @@ pub enum Msg {      UpdateRegion(String),      UpdateCode(String),      UpdateCountry(String), +    ToggleWork, +    ToggleHome,  }  #[derive(Clone, PartialEq, Properties)]  pub struct Props {      pub oninput: Callback<Address>, -    pub address_type: AddressType,      //pub errors: Vec<String>,  } +impl VCardPropertyInputComponent<properties::Address, Address> for AddressView { +    fn get_input_object(&self) -> Address { +        self.value.clone() +    } +    fn get_title(&self) -> String { +        "Address".to_string() +    } +    fn get_errors(&self) -> Vec<String> { +        self.errors.clone() +    } +} +  impl Component for AddressView {      type Message = Msg;      type Properties = Props;      fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self {          Self {              link, -            value: Address::new_with_type(props.address_type), +            value: Address::new(),              oninput: props.oninput, +            errors: vec![],          }      }      fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { @@ -141,72 +61,29 @@ impl Component for AddressView {              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,          };          self.oninput.emit(self.value.clone());          true      }      fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool {           self.oninput = props.oninput; -        self.value.address_type = props.address_type;          true      }      fn view(&self) -> yew::virtual_dom::VNode {          html!{              <div class="box"> -                <h3 class="subtitle">{ format!("{} Address", self.value.address_type.to_str()) }</h3> - -                <div class="columns is-mobile is-multiline"> - -                    { util::text_field_input( -                        "Post Office Box",  -                        "post_office_box",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdatePostOfficeBox(e.value)) -                    ) } - -                    { util::text_field_input( -                        "Extension",  -                        "extension",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateExtension(e.value)) -                    ) } - -                    { util::text_field_input( -                        "Street",  -                        "street",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateStreet(e.value)) -                    ) } - -                    { util::text_field_input( -                        "Locality",  -                        "locality",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateLocality(e.value)) -                    ) } - -                    { util::text_field_input( -                        "Region",  -                        "region",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateRegion(e.value)) -                    ) } +                { +                    self.render_errors() +                } -                    { util::text_field_input( -                        "Postal Code",  -                        "code",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateCode(e.value)) -                    ) } +                <h3 class="subtitle">{ self.get_title() }</h3> -                    { util::text_field_input( -                        "Country",  -                        "country",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateCountry(e.value)) -                    ) } +                { +                    self.get_input_object().render(&self.link) +                } -                </div>              </div>          }      } diff --git a/src/view/input_objects/address.rs b/src/view/input_objects/address.rs new file mode 100644 index 0000000..3975200 --- /dev/null +++ b/src/view/input_objects/address.rs @@ -0,0 +1,151 @@ +use vcard::properties; +use vcard::parameters; +use vcard::values::{self, text}; +use std::collections::HashSet; +use super::*; +use super::super::address::*; + +#[derive(Clone)] +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<properties::Address, AddressView> 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<AddressView>) -> Vec<VCardPropertyInputField> { +        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(), +            }, +            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(), +            }, +            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(), +            }, +            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(), +            }, +            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(), +            }, +            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(), +            }, +            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(), +            }, +            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 to_vcard_property(&self) -> Result<properties::Address, VCardPropertyInputError> { +        let address_value = values::address_value::AddressValue::from_components( +            match self.post_office_box.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.post_office_box).unwrap()), +            }, +            match self.extension.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.extension).unwrap()), +            }, +            match self.street.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.street).unwrap()), +            }, +            match self.locality.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.locality).unwrap()), +            }, +            match self.region.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.region).unwrap()), +            }, +            match self.code.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.code).unwrap()), +            }, +            match self.country.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.country).unwrap()), +            }, +        ); + +        let mut address = properties::Address::from_address_value(address_value); + +        let type_values = { +            let mut type_values = HashSet::new(); + +            if self.work { +                type_values.insert(values::type_value::TypeValue::Work); +            } +            if self.home { +                type_values.insert(values::type_value::TypeValue::Home); +            } + +            vcard::Set::from_hash_set(type_values).unwrap() +        }; + +        address.typ = Some(parameters::typ::Type::from_type_values(type_values)); + +        Ok(address) +    } +}
\ No newline at end of file diff --git a/src/view/input_objects/mod.rs b/src/view/input_objects/mod.rs new file mode 100644 index 0000000..7c0fdff --- /dev/null +++ b/src/view/input_objects/mod.rs @@ -0,0 +1,106 @@ +use vcard::properties; +use yew::prelude::*; +use super::VCardPropertyInputComponent; + +pub mod address; +pub mod birthday; +pub mod name; +pub mod photo; +pub mod telephone; +pub mod utility; + +pub trait VCardPropertyInputObject<P: properties::Property, C: VCardPropertyInputComponent<P, Self>>  +    where   Self: Sized  +{ +    fn new() -> Self; +    fn get_input_fields(&self, link: &ComponentLink<C>) -> Vec<VCardPropertyInputField>; +    fn render(&self, link: &ComponentLink<C>) -> Html { +        html!{ +            <div class="columns is-mobile is-multiline"> +                {  +                    for self.get_input_fields(link).iter().map(|field| +                        field.render() +                    )  +                } +            </div> +        } +    } +    fn to_vcard_property(&self) -> Result<P, VCardPropertyInputError>; +} + +#[derive(Debug)] +pub struct VCardPropertyInputError { +    msg: String, +} + +pub enum VCardPropertyInputField { +    Text { +        label: String, +        id: Option<String>, +        placeholder: Option<String>, +        oninput: Callback<InputData>, +        value: String, +    }, +    CheckBox { +        label: String, +        id: Option<String>, +        onclick: Callback<MouseEvent>, +        value: bool, +    }, +} + +impl VCardPropertyInputField { +    pub fn render(&self) -> Html { +        match self { +            Self::Text { +                label, +                id, +                placeholder, +                oninput, +                value: _, +            } => Self::text_field_input(label, id, placeholder, oninput), +            Self::CheckBox { +                label, +                id, +                onclick, +                value, +            } => Self::checkbox_field_input(label, id, value, onclick), +        } +    } +    fn text_field_input(label: &str, id: &Option<String>, placeholder: &Option<String>, oninput: &Callback<InputData>) -> Html { +        html!{ +            <div class="field column  +                        is-one-fifth-widescreen +                        is-one-quarter-desktop +                        is-one-third-tablet +                        is-half-mobile" > +                <label class="label">{ label }</label> +                <div class="control"> +                    <input  id=id.as_ref().unwrap_or(&"".to_string()) +                            type="text"  +                            placeholder=placeholder.as_ref().unwrap_or(&"".to_string()) +                            oninput=oninput +                    /> +                </div> +            </div> +        } +    } +    fn checkbox_field_input(label: &str, id: &Option<String>, checked: &bool, onclick: &Callback<MouseEvent>) -> Html { +        html!{ +            <div class="field column  +                        is-one-fifth-widescreen  +                        is-one-quarter-desktop  +                        is-one-third-tablet  +                        is-half-mobile" > +                <label class="checkbox"> +                    <input  id=id.as_ref().unwrap_or(&"".to_string()) +                            type="checkbox"  +                            checked=*checked +                            onclick=onclick +                    /> +                    { label } +                </label> +            </div> +        } +    } +}
\ No newline at end of file diff --git a/src/view/input_objects/name.rs b/src/view/input_objects/name.rs new file mode 100644 index 0000000..0fe9e64 --- /dev/null +++ b/src/view/input_objects/name.rs @@ -0,0 +1,118 @@ +use vcard::properties; +use vcard::values::{self, text}; +use super::*; +use super::super::name::*; + +#[derive(Clone)] +pub struct Name { +    pub prefix: String, +    pub first_name: String, +    pub middle_name: String, +    pub last_name: String, +    pub suffix: String, +} + +impl VCardPropertyInputObject<properties::Name, NameView> 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<NameView>) -> std::vec::Vec<VCardPropertyInputField> { +        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(), +            }, +            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(), +            }, +            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(), +            }, +            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(), +            }, +            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(), +            }, +        ] +    } +    fn to_vcard_property(&self) -> std::result::Result<properties::Name, VCardPropertyInputError> { +        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()), +            }, +            match self.first_name.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.first_name).unwrap()), +            }, +            match self.middle_name.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.middle_name).unwrap()), +            }, +            match self.prefix.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.prefix).unwrap()), +            }, +            match self.suffix.is_empty() { +                true => None, +                false => Some(text::Component::from_str(&self.suffix).unwrap()), +            }, +        ); + +        Ok(properties::Name::from_name_value(name_value)) +    } +} + +impl Name { +    pub fn formatted_name(&self) -> String { +        let mut formatted_name = String::new(); + +        if !self.prefix.is_empty() { +            formatted_name.push_str(&self.prefix); +        } +        if !self.first_name.is_empty() { +            formatted_name.push_str(" "); +            formatted_name.push_str(&self.first_name); +        } +        if !self.middle_name.is_empty() { +            formatted_name.push_str(" "); +            formatted_name.push_str(&self.middle_name); +        } +        if !self.last_name.is_empty() { +            formatted_name.push_str(" "); +            formatted_name.push_str(&self.last_name); +        } +        if !self.suffix.is_empty() { +            formatted_name.push_str(", "); +            formatted_name.push_str(&self.suffix); +        } + +        formatted_name +    } +}
\ No newline at end of file diff --git a/src/view/input_objects/telephone.rs b/src/view/input_objects/telephone.rs new file mode 100644 index 0000000..d7f7194 --- /dev/null +++ b/src/view/input_objects/telephone.rs @@ -0,0 +1,162 @@ +use vcard::properties; +use vcard::parameters; +use vcard::values; +use std::collections::HashSet; +use super::*; +use super::super::telephone::*; + +#[derive(Clone)] +pub struct Telephone { +    pub number: String, +    pub extension: 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<properties::Telephone, TelephoneView> for Telephone { +    fn new() -> Self { +        Self { +            number: String::new(), +            extension: 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<TelephoneView>) -> Vec<VCardPropertyInputField> { +        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(), +            }, +            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(), +            }, +            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 to_vcard_property(&self) -> Result<properties::Telephone, VCardPropertyInputError> { +        let mut telephone = properties::Telephone::from_telephone_value( +            values::telephone_value::TelephoneValue::from_telephone_number_str( +                self.number.clone(),  +                match self.extension.is_empty() { +                    true => None::<&str>, +                    false => Some(&self.extension), +                }, +            ).unwrap() +        ); + +        let type_values = { +            let mut type_values = HashSet::new(); + +            if self.work { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Work); +            } +            if self.home { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Home); +            } +            if self.text { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Text); +            } +            if self.voice { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Voice); +            } +            if self.fax { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Fax); +            } +            if self.cell { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Cell); +            } +            if self.video { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Video); +            } +            if self.pager { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::Pager); +            } +            if self.text_phone { +                type_values.insert(values::type_value::TypeValueWithTelephoneType::TextPhone); +            } + +            vcard::Set::from_hash_set(type_values).unwrap() +        }; + +        if let properties::Telephone::TelephoneValue { ref mut typ, .. } = telephone { +            *typ = Some(parameters::typ::TypeWithTelType::from_type_values(type_values)); +        } + +        Ok(telephone) +    } +}
\ No newline at end of file diff --git a/src/view/input_objects/utility.rs b/src/view/input_objects/utility.rs new file mode 100644 index 0000000..a296c1e --- /dev/null +++ b/src/view/input_objects/utility.rs @@ -0,0 +1,39 @@ +#[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)] +pub enum DownloadOption { +    PDF, +    VCard, +    QrCode, +}
\ No newline at end of file diff --git a/src/view/main.rs b/src/view/main.rs new file mode 100644 index 0000000..e1753b4 --- /dev/null +++ b/src/view/main.rs @@ -0,0 +1,335 @@ +use crate::view::telephone::TelephoneView; +use std::collections::HashSet; +use super::name::{NameView}; +use super::address::AddressView; +use genpdf::Element as _; +use genpdf::{elements, style, fonts}; +use qrcodegen::QrCode; +use qrcodegen::QrCodeEcc; +use yew::prelude::*; +use vcard::{VCard, VCardError}; +use super::input_objects::utility::*; +use super::input_objects::name::Name; +use super::input_objects::address::Address; +use super::input_objects::telephone::Telephone; +use super::input_objects::VCardPropertyInputObject; + + +pub struct MainView { +    link: ComponentLink<Self>, +    error: Vec<String>, +    name: Name, +    address: Address, +    telephone: Telephone, +    download: Option<Download>, +    selected_option: DownloadOption, +} + +pub enum Msg { +    UpdateName(Name), +    UpdateAddress(Address), +    UpdateTelephone(Telephone), +    Generate(DownloadOption), +    Nope, +} + +impl Component for MainView { +    type Message = Msg; +    type Properties = (); + +    fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self { +        MainView {  +            link,  +            error: vec![],  +            name: Name::new(),  +            address: Address::new(), +            telephone: Telephone::new(), +            download: None,  +            selected_option: DownloadOption::VCard  +        } +    } + +    fn update(&mut self, msg: Self::Message) -> ShouldRender { +        self.error.clear(); +        match msg { +            Msg::UpdateName(value) => { +                self.name = value; +                self.link.send_message(Msg::Generate(self.selected_option)); +            }, +            Msg::UpdateAddress(value) => { +                self.address = value; +                self.link.send_message(Msg::Generate(self.selected_option)); +            }, +            Msg::UpdateTelephone(value) => { +                self.telephone = value; +                self.link.send_message(Msg::Generate(self.selected_option)); +            }, +            Msg::Generate(option) => { +                self.selected_option = option; + +                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 +                    } +                }; + +                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, +                                } +                            ) +                        } +                    } +                    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!")), +                            }; +                        } +                    } +                    DownloadOption::PDF => { +                        match self.generate_pdf() { +                            Ok(pdf) => self.download = Some( +                                Download { +                                    file_name: format!("Visitenkarten {}.pdf", self.name.formatted_name()), +                                    content: pdf, +                                    mime_type: MimeType::PDF, +                                } +                            ), +                            Err(_) => self.error.push(String::from("Unexpected error while generating the PDF. Please contact me about it.")), +                        } +                    } +                } +            } +            Msg::Nope => return false, +        }; +        if self.error.len() > 0 { +            self.download = None; +        } +        true +    } + +    fn change(&mut self, _props: Self::Properties) -> ShouldRender { +        false +    } + +    fn view(&self) -> Html { + +        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), +                    _ => Msg::Nope, +                }, +                _ => Msg::Nope, +            } +        ); + +        html!{ +            <> +                <main> +                    <section class="hero"> +                        <div class="hero-body"> +                            <div class="container is-max-widescreen"> +                                <h1 class="title">{ "A Generator for vCards" }</h1> +                                <h2 class="subtitle">{ "Supports generating vCards (.vcf), print-ready PDF business cards and QR Codes" }</h2> +                            </div> +                        </div> +                    </section> + +                    <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)) /> + +                            <div class="block level-left"> +                                <div class="select level-item"> +                                    <select id="download_options" onchange=download_options> +                                        <option value="vcard">{ "VCard (.vcf)" }</option> +                                        <option value="pdf">{ "Print-ready PDF" }</option> +                                        <option value="qrcode">{ "QR Code" }</option> +                                    </select> +                                </div> +                                 +                                { self.render_download() } +                            </div> + +                            <div class="block"> +                                { self.render_preview() } +                            </div> + +                        </div> +                    </section> +                </main> + +                <footer class="footer"> +                    <div class="content has-text-centered"> +                        <p> +                            <strong>{ "VCard Generator" }</strong> { " by " } <a href="https://jelemux.dev">{ "Jeremias Weber" }</a>{ ". "} +                            { "The source code is licenced " } <a href="http://opensource.org/licenses/mit-license.php">{ "MIT" }</a>{"."} +                        </p> +                    </div> +                </footer> +            </> +        } +    } +} + +impl MainView { +    fn render_errors(&self) -> Html { +        html!{ +            <> +                { +                    for self.error.iter().map(|err|  +                        html!{ +                            <div class="notification is-danger is-light"> +                                { err } +                            </div> +                        } +                    )  +                } +            </> +        } +    } +    fn render_download(&self) -> Html { +        if self.download.is_some() { +            let download = self.download.as_ref().unwrap(); + +            html!{ +                <a href=download.as_data_link() download=download.file_name class="button is-primary level-item" > +                    { "Download" } +                </a> +            } +        } else { +            html!{} +        } +    } +    fn render_preview(&self) -> Html { +        if self.download.is_some() { +            let download = self.download.as_ref().unwrap(); + +            match download.mime_type { +                MimeType::PDF => html!{ +                    <iframe src=download.as_data_link() alt="PDF Preview" width="400" height="550"/> +                }, +                MimeType::VCard => { +                    html!{ +                        <pre> +                            <code> { download.content.clone() } </code> +                        </pre> +                    } +                } +                MimeType::SVG => html!{ +                    <img src=download.as_data_link() alt="Image Preview" class="image is-square" width="300" height="300"/> +                }, +            } +        } else { +            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"); + +        let bold_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Bold.ttf"); +        let bold_font_data = fonts::FontData::new(bold_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBold)).expect("font data should be correct"); + +        let italic_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Italic.ttf"); +        let italic_font_data = fonts::FontData::new(italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaOblique)).expect("font data should be correct"); + +        let bold_italic_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf"); +        let bold_italic_font_data = fonts::FontData::new(bold_italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBoldOblique)).expect("font data should be correct"); + +        let font_family = fonts::FontFamily{  +            regular: regular_font_data,  +            bold: bold_font_data,  +            italic: italic_font_data,  +            bold_italic: bold_italic_font_data  +        }; + +        let mut doc = genpdf::Document::new(font_family); + +        doc.set_title("BCard test"); +        doc.set_minimal_conformance(); +        doc.set_margins(10); +        doc.set_line_spacing(1.25); + +        doc.push( +            elements::Paragraph::new("genpdf Demo Document") +                .aligned(elements::Alignment::Center) +                .styled(style::Style::new().bold().with_font_size(20)), +        ); + +        // TODO fill doc with real data + +        let mut buf: Vec<u8> = Vec::new(); +        match doc.render(&mut buf) { +            Ok(_) => Ok(match String::from_utf8(buf) { +                Ok(s) => s, +                Err(_) => return Err(()), +            }), +            Err(_) => Err(()), +        } +    } +}
\ No newline at end of file diff --git a/src/view/mod.rs b/src/view/mod.rs index d34eb39..b46accf 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,262 +1,26 @@ -use crate::view::telephone::{TelephoneView,Telephone}; -use std::collections::HashSet; -use name::{NameView,Name}; -use address::{AddressView,Address,AddressType}; -use genpdf::Element as _; -use genpdf::{elements, style, fonts}; -use qrcodegen::QrCode; -use qrcodegen::QrCodeEcc;  use yew::prelude::*; -use vcard::{VCard, VCardError}; - - -mod name; -mod photo; -mod birthday; -mod address; -mod telephone; - -#[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)] -pub enum DownloadOption { -    PDF, -    VCard, -    QrCode, -} - -pub struct MainView { -    link: ComponentLink<Self>, -    error: Vec<String>, -    name: Name, -    work_address: Address, -    home_address: Address, -    telephone: Telephone, -    download: Option<Download>, -    selected_option: DownloadOption, -} - -pub enum Msg { -    UpdateName(Name), -    UpdateHomeAddress(Address), -    UpdateWorkAddress(Address), -    UpdateTelephone(Telephone), -    Generate(DownloadOption), -    Nope, -} - -impl Component for MainView { -    type Message = Msg; -    type Properties = (); - -    fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self { -        MainView {  -            link,  -            error: vec![],  -            name: Name::new(),  -            work_address: Address::new_with_type(AddressType::Work),  -            home_address: Address::new_with_type(AddressType::Home),  -            telephone: Telephone::new(), -            download: None,  -            selected_option: DownloadOption::VCard  -        } -    } - -    fn update(&mut self, msg: Self::Message) -> ShouldRender { -        self.error.clear(); -        match msg { -            Msg::UpdateName(value) => { -                self.name = value; -                self.link.send_message(Msg::Generate(self.selected_option)); -            }, -            Msg::UpdateHomeAddress(value) => { -                self.home_address = value; -                self.link.send_message(Msg::Generate(self.selected_option)); -            }, -            Msg::UpdateWorkAddress(value) => { -                self.work_address = value; -                self.link.send_message(Msg::Generate(self.selected_option)); -            }, -            Msg::UpdateTelephone(value) => { -                self.telephone = value; -                self.link.send_message(Msg::Generate(self.selected_option)); -            }, -            Msg::Generate(option) => { -                self.selected_option = option; - -                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 -                    } -                }; - -                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, -                                } -                            ) -                        } -                    } -                    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!")), -                            }; -                        } -                    } -                    DownloadOption::PDF => { -                        match self.generate_pdf() { -                            Ok(pdf) => self.download = Some( -                                Download { -                                    file_name: format!("Visitenkarten {}.pdf", self.name.formatted_name()), -                                    content: pdf, -                                    mime_type: MimeType::PDF, -                                } -                            ), -                            Err(_) => self.error.push(String::from("Unexpected error while generating the PDF. Please contact me about it.")), -                        } -                    } -                } -            } -            Msg::Nope => return false, -        }; -        if self.error.len() > 0 { -            self.download = None; -        } -        true -    } - -    fn change(&mut self, _props: Self::Properties) -> ShouldRender { -        false -    } - -    fn view(&self) -> Html { - -        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), -                    _ => Msg::Nope, -                }, -                _ => Msg::Nope, -            } -        ); - -        html!{ -            <> -                <main> -                    <section class="hero"> -                        <div class="hero-body"> -                            <div class="container is-max-widescreen"> -                                <h1 class="title">{ "A Generator for vCards" }</h1> -                                <h2 class="subtitle">{ "Supports generating vCards (.vcf), print-ready PDF business cards and QR Codes" }</h2> -                            </div> -                        </div> -                    </section> - -                    <section class="section"> -                        <div class="container is-max-widescreen"> - -                            { self.render_errors() } - -                            <NameView oninput=self.link.callback(|n: Name| Msg::UpdateName(n)) /> - -                            <AddressView address_type=AddressType::Home oninput=self.link.callback(|a: Address| Msg::UpdateHomeAddress(a)) /> - -                            <AddressView address_type=AddressType::Work oninput=self.link.callback(|a: Address| Msg::UpdateWorkAddress(a)) /> - -                            <TelephoneView oninput=self.link.callback(|t: Telephone| Msg::UpdateTelephone(t)) /> - -                            <div class="block level-left"> -                                <div class="select level-item"> -                                    <select id="download_options" onchange=download_options> -                                        <option value="vcard">{ "VCard (.vcf)" }</option> -                                        <option value="pdf">{ "Print-ready PDF" }</option> -                                        <option value="qrcode">{ "QR Code" }</option> -                                    </select> -                                </div> -                                 -                                { self.render_download() } -                            </div> - -                            <div class="block"> -                                { self.render_preview() } -                            </div> - -                        </div> -                    </section> -                </main> - -                <footer class="footer"> -                    <div class="content has-text-centered"> -                        <p> -                            <strong>{ "VCard Generator" }</strong> { " by " } <a href="https://jelemux.dev">{ "Jeremias Weber" }</a>{ ". "} -                            { "The source code is licenced " } <a href="http://opensource.org/licenses/mit-license.php">{ "MIT" }</a>{"."} -                        </p> -                    </div> -                </footer> -            </> -        } -    } -} - -impl MainView { +use input_objects::*; +pub mod input_objects; + +pub mod main; +pub mod name; +pub mod photo; +pub mod birthday; +pub mod address; +pub mod telephone; + +pub trait VCardPropertyInputComponent<P, T>: Component  +    where   P: vcard::properties::Property, +            T: VCardPropertyInputObject<P, Self> +{ +    fn get_input_object(&self) -> T; +    fn get_title(&self) -> String; +    fn get_errors(&self) -> Vec<String>;      fn render_errors(&self) -> Html {          html!{              <>                  { -                    for self.error.iter().map(|err|  +                    for self.get_errors().iter().map(|err|                           html!{                              <div class="notification is-danger is-light">                                  { err } @@ -267,120 +31,4 @@ impl MainView {              </>          }      } -    fn render_download(&self) -> Html { -        if self.download.is_some() { -            let download = self.download.as_ref().unwrap(); - -            html!{ -                <a href=download.as_data_link() download=download.file_name class="button is-primary level-item" > -                    { "Download" } -                </a> -            } -        } else { -            html!{} -        } -    } -    fn render_preview(&self) -> Html { -        if self.download.is_some() { -            let download = self.download.as_ref().unwrap(); - -            match download.mime_type { -                MimeType::PDF => html!{ -                    <iframe src=download.as_data_link() alt="PDF Preview" width="400" height="550"/> -                }, -                MimeType::VCard => { -                    html!{ -                        <pre> -                            <code> { download.content.clone() } </code> -                        </pre> -                    } -                } -                MimeType::SVG => html!{ -                    <img src=download.as_data_link() alt="Image Preview" class="image is-square" width="300" height="300"/> -                }, -            } -        } else { -            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_value()); - -                    names -                }; - -                let addresses = { -                    let mut addresses = HashSet::new(); -                    addresses.insert(self.home_address.to_vcard_value()); -                    addresses.insert(self.work_address.to_vcard_value()); - -                    addresses -                }; - -                let telephones = { -                    let mut telephones = HashSet::new(); -                    telephones.insert(self.telephone.to_vcard_value()); -                     -                    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"); - -        let bold_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Bold.ttf"); -        let bold_font_data = fonts::FontData::new(bold_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBold)).expect("font data should be correct"); - -        let italic_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Italic.ttf"); -        let italic_font_data = fonts::FontData::new(italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaOblique)).expect("font data should be correct"); - -        let bold_italic_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf"); -        let bold_italic_font_data = fonts::FontData::new(bold_italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBoldOblique)).expect("font data should be correct"); - -        let font_family = fonts::FontFamily{  -            regular: regular_font_data,  -            bold: bold_font_data,  -            italic: italic_font_data,  -            bold_italic: bold_italic_font_data  -        }; - -        let mut doc = genpdf::Document::new(font_family); - -        doc.set_title("BCard test"); -        doc.set_minimal_conformance(); -        doc.set_margins(10); -        doc.set_line_spacing(1.25); - -        doc.push( -            elements::Paragraph::new("genpdf Demo Document") -                .aligned(elements::Alignment::Center) -                .styled(style::Style::new().bold().with_font_size(20)), -        ); - -        // TODO fill doc with real data - -        let mut buf: Vec<u8> = Vec::new(); -        match doc.render(&mut buf) { -            Ok(_) => Ok(match String::from_utf8(buf) { -                Ok(s) => s, -                Err(_) => return Err(()), -            }), -            Err(_) => Err(()), -        } -    }  }
\ No newline at end of file diff --git a/src/view/name.rs b/src/view/name.rs index 872676c..4ac78a4 100644 --- a/src/view/name.rs +++ b/src/view/name.rs @@ -1,86 +1,14 @@  use yew::prelude::*;  use vcard::properties; -use vcard::values::{self, text}; - -use crate::util; - -#[derive(Clone)] -pub struct Name { -    pub prefix: String, -    pub first_name: String, -    pub middle_name: String, -    pub last_name: String, -    pub suffix: String, -} - -impl Name { -    pub fn new() -> Self {  -        Self { -            prefix: String::new(), -            first_name: String::new(), -            middle_name: String::new(), -            last_name: String::new(), -            suffix: String::new(), -        } -    } -    pub fn formatted_name(&self) -> String { -        let mut formatted_name = String::new(); - -        if !self.prefix.is_empty() { -            formatted_name.push_str(&self.prefix); -        } -        if !self.first_name.is_empty() { -            formatted_name.push_str(" "); -            formatted_name.push_str(&self.first_name); -        } -        if !self.middle_name.is_empty() { -            formatted_name.push_str(" "); -            formatted_name.push_str(&self.middle_name); -        } -        if !self.last_name.is_empty() { -            formatted_name.push_str(" "); -            formatted_name.push_str(&self.last_name); -        } -        if !self.suffix.is_empty() { -            formatted_name.push_str(", "); -            formatted_name.push_str(&self.suffix); -        } - -        formatted_name -    } -    pub fn to_vcard_value(&self) -> properties::Name { -        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()), -            }, -            match self.first_name.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.first_name).unwrap()), -            }, -            match self.middle_name.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.middle_name).unwrap()), -            }, -            match self.prefix.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.prefix).unwrap()), -            }, -            match self.suffix.is_empty() { -                true => None, -                false => Some(text::Component::from_str(&self.suffix).unwrap()), -            }, -        ); - -        properties::Name::from_name_value(name_value) -    } -} +use super::input_objects::name::*; +use super::input_objects::VCardPropertyInputObject; +use super::VCardPropertyInputComponent;  pub struct NameView {      link: ComponentLink<Self>,      value: Name,      oninput: Callback<Name>, -    //errors: Vec<String>, +    errors: Vec<String>,  }  pub enum Msg { @@ -94,7 +22,18 @@ pub enum Msg {  #[derive(Clone, PartialEq, Properties)]  pub struct Props {      pub oninput: Callback<Name>, -    //pub errors: Vec<String>, +} + +impl VCardPropertyInputComponent<properties::Name, Name> for NameView { +    fn get_input_object(&self) -> Name { +        self.value.clone() +    } +    fn get_title(&self) -> String { +        "Name".to_string() +    } +    fn get_errors(&self) -> Vec<String> { +        self.errors.clone() +    }  }  impl Component for NameView { @@ -105,6 +44,7 @@ impl Component for NameView {              link,              value: Name::new(),              oninput: props.oninput, +            errors: vec![],          }      }      fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { @@ -125,46 +65,16 @@ impl Component for NameView {      fn view(&self) -> yew::virtual_dom::VNode {          html!{              <div class="box"> -                <h3 class="subtitle">{ "Name" }</h3> - -                <div class="columns is-mobile is-multiline"> - -                    { util::text_field_input( -                        "Prefix",  -                        "prefix",  -                        Some("Sir"),  -                        self.link.callback(|e: InputData| Msg::UpdatePrefix(e.value)) -                    ) } - -                    { util::text_field_input( -                        "First name",  -                        "first_name",  -                        Some("Arthur"),  -                        self.link.callback(|e: InputData| Msg::UpdateFirstName(e.value)) -                    ) } - -                    { util::text_field_input( -                        "Middle name",  -                        "middle_name",  -                        Some("Charles"),  -                        self.link.callback(|e: InputData| Msg::UpdateMiddleName(e.value)) -                    ) } +                { +                    self.render_errors() +                } -                    { util::text_field_input( -                        "Last name",  -                        "last_name",  -                        Some("Clarke"),  -                        self.link.callback(|e: InputData| Msg::UpdateLastName(e.value)) -                    ) } +                <h3 class="subtitle">{ self.get_title() }</h3> -                    { util::text_field_input( -                        "Suffix",  -                        "suffix",  -                        Some("CBE FRAS"),  -                        self.link.callback(|e: InputData| Msg::UpdateSuffix(e.value)) -                    ) } +                { +                    self.get_input_object().render(&self.link) +                } -                </div>              </div>          }      } diff --git a/src/view/telephone.rs b/src/view/telephone.rs index 28a6f01..516552e 100644 --- a/src/view/telephone.rs +++ b/src/view/telephone.rs @@ -1,100 +1,14 @@ -use yew::services::ConsoleService;  use yew::prelude::*;  use vcard::properties; -use vcard::parameters; -use vcard::values::{self, text}; -use vcard::validators::ValidatedWrapper; -use std::collections::HashSet; -use crate::util; - -#[derive(Clone)] -pub struct Telephone { -    pub number: String, -    pub extension: 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 Telephone { -    pub fn new() -> Self { -        Self { -            number: String::new(), -            extension: String::new(), -            work: false, -            home: false, -            text: false, -            voice: false, -            fax: false, -            cell: false, -            video: false, -            pager: false, -            text_phone: false, -        } -    } -    pub fn to_vcard_value(&self) -> properties::Telephone { -        let mut telephone = properties::Telephone::from_telephone_value( -            values::telephone_value::TelephoneValue::from_telephone_number_str( -                self.number.clone(),  -                match self.extension.is_empty() { -                    true => None::<&str>, -                    false => Some(&self.extension), -                }, -            ).unwrap() -        ); - -        let type_values = { -            let mut type_values = HashSet::new(); - -            if self.work { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Work); -            } -            if self.home { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Home); -            } -            if self.text { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Text); -            } -            if self.voice { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Voice); -            } -            if self.fax { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Fax); -            } -            if self.cell { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Cell); -            } -            if self.video { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Video); -            } -            if self.pager { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::Pager); -            } -            if self.text_phone { -                type_values.insert(values::type_value::TypeValueWithTelephoneType::TextPhone); -            } - -            vcard::Set::from_hash_set(type_values).unwrap() -        }; - -        if let properties::Telephone::TelephoneValue { ref mut typ, .. } = telephone { -            *typ = Some(parameters::typ::TypeWithTelType::from_type_values(type_values)); -        } - -        telephone -    } -} +use super::input_objects::telephone::*; +use super::input_objects::VCardPropertyInputObject; +use super::VCardPropertyInputComponent;  pub struct TelephoneView {      link: ComponentLink<Self>,      value: Telephone,      oninput: Callback<Telephone>, +    errors: Vec<String>,  }  pub enum Msg { @@ -117,6 +31,18 @@ pub struct Props {      //pub errors: Vec<String>,  } +impl VCardPropertyInputComponent<properties::Telephone, Telephone> for TelephoneView { +    fn get_input_object(&self) -> Telephone { +        self.value.clone() +    } +    fn get_title(&self) -> String { +        "Telephone".to_string() +    } +    fn get_errors(&self) -> Vec<String> { +        self.errors.clone() +    } +} +  impl Component for TelephoneView {      type Message = Msg;      type Properties = Props; @@ -125,6 +51,7 @@ impl Component for TelephoneView {              link,              value: Telephone::new(),              oninput: props.oninput, +            errors: vec![],          }      }      fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { @@ -151,32 +78,16 @@ impl Component for TelephoneView {      fn view(&self) -> yew::virtual_dom::VNode {          html!{              <div class="box"> -                <h3 class="subtitle">{ "Telephone" }</h3> - -                <div class="columns is-mobile is-multiline"> -                 -                    { util::text_field_input( -                        "Number",  -                        "number",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateNumber(e.value)) -                    ) } +                { +                    self.render_errors() +                } -                    { util::text_field_input( -                        "Extension",  -                        "extension",  -                        None,  -                        self.link.callback(|e: InputData| Msg::UpdateExtension(e.value)) -                    ) } +                <h3 class="subtitle">{ self.get_title() }</h3> -                    { util::checkbox_field_input( -                        "Work",  -                        "work", -                        self.value.work, -                        self.link.callback(|_| Msg::ToggleWork) -                    ) } +                { +                    self.get_input_object().render(&self.link) +                } -                </div>              </div>          }      } | 
