diff --git a/Cargo.lock b/Cargo.lock index 69604f8..4ef6fae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bincode" @@ -456,11 +456,11 @@ name = "filegram-web" version = "0.1.0" dependencies = [ "base64", - "console_error_panic_hook", "filegram", "futures", "getrandom 0.2.11", - "gloo-worker 0.5.0", + "gloo 0.10.0", + "gloo-file 0.3.0", "image", "js-sys", "serde", @@ -651,19 +651,38 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d" dependencies = [ - "gloo-console", - "gloo-dialogs", - "gloo-events", - "gloo-file", - "gloo-history", - "gloo-net", - "gloo-render", - "gloo-storage", - "gloo-timers", + "gloo-console 0.2.3", + "gloo-dialogs 0.1.1", + "gloo-events 0.1.2", + "gloo-file 0.2.3", + "gloo-history 0.1.5", + "gloo-net 0.3.1", + "gloo-render 0.1.1", + "gloo-storage 0.2.2", + "gloo-timers 0.2.6", "gloo-utils 0.1.7", "gloo-worker 0.2.1", ] +[[package]] +name = "gloo" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd35526c28cc55c1db77aed6296de58677dbab863b118483a27845631d870249" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.1", + "gloo-net 0.4.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.4.0", +] + [[package]] name = "gloo-console" version = "0.2.3" @@ -677,6 +696,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-dialogs" version = "0.1.1" @@ -687,6 +719,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-events" version = "0.1.2" @@ -697,13 +739,35 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-file" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" dependencies = [ - "gloo-events", + "gloo-events 0.1.2", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "gloo-events 0.2.0", "js-sys", "wasm-bindgen", "web-sys", @@ -715,7 +779,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" dependencies = [ - "gloo-events", + "gloo-events 0.1.2", "gloo-utils 0.1.7", "serde", "serde-wasm-bindgen 0.5.0", @@ -725,6 +789,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-history" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4022e82f5f9e03cb1251b13c0a967e0600e97aa179c617f6519bac40640160" +dependencies = [ + "getrandom 0.2.11", + "gloo-events 0.2.0", + "gloo-utils 0.2.0", + "serde", + "serde-wasm-bindgen 0.6.1", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-net" version = "0.3.1" @@ -746,6 +827,27 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gloo-render" version = "0.1.1" @@ -756,6 +858,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-storage" version = "0.2.2" @@ -771,6 +883,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-timers" version = "0.2.6" @@ -781,6 +908,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.1.7" @@ -815,7 +952,7 @@ checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" dependencies = [ "anymap2", "bincode", - "gloo-console", + "gloo-console 0.2.3", "gloo-utils 0.1.7", "js-sys", "serde", @@ -826,9 +963,9 @@ dependencies = [ [[package]] name = "gloo-worker" -version = "0.5.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" +checksum = "76495d3dd87de51da268fa3a593da118ab43eb7f8809e17eb38d3319b424e400" dependencies = [ "bincode", "futures", @@ -1368,7 +1505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" dependencies = [ "futures", - "gloo", + "gloo 0.8.1", "num_cpus", "once_cell", "pin-project", @@ -2027,7 +2164,7 @@ checksum = "5dbecfe44343b70cc2932c3eb445425969ae21754a8ab3a0966981c1cf7af1cc" dependencies = [ "console_error_panic_hook", "futures", - "gloo", + "gloo 0.8.1", "implicit-clone", "indexmap 1.9.3", "js-sys", diff --git a/filegram-web/Cargo.toml b/filegram-web/Cargo.toml index a2097d5..55d5290 100644 --- a/filegram-web/Cargo.toml +++ b/filegram-web/Cargo.toml @@ -7,15 +7,9 @@ edition = "2021" [dependencies] filegram = { path = "../filegram" } -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = "0.1.7" getrandom = { version = "0.2.11", features = ["js"] } js-sys = "0.3.65" image = "0.24.7" -gloo-worker = "0.5.0" serde = "1.0.163" web-sys = { version = "0.3.63", features = [ "File", @@ -29,4 +23,6 @@ wasm-bindgen = "0.2.86" futures = "0.3.28" yew = { version = "0.20.0", features = ["csr"] } serde-wasm-bindgen = "0.6.0" -base64 = "0.13.0" +gloo = "0.10.0" +gloo-file = "0.3.0" +base64 = "0.21.5" diff --git a/filegram-web/css/style.css b/filegram-web/css/style.css new file mode 100644 index 0000000..8c737eb --- /dev/null +++ b/filegram-web/css/style.css @@ -0,0 +1,7 @@ +img { + padding: 10px; +} + +.img { + padding: 10px; +} \ No newline at end of file diff --git a/filegram-web/index.html b/filegram-web/index.html index 35c2576..524c8e8 100644 --- a/filegram-web/index.html +++ b/filegram-web/index.html @@ -1,18 +1,16 @@ - + - - + + + Filegram - - - + -
+ \ No newline at end of file diff --git a/filegram-web/src/codec.rs b/filegram-web/src/codec.rs deleted file mode 100644 index 65ca220..0000000 --- a/filegram-web/src/codec.rs +++ /dev/null @@ -1,21 +0,0 @@ -use gloo_worker::Codec; - -pub struct TransferrableCodec {} - -// This codec implementation relys on some internal implementation details about gloo worker message types. -// Fields marked with `#[serde(with = "serde_wasm_bindgen::preserve")]` will be passed as-is. -impl Codec for TransferrableCodec { - fn encode(input: I) -> wasm_bindgen::JsValue - where - I: serde::Serialize, - { - serde_wasm_bindgen::to_value(&input).expect("failed to encode") - } - - fn decode(input: wasm_bindgen::JsValue) -> O - where - O: for<'de> serde::Deserialize<'de>, - { - serde_wasm_bindgen::from_value(input).expect("failed to decode") - } -} diff --git a/filegram-web/src/lib.rs b/filegram-web/src/lib.rs deleted file mode 100644 index a2d103d..0000000 --- a/filegram-web/src/lib.rs +++ /dev/null @@ -1,68 +0,0 @@ -pub mod codec; - -use std::io::Cursor; - -use filegram::encode; -use futures::TryStreamExt; -use gloo_worker::{HandlerId, Worker, WorkerScope}; -use js_sys::Uint8Array; -use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::spawn_local; -use wasm_streams::ReadableStream; -use web_sys::{Blob, File}; - -#[derive(Serialize, Deserialize)] -pub struct FilegramInput { - #[serde(with = "serde_wasm_bindgen::preserve")] - pub file: File, -} - -#[derive(Serialize, Deserialize)] -pub struct FilegramOutput { - #[serde(with = "serde_wasm_bindgen::preserve")] - pub blob: Uint8Array, -} - -pub struct FilegramWorker {} - -impl Worker for FilegramWorker { - type Input = FilegramInput; - type Output = FilegramOutput; - type Message = (); - - fn create(_scope: &WorkerScope) -> Self { - Self {} - } - - fn connected(&mut self, _scope: &WorkerScope, _id: HandlerId) {} - - fn update(&mut self, _scope: &WorkerScope, _msg: Self::Message) {} - - fn received(&mut self, scope: &WorkerScope, msg: Self::Input, id: HandlerId) { - let scope = scope.clone(); - - spawn_local(async move { - let mut contents = vec![]; - - // We assume that this file is big and cannot be loaded into the memory in one chunk. - // So we process this as a stream. - let mut s = ReadableStream::from_raw(msg.file.stream().unchecked_into()).into_stream(); - - while let Some(chunk) = s.try_next().await.unwrap() { - contents.append(&mut chunk.unchecked_into::().to_vec()); - } - - let rgb = encode::from_slice(&contents); - - let mut bytes: Vec = Vec::new(); - rgb.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png) - .unwrap(); - - let blob = Uint8Array::from(&bytes[..]); - // let blob = Blob::new_with_u8_array_sequence(&file_contents).unwrap(); - - scope.respond(id, FilegramOutput { blob }); - }); - } -} diff --git a/filegram-web/src/main.rs b/filegram-web/src/main.rs new file mode 100644 index 0000000..10e5ac3 --- /dev/null +++ b/filegram-web/src/main.rs @@ -0,0 +1,133 @@ +use base64::{engine::general_purpose, Engine as _}; +use filegram::encode; +use gloo_file::{callbacks::FileReader, File}; +use std::collections::HashMap; +use std::io::{Read, Seek, SeekFrom}; +use web_sys::{Event, HtmlInputElement}; +use yew::prelude::*; + +pub enum Msg { + LoadedBytes(String, Vec), + Files(Vec), +} + +type FileName = String; +type Data = String; + +pub struct FilegramComponent { + files: Vec<(FileName, Data)>, + readers: HashMap, +} + +impl Component for FilegramComponent { + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + files: Vec::new(), + readers: HashMap::default(), + } + } + + fn view(&self, ctx: &Context) -> Html { + let on_change = ctx.link().callback(move |e: Event| { + let mut selected_files = Vec::new(); + let input: HtmlInputElement = e.target_unchecked_into(); + if let Some(files) = input.files() { + let files = js_sys::try_iter(&files) + .unwrap() + .unwrap() + .map(|v| web_sys::File::from(v.unwrap())) + .map(File::from); + selected_files.extend(files); + } + Msg::Files(selected_files) + }); + + html! { +
+
+

{"Choose a file to encode as an image:"}

+
+
+ +
+
+ { for self.files.iter().map(|(n,d)| Self::view_file(n,d))} +
+
+ } + } + + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::Files(files) => { + for file in files.into_iter() { + let file_name = file.name(); + let task = { + let file_name = file_name.clone(); + let link = ctx.link().clone(); + + gloo_file::callbacks::read_as_bytes(&file, move |res| { + link.send_message(Msg::LoadedBytes( + file_name, + res.expect("failed to read file"), + )) + }) + }; + self.readers.insert(file_name, task); + } + true + } + Msg::LoadedBytes(file_name, data) => { + let image = Self::encode(data); + let image_data = general_purpose::STANDARD_NO_PAD.encode(image); + self.files.push((file_name.clone(), image_data)); + self.readers.remove(&file_name); + true + } + } + } +} + +impl FilegramComponent { + fn view_file(name: &str, data: &str) -> Html { + let img = format!("data:image/png;base64,{}", data.to_string()); + html! { +
+

{name}

+ +
+ } + } + + fn encode(data: Vec) -> Vec { + let img = encode::from_slice(&data); + + let mut cursor = std::io::Cursor::new(Vec::new()); + img.write_to(&mut cursor, image::ImageFormat::Png).unwrap(); + + cursor.seek(SeekFrom::Start(0)).unwrap(); + let mut out = Vec::new(); + cursor.read_to_end(&mut out).unwrap(); + + out + } +} + +#[function_component(App)] +fn app() -> Html { + html! { + <> +
+

{"Filegram image app"}

+ +
+ + } +} + +fn main() { + yew::Renderer::::new().render(); +}