summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs370
1 files changed, 5 insertions, 365 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 30d6d59..6982920 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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();