mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Store web_sys::File inside of DroppedFile (#8354)
* Closes #4654 * Related #4667 * [x] I have followed the instructions in the PR template This PR avoids materializing the contents of a file that was dragged into an egui application on the web. It does so by storing the `web_sys::File` handle directly on WASM. This breaks the existing API of `DroppedFile` on the web, because there is no way to retrieve the bytes synchronously form a `DroppedFile` anymore, forcing handling call sites to become asynchronous. The native API remains the same. --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -209,7 +209,6 @@ image = { workspace = true, features = ["png"] } # For copying images
|
||||
js-sys.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
wasm-bindgen-futures.workspace = true
|
||||
web-sys = { workspace = true, features = [
|
||||
"AddEventListenerOptions",
|
||||
"BinaryType",
|
||||
|
||||
48
crates/eframe/src/web/dropped_file.rs
Normal file
48
crates/eframe/src/web/dropped_file.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WebFile {
|
||||
file: web_sys::File,
|
||||
// We store a `PathBuf` here so that we can hand out `Path`s
|
||||
// without allocating each time.
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl From<web_sys::File> for WebFile {
|
||||
fn from(file: web_sys::File) -> Self {
|
||||
let path = file.name().into();
|
||||
Self { file, path }
|
||||
}
|
||||
}
|
||||
|
||||
impl egui::DroppedFile for WebFile {
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn bytes_async(&self) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + '_>> {
|
||||
let file = self.file.clone();
|
||||
Box::pin(async move {
|
||||
if file.size() > f64::from(u32::MAX) {
|
||||
return Err(format!(
|
||||
"File is too large: browser file reads are limited to {} bytes",
|
||||
u32::MAX
|
||||
));
|
||||
}
|
||||
|
||||
let array_buffer = file
|
||||
.array_buffer()
|
||||
.await
|
||||
.map_err(|err| crate::web::string_from_js_value(&err))?;
|
||||
Ok(js_sys::Uint8Array::new(&array_buffer).to_vec())
|
||||
})
|
||||
}
|
||||
|
||||
fn web_file(&self) -> Option<&web_sys::File> {
|
||||
Some(&self.file)
|
||||
}
|
||||
}
|
||||
@@ -973,62 +973,25 @@ fn install_drag_and_drop(runner_ref: &WebRunner, target: &EventTarget) -> Result
|
||||
event.prevent_default();
|
||||
})?;
|
||||
|
||||
runner_ref.add_event_listener(target, "drop", {
|
||||
let runner_ref = runner_ref.clone();
|
||||
runner_ref.add_event_listener(target, "drop", |event: web_sys::DragEvent, runner| {
|
||||
if let Some(data_transfer) = event.data_transfer() {
|
||||
// TODO(https://github.com/emilk/egui/issues/3702): support dropping folders
|
||||
runner.input.raw.hovered_files.clear();
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
move |event: web_sys::DragEvent, runner| {
|
||||
if let Some(data_transfer) = event.data_transfer() {
|
||||
// TODO(https://github.com/emilk/egui/issues/3702): support dropping folders
|
||||
runner.input.raw.hovered_files.clear();
|
||||
runner.needs_repaint.repaint_asap();
|
||||
if let Some(files) = data_transfer.files() {
|
||||
for i in 0..files.length() {
|
||||
if let Some(file) = files.get(i) {
|
||||
log::debug!("Dropped {:?} ({} bytes)", file.name(), file.size());
|
||||
|
||||
if let Some(files) = data_transfer.files() {
|
||||
for i in 0..files.length() {
|
||||
if let Some(file) = files.get(i) {
|
||||
let name = file.name();
|
||||
let mime = file.type_();
|
||||
let last_modified = std::time::UNIX_EPOCH
|
||||
+ std::time::Duration::from_millis(file.last_modified() as u64);
|
||||
|
||||
log::debug!("Loading {:?} ({} bytes)…", name, file.size());
|
||||
|
||||
let future = wasm_bindgen_futures::JsFuture::from(file.array_buffer());
|
||||
|
||||
let runner_ref = runner_ref.clone();
|
||||
let future = async move {
|
||||
match future.await {
|
||||
Ok(array_buffer) => {
|
||||
let bytes = js_sys::Uint8Array::new(&array_buffer).to_vec();
|
||||
log::debug!("Loaded {:?} ({} bytes).", name, bytes.len());
|
||||
|
||||
if let Some(mut runner_lock) = runner_ref.try_lock() {
|
||||
runner_lock.input.raw.dropped_files.push(
|
||||
egui::DroppedFile {
|
||||
name,
|
||||
mime,
|
||||
last_modified: Some(last_modified),
|
||||
bytes: Some(bytes.into()),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to read file: {}",
|
||||
string_from_js_value(&err)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
}
|
||||
runner.input.raw.dropped_files.push(std::sync::Arc::new(
|
||||
super::dropped_file::WebFile::from(file),
|
||||
));
|
||||
}
|
||||
}
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
})?;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
mod app_runner;
|
||||
mod backend;
|
||||
mod dropped_file;
|
||||
mod events;
|
||||
mod input;
|
||||
mod panic_handler;
|
||||
@@ -207,13 +208,12 @@ fn set_clipboard_text(s: &str) {
|
||||
return;
|
||||
}
|
||||
let promise = window.navigator().clipboard().write_text(s);
|
||||
let future = wasm_bindgen_futures::JsFuture::from(promise);
|
||||
let future = async move {
|
||||
if let Err(err) = future.await {
|
||||
if let Err(err) = promise.await {
|
||||
log::error!("Copy/cut action failed: {}", string_from_js_value(&err));
|
||||
}
|
||||
};
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
js_sys::futures::spawn_local(future);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,16 +248,15 @@ fn set_clipboard_image(image: &egui::ColorImage) {
|
||||
};
|
||||
let items = js_sys::Array::of1(&item);
|
||||
let promise = window.navigator().clipboard().write(&items);
|
||||
let future = wasm_bindgen_futures::JsFuture::from(promise);
|
||||
let future = async move {
|
||||
if let Err(err) = future.await {
|
||||
if let Err(err) = promise.await {
|
||||
log::error!(
|
||||
"Copy/cut image action failed: {}",
|
||||
string_from_js_value(&err)
|
||||
);
|
||||
}
|
||||
};
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
js_sys::futures::spawn_local(future);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user