summaryrefslogtreecommitdiff
path: root/src/view/mod.rs
diff options
context:
space:
mode:
authorjelemux <jeremias.weber@protonmail.com>2020-11-19 07:37:20 +0100
committerjelemux <jeremias.weber@protonmail.com>2020-11-19 07:37:20 +0100
commit49588f22f7d20193f899226107c9e323a82c6951 (patch)
tree7f7bb739336f87aa2c950038f7d5a7e154f09dbd /src/view/mod.rs
parent104f70b0968d7138d6cf944da98d95a405b1a049 (diff)
downloadwasm-card-49588f22f7d20193f899226107c9e323a82c6951.tar.gz
wasm-card-49588f22f7d20193f899226107c9e323a82c6951.tar.bz2
added telephone, but causes problems
Diffstat (limited to 'src/view/mod.rs')
-rw-r--r--src/view/mod.rs386
1 files changed, 386 insertions, 0 deletions
diff --git a/src/view/mod.rs b/src/view/mod.rs
new file mode 100644
index 0000000..d34eb39
--- /dev/null
+++ b/src/view/mod.rs
@@ -0,0 +1,386 @@
+use crate::view::telephone::{TelephoneView,Telephone};
+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 yew::prelude::*;
+use vcard::{VCard, VCardError};
+
+
+mod name;
+mod photo;
+mod birthday;
+mod address;
+mod telephone;
+
+#[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,
+ telephone: Telephone,
+ download: Option<Download>,
+ selected_option: DownloadOption,
+}
+
+pub enum Msg {
+ UpdateName(Name),
+ UpdateHomeAddress(Address),
+ UpdateWorkAddress(Address),
+ UpdateTelephone(Telephone),
+ 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),
+ telephone: Telephone::new(),
+ 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::UpdateTelephone(value) => {
+ self.telephone = 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)) />
+
+ <TelephoneView oninput=self.link.callback(|t: Telephone| Msg::UpdateTelephone(t)) />
+
+ <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_value());
+
+ names
+ };
+
+ let addresses = {
+ let mut addresses = HashSet::new();
+ addresses.insert(self.home_address.to_vcard_value());
+ addresses.insert(self.work_address.to_vcard_value());
+
+ addresses
+ };
+
+ let telephones = {
+ let mut telephones = HashSet::new();
+ telephones.insert(self.telephone.to_vcard_value());
+
+ telephones
+ };
+
+ vcard.names = Some(vcard::Set::from_hash_set(names).unwrap());
+ vcard.addresses = Some(vcard::Set::from_hash_set(addresses).unwrap());
+ vcard.telephones = Some(vcard::Set::from_hash_set(telephones).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(()),
+ }
+ }
+} \ No newline at end of file