From 5b9f62a7b12bc67d982e53d8f73824822c199401 Mon Sep 17 00:00:00 2001 From: jelemux Date: Tue, 10 Nov 2020 23:43:24 +0100 Subject: seperate component for name + switch to bulma.css --- Cargo.toml | 5 +- excluded/model.rs | 88 -------------- src/lib.rs | 346 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/name.rs | 161 +++++++++++++++++++++++++ src/view.rs | 287 -------------------------------------------- static/index.html | 4 +- 6 files changed, 512 insertions(+), 379 deletions(-) delete mode 100644 excluded/model.rs create mode 100644 src/name.rs delete mode 100644 src/view.rs diff --git a/Cargo.toml b/Cargo.toml index 2dc092e..0603273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,13 @@ crate-type = ["cdylib", "rlib"] [dependencies] yew = "0.17.4" wasm-bindgen = "0.2.68" -genpdf = { path = "../genpdf-rs" } js-sys = "0.3.45" +console_error_panic_hook = "0.1.6" +wee_alloc = "0.4.5" printpdf = "0.3.3" base64 = "0.13.0" vcard = "0.4.7" -console_error_panic_hook = "0.1.6" +genpdf = { path = "../genpdf-rs" } qrcodegen = "1.6.0" [dependencies.chrono] diff --git a/excluded/model.rs b/excluded/model.rs deleted file mode 100644 index 39e4c96..0000000 --- a/excluded/model.rs +++ /dev/null @@ -1,88 +0,0 @@ -use chrono::NaiveDateTime; -use crate::validation::{self, *}; - -pub struct BCard { - pub name: Option, - pub nickname: Option, - pub label: Option>, - pub address: Option>, - pub emails: Option>>, - pub title: Option, - pub role: Option, - pub organization: Option, - pub urls: Option>>, - pub telephones: Option>>, - pub revision: Option, -} - -impl BCard { - pub fn new() -> Self { - Self { - name: None, - nickname: None, - label: None, - address: None, - emails: None, - title: None, - role: None, - organization: None, - urls: None, - telephones: None, - revision: None, - } - } -} - -impl Validation for BCard { - fn validate(&self) -> Result<(), ValidationError> { - let mut result = Ok(()); - result = match &self.name { - Some(n) => validation::add_results(result, n.validate()), - None => Err( ValidationError{ messages: vec![String::from("Name cannot be empty")] } ), - }; - // TODO add some more validation - result - } -} - -pub struct Name { - pub prefix: Option, - pub first_name: Option, - pub middle_name: Option, - pub family_name: Option, - pub suffix: Option, -} - -impl Name { - pub fn new() -> Self { - Self { - prefix: None, - first_name: None, - middle_name: None, - family_name: None, - suffix: None, - } - } -} - -impl Validation for Name { - fn validate(&self) -> std::result::Result<(), ValidationError> { todo!() } -} - -pub enum WorkHomeType { - Home, - Work, -} - -pub struct TypedProperty { - pub p_type: Option, - pub value: T, -} - -pub struct Address { - pub street: Option, - pub city: Option, - pub locality: Option, - pub postal_code: Option, - pub country: Option, -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 941067e..67c2eed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,347 @@ #![recursion_limit="1024"] +extern crate wee_alloc; +extern crate console_error_panic_hook; +use name::{NameView,Name}; +use genpdf::Element as _; +use genpdf::{elements, style, fonts}; +use qrcodegen::QrCode; +use qrcodegen::QrCodeEcc; +use wasm_bindgen::prelude::*; +use yew::prelude::*; +use vcard::{VCard, VCardError}; +use std::panic; -mod view; \ No newline at end of file +mod name; + +// Use `wee_alloc` as the global allocator. +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +fn init() { + panic::set_hook(Box::new(console_error_panic_hook::hook)); +} + +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) + } +} + +impl Clone for Download { + fn clone(&self) -> Self { + Self { + file_name: self.file_name.clone(), + content: self.content.clone(), + mime_type: self.mime_type.clone(), + } + } +} + +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", + } + } +} + +impl Clone for MimeType { + fn clone(&self) -> Self { + match self { + MimeType::PDF => MimeType::PDF, + MimeType::VCard => MimeType::VCard, + MimeType::SVG => MimeType::SVG, + } + } +} + +pub struct MainView { + link: ComponentLink, + error: Vec, + name: Name, + download: Option, +} + +pub enum Msg { + UpdateName(Name), + GenerateVCard, + GeneratePdf, + GenerateQrCode, + Nope, +} + +impl Component for MainView { + type Message = Msg; + type Properties = (); + + fn create(_props: Self::Properties, link: ComponentLink) -> Self { + MainView { link, error: vec![], name: Name::new(), download: None, } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + self.error.clear(); + match msg { + Msg::UpdateName(value) => self.name = value, + Msg::GenerateVCard => { + match self.generate_vcard() { + Ok(vcard) => self.download = Some( + Download { + file_name: format!("{}.vcs", self.name.formatted_name()), + content: vcard.to_string(), + mime_type: MimeType::VCard, + } + ), + Err(VCardError::FormatError(err)) => self.error.push(err.to_string()), + Err(VCardError::EmptyFormatName) => self.error.push(String::from("A VCard should have at least one formatted name.")), + }; + } + Msg::GeneratePdf => { + match self.generate_pdf() { + Ok(pdf) => self.download = Some( + Download { + file_name: format!("Visitenkarten {}.pdf", self.name.formatted_name()), + content: pdf, + mime_type: MimeType::PDF, + } + ), + Err(_) => self.error.push(String::from("Unexpected error while generating the PDF. Please contact me about it.")), + } + } + Msg::GenerateQrCode => { + let mut vcard_content = None; + match self.generate_vcard() { + Ok(vcard) => vcard_content = Some(vcard.to_string()), + Err(VCardError::FormatError(err)) => self.error.push(err.to_string()), + Err(VCardError::EmptyFormatName) => self.error.push(String::from("A VCard should have at least one formatted name.")), + }; + if vcard_content.is_some() { + match QrCode::encode_text(vcard_content.as_ref().unwrap(), QrCodeEcc::Low) { + Ok(qr) => self.download = Some( + Download { + file_name: format!("QR-Code VCard {}.svg", self.name.formatted_name()), + content: qr.to_svg_string(4), + mime_type: MimeType::SVG, + } + ), + Err(_) => self.error.push(String::from("Sorry, VCard is too long!")), + }; + } + } + Msg::Nope => return false, + }; + if self.error.len() > 0 { + self.download = None; + } + true + } + + fn change(&mut self, _props: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + + let download = self.download.clone(); + + let on_name_input = self.link.batch_callback(move |n: Name| + if download.is_some() { + vec![ + Msg::UpdateName(n), + match download.as_ref().unwrap().mime_type { + MimeType::PDF => Msg::GeneratePdf, + MimeType::SVG => Msg::GenerateQrCode, + MimeType::VCard => Msg::GenerateVCard, + } + ] + } else { + vec![Msg::UpdateName(n), Msg::GenerateVCard] + } + ); + + let download_options = self.link.callback(|e: ChangeData| + match e { + ChangeData::Select(v) => match v.value().as_str() { + "vcard" => Msg::GenerateVCard, + "pdf" => Msg::GeneratePdf, + "qrcode" => Msg::GenerateQrCode, + _ => Msg::Nope, + }, + _ => Msg::Nope, + } + ); + + html!{ + <> +
+
+
+
+

{ "A Generator for vCards" }

+

{ "Supports generating vCards (.vcf), print-ready PDF business cards and QR Codes" }

+
+
+
+ +
+
+ + { self.render_errors() } + + + +
+
+ +
+ + { self.render_download() } +
+ +
+ { self.render_preview() } +
+ +
+
+
+ + + + } + } +} + +impl MainView { + fn render_errors(&self) -> Html { + html!{ + <> + { + for self.error.iter().map(|err| + html!{ +
+ { err } +
+ } + ) + } + + } + } + fn render_download(&self) -> Html { + if self.download.is_some() { + let download = self.download.as_ref().unwrap(); + + html!{ + + { "Download" } + + } + } else { + html!{} + } + } + fn render_preview(&self) -> Html { + if self.download.is_some() { + let download = self.download.as_ref().unwrap(); + + match download.mime_type { + MimeType::PDF => html!{ +