use super::{
FilePickerError, FilePickerOptions, PickedEntry, PickedEntryRef, PickedKind, PickerFuture,
SaveFileRequest,
};
use std::rc::Rc;
use wasm_bindgen::JsCast;
pub(super) fn save(request: SaveFileRequest) -> PickerFuture<Result<bool, FilePickerError>> {
Box::pin(async move {
let window =
web_sys::window().ok_or_else(|| FilePickerError::Failed("no window".to_string()))?;
let document = window
.document()
.ok_or_else(|| FilePickerError::Failed("no document".to_string()))?;
let bytes = js_sys::Uint8Array::from(request.bytes.as_slice());
let parts = js_sys::Array::new();
parts.push(&bytes.buffer());
let options = web_sys::BlobPropertyBag::new();
options.set_type(&request.mime_type);
let blob = web_sys::Blob::new_with_u8_array_sequence_and_options(&parts, &options)
.map_err(|error| FilePickerError::Failed(format!("blob failed: {error:?}")))?;
let url = web_sys::Url::create_object_url_with_blob(&blob)
.map_err(|error| FilePickerError::Failed(format!("object URL failed: {error:?}")))?;
let anchor = document
.create_element("a")
.map_err(|error| FilePickerError::Failed(format!("anchor failed: {error:?}")))?;
let _ = anchor.set_attribute("href", &url);
let _ = anchor.set_attribute("download", &request.file_name);
if let Some(html_anchor) = anchor.dyn_ref::<web_sys::HtmlElement>() {
html_anchor.click();
}
let _ = web_sys::Url::revoke_object_url(&url);
Ok(true)
})
}
pub(super) fn pick(
options: FilePickerOptions,
kind: PickedKind,
) -> PickerFuture<Result<Option<PickedEntryRef>, FilePickerError>> {
Box::pin(async move {
match kind {
PickedKind::Folder => Err(FilePickerError::UnsupportedPlatform),
PickedKind::File => {
let mut dialog = rfd::AsyncFileDialog::new();
if let Some(title) = &options.title {
dialog = dialog.set_title(title);
}
for filter in &options.filters {
let extensions: Vec<&str> =
filter.extensions.iter().map(String::as_str).collect();
dialog = dialog.add_filter(filter.label.clone(), &extensions);
}
Ok(dialog
.pick_file()
.await
.map(|handle| Rc::new(WebFileEntry { handle }) as PickedEntryRef))
}
}
})
}
struct WebFileEntry {
handle: rfd::FileHandle,
}
impl PickedEntry for WebFileEntry {
fn name(&self) -> String {
self.handle.file_name()
}
fn kind(&self) -> PickedKind {
PickedKind::File
}
fn display_path(&self) -> String {
self.handle.file_name()
}
fn read_bytes(&self) -> PickerFuture<Result<Vec<u8>, FilePickerError>> {
let handle = self.handle.clone();
Box::pin(async move { Ok(handle.read().await) })
}
fn list(&self) -> PickerFuture<Result<Vec<PickedEntryRef>, FilePickerError>> {
Box::pin(async {
Err(FilePickerError::WrongKind {
actual: "file",
expected: "folder",
})
})
}
}