From 9df3ff8d633a18e934d4e62b0e2e718620760552 Mon Sep 17 00:00:00 2001 From: jelemux Date: Tue, 9 Feb 2021 22:38:40 +0100 Subject: add file input field, include file as data url in vcard --- Cargo.toml | 6 ++- src/view/main.rs | 7 +-- src/view/organizational.rs | 3 +- src/viewmodel/mod.rs | 95 +++++++++++++++++++++++++++++++++++++++- src/viewmodel/organizational.rs | 47 ++++++++++++++++---- src/viewmodel/utility.rs | 6 +++ static/fontawesome-solid.min.css | 5 +++ static/index.html | 1 + 8 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 static/fontawesome-solid.min.css diff --git a/Cargo.toml b/Cargo.toml index 34281a3..54657ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,4 +29,8 @@ features = ["services"] [dependencies.chrono] version = "0.4.19" default-features = false -features = ["wasmbind", "js-sys"] \ No newline at end of file +features = ["wasmbind", "js-sys"] + +[dependencies.web-sys] +version = "0.3.47" +features = ["FileReaderSync"] \ No newline at end of file diff --git a/src/view/main.rs b/src/view/main.rs index c2af72a..d6b2cb9 100644 --- a/src/view/main.rs +++ b/src/view/main.rs @@ -377,9 +377,10 @@ impl Component for MainView { builder = builder.with_org(vec![organizational.org]); } - if !organizational.logo.is_empty() { - builder = builder.with_logo(organizational.logo); - } + match organizational.logo { + Some(file) => builder = builder.with_logo(file.content), + None => (), + }; if !organizational.title.is_empty() { builder = builder.with_title(organizational.title); diff --git a/src/view/organizational.rs b/src/view/organizational.rs index b11c181..e6636ec 100644 --- a/src/view/organizational.rs +++ b/src/view/organizational.rs @@ -1,3 +1,4 @@ +use crate::viewmodel::utility::File; use yew::prelude::*; use yewtil::NeqAssign; use crate::viewmodel::Error; @@ -17,7 +18,7 @@ pub struct OrganizationalView { pub enum Msg { UpdateOrg(String), - UpdateLogo(String), + UpdateLogo(Option), UpdateTitle(String), UpdateRole(String), UpdateMember(String), diff --git a/src/viewmodel/mod.rs b/src/viewmodel/mod.rs index 75ed1d2..044dbad 100644 --- a/src/viewmodel/mod.rs +++ b/src/viewmodel/mod.rs @@ -1,3 +1,8 @@ +use wasm_bindgen::closure::Closure; +use web_sys::FileReader; +use wasm_bindgen::JsCast; +use yew::services::ConsoleService; +use crate::viewmodel::utility::File; use yew::prelude::*; use crate::view::VCardPropertyInputComponent; @@ -50,7 +55,13 @@ pub enum VCardPropertyInputField { placeholder: Option, oninput: Callback, value: String, - typ: String + typ: String, + }, + File { + label: String, + name: String, + callback: Callback>, + value: Option, }, CheckBox { label: String, @@ -72,6 +83,12 @@ impl VCardPropertyInputField { value: _, typ, } => Self::text_field_input(label, id, placeholder, oninput, typ), + Self::File { + label, + name, + callback, + value, + } => Self::file_field_input(label, name, callback, value), Self::CheckBox { label, id, @@ -99,6 +116,82 @@ impl VCardPropertyInputField { } } + /// Returns an `Html` representation of a file input field with the given parameters. + fn file_field_input(label: &str, name: &str, callback: &Callback>, file: &Option) -> Html { + let callback = callback.clone(); + let onchange = Callback::<()>::default(); + let onchange = onchange.reform(move |c: ChangeData| + if let ChangeData::Files(files) = c { + match files.item(0) { + Some(file) => { + let file_reader = FileReader::new().unwrap(); + match file_reader.read_as_data_url(&file) { + Ok(_) => (), + Err(_) => ConsoleService::warn("Error: Couldn't get file as data url."), + }; + + let callback = callback.clone(); + let onload = Closure::wrap(Box::new(move |event: Event|{ + let file_reader: FileReader = event.target().unwrap().dyn_into().unwrap(); + let data_url: Option = file_reader.result().unwrap().as_string(); + match data_url { + Some(content) => callback.emit( + Some(File { + name: file.name(), + content, + }) + ), + None => { + ConsoleService::warn("Couldn't get data url as string."); + callback.emit(None); + }, + }; + }) as Box); + + file_reader.set_onload(Some(onload.as_ref().unchecked_ref())); + onload.forget(); + }, + None => callback.emit(None), + } + } else { + callback.emit(None); + } + ); + html!{ +
+ +
+ +
+
+ } + } /// Returns an `Html` representation of a checkbox input field with the given parameters. fn checkbox_field_input(label: &str, id: &Option, checked: &bool, onclick: &Callback) -> Html { html!{ diff --git a/src/viewmodel/organizational.rs b/src/viewmodel/organizational.rs index e7a4ae7..c8f7164 100644 --- a/src/viewmodel/organizational.rs +++ b/src/viewmodel/organizational.rs @@ -4,7 +4,7 @@ use super::*; #[derive(Clone,Debug,PartialEq)] pub struct Organizational { pub org: String, - pub logo: String, + pub logo: Option, pub title: String, pub role: String, pub member: String, @@ -15,7 +15,7 @@ impl VCardPropertyInputObject for Organizational { fn new() -> Self { Self { org: String::new(), - logo: String::new(), + logo: None, title: String::new(), role: String::new(), member: String::new(), @@ -33,13 +33,44 @@ impl VCardPropertyInputObject for Organizational { value: self.org.clone(), typ: typ.clone(), }, - VCardPropertyInputField::Text{ // TODO: Add Upload for logo + VCardPropertyInputField::File{ // TODO: Add Upload for logo label: "Logo".to_string(), - id: Some("logo".to_string()), - placeholder: None, - oninput: link.callback(|e: InputData| Msg::UpdateLogo(e.value)), + name: "logo".to_string(), + callback: link.callback(|file: Option| + /* + if let ChangeData::Files(files) = c { + match files.item(0) { + Some(file) => { + let filereader = match FileReaderSync::new() { + Ok(reader) => reader, + Err(_) => { + ConsoleService::warn("Couldn't create new filereader."); + return Msg::UpdateLogo(None) + }, + }; + let content = match filereader.read_as_data_url(&file) { + Ok(content) => content, + Err(_) => { + ConsoleService::warn("Error: Couldn't get file as data url."); + return Msg::UpdateLogo(None) + }, + }; + Msg::UpdateLogo( + Some(File { + name: file.name(), + content, + }) + ) + }, + None => Msg::UpdateLogo(None), + } + } else { + Msg::UpdateLogo(None) + } + */ + Msg::UpdateLogo(file) + ), value: self.logo.clone(), - typ: typ.clone(), }, VCardPropertyInputField::Text{ label: "Title".to_string(), @@ -77,7 +108,7 @@ impl VCardPropertyInputObject for Organizational { } fn is_empty(&self) -> bool { self.org.is_empty() && - self.logo.is_empty() && + self.logo.is_none() && self.title.is_empty() && self.role.is_empty() && self.member.is_empty() && diff --git a/src/viewmodel/utility.rs b/src/viewmodel/utility.rs index 4a82a42..cb581ac 100644 --- a/src/viewmodel/utility.rs +++ b/src/viewmodel/utility.rs @@ -38,4 +38,10 @@ pub enum DownloadOption { PDF, VCard, QrCode, +} + +#[derive(Clone,Debug,PartialEq)] +pub struct File { + pub name: String, + pub content: String, } \ No newline at end of file diff --git a/static/fontawesome-solid.min.css b/static/fontawesome-solid.min.css new file mode 100644 index 0000000..d61a2d3 --- /dev/null +++ b/static/fontawesome-solid.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900} \ No newline at end of file diff --git a/static/index.html b/static/index.html index 21f35d4..c067b51 100644 --- a/static/index.html +++ b/static/index.html @@ -5,6 +5,7 @@ + BCard Wasm Web App -- cgit v1.2.3