summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorjelemux <jeremias.weber@protonmail.com>2021-02-17 17:06:48 +0100
committerjelemux <jeremias.weber@protonmail.com>2021-02-17 17:06:48 +0100
commitcb310a66e94db4e0c4f7d0373a670156b012412a (patch)
tree501fd4ed17d892d693d4123fffaca8d942144e3b /src/model
parentbe3367dd2921eb30ae7970d233b83ac3af861952 (diff)
parent0660151a8b641fa0a23dde2598132029970f7ae4 (diff)
downloadwasm-card-cb310a66e94db4e0c4f7d0373a670156b012412a.tar.gz
wasm-card-cb310a66e94db4e0c4f7d0373a670156b012412a.tar.bz2
Merge branch 'main' of codeberg.org:jelemux/wasm-card
Diffstat (limited to 'src/model')
-rw-r--r--src/model/address.rs154
-rw-r--r--src/model/dates.rs69
-rw-r--r--src/model/mod.rs240
-rw-r--r--src/model/name.rs149
-rw-r--r--src/model/organizational.rs120
-rw-r--r--src/model/telephone.rs146
-rw-r--r--src/model/utility.rs45
-rw-r--r--src/model/vcard.rs40
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 );
+}