diff options
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/address.rs | 154 | ||||
| -rw-r--r-- | src/model/dates.rs | 69 | ||||
| -rw-r--r-- | src/model/mod.rs | 240 | ||||
| -rw-r--r-- | src/model/name.rs | 149 | ||||
| -rw-r--r-- | src/model/organizational.rs | 120 | ||||
| -rw-r--r-- | src/model/telephone.rs | 146 | ||||
| -rw-r--r-- | src/model/utility.rs | 45 | ||||
| -rw-r--r-- | src/model/vcard.rs | 40 | 
8 files changed, 963 insertions, 0 deletions
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<AddressMsg> 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<PropertyGroupInputComponent<Self, AddressMsg>>, +    ) -> Vec<VCardPropertyInputField> { +        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<Self, AddressMsg>, +        msg: <PropertyGroupInputComponent<Self, AddressMsg> 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<DatesMsg> 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<PropertyGroupInputComponent<Self, DatesMsg>>, +    ) -> 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| 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<Self, DatesMsg>, +        msg: <PropertyGroupInputComponent<Self, DatesMsg> 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<M: 'static + PartialEq + Clone>: 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<PropertyGroupInputComponent<Self, M>>, +    ) -> Vec<VCardPropertyInputField>; +    fn update( +        &mut self, +        props: InputProps<Self, M>, +        msg: <PropertyGroupInputComponent<Self, M> as yew::Component>::Message, +    ) -> bool; +    /// Returns a `Html` representation of the `VCardPropertyInputObject`. +    fn render(&self, link: &ComponentLink<PropertyGroupInputComponent<Self, M>>) -> Html { +        html! { +            <div class="columns is-mobile is-multiline"> +                { +                    for self.get_input_fields(link).iter().map(|field| +                        field.render() +                    ) +                } +            </div> +        } +    } +    /// 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<String>, +        placeholder: Option<String>, +        oninput: Callback<InputData>, +        value: String, +        typ: String, +    }, +    File { +        label: String, +        name: String, +        callback: Callback<Option<File>>, +        value: Option<File>, +    }, +    CheckBox { +        label: String, +        id: Option<String>, +        onclick: Callback<MouseEvent>, +        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<String>, +        placeholder: &Option<String>, +        oninput: &Callback<InputData>, +        typ: &str, +    ) -> 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=typ +                            placeholder=placeholder.as_ref().unwrap_or(&"".to_string()) +                            oninput=oninput +                    /> +                </div> +            </div> +        } +    } +    /// Returns an `Html` representation of a file input field with the given parameters. +    fn file_field_input( +        label: &str, +        name: &str, +        callback: &Callback<Option<File>>, +        file: &Option<File>, +    ) -> 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<String> = +                                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<dyn FnMut(_)>); + +                        file_reader.set_onload(Some(onload.as_ref().unchecked_ref())); +                        onload.forget(); +                    } +                    None => callback.emit(None), +                } +            } else { +                callback.emit(None); +            } +        }); +        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="file has-name control"> +                    <label class="file-label"> +                        <input  class="file-input" +                                type="file" +                                name=name +                                onchange=onchange +                        /> +                        <span class="file-cta"> +                        <span class="file-icon"> +                            <i class="fas fa-upload"></i> +                        </span> +                        <span class="file-label"> +                            { "Choose file..." } +                        </span> +                        </span> +                        <span class="file-name"> +                            { +                                match file { +                                    Some(file) => file.name.clone(), +                                    None => String::from("No file selected"), +                                } +                            } +                        </span> +                    </label> +                </div> +            </div> +        } +    } +    /// Returns an `Html` representation of a checkbox input field with the given parameters. +    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> +        } +    } +} 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<NameMsg> 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<PropertyGroupInputComponent<Self, NameMsg>>, +    ) -> std::vec::Vec<VCardPropertyInputField> { +        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<Self, NameMsg>, +        msg: <PropertyGroupInputComponent<Self, NameMsg> 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<File>, +    pub title: String, +    pub role: String, +    pub member: String, +    pub related: String, +} + +#[derive(Clone, PartialEq)] +pub enum OrganizationalMsg { +    UpdateOrg(String), +    UpdateLogo(Option<File>), +    UpdateTitle(String), +    UpdateRole(String), +    UpdateMember(String), +    UpdateRelated(String), + +    Generate, +} + +impl VCardPropertyInputObject<OrganizationalMsg> 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<PropertyGroupInputComponent<Self, OrganizationalMsg>>, +    ) -> std::vec::Vec<VCardPropertyInputField> { +        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<File>| 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<Self, OrganizationalMsg>, +        msg: <PropertyGroupInputComponent<Self, OrganizationalMsg> 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<TelephoneMsg> 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<PropertyGroupInputComponent<Self, TelephoneMsg>>, +    ) -> Vec<VCardPropertyInputField> { +        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<Self, TelephoneMsg>, +        msg: <PropertyGroupInputComponent<Self, TelephoneMsg> 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<Name>, +    pub addresses: Vec<Address>, +    pub telephones: Vec<Telephone>, +    pub datess: Vec<Dates>, +    pub organizationals: Vec<Organizational>, +} + +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 ); +}  | 
