diff options
author | jelemux <jeremias.weber@protonmail.com> | 2021-02-17 17:06:48 +0100 |
---|---|---|
committer | jelemux <jeremias.weber@protonmail.com> | 2021-02-17 17:06:48 +0100 |
commit | cb310a66e94db4e0c4f7d0373a670156b012412a (patch) | |
tree | 501fd4ed17d892d693d4123fffaca8d942144e3b /src | |
parent | be3367dd2921eb30ae7970d233b83ac3af861952 (diff) | |
parent | 0660151a8b641fa0a23dde2598132029970f7ae4 (diff) | |
download | wasm-card-cb310a66e94db4e0c4f7d0373a670156b012412a.tar.gz wasm-card-cb310a66e94db4e0c4f7d0373a670156b012412a.tar.bz2 |
Merge branch 'main' of codeberg.org:jelemux/wasm-card
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/model/address.rs (renamed from src/viewmodel/address.rs) | 65 | ||||
-rw-r--r-- | src/model/dates.rs (renamed from src/viewmodel/dates.rs) | 34 | ||||
-rw-r--r-- | src/model/mod.rs (renamed from src/viewmodel/mod.rs) | 21 | ||||
-rw-r--r-- | src/model/name.rs (renamed from src/viewmodel/name.rs) | 46 | ||||
-rw-r--r-- | src/model/organizational.rs (renamed from src/viewmodel/organizational.rs) | 50 | ||||
-rw-r--r-- | src/model/telephone.rs (renamed from src/viewmodel/telephone.rs) | 66 | ||||
-rw-r--r-- | src/model/utility.rs (renamed from src/viewmodel/utility.rs) | 0 | ||||
-rw-r--r-- | src/model/vcard.rs (renamed from src/viewmodel/vcard.rs) | 10 | ||||
-rw-r--r-- | src/view/address.rs | 103 | ||||
-rw-r--r-- | src/view/dates.rs | 75 | ||||
-rw-r--r-- | src/view/main.rs | 42 | ||||
-rw-r--r-- | src/view/mod.rs | 82 | ||||
-rw-r--r-- | src/view/name.rs | 95 | ||||
-rw-r--r-- | src/view/organizational.rs | 84 | ||||
-rw-r--r-- | src/view/property_group.rs | 81 | ||||
-rw-r--r-- | src/view/telephone.rs | 105 | ||||
-rw-r--r-- | src/view/weak_links.rs | 33 |
18 files changed, 373 insertions, 621 deletions
@@ -10,8 +10,8 @@ use yew::prelude::App; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +pub mod model; pub mod view; -pub mod viewmodel; fn init() { panic::set_hook(Box::new(console_error_panic_hook::hook)); diff --git a/src/viewmodel/address.rs b/src/model/address.rs index 9542675..2c42a98 100644 --- a/src/viewmodel/address.rs +++ b/src/model/address.rs @@ -1,5 +1,4 @@ use super::*; -use crate::view::address::*; #[derive(Clone, Debug, PartialEq)] pub struct Address { @@ -14,7 +13,22 @@ pub struct Address { pub home: bool, } -impl VCardPropertyInputObject<AddressView> for Address { +#[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(), @@ -28,14 +42,20 @@ impl VCardPropertyInputObject<AddressView> for Address { home: false, } } - fn get_input_fields(&self, link: &ComponentLink<AddressView>) -> Vec<VCardPropertyInputField> { + 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| Msg::UpdatePostOfficeBox(e.value)), + oninput: link.callback(|e: InputData| AddressMsg::UpdatePostOfficeBox(e.value)), value: self.post_office_box.clone(), typ: typ.clone(), }, @@ -43,7 +63,7 @@ impl VCardPropertyInputObject<AddressView> for Address { label: "Extension".to_string(), id: Some("extension".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateExtension(e.value)), + oninput: link.callback(|e: InputData| AddressMsg::UpdateExtension(e.value)), value: self.extension.clone(), typ: typ.clone(), }, @@ -51,7 +71,7 @@ impl VCardPropertyInputObject<AddressView> for Address { label: "Street".to_string(), id: Some("street".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateStreet(e.value)), + oninput: link.callback(|e: InputData| AddressMsg::UpdateStreet(e.value)), value: self.street.clone(), typ: typ.clone(), }, @@ -59,7 +79,7 @@ impl VCardPropertyInputObject<AddressView> for Address { label: "Locality".to_string(), id: Some("locality".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateLocality(e.value)), + oninput: link.callback(|e: InputData| AddressMsg::UpdateLocality(e.value)), value: self.locality.clone(), typ: typ.clone(), }, @@ -67,7 +87,7 @@ impl VCardPropertyInputObject<AddressView> for Address { label: "Region".to_string(), id: Some("region".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateRegion(e.value)), + oninput: link.callback(|e: InputData| AddressMsg::UpdateRegion(e.value)), value: self.region.clone(), typ: typ.clone(), }, @@ -75,7 +95,7 @@ impl VCardPropertyInputObject<AddressView> for Address { label: "Code".to_string(), id: Some("code".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateCode(e.value)), + oninput: link.callback(|e: InputData| AddressMsg::UpdateCode(e.value)), value: self.code.clone(), typ: typ.clone(), }, @@ -83,24 +103,45 @@ impl VCardPropertyInputObject<AddressView> for Address { label: "Country".to_string(), id: Some("country".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateCountry(e.value)), + 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| Msg::ToggleWork), + onclick: link.callback(|_: MouseEvent| AddressMsg::ToggleWork), value: self.work, }, VCardPropertyInputField::CheckBox { label: "Home".to_string(), id: Some("home".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleHome), + 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() diff --git a/src/viewmodel/dates.rs b/src/model/dates.rs index 7d8d394..192f986 100644 --- a/src/viewmodel/dates.rs +++ b/src/model/dates.rs @@ -1,5 +1,4 @@ use super::*; -use crate::view::dates::*; /// Type that represents the vcard `anniversary` and `birthday` properties. #[derive(Clone, Debug, PartialEq)] @@ -8,16 +7,27 @@ pub struct Dates { pub birthday: String, } -impl VCardPropertyInputObject<DatesView> for Dates { +#[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<DatesView>, + link: &yew::html::Scope<PropertyGroupInputComponent<Self, DatesMsg>>, ) -> std::vec::Vec<VCardPropertyInputField> { let typ = String::from("date"); vec![ @@ -25,7 +35,7 @@ impl VCardPropertyInputObject<DatesView> for Dates { label: "Anniversary".to_string(), id: Some("anniversary".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateAnniversary(e.value)), + oninput: link.callback(|e: InputData| DatesMsg::UpdateAnniversary(e.value)), value: self.anniversary.clone(), typ: typ.clone(), }, @@ -33,12 +43,26 @@ impl VCardPropertyInputObject<DatesView> for Dates { label: "Birthday".to_string(), id: Some("birthday".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateBirthday(e.value)), + 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/viewmodel/mod.rs b/src/model/mod.rs index 0385c61..45c91f1 100644 --- a/src/viewmodel/mod.rs +++ b/src/model/mod.rs @@ -1,5 +1,5 @@ -use crate::view::VCardPropertyInputComponent; -use crate::viewmodel::utility::File; +use crate::model::utility::File; +use crate::view::property_group::*; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; use web_sys::FileReader; @@ -15,17 +15,26 @@ 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<C: VCardPropertyInputComponent<Self>>: - Clone + PartialEq +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<C>) -> Vec<VCardPropertyInputField>; + 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<C>) -> Html { + fn render(&self, link: &ComponentLink<PropertyGroupInputComponent<Self, M>>) -> Html { html! { <div class="columns is-mobile is-multiline"> { diff --git a/src/viewmodel/name.rs b/src/model/name.rs index de0aa81..915579d 100644 --- a/src/viewmodel/name.rs +++ b/src/model/name.rs @@ -1,5 +1,4 @@ use super::*; -use crate::view::name::*; /// Type that represents a vcard `name` property /// @@ -25,7 +24,18 @@ pub struct Name { pub suffix: String, } -impl VCardPropertyInputObject<NameView> for Name { +#[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(), @@ -35,9 +45,12 @@ impl VCardPropertyInputObject<NameView> for Name { suffix: String::new(), } } + fn get_title(&self) -> String { + "Name".to_string() + } fn get_input_fields( &self, - link: &ComponentLink<NameView>, + link: &ComponentLink<PropertyGroupInputComponent<Self, NameMsg>>, ) -> std::vec::Vec<VCardPropertyInputField> { let typ = String::from("text"); vec![ @@ -45,7 +58,7 @@ impl VCardPropertyInputObject<NameView> for Name { label: "Prefix".to_string(), id: Some("prefix".to_string()), placeholder: Some("Sir".to_string()), - oninput: link.callback(|e: InputData| Msg::UpdatePrefix(e.value)), + oninput: link.callback(|e: InputData| NameMsg::UpdatePrefix(e.value)), value: self.prefix.clone(), typ: typ.clone(), }, @@ -53,7 +66,7 @@ impl VCardPropertyInputObject<NameView> for Name { 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)), + oninput: link.callback(|e: InputData| NameMsg::UpdateFirstName(e.value)), value: self.first_name.clone(), typ: typ.clone(), }, @@ -61,7 +74,7 @@ impl VCardPropertyInputObject<NameView> for Name { 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)), + oninput: link.callback(|e: InputData| NameMsg::UpdateMiddleName(e.value)), value: self.middle_name.clone(), typ: typ.clone(), }, @@ -69,7 +82,7 @@ impl VCardPropertyInputObject<NameView> for Name { 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)), + oninput: link.callback(|e: InputData| NameMsg::UpdateLastName(e.value)), value: self.last_name.clone(), typ: typ.clone(), }, @@ -77,12 +90,29 @@ impl VCardPropertyInputObject<NameView> for Name { label: "Suffix".to_string(), id: Some("suffix".to_string()), placeholder: Some("CBE FRAS".to_string()), - oninput: link.callback(|e: InputData| Msg::UpdateSuffix(e.value)), + 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() diff --git a/src/viewmodel/organizational.rs b/src/model/organizational.rs index 72b19d2..f82f5d7 100644 --- a/src/viewmodel/organizational.rs +++ b/src/model/organizational.rs @@ -1,5 +1,4 @@ use super::*; -use crate::view::organizational::*; #[derive(Clone, Debug, PartialEq)] pub struct Organizational { @@ -11,7 +10,19 @@ pub struct Organizational { pub related: String, } -impl VCardPropertyInputObject<OrganizationalView> for Organizational { +#[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(), @@ -22,9 +33,12 @@ impl VCardPropertyInputObject<OrganizationalView> for Organizational { related: String::new(), } } + fn get_title(&self) -> std::string::String { + "Organizational".to_string() + } fn get_input_fields( &self, - link: &yew::html::Scope<OrganizationalView>, + link: &yew::html::Scope<PropertyGroupInputComponent<Self, OrganizationalMsg>>, ) -> std::vec::Vec<VCardPropertyInputField> { let typ = String::from("text"); vec![ @@ -32,7 +46,7 @@ impl VCardPropertyInputObject<OrganizationalView> for Organizational { label: "Organisation".to_string(), id: Some("org".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateOrg(e.value)), + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateOrg(e.value)), value: self.org.clone(), typ: typ.clone(), }, @@ -40,14 +54,14 @@ impl VCardPropertyInputObject<OrganizationalView> for Organizational { // TODO: Add Upload for logo label: "Logo".to_string(), name: "logo".to_string(), - callback: link.callback(|file: Option<File>| Msg::UpdateLogo(file)), + 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| Msg::UpdateTitle(e.value)), + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateTitle(e.value)), value: self.title.clone(), typ: typ.clone(), }, @@ -55,7 +69,7 @@ impl VCardPropertyInputObject<OrganizationalView> for Organizational { label: "Role".to_string(), id: Some("role".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateRole(e.value)), + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateRole(e.value)), value: self.role.clone(), typ: typ.clone(), }, @@ -63,7 +77,7 @@ impl VCardPropertyInputObject<OrganizationalView> for Organizational { label: "Member".to_string(), id: Some("member".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateMember(e.value)), + oninput: link.callback(|e: InputData| OrganizationalMsg::UpdateMember(e.value)), value: self.member.clone(), typ: typ.clone(), }, @@ -71,12 +85,30 @@ impl VCardPropertyInputObject<OrganizationalView> for Organizational { label: "Related".to_string(), id: Some("related".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateRelated(e.value)), + 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() diff --git a/src/viewmodel/telephone.rs b/src/model/telephone.rs index 44b938f..946da33 100644 --- a/src/viewmodel/telephone.rs +++ b/src/model/telephone.rs @@ -1,5 +1,4 @@ use super::*; -use crate::view::telephone::*; #[derive(Clone, Debug, PartialEq)] pub struct Telephone { @@ -15,7 +14,23 @@ pub struct Telephone { pub text_phone: bool, } -impl VCardPropertyInputObject<TelephoneView> for Telephone { +#[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(), @@ -30,9 +45,12 @@ impl VCardPropertyInputObject<TelephoneView> for Telephone { text_phone: false, } } + fn get_title(&self) -> String { + "Telephone".to_string() + } fn get_input_fields( &self, - link: &ComponentLink<TelephoneView>, + link: &ComponentLink<PropertyGroupInputComponent<Self, TelephoneMsg>>, ) -> Vec<VCardPropertyInputField> { let typ = String::from("tel"); vec![ @@ -40,66 +58,88 @@ impl VCardPropertyInputObject<TelephoneView> for Telephone { label: "Number".to_string(), id: Some("number".to_string()), placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateNumber(e.value)), + 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| Msg::ToggleWork), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleWork), value: self.work, }, VCardPropertyInputField::CheckBox { label: "Home".to_string(), id: Some("home".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleHome), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleHome), value: self.home, }, VCardPropertyInputField::CheckBox { label: "Text".to_string(), id: Some("text".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleText), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleText), value: self.text, }, VCardPropertyInputField::CheckBox { label: "Voice".to_string(), id: Some("voice".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleVoice), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleVoice), value: self.voice, }, VCardPropertyInputField::CheckBox { label: "Fax".to_string(), id: Some("fax".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleFax), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleFax), value: self.fax, }, VCardPropertyInputField::CheckBox { label: "Cell".to_string(), id: Some("cell".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleCell), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleCell), value: self.cell, }, VCardPropertyInputField::CheckBox { label: "Video".to_string(), id: Some("video".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::ToggleVideo), + onclick: link.callback(|_: MouseEvent| TelephoneMsg::ToggleVideo), value: self.video, }, VCardPropertyInputField::CheckBox { label: "Pager".to_string(), id: Some("pager".to_string()), - onclick: link.callback(|_: MouseEvent| Msg::TogglePager), + 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| Msg::ToggleTextPhone), + 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/viewmodel/utility.rs b/src/model/utility.rs index 617ee45..617ee45 100644 --- a/src/viewmodel/utility.rs +++ b/src/model/utility.rs diff --git a/src/viewmodel/vcard.rs b/src/model/vcard.rs index 18ce43a..ee7e28e 100644 --- a/src/viewmodel/vcard.rs +++ b/src/model/vcard.rs @@ -1,8 +1,8 @@ -use crate::viewmodel::address::Address; -use crate::viewmodel::dates::Dates; -use crate::viewmodel::name::Name; -use crate::viewmodel::organizational::Organizational; -use crate::viewmodel::telephone::Telephone; +use crate::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)] diff --git a/src/view/address.rs b/src/view/address.rs deleted file mode 100644 index 57ea7e4..0000000 --- a/src/view/address.rs +++ /dev/null @@ -1,103 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::address::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps<Address, AddressView>; - -/// View Component for a `address` field -/// -/// # Examples -/// -/// ```compile_fail -/// let html = html!{ -/// <AddressView weak_link=some_weak_component_link -/// generated=self.link.callback( -/// |n: Irc<Address>| -/// Msg::GeneratedAddress(some_address) -/// ) -/// /> -/// }; -/// ``` -#[derive(Clone, PartialEq)] -pub struct AddressView { - props: Props, - value: Address, - error: Option<Error>, -} - -pub enum Msg { - UpdatePostOfficeBox(String), - UpdateExtension(String), - UpdateStreet(String), - UpdateLocality(String), - UpdateRegion(String), - UpdateCode(String), - UpdateCountry(String), - ToggleWork, - ToggleHome, - - Generate, -} - -impl VCardPropertyInputComponent<Address> for AddressView { - fn get_input_object(&self) -> Address { - self.value.clone() - } - fn get_title(&self) -> String { - "Address".to_string() - } - fn get_error(&self) -> Option<Error> { - self.error.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 { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Address::new(), - error: None, - } - } - fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { - match msg { - Msg::UpdatePostOfficeBox(b) => self.value.post_office_box = b, - Msg::UpdateExtension(e) => self.value.extension = e, - Msg::UpdateStreet(s) => self.value.street = s, - Msg::UpdateLocality(l) => self.value.locality = l, - Msg::UpdateRegion(r) => self.value.region = r, - Msg::UpdateCode(p) => self.value.code = p, - Msg::UpdateCountry(c) => self.value.country = c, - Msg::ToggleWork => self.value.work = !self.value.work, - Msg::ToggleHome => self.value.home = !self.value.home, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { - <div class="box"> - { self.render_error() } - - <h3 class="subtitle">{ self.get_title() }</h3> - - { self.get_input_object().render(&link) } - - </div> - } - } -} diff --git a/src/view/dates.rs b/src/view/dates.rs deleted file mode 100644 index b2a6dd3..0000000 --- a/src/view/dates.rs +++ /dev/null @@ -1,75 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::dates::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps<Dates, DatesView>; - -#[derive(Clone, PartialEq)] -pub struct DatesView { - props: Props, - value: Dates, - error: Option<Error>, -} - -pub enum Msg { - UpdateAnniversary(String), - UpdateBirthday(String), - - Generate, -} - -impl VCardPropertyInputComponent<Dates> for DatesView { - fn get_input_object(&self) -> Dates { - self.value.clone() - } - fn get_title(&self) -> std::string::String { - "Dates".to_string() - } - fn get_error(&self) -> std::option::Option<Error> { - self.error.clone() - } -} - -impl Component for DatesView { - type Message = Msg; - type Properties = Props; - fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Dates::new(), - error: None, - } - } - fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { - match msg { - Msg::UpdateAnniversary(a) => self.value.anniversary = a, - Msg::UpdateBirthday(b) => self.value.birthday = b, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { - <div class="box"> - { self.render_error() } - - <h3 class="subtitle">{ self.get_title() }</h3> - - { self.get_input_object().render(&link) } - - </div> - } - } -} diff --git a/src/view/main.rs b/src/view/main.rs index 196afa0..d35fb59 100644 --- a/src/view/main.rs +++ b/src/view/main.rs @@ -1,18 +1,14 @@ -use super::address::{self, AddressView}; -use super::dates::{self, DatesView}; -use super::name::{self, NameView}; -use super::WeakComponentLink; -use crate::view::organizational::{self, OrganizationalView}; -use crate::view::telephone::{self, TelephoneView}; -use crate::viewmodel::address::Address; -use crate::viewmodel::dates::Dates; -use crate::viewmodel::name::Name; -use crate::viewmodel::organizational::Organizational; -use crate::viewmodel::telephone::Telephone; -use crate::viewmodel::utility::*; -use crate::viewmodel::vcard::VCardData; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; +use crate::view::property_group::PropertyGroupInputComponent; +use crate::view::weak_links::WeakComponentLink; +use crate::model::address::*; +use crate::model::dates::*; +use crate::model::name::*; +use crate::model::organizational::*; +use crate::model::telephone::*; +use crate::model::utility::*; +use crate::model::vcard::VCardData; +use crate::model::Error; +use crate::model::VCardPropertyInputObject; use boolinator::Boolinator; use chrono::prelude::*; use genpdf::Element as _; @@ -25,6 +21,12 @@ use yew::prelude::*; use yew::services::ConsoleService; use yewtil::ptr::Mrc; +type NameView = PropertyGroupInputComponent<Name, NameMsg>; +type AddressView = PropertyGroupInputComponent<Address, AddressMsg>; +type DatesView = PropertyGroupInputComponent<Dates, DatesMsg>; +type OrganizationalView = PropertyGroupInputComponent<Organizational, OrganizationalMsg>; +type TelephoneView = PropertyGroupInputComponent<Telephone, TelephoneMsg>; + pub struct MainView { link: ComponentLink<Self>, error: Option<Error>, @@ -117,27 +119,27 @@ impl Component for MainView { { for name_link in self.name_links.iter() { let name_link = name_link.borrow().clone().unwrap(); - name_link.send_message(name::Msg::Generate); + name_link.send_message(NameMsg::Generate); } for address_link in self.address_links.iter() { let address_link = address_link.borrow().clone().unwrap(); - address_link.send_message(address::Msg::Generate); + address_link.send_message(AddressMsg::Generate); } for telephone_link in self.telephone_links.iter() { let telephone_link = telephone_link.borrow().clone().unwrap(); - telephone_link.send_message(telephone::Msg::Generate); + telephone_link.send_message(TelephoneMsg::Generate); } for dates_link in self.dates_links.iter() { let dates_link = dates_link.borrow().clone().unwrap(); - dates_link.send_message(dates::Msg::Generate) + dates_link.send_message(DatesMsg::Generate) } for organizational_links in self.organizational_links.iter() { let organizational_link = organizational_links.borrow().clone().unwrap(); - organizational_link.send_message(organizational::Msg::Generate) + organizational_link.send_message(OrganizationalMsg::Generate) } } /* diff --git a/src/view/mod.rs b/src/view/mod.rs index cbff0fd..691f19a 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,81 +1,3 @@ -use crate::viewmodel::*; -use std::cell::RefCell; -use std::ops::Deref; -use std::rc::Rc; -use yew::prelude::*; - -pub mod address; -pub mod dates; pub mod main; -pub mod name; -pub mod organizational; -pub mod telephone; - -#[derive(Clone, PartialEq, Properties)] -pub struct InputProps<O, C> -where - O: VCardPropertyInputObject<C> + Clone, - C: VCardPropertyInputComponent<O> + Clone, -{ - pub generated: Callback<O>, - pub weak_link: WeakComponentLink<C>, -} - -/// Trait for types that represent an input component for a vcard property. -pub trait VCardPropertyInputComponent<T: VCardPropertyInputObject<Self>>: - Component + Clone + PartialEq -{ - /// Returns the object containing the input data. - fn get_input_object(&self) -> T; - /// Getter function for the title of the component - fn get_title(&self) -> String; - /// Getter function for an eventual error. - fn get_error(&self) -> Option<Error>; - /// Returns the error as `Html` - fn render_error(&self) -> Html { - html! { - <> - { - if self.get_error().is_some() { - html!{ - <div class="notification is-danger is-light"> - { self.get_error().unwrap().msg } - </div> - } - } else { - html!{} - } - } - </> - } - } -} - -/// Weak link; Useful for being able to have a list of subcomponents. -pub struct WeakComponentLink<COMP: Component>(Rc<RefCell<Option<ComponentLink<COMP>>>>); - -impl<COMP: Component> Clone for WeakComponentLink<COMP> { - fn clone(&self) -> Self { - Self(Rc::clone(&self.0)) - } -} - -impl<COMP: Component> Default for WeakComponentLink<COMP> { - fn default() -> Self { - Self(Rc::default()) - } -} - -impl<COMP: Component> Deref for WeakComponentLink<COMP> { - type Target = Rc<RefCell<Option<ComponentLink<COMP>>>>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<COMP: Component> PartialEq for WeakComponentLink<COMP> { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) - } -} +pub mod property_group; +pub mod weak_links; diff --git a/src/view/name.rs b/src/view/name.rs deleted file mode 100644 index 4b7089b..0000000 --- a/src/view/name.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::name::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps<Name, NameView>; - -/// View Component for a `name` field -/// -/// # Examples -/// -/// ```compile_fail -/// let html = html!{ -/// <NameView weak_link=some_weak_component_link -/// generated=self.link.callback( -/// |n: Irc<Name>| -/// Msg::GeneratedName(some_name) -/// ) -/// /> -/// }; -/// ``` -#[derive(Clone, PartialEq)] -pub struct NameView { - props: Props, - value: Name, - error: Option<Error>, -} - -pub enum Msg { - UpdatePrefix(String), - UpdateFirstName(String), - UpdateMiddleName(String), - UpdateLastName(String), - UpdateSuffix(String), - - Generate, -} - -impl VCardPropertyInputComponent<Name> for NameView { - fn get_input_object(&self) -> Name { - self.value.clone() - } - fn get_title(&self) -> String { - "Name".to_string() - } - fn get_error(&self) -> Option<Error> { - self.error.clone() - } -} - -impl Component for NameView { - type Message = Msg; - type Properties = Props; - fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Name::new(), - error: None, - } - } - fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { - match msg { - Msg::UpdatePrefix(p) => self.value.prefix = p, - Msg::UpdateFirstName(f) => self.value.first_name = f, - Msg::UpdateMiddleName(m) => self.value.middle_name = m, - Msg::UpdateLastName(l) => self.value.last_name = l, - Msg::UpdateSuffix(s) => self.value.suffix = s, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { - <div class="box"> - { self.render_error() } - - <h3 class="subtitle">{ self.get_title() }</h3> - - { self.get_input_object().render(&link) } - - </div> - } - } -} diff --git a/src/view/organizational.rs b/src/view/organizational.rs deleted file mode 100644 index 7f2ca69..0000000 --- a/src/view/organizational.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::organizational::*; -use crate::viewmodel::utility::File; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps<Organizational, OrganizationalView>; - -#[derive(Clone, PartialEq)] -pub struct OrganizationalView { - props: Props, - value: Organizational, - error: Option<Error>, -} - -pub enum Msg { - UpdateOrg(String), - UpdateLogo(Option<File>), - UpdateTitle(String), - UpdateRole(String), - UpdateMember(String), - UpdateRelated(String), - - Generate, -} - -impl VCardPropertyInputComponent<Organizational> for OrganizationalView { - fn get_input_object(&self) -> Organizational { - self.value.clone() - } - fn get_title(&self) -> std::string::String { - "Organizational".to_string() - } - fn get_error(&self) -> std::option::Option<Error> { - self.error.clone() - } -} - -impl Component for OrganizationalView { - type Message = Msg; - type Properties = Props; - fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Organizational::new(), - error: None, - } - } - fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { - match msg { - Msg::UpdateOrg(o) => self.value.org = o, - Msg::UpdateLogo(l) => self.value.logo = l, - Msg::UpdateTitle(t) => self.value.title = t, - Msg::UpdateRole(r) => self.value.role = r, - Msg::UpdateMember(m) => self.value.member = m, - Msg::UpdateRelated(r) => self.value.related = r, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { - <div class="box"> - { self.render_error() } - - <h3 class="subtitle">{ self.get_title() }</h3> - - { self.get_input_object().render(&link) } - - </div> - } - } -} diff --git a/src/view/property_group.rs b/src/view/property_group.rs new file mode 100644 index 0000000..9c86e80 --- /dev/null +++ b/src/view/property_group.rs @@ -0,0 +1,81 @@ +use crate::model::*; +use crate::view::weak_links::WeakComponentLink; +use yew::prelude::*; +use yewtil::NeqAssign; + +#[derive(Clone, PartialEq, Properties)] +pub struct InputProps< + O: 'static + VCardPropertyInputObject<M> + Clone, + M: 'static + PartialEq + Clone, +> { + pub generated: Callback<O>, + pub weak_link: WeakComponentLink<PropertyGroupInputComponent<O, M>>, +} + +#[derive(Clone, PartialEq)] +pub struct PropertyGroupInputComponent< + O: 'static + VCardPropertyInputObject<M>, + M: 'static + PartialEq + Clone, +> { + pub props: InputProps<O, M>, + pub value: O, + pub error: Option<Error>, +} + +impl<O: 'static + VCardPropertyInputObject<M>, M: 'static + PartialEq + Clone> Component + for PropertyGroupInputComponent<O, M> +{ + type Message = M; + type Properties = InputProps<O, M>; + fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { + props.weak_link.borrow_mut().replace(link); + Self { + props, + value: O::new(), + error: None, + } + } + fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { + self.value.update(self.props.clone(), msg) + } + fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool { + self.props.neq_assign(props) + } + fn view(&self) -> yew::virtual_dom::VNode { + let link = self.props.weak_link.borrow().clone().unwrap(); + + html! { + <div class="box"> + { self.render_error() } + + <h3 class="subtitle">{ self.value.get_title() }</h3> + + { self.value.render(&link) } + + </div> + } + } +} + +impl<O: VCardPropertyInputObject<M>, M: 'static + PartialEq + Clone> + PropertyGroupInputComponent<O, M> +{ + /// Returns the error as `Html` + fn render_error(&self) -> Html { + html! { + <> + { + if self.error.is_some() { + html!{ + <div class="notification is-danger is-light"> + { self.error.clone().unwrap().msg } + </div> + } + } else { + html!{} + } + } + </> + } + } +} diff --git a/src/view/telephone.rs b/src/view/telephone.rs deleted file mode 100644 index 68389ba..0000000 --- a/src/view/telephone.rs +++ /dev/null @@ -1,105 +0,0 @@ -use super::VCardPropertyInputComponent; -use crate::view::InputProps; -use crate::viewmodel::telephone::*; -use crate::viewmodel::Error; -use crate::viewmodel::VCardPropertyInputObject; -use yew::prelude::*; -use yewtil::NeqAssign; - -type Props = InputProps<Telephone, TelephoneView>; - -/// View Component for a `telephone` field -/// -/// # Examples -/// -/// ```compile_fail -/// let html = html!{ -/// <TelephoneView weak_link=some_weak_component_link -/// generated=self.link.callback( -/// |n: Irc<Telephone>| -/// Msg::GeneratedTelephone(some_telephone) -/// ) -/// /> -/// }; -/// ``` -#[derive(Clone, PartialEq)] -pub struct TelephoneView { - props: Props, - value: Telephone, - error: Option<Error>, -} - -pub enum Msg { - UpdateNumber(String), - ToggleWork, - ToggleHome, - ToggleText, - ToggleVoice, - ToggleFax, - ToggleCell, - ToggleVideo, - TogglePager, - ToggleTextPhone, - - Generate, -} - -impl VCardPropertyInputComponent<Telephone> for TelephoneView { - fn get_input_object(&self) -> Telephone { - self.value.clone() - } - fn get_title(&self) -> String { - "Telephone".to_string() - } - fn get_error(&self) -> Option<Error> { - self.error.clone() - } -} - -impl Component for TelephoneView { - type Message = Msg; - type Properties = Props; - fn create(props: <Self as yew::Component>::Properties, link: yew::html::Scope<Self>) -> Self { - props.weak_link.borrow_mut().replace(link); - Self { - props, - value: Telephone::new(), - error: None, - } - } - fn update(&mut self, msg: <Self as yew::Component>::Message) -> bool { - match msg { - Msg::UpdateNumber(n) => self.value.number = n, - Msg::ToggleWork => self.value.work = !self.value.work, - Msg::ToggleHome => self.value.home = !self.value.home, - Msg::ToggleText => self.value.text = !self.value.text, - Msg::ToggleVoice => self.value.voice = !self.value.voice, - Msg::ToggleFax => self.value.fax = !self.value.fax, - Msg::ToggleCell => self.value.cell = !self.value.cell, - Msg::ToggleVideo => self.value.video = !self.value.video, - Msg::TogglePager => self.value.pager = !self.value.pager, - Msg::ToggleTextPhone => self.value.text_phone = !self.value.text_phone, - Msg::Generate => { - self.props.generated.emit(self.value.clone()); - } - }; - true - } - fn change(&mut self, props: <Self as yew::Component>::Properties) -> bool { - self.props.neq_assign(props) - } - fn view(&self) -> yew::virtual_dom::VNode { - let link = self.props.weak_link.borrow().clone().unwrap(); - - html! { - <div class="box"> - { self.render_error() } - - <h3 class="subtitle">{ self.get_title() }</h3> - - { self.get_input_object().render(&link) } - - </div> - } - } -} diff --git a/src/view/weak_links.rs b/src/view/weak_links.rs new file mode 100644 index 0000000..689c3c4 --- /dev/null +++ b/src/view/weak_links.rs @@ -0,0 +1,33 @@ +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::Rc; +use yew::prelude::*; + +/// Weak link; Useful for being able to have a list of subcomponents. +pub struct WeakComponentLink<COMP: Component>(Rc<RefCell<Option<ComponentLink<COMP>>>>); + +impl<COMP: Component> Clone for WeakComponentLink<COMP> { + fn clone(&self) -> Self { + Self(Rc::clone(&self.0)) + } +} + +impl<COMP: Component> Default for WeakComponentLink<COMP> { + fn default() -> Self { + Self(Rc::default()) + } +} + +impl<COMP: Component> Deref for WeakComponentLink<COMP> { + type Target = Rc<RefCell<Option<ComponentLink<COMP>>>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<COMP: Component> PartialEq for WeakComponentLink<COMP> { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} |