diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 370 |
1 files changed, 5 insertions, 365 deletions
@@ -1,382 +1,22 @@ #![recursion_limit="1024"] extern crate wee_alloc; extern crate console_error_panic_hook; -use std::collections::HashSet; -use name::{NameView,Name}; -use address::{AddressView,Address,AddressType}; -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 yew::prelude::App; use std::panic; - -mod name; -mod address; +use view::MainView; // Use `wee_alloc` as the global allocator. #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +mod view; +mod util; + fn init() { panic::set_hook(Box::new(console_error_panic_hook::hook)); } -#[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)] -pub enum DownloadOption { - PDF, - VCard, - QrCode, -} - -pub struct MainView { - link: ComponentLink<Self>, - error: Vec<String>, - name: Name, - work_address: Address, - home_address: Address, - download: Option<Download>, - selected_option: DownloadOption, -} - -pub enum Msg { - UpdateName(Name), - UpdateHomeAddress(Address), - UpdateWorkAddress(Address), - Generate(DownloadOption), - Nope, -} - -impl Component for MainView { - type Message = Msg; - type Properties = (); - - fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self { - MainView { - link, - error: vec![], - name: Name::new(), - work_address: Address::new_with_type(AddressType::Work), - home_address: Address::new_with_type(AddressType::Home), - download: None, - selected_option: DownloadOption::VCard - } - } - - fn update(&mut self, msg: Self::Message) -> ShouldRender { - self.error.clear(); - match msg { - Msg::UpdateName(value) => { - self.name = value; - self.link.send_message(Msg::Generate(self.selected_option)); - }, - Msg::UpdateHomeAddress(value) => { - self.home_address = value; - self.link.send_message(Msg::Generate(self.selected_option)); - }, - Msg::UpdateWorkAddress(value) => { - self.work_address = value; - self.link.send_message(Msg::Generate(self.selected_option)); - }, - Msg::Generate(option) => { - self.selected_option = option; - - let vcard_content = match self.generate_vcard() { - Ok(vcard) => Some(vcard.to_string()), - Err(VCardError::FormatError(err)) => { - self.error.push(err.to_string()); - None - } - Err(VCardError::EmptyFormatName) => { - self.error.push(String::from("At least one of the name fields should be filled out.")); - None - } - }; - - match option { - DownloadOption::VCard => { - if vcard_content.is_some() { - self.download = Some( - Download { - file_name: format!("{}.vcs", self.name.formatted_name()), - content: vcard_content.unwrap().to_string(), - mime_type: MimeType::VCard, - } - ) - } - } - DownloadOption::QrCode => { - 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!")), - }; - } - } - DownloadOption::PDF => { - 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::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_options = self.link.callback(|e: ChangeData| - match e { - ChangeData::Select(v) => match v.value().as_str() { - "vcard" => Msg::Generate(DownloadOption::VCard), - "pdf" => Msg::Generate(DownloadOption::PDF), - "qrcode" => Msg::Generate(DownloadOption::QrCode), - _ => Msg::Nope, - }, - _ => Msg::Nope, - } - ); - - html!{ - <> - <main> - <section class="hero"> - <div class="hero-body"> - <div class="container is-max-widescreen"> - <h1 class="title">{ "A Generator for vCards" }</h1> - <h2 class="subtitle">{ "Supports generating vCards (.vcf), print-ready PDF business cards and QR Codes" }</h2> - </div> - </div> - </section> - - <section class="section"> - <div class="container is-max-widescreen"> - - { self.render_errors() } - - <NameView oninput=self.link.callback(|n: Name| Msg::UpdateName(n)) /> - - <AddressView address_type=AddressType::Home oninput=self.link.callback(|a: Address| Msg::UpdateHomeAddress(a)) /> - - <AddressView address_type=AddressType::Work oninput=self.link.callback(|a: Address| Msg::UpdateWorkAddress(a)) /> - - <div class="block level-left"> - <div class="select level-item"> - <select id="download_options" onchange=download_options> - <option value="vcard">{ "VCard (.vcf)" }</option> - <option value="pdf">{ "Print-ready PDF" }</option> - <option value="qrcode">{ "QR Code" }</option> - </select> - </div> - - { self.render_download() } - </div> - - <div class="block"> - { self.render_preview() } - </div> - - </div> - </section> - </main> - - <footer class="footer"> - <div class="content has-text-centered"> - <p> - <strong>{ "VCard Generator" }</strong> { " by " } <a href="https://jelemux.dev">{ "Jeremias Weber" }</a>{ ". "} - { "The source code is licenced " } <a href="http://opensource.org/licenses/mit-license.php">{ "MIT" }</a>{"."} - </p> - </div> - </footer> - </> - } - } -} - -impl MainView { - fn render_errors(&self) -> Html { - html!{ - <> - { - for self.error.iter().map(|err| - html!{ - <div class="notification is-danger is-light"> - { err } - </div> - } - ) - } - </> - } - } - fn render_download(&self) -> Html { - if self.download.is_some() { - let download = self.download.as_ref().unwrap(); - - html!{ - <a href=download.as_data_link() download=download.file_name class="button is-primary level-item" > - { "Download" } - </a> - } - } 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!{ - <iframe src=download.as_data_link() alt="PDF Preview" width="400" height="550"/> - }, - MimeType::VCard => { - html!{ - <pre> - <code> { download.content.clone() } </code> - </pre> - } - } - MimeType::SVG => html!{ - <img src=download.as_data_link() alt="Image Preview" class="image is-square" width="300" height="300"/> - }, - } - } else { - html!{} - } - } - fn generate_vcard(&self) -> Result<VCard, VCardError> { - match VCard::from_formatted_name_str(&self.name.formatted_name()) { - Ok(vcard) => { - let mut vcard = vcard; - - let names = { - let mut names = HashSet::new(); - names.insert(self.name.to_vcard_name()); - - names - }; - - let addresses = { - let mut addresses = HashSet::new(); - addresses.insert(self.home_address.to_vcard_address()); - addresses.insert(self.work_address.to_vcard_address()); - - addresses - }; - - vcard.names = Some(vcard::Set::from_hash_set(names).unwrap()); - vcard.addresses = Some(vcard::Set::from_hash_set(addresses).unwrap()); - - Ok(vcard) - } - Err(err) => Err(err), - } - } - fn generate_pdf(&self) -> Result<String, ()>{ - let regular_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Regular.ttf"); - let regular_font_data = fonts::FontData::new(regular_bytes.to_vec(), Some(printpdf::BuiltinFont::Helvetica)).expect("font data should be correct"); - - let bold_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Bold.ttf"); - let bold_font_data = fonts::FontData::new(bold_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBold)).expect("font data should be correct"); - - let italic_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-Italic.ttf"); - let italic_font_data = fonts::FontData::new(italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaOblique)).expect("font data should be correct"); - - let bold_italic_bytes = include_bytes!("/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf"); - let bold_italic_font_data = fonts::FontData::new(bold_italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBoldOblique)).expect("font data should be correct"); - - let font_family = fonts::FontFamily{ - regular: regular_font_data, - bold: bold_font_data, - italic: italic_font_data, - bold_italic: bold_italic_font_data - }; - - let mut doc = genpdf::Document::new(font_family); - - doc.set_title("BCard test"); - doc.set_minimal_conformance(); - doc.set_margins(10); - doc.set_line_spacing(1.25); - - doc.push( - elements::Paragraph::new("genpdf Demo Document") - .aligned(elements::Alignment::Center) - .styled(style::Style::new().bold().with_font_size(20)), - ); - - // TODO fill doc with real data - - let mut buf: Vec<u8> = Vec::new(); - match doc.render(&mut buf) { - Ok(_) => Ok(match String::from_utf8(buf) { - Ok(s) => s, - Err(_) => return Err(()), - }), - Err(_) => Err(()), - } - } -} - - #[wasm_bindgen(start)] pub fn run_app() { init(); |