batbox_file_dialog/
lib.rs

1//! File dialogs
2#![warn(missing_docs)]
3
4use futures::prelude::*;
5#[cfg(target_arch = "wasm32")]
6use wasm_bindgen::prelude::*;
7
8/// A file selected using [select] dialog
9pub struct SelectedFile {
10    #[cfg(target_arch = "wasm32")]
11    file: web_sys::File,
12    #[cfg(not(target_arch = "wasm32"))]
13    path: std::path::PathBuf,
14}
15
16impl SelectedFile {
17    /// Find out the name of the file
18    pub fn name(&self) -> std::ffi::OsString {
19        #[cfg(target_arch = "wasm32")]
20        {
21            self.file.name().into()
22        }
23        #[cfg(not(target_arch = "wasm32"))]
24        {
25            self.path.file_name().expect("no filename").to_owned()
26        }
27    }
28
29    /// Get file path
30    ///
31    /// Not available on the web
32    #[cfg(not(target_arch = "wasm32"))]
33    pub fn path(&self) -> &std::path::Path {
34        &self.path
35    }
36
37    /// Get reader for the file
38    pub fn reader(self) -> anyhow::Result<impl AsyncBufRead> {
39        #[cfg(target_arch = "wasm32")]
40        {
41            Ok(futures::io::BufReader::new(batbox_file::read_stream(
42                self.file.stream(),
43            )))
44        }
45        #[cfg(not(target_arch = "wasm32"))]
46        {
47            futures::executor::block_on(batbox_file::load(self.path))
48        }
49    }
50}
51
52/// Show a select file dialog
53///
54/// The callback may be called at any moment in the future.
55/// The callback will not be called if user cancels the dialog.
56/// On the web this will only work if called during user interaction.
57// TODO: filter
58pub fn select(callback: impl FnOnce(SelectedFile) + 'static) {
59    #[cfg(target_arch = "wasm32")]
60    {
61        let input: web_sys::HtmlInputElement = web_sys::window()
62            .expect("no window")
63            .document()
64            .expect("no document")
65            .create_element("input")
66            .expect("failed to create input")
67            .unchecked_into();
68        input.set_type("file");
69        input.set_onchange(Some(
70            Closure::once_into_js({
71                let input = input.clone();
72                move || {
73                    let Some(files) = input.files() else { return };
74                    let Some(file) = files.get(0) else { return };
75                    let file = SelectedFile { file };
76                    callback(file);
77                }
78            })
79            .unchecked_ref(),
80        ));
81        input.click();
82    }
83    #[cfg(not(target_arch = "wasm32"))]
84    {
85        // TODO migrate to rfd maybe?
86        if let Some(path) = tinyfiledialogs::open_file_dialog("Select", "", None) {
87            let file = SelectedFile { path: path.into() };
88            callback(file);
89        }
90    }
91}
92
93/// Show a save file dialog and write the data into the file
94pub fn save(file_name: &str, data: &[u8]) -> anyhow::Result<()> {
95    #[cfg(target_arch = "wasm32")]
96    {
97        let data = web_sys::Blob::new_with_u8_array_sequence(&js_sys::Array::of1(
98            &js_sys::Uint8Array::from(data), // TODO: no copy?
99        ))
100        .expect("failed to create blob");
101        let a: web_sys::HtmlAnchorElement = web_sys::window()
102            .expect("no window")
103            .document()
104            .expect("no document")
105            .create_element("a")
106            .expect("failed to create <a> element")
107            .unchecked_into();
108        let url =
109            web_sys::Url::create_object_url_with_blob(&data).expect("failed to create blob url");
110        a.set_href(&url);
111        a.set_download(file_name);
112        // TODO force "Save As"? Currently no dialog appearing
113        a.click();
114        web_sys::Url::revoke_object_url(&a.href()).expect("failed to revoke url");
115    }
116    #[cfg(not(target_arch = "wasm32"))]
117    {
118        if let Some(path) = tinyfiledialogs::save_file_dialog("Save", file_name) {
119            let file = std::fs::File::create(path)?;
120            let mut writer = std::io::BufWriter::new(file);
121            std::io::Write::write_all(&mut writer, data)?;
122        }
123    }
124    Ok(())
125}