Skip to content

Commit

Permalink
file uploader example from yew
Browse files Browse the repository at this point in the history
  • Loading branch information
knzai committed Jul 17, 2024
1 parent 2052128 commit 2685985
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 24 deletions.
17 changes: 12 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ required-features = ["wasm"]
default = ["terminal", "png"]
terminal = ["dep:clap"]
png = ["dep:image"]
wasm = ["dep:yew", "png", "dep:gloo"]
wasm = ["png", "dep:base64", "dep:gloo", "dep:js-sys", "dep:web-sys", "dep:yew"]
#sdl2 = ["dep:sdl2"]

[dev-dependencies]
Expand All @@ -36,8 +36,15 @@ bitvec = "1"
factor = "0.4.0"

#optional deps
clap = { optional = true, version = "4.5.7", features = ["derive"] }
gloo = { optional = true, version = "0.10" }
image = { optional = true, version = "0.25.1" }
sdl2 = { optional = true, version = "0.37.0", default-features = false, features = ["gfx"] }
yew = { optional = true, git = "https://github.com/yewstack/yew/", features = ["csr"] }

#terminal
clap = { optional = true, version = "4.5.7", features = ["derive"] }
sdl2 = { optional = true, version = "0.37.0", features = ["gfx"], default-features = false }

#wasm
base64 = { optional = true, version = "0.21.5"}
gloo = { optional = true, version = "0.10" }
js-sys = { optional = true, version = "0.3"}
web-sys = { optional = true, version = "0.3", features = ["File", "DragEvent", "DataTransfer"] }
yew = { optional = true, git = "https://github.com/yewstack/yew/", features = ["csr"] }
164 changes: 145 additions & 19 deletions src/wasm/main.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,152 @@
#![cfg(feature = "wasm")]
use yew::prelude::*;

#[function_component]
fn App() -> Html {
let counter = use_state(|| 0);
let onclick = {
let counter = counter.clone();
move |_| {
let value = *counter + 1;
counter.set(value);
extern crate base64;
use std::collections::HashMap;


use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use gloo::file::callbacks::FileReader;
use gloo::file::File;
use web_sys::{DragEvent, Event, FileList, HtmlInputElement};
use yew::html::TargetCast;
use yew::{html, Callback, Component, Context, Html};

struct FileDetails {
name: String,
file_type: String,
data: Vec<u8>,
}

pub enum Msg {
Loaded(String, String, Vec<u8>),
Files(Vec<File>),
}

pub struct App {
readers: HashMap<String, FileReader>,
files: Vec<FileDetails>,
}

impl Component for App {
type Message = Msg;
type Properties = ();

fn create(_ctx: &Context<Self>) -> Self {
Self {
readers: HashMap::default(),
files: Vec::default(),
}
}

fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::Loaded(file_name, file_type, data) => {
self.files.push(FileDetails {
data,
file_type,
name: file_name.clone(),
});
self.readers.remove(&file_name);
true
}
Msg::Files(files) => {
for file in files.into_iter() {
let file_name = file.name();
let file_type = file.raw_mime_type();

let task = {
let link = ctx.link().clone();
let file_name = file_name.clone();

gloo::file::callbacks::read_as_bytes(&file, move |res| {
link.send_message(Msg::Loaded(
file_name,
file_type,
res.expect("failed to read file"),
))
})
};
self.readers.insert(file_name, task);
}
true
}
}
};
}

html! {
<div>
<button {onclick}>{ "+1" }</button>
<p>{ *counter }</p>
</div>
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div id="wrapper">
<p id="title">{ "Upload Your Files To The Cloud" }</p>
<label for="file-upload">
<div
id="drop-container"
ondrop={ctx.link().callback(|event: DragEvent| {
event.prevent_default();
let files = event.data_transfer().unwrap().files();
Self::upload_files(files)
})}
ondragover={Callback::from(|event: DragEvent| {
event.prevent_default();
})}
ondragenter={Callback::from(|event: DragEvent| {
event.prevent_default();
})}
>
<i class="fa fa-cloud-upload"></i>
<p>{"Drop your images here or click to select"}</p>
</div>
</label>
<input
id="file-upload"
type="file"
accept="image/*,video/*"
multiple={true}
onchange={ctx.link().callback(move |e: Event| {
let input: HtmlInputElement = e.target_unchecked_into();
Self::upload_files(input.files())
})}
/>
<div id="preview-area">
{ for self.files.iter().map(Self::view_file) }
</div>
</div>
}
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
yew::Renderer::<App>::new().render();
Ok(())
impl App {
fn view_file(file: &FileDetails) -> Html {
html! {
<div class="preview-tile">
<p class="preview-name">{ format!("{}", file.name) }</p>
<div class="preview-media">
if file.file_type.contains("image") {
<img src={format!("data:{};base64,{}", file.file_type, STANDARD.encode(&file.data))} />
} else if file.file_type.contains("video") {
<video controls={true}>
<source src={format!("data:{};base64,{}", file.file_type, STANDARD.encode(&file.data))} type={file.file_type.clone()}/>
</video>
}
</div>
</div>
}
}

fn upload_files(files: Option<FileList>) -> Msg {
let mut result = Vec::new();

if let Some(files) = files {
let files = js_sys::try_iter(&files)
.unwrap()
.unwrap()
.map(|v| web_sys::File::from(v.unwrap()))
.map(File::from);
result.extend(files);
}
Msg::Files(result)
}
}

fn main() {
yew::Renderer::<App>::new().render();
}

0 comments on commit 2685985

Please sign in to comment.