From 036a567bae8346eb38f9237f59645dbcc4f1cd8c Mon Sep 17 00:00:00 2001 From: jelemux Date: Tue, 9 Feb 2021 23:18:45 +0100 Subject: switch to cargo-make; format files with rustfmt --- Makefile.toml | 71 +++++++++ Readme.md | 25 ++- index.html | 22 +++ src/lib.rs | 10 +- src/view/address.rs | 28 ++-- src/view/dates.rs | 18 +-- src/view/main.rs | 345 +++++++++++++++++++--------------------- src/view/mod.rs | 25 +-- src/view/name.rs | 28 ++-- src/view/organizational.rs | 20 +-- src/view/telephone.rs | 26 +-- src/viewmodel/address.rs | 36 ++--- src/viewmodel/dates.rs | 16 +- src/viewmodel/mod.rs | 103 +++++++----- src/viewmodel/name.rs | 31 ++-- src/viewmodel/organizational.rs | 71 +++------ src/viewmodel/telephone.rs | 27 ++-- src/viewmodel/utility.rs | 6 +- src/viewmodel/vcard.rs | 8 +- static/index.html | 22 --- 20 files changed, 501 insertions(+), 437 deletions(-) create mode 100644 Makefile.toml create mode 100644 index.html delete mode 100644 static/index.html diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..00ea9b9 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,71 @@ +[env] +PORT = "8000" + +[config] +skip_core_tasks = true + +# ---- BASIC ---- + +[tasks.watch] +description = "Watch files and recompile the project on change" +run_task = [ + { name = "build" }, +] +watch = true + +[tasks.serve] +description = "Start server" +install_crate = { crate_name = "microserver", binary = "microserver", test_arg = "-h" } +command = "microserver" +args = ["--port", "${PORT}"] + +[tasks.verify] +description = "Format, lint with Clippy and run tests" +dependencies = ["fmt", "clippy", "test_h_firefox"] + +# ---- BUILD ---- + +[tasks.build] +description = "Build with wasm-pack" +install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "-V" } +command = "wasm-pack" +args = ["build", "--target", "web", "--out-name", "app", "--dev", "--debug"] + +[tasks.build_release] +description = "Build with wasm-pack in release mode" +install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "-V" } +command = "wasm-pack" +args = ["build", "--target", "web", "--out-name", "app", "--release"] + +# ---- LINT ---- + +[tasks.clippy] +description = "Lint with Clippy" +install_crate = { rustup_component_name = "clippy", binary = "cargo-clippy", test_arg = "--help" } +command = "cargo" +args = ["clippy", "--all-features", "--", "--deny", "warnings", "--deny", "clippy::pedantic", "--deny", "clippy::nursery"] + +[tasks.fmt] +description = "Format with rustfmt" +install_crate = { rustup_component_name = "rustfmt", binary = "rustfmt", test_arg = "-V" } +command = "cargo" +args = ["fmt"] + + +# ---- TEST ---- + +[tasks.test_h] +description = "Run headless tests. Ex: 'cargo make test_h firefox'. Test envs: [chrome, firefox, safari]" +extend = "test" +args = ["test", "--headless", "--${@}"] + +[tasks.test_h_firefox] +description = "Run headless tests with Firefox." +extend = "test" +args = ["test", "--headless", "--firefox"] + +[tasks.test] +description = "Run tests. Ex: 'cargo make test firefox'. Test envs: [chrome, firefox, safari]" +install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "-V" } +command = "wasm-pack" +args = ["test", "--${@}"] \ No newline at end of file diff --git a/Readme.md b/Readme.md index cb0e50a..b257c04 100644 --- a/Readme.md +++ b/Readme.md @@ -32,23 +32,32 @@ Supports generating vCards (.vcf), print-ready PDF business cards and QR Codes. * [Standard rust toolchain](https://www.rust-lang.org/tools/install) * [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) -* HTTP Server (python has one!) +* cargo-make: `cargo install cargo-make` -### 📦 Build with `wasm-pack build` - +### 📦 Build +Debug: +``` +cargo make build +``` +Release: ``` -wasm-pack build --target web +cargo make build_release ``` -### 🔍 Use the debug flag for tracing errors +### 🍲 Serve ``` -wasm-pack build --debug --target web +cargo make serve ``` -### 🎬 Run on python server +### 🔍 Linting + +``` +cargo make clippy +``` +### 📏 Format ``` -python -m http.server 8000 +cargo make fmt ``` \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..7c91ad5 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + + + BCard Wasm Web App + + + + + + + + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 401e996..b8e10a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,10 @@ -#![recursion_limit="1024"] -extern crate wee_alloc; +#![recursion_limit = "1024"] extern crate console_error_panic_hook; -use wasm_bindgen::prelude::*; -use yew::prelude::App; +extern crate wee_alloc; use std::panic; use view::main::MainView; +use wasm_bindgen::prelude::*; +use yew::prelude::App; // Use `wee_alloc` as the global allocator. #[global_allocator] @@ -21,4 +21,4 @@ fn init() { pub fn run_app() { init(); App::::new().mount_to_body(); -} \ No newline at end of file +} diff --git a/src/view/address.rs b/src/view/address.rs index a945272..57ea7e4 100644 --- a/src/view/address.rs +++ b/src/view/address.rs @@ -1,28 +1,28 @@ +use super::VCardPropertyInputComponent; use crate::view::InputProps; -use yew::prelude::*; -use yewtil::NeqAssign; use crate::viewmodel::address::*; -use crate::viewmodel::VCardPropertyInputObject; -use super::VCardPropertyInputComponent; use crate::viewmodel::Error; +use crate::viewmodel::VCardPropertyInputObject; +use yew::prelude::*; +use yewtil::NeqAssign; -type Props = InputProps; +type Props = InputProps; /// View Component for a `address` field -/// +/// /// # Examples -/// +/// /// ```compile_fail /// let html = html!{ -/// | +/// |n: Irc
| /// Msg::GeneratedAddress(some_address) /// ) /// /> /// }; /// ``` -#[derive(Clone,PartialEq)] +#[derive(Clone, PartialEq)] pub struct AddressView { props: Props, value: Address, @@ -79,17 +79,17 @@ impl Component for AddressView { Msg::ToggleHome => self.value.home = !self.value.home, Msg::Generate => { self.props.generated.emit(self.value.clone()); - }, + } }; true } - fn change(&mut self, props: ::Properties) -> bool { + fn change(&mut self, props: ::Properties) -> bool { self.props.neq_assign(props) } fn view(&self) -> yew::virtual_dom::VNode { let link = self.props.weak_link.borrow().clone().unwrap(); - html!{ + html! {
{ self.render_error() } @@ -100,4 +100,4 @@ impl Component for AddressView {
} } -} \ No newline at end of file +} diff --git a/src/view/dates.rs b/src/view/dates.rs index de3d311..b2a6dd3 100644 --- a/src/view/dates.rs +++ b/src/view/dates.rs @@ -1,14 +1,14 @@ +use super::VCardPropertyInputComponent; use crate::view::InputProps; -use yew::prelude::*; -use yewtil::NeqAssign; -use crate::viewmodel::Error; use crate::viewmodel::dates::*; +use crate::viewmodel::Error; use crate::viewmodel::VCardPropertyInputObject; -use super::VCardPropertyInputComponent; +use yew::prelude::*; +use yewtil::NeqAssign; -type Props = InputProps; +type Props = InputProps; -#[derive(Clone,PartialEq)] +#[derive(Clone, PartialEq)] pub struct DatesView { props: Props, value: Dates, @@ -51,7 +51,7 @@ impl Component for DatesView { Msg::UpdateBirthday(b) => self.value.birthday = b, Msg::Generate => { self.props.generated.emit(self.value.clone()); - }, + } }; true } @@ -61,7 +61,7 @@ impl Component for DatesView { fn view(&self) -> yew::virtual_dom::VNode { let link = self.props.weak_link.borrow().clone().unwrap(); - html!{ + html! {
{ self.render_error() } @@ -72,4 +72,4 @@ impl Component for DatesView {
} } -} \ No newline at end of file +} diff --git a/src/view/main.rs b/src/view/main.rs index d6b2cb9..196afa0 100644 --- a/src/view/main.rs +++ b/src/view/main.rs @@ -1,30 +1,29 @@ -use crate::viewmodel::organizational::Organizational; -use crate::view::organizational::{self,OrganizationalView}; +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 yew::services::ConsoleService; +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::view::telephone::{self,TelephoneView}; use crate::viewmodel::VCardPropertyInputObject; -use super::WeakComponentLink; -use super::name::{self,NameView}; -use super::address::{self,AddressView}; -use super::dates::{self,DatesView}; +use boolinator::Boolinator; +use chrono::prelude::*; use genpdf::Element as _; -use genpdf::{elements, style, fonts}; +use genpdf::{elements, fonts, style}; use qrcodegen::QrCode; use qrcodegen::QrCodeEcc; +use vobject::parameters; +use vobject::vcard::VcardBuilder; use yew::prelude::*; +use yew::services::ConsoleService; use yewtil::ptr::Mrc; -use vobject::vcard::VcardBuilder; -use vobject::parameters; -use chrono::prelude::*; -use crate::viewmodel::name::Name; -use crate::viewmodel::address::Address; -use crate::viewmodel::telephone::Telephone; -use crate::viewmodel::utility::*; -use boolinator::Boolinator; - pub struct MainView { link: ComponentLink, @@ -67,10 +66,10 @@ impl Component for MainView { type Properties = (); fn create(_props: Self::Properties, link: ComponentLink) -> Self { - MainView { - link, - error: None, - download: None, + MainView { + link, + error: None, + download: None, selected_option: DownloadOption::VCard, vcard_data: Mrc::new(VCardData::new()), @@ -91,31 +90,31 @@ impl Component for MainView { Msg::AddName => { self.name_links.push(WeakComponentLink::default()); shouldrender = true; - }, + } Msg::AddAddress => { self.address_links.push(WeakComponentLink::default()); shouldrender = true; - }, + } Msg::AddTelephone => { self.telephone_links.push(WeakComponentLink::default()); shouldrender = true; - }, + } Msg::AddDates => { self.dates_links.push(WeakComponentLink::default()); shouldrender = true; - }, + } Msg::AddOrganizational => { self.organizational_links.push(WeakComponentLink::default()); shouldrender = true; - }, + } Msg::ChangeDownloadOption(option) => { self.selected_option = option; shouldrender = false; - }, + } Msg::Generate => { - - if self.selected_option == DownloadOption::VCard || self.selected_option == DownloadOption::QrCode { - + if self.selected_option == DownloadOption::VCard + || self.selected_option == DownloadOption::QrCode + { for name_link in self.name_links.iter() { let name_link = name_link.borrow().clone().unwrap(); name_link.send_message(name::Msg::Generate); @@ -158,52 +157,55 @@ impl Component for MainView { */ shouldrender = true; - }, + } Msg::GeneratedName(name) => { - self.answer_count += 1; match self.vcard_data.get_mut() { Some(vcard_data) => vcard_data.add_name(name), - None => ConsoleService::info("Error in GeneratedName: Couldn't get mutable borrow of VCardData"), + None => ConsoleService::info( + "Error in GeneratedName: Couldn't get mutable borrow of VCardData", + ), }; shouldrender = true; - - }, + } Msg::GeneratedAddress(address) => { - self.answer_count += 1; match self.vcard_data.get_mut() { Some(vcard_data) => vcard_data.add_address(address), - None => ConsoleService::info("Error in GeneratedAddress: Couldn't get mutable borrow of VCardData"), + None => ConsoleService::info( + "Error in GeneratedAddress: Couldn't get mutable borrow of VCardData", + ), }; - + shouldrender = true; - }, + } Msg::GeneratedTelephone(telephone) => { - self.answer_count += 1; match self.vcard_data.get_mut() { Some(vcard_data) => vcard_data.add_telephone(telephone), - None => ConsoleService::info("Error in GeneratedTelephone: Couldn't get mutable borrow of VCardData"), + None => ConsoleService::info( + "Error in GeneratedTelephone: Couldn't get mutable borrow of VCardData", + ), }; shouldrender = true; - }, + } Msg::GeneratedDates(dates) => { - self.answer_count += 1; match self.vcard_data.get_mut() { Some(vcard_data) => vcard_data.add_dates(dates), - None => ConsoleService::info("Error in GeneratedDates: Couldn't get mutable borrow of VCardData"), + None => ConsoleService::info( + "Error in GeneratedDates: Couldn't get mutable borrow of VCardData", + ), }; shouldrender = true; - }, + } Msg::GeneratedOrganizational(organizational) => { self.answer_count += 1; @@ -213,9 +215,8 @@ impl Component for MainView { }; shouldrender = true; - }, + } Msg::GenerationComplete => { - self.answer_count = 0; let vcard_data = self.vcard_data.clone_inner(); @@ -223,33 +224,20 @@ impl Component for MainView { let mut builder = VcardBuilder::new(); for name in vcard_data.names { - if !name.is_empty() { - - builder = builder - .with_fullname( - name.generate_fn() - ) - .with_name( + builder = builder.with_fullname(name.generate_fn()).with_name( parameters!(), - (!name.last_name.is_empty()) - .as_some(name.last_name.clone()), - (!name.first_name.is_empty()) - .as_some(name.first_name.clone()), - (!name.middle_name.is_empty()) - .as_some(name.middle_name.clone()), - (!name.prefix.is_empty()) - .as_some(name.prefix.clone()), - (!name.suffix.is_empty()) - .as_some(name.suffix.clone()) + (!name.last_name.is_empty()).as_some(name.last_name.clone()), + (!name.first_name.is_empty()).as_some(name.first_name.clone()), + (!name.middle_name.is_empty()).as_some(name.middle_name.clone()), + (!name.prefix.is_empty()).as_some(name.prefix.clone()), + (!name.suffix.is_empty()).as_some(name.suffix.clone()), ); } } for address in vcard_data.addresses { - if !address.is_empty() { - let mut types = String::new(); if address.work { types.push_str("WORK"); @@ -260,37 +248,29 @@ impl Component for MainView { } types.push_str("HOME") } - + let params = if types.is_empty() { parameters!() } else { parameters!("TYPE" => types) }; - + builder = builder.with_adr( params, (!address.post_office_box.is_empty()) .as_some(address.post_office_box.clone()), - (!address.extension.is_empty()) - .as_some(address.extension.clone()), - (!address.street.is_empty()) - .as_some(address.street.clone()), - (!address.locality.is_empty()) - .as_some(address.locality.clone()), - (!address.region.is_empty()) - .as_some(address.region.clone()), - (!address.code.is_empty()) - .as_some(address.code.clone()), - (!address.country.is_empty()) - .as_some(address.country.clone()), + (!address.extension.is_empty()).as_some(address.extension.clone()), + (!address.street.is_empty()).as_some(address.street.clone()), + (!address.locality.is_empty()).as_some(address.locality.clone()), + (!address.region.is_empty()).as_some(address.region.clone()), + (!address.code.is_empty()).as_some(address.code.clone()), + (!address.country.is_empty()).as_some(address.country.clone()), ); } } for telephone in vcard_data.telephones { - if !telephone.is_empty() { - let mut types = String::new(); if telephone.work { types.push_str("WORK"); @@ -343,36 +323,28 @@ impl Component for MainView { } types.push_str("TEXTPHONE") } - + let params = if types.is_empty() { parameters!() } else { parameters!("TYPE" => types) }; - - builder = builder.with_tel( - params, - telephone.number.clone(), - ); + + builder = builder.with_tel(params, telephone.number.clone()); } } for dates in vcard_data.datess { - if !dates.anniversary.is_empty() { builder = builder.with_anniversary(dates.anniversary); } if !dates.birthday.is_empty() { - builder = builder.with_bday( - parameters!(), - dates.birthday - ); + builder = builder.with_bday(parameters!(), dates.birthday); } } for organizational in vcard_data.organizationals { - if !organizational.org.is_empty() { builder = builder.with_org(vec![organizational.org]); } @@ -398,50 +370,51 @@ impl Component for MainView { builder = builder.with_related(organizational.related); } } - let rev = Local::now(); - + match builder .with_version("4.0".to_string()) .with_rev(format!("{}", rev)) - .build() { - Ok(vcard) => { - match self.selected_option { - DownloadOption::VCard => { - self.download = Some( - Download { - file_name: String::from("VCard.vcs"), - content: vobject::write_component(&vcard), - mime_type: MimeType::VCard, - } - ); - }, - DownloadOption::QrCode => { - match QrCode::encode_text(&vobject::write_component(&vcard), QrCodeEcc::Low) { - Ok(qr) => self.download = Some( - Download { - file_name: String::from("QR-Code VCard.svg"), - content: qr.to_svg_string(4), - mime_type: MimeType::SVG, - } - ), - Err(_) => self.error = Some( - Error{ - msg: String::from("Sorry, VCard is too long!"), - } - ), - }; - }, - _ => (), - }; - }, - Err(err) => self.error = Some( - Error{ - msg: err.to_string(), + .build() + { + Ok(vcard) => { + match self.selected_option { + DownloadOption::VCard => { + self.download = Some(Download { + file_name: String::from("VCard.vcs"), + content: vobject::write_component(&vcard), + mime_type: MimeType::VCard, + }); } - ), - }; + DownloadOption::QrCode => { + match QrCode::encode_text( + &vobject::write_component(&vcard), + QrCodeEcc::Low, + ) { + Ok(qr) => { + self.download = Some(Download { + file_name: String::from("QR-Code VCard.svg"), + content: qr.to_svg_string(4), + mime_type: MimeType::SVG, + }) + } + Err(_) => { + self.error = Some(Error { + msg: String::from("Sorry, VCard is too long!"), + }) + } + }; + } + _ => (), + }; + } + Err(err) => { + self.error = Some(Error { + msg: err.to_string(), + }) + } + }; match self.vcard_data.get_mut() { Some(vcard_data) => *vcard_data = VCardData::new(), @@ -449,8 +422,7 @@ impl Component for MainView { }; shouldrender = true; - - }, + } Msg::Nope => shouldrender = false, }; @@ -460,7 +432,7 @@ impl Component for MainView { if self.error.is_some() { self.download = None; } - + shouldrender } @@ -469,20 +441,17 @@ impl Component for MainView { } fn view(&self) -> Html { - - let download_options = self.link.callback(|e: ChangeData| - match e { - ChangeData::Select(v) => match v.value().as_str() { - "vcard" => Msg::ChangeDownloadOption(DownloadOption::VCard), - "pdf" => Msg::ChangeDownloadOption(DownloadOption::PDF), - "qrcode" => Msg::ChangeDownloadOption(DownloadOption::QrCode), - _ => Msg::Nope, - }, + let download_options = self.link.callback(|e: ChangeData| match e { + ChangeData::Select(v) => match v.value().as_str() { + "vcard" => Msg::ChangeDownloadOption(DownloadOption::VCard), + "pdf" => Msg::ChangeDownloadOption(DownloadOption::PDF), + "qrcode" => Msg::ChangeDownloadOption(DownloadOption::QrCode), _ => Msg::Nope, - } - ); + }, + _ => Msg::Nope, + }); - html!{ + html! { <>
@@ -502,9 +471,9 @@ impl Component for MainView { { for self.name_links.iter().map(|link| html!{ - @@ -574,7 +543,7 @@ impl Component for MainView { - + { self.render_download() } @@ -601,7 +570,7 @@ impl Component for MainView { impl MainView { fn render_error(&self) -> Html { - html!{ + html! { <> { match &self.error { @@ -622,13 +591,13 @@ impl MainView { if self.download.is_some() { let download = self.download.as_ref().unwrap(); - html!{ + html! { { "Download" } } } else { - html!{} + html! {} } } fn render_preview(&self) -> Html { @@ -636,53 +605,71 @@ impl MainView { let download = self.download.as_ref().unwrap(); match download.mime_type { - MimeType::PDF => html!{ + MimeType::PDF => html! {