use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{JsFuture, spawn_local};
use web_sys::{Blob, DataTransfer};
pub fn download_file(name: &str, bytes: &[u8]) {
let Some(document) = web_sys::window().and_then(|window| window.document()) else {
return;
};
let array = js_sys::Uint8Array::from(bytes);
let parts = js_sys::Array::new();
parts.push(&array);
let options = web_sys::BlobPropertyBag::new();
options.set_type("application/octet-stream");
let Ok(blob) = Blob::new_with_u8_array_sequence_and_options(&parts, &options) else {
return;
};
let Ok(url) = web_sys::Url::create_object_url_with_blob(&blob) else {
return;
};
if let Ok(element) = document.create_element("a")
&& let Ok(anchor) = element.dyn_into::<web_sys::HtmlAnchorElement>()
{
anchor.set_href(&url);
anchor.set_download(name);
anchor.click();
}
let _ = web_sys::Url::revoke_object_url(&url);
}
pub fn apply_pointer_lock(locked: bool) {
let Some(document) = web_sys::window().and_then(|window| window.document()) else {
return;
};
if locked {
if let Some(canvas) = document.get_element_by_id("canvas") {
canvas.request_pointer_lock();
}
} else {
document.exit_pointer_lock();
}
}
pub fn pick_file(accept: &str, on_file: impl FnOnce(String, Vec<u8>) + 'static) {
let Some(document) = web_sys::window().and_then(|window| window.document()) else {
return;
};
let Ok(element) = document.create_element("input") else {
return;
};
let Ok(input) = element.dyn_into::<web_sys::HtmlInputElement>() else {
return;
};
input.set_type("file");
input.set_accept(accept);
let input_for_closure = input.clone();
let closure = wasm_bindgen::closure::Closure::once(Box::new(move |_event: web_sys::Event| {
let Some(files) = input_for_closure.files() else {
return;
};
let Some(file) = files.get(0) else {
return;
};
read_file(file, on_file);
}) as Box<dyn FnOnce(_)>);
input.set_onchange(Some(closure.as_ref().unchecked_ref()));
closure.forget();
input.click();
}
pub fn read_file(file: web_sys::File, on_file: impl FnOnce(String, Vec<u8>) + 'static) {
spawn_local(async move {
let name = file.name();
let blob: &Blob = file.as_ref();
if let Ok(buffer) = JsFuture::from(blob.array_buffer()).await {
on_file(name, js_sys::Uint8Array::new(&buffer).to_vec());
}
});
}
pub fn read_dropped_files(
transfer: DataTransfer,
on_files: impl FnOnce(Vec<(String, Vec<u8>)>) + 'static,
) {
let Some(files) = transfer.files() else {
return;
};
if files.length() == 0 {
return;
}
spawn_local(async move {
let mut collected: Vec<(String, Vec<u8>)> = Vec::new();
for index in 0..files.length() {
if let Some(file) = files.item(index) {
let name = file.name();
let blob: &Blob = file.as_ref();
if let Ok(buffer) = JsFuture::from(blob.array_buffer()).await {
collected.push((name, js_sys::Uint8Array::new(&buffer).to_vec()));
}
}
}
if collected.len() == 1
&& collected[0].0.to_lowercase().ends_with(".zip")
&& let Some(unzipped) = unzip(&collected[0].1)
{
collected = unzipped;
}
on_files(collected);
});
}
pub enum DroppedFiles {
GltfBundle {
name: String,
gltf: Vec<u8>,
resources: Vec<(String, Vec<u8>)>,
},
Files(Vec<(String, Vec<u8>)>),
}
pub fn route_dropped(mut files: Vec<(String, Vec<u8>)>) -> DroppedFiles {
let gltf_index = files
.iter()
.position(|(name, _)| name.to_lowercase().ends_with(".gltf"));
if let Some(index) = gltf_index
&& files.len() > 1
{
let (name, gltf) = files.remove(index);
let directory = name
.rsplit_once('/')
.map(|(directory, _)| format!("{directory}/"))
.unwrap_or_default();
let resources: Vec<(String, Vec<u8>)> = files
.into_iter()
.map(|(path, bytes)| {
(
path.strip_prefix(&directory).unwrap_or(&path).to_string(),
bytes,
)
})
.collect();
return DroppedFiles::GltfBundle {
name,
gltf,
resources,
};
}
DroppedFiles::Files(files)
}
fn unzip(bytes: &[u8]) -> Option<Vec<(String, Vec<u8>)>> {
use std::io::Read;
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(bytes)).ok()?;
let mut out = Vec::new();
for index in 0..archive.len() {
let Ok(mut entry) = archive.by_index(index) else {
continue;
};
if entry.is_dir() {
continue;
}
let name = entry.name().to_string();
let mut data = Vec::new();
if entry.read_to_end(&mut data).is_ok() {
out.push((name, data));
}
}
Some(out)
}