Skip to content

Commit 2326bcd

Browse files
authored
refactor(core): use nfd for file dialogs, closes #1251 (#1257)
1 parent e7bd8c5 commit 2326bcd

File tree

12 files changed

+235
-176
lines changed

12 files changed

+235
-176
lines changed

.changes/file-dialog-refactor.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-api": minor
3+
"api": minor
4+
---
5+
6+
The file dialog API now uses [rfd](https://github.com/PolyMeilex/rfd). The filter option is now an array of `{ name: string, extensions: string[] }`.

api/src/dialog.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import { invoke } from './tauri'
22

3+
export interface DialogFilter {
4+
name: string
5+
extensions: string[]
6+
}
7+
38
export interface OpenDialogOptions {
4-
filter?: string
9+
filters?: DialogFilter[]
510
defaultPath?: string
611
multiple?: boolean
712
directory?: boolean
813
}
914

10-
export type SaveDialogOptions = Pick<
11-
OpenDialogOptions,
12-
'filter' | 'defaultPath'
13-
>
15+
export interface SaveDialogOptions {
16+
filters?: DialogFilter[]
17+
defaultPath?: string
18+
}
1419

1520
/**
1621
* @name openDialog

tauri-api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ tar = "0.4"
2727
flate2 = "1.0"
2828
thiserror = "1.0.23"
2929
rand = "0.8"
30-
nfd = "0.0.4"
30+
rfd = "0.2.1"
3131
tinyfiledialogs = "3.3"
3232
reqwest = { version = "0.11", features = [ "json", "multipart" ] }
3333
bytes = { version = "1", features = ["serde"] }

tauri-api/src/dialog.rs

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,52 @@
1-
use std::path::Path;
2-
3-
pub use nfd::Response;
4-
use nfd::{open_dialog, DialogType};
1+
use std::path::{Path, PathBuf};
52

3+
use rfd::FileDialog;
64
use tinyfiledialogs::{message_box_ok, message_box_yes_no, MessageBoxIcon, YesNo};
75

6+
/// The file dialog builder.
7+
/// Constructs file picker dialogs that can select single/multiple files or directories.
8+
#[derive(Default)]
9+
pub struct FileDialogBuilder(FileDialog);
10+
11+
impl FileDialogBuilder {
12+
/// Gets the default file dialog builder.
13+
pub fn new() -> Self {
14+
Default::default()
15+
}
16+
17+
/// Add file extension filter. Takes in the name of the filter, and list of extensions
18+
pub fn add_filter(mut self, name: impl AsRef<str>, extensions: &[&str]) -> Self {
19+
self.0 = self.0.add_filter(name.as_ref(), extensions);
20+
self
21+
}
22+
23+
/// Set starting directory of the dialog.
24+
pub fn set_directory<P: AsRef<Path>>(mut self, directory: P) -> Self {
25+
self.0 = self.0.set_directory(&directory);
26+
self
27+
}
28+
29+
/// Pick one file.
30+
pub fn pick_file(self) -> Option<PathBuf> {
31+
self.0.pick_file()
32+
}
33+
34+
/// Pick multiple files.
35+
pub fn pick_files(self) -> Option<Vec<PathBuf>> {
36+
self.0.pick_files()
37+
}
38+
39+
/// Pick one folder.
40+
pub fn pick_folder(self) -> Option<PathBuf> {
41+
self.0.pick_folder()
42+
}
43+
44+
/// Opens save file dialog.
45+
pub fn save_file(self) -> Option<PathBuf> {
46+
self.0.save_file()
47+
}
48+
}
49+
850
/// Response for the ask dialog
951
pub enum AskResponse {
1052
/// User confirmed.
@@ -30,52 +72,3 @@ pub fn ask(title: impl AsRef<str>, message: impl AsRef<str>) -> AskResponse {
3072
pub fn message(title: impl AsRef<str>, message: impl AsRef<str>) {
3173
message_box_ok(title.as_ref(), message.as_ref(), MessageBoxIcon::Info);
3274
}
33-
34-
fn open_dialog_internal(
35-
dialog_type: DialogType,
36-
filter: Option<impl AsRef<str>>,
37-
default_path: Option<impl AsRef<Path>>,
38-
) -> crate::Result<Response> {
39-
let response = open_dialog(
40-
filter.map(|s| s.as_ref().to_string()).as_deref(),
41-
default_path
42-
.map(|s| s.as_ref().to_string_lossy().to_string())
43-
.as_deref(),
44-
dialog_type,
45-
)
46-
.map_err(|e| crate::Error::Dialog(e.to_string()))?;
47-
match response {
48-
Response::Cancel => Err(crate::Error::DialogCancelled),
49-
_ => Ok(response),
50-
}
51-
}
52-
53-
/// Open single select file dialog
54-
pub fn select(
55-
filter_list: Option<impl AsRef<str>>,
56-
default_path: Option<impl AsRef<Path>>,
57-
) -> crate::Result<Response> {
58-
open_dialog_internal(DialogType::SingleFile, filter_list, default_path)
59-
}
60-
61-
/// Open multiple select file dialog
62-
pub fn select_multiple(
63-
filter_list: Option<impl AsRef<str>>,
64-
default_path: Option<impl AsRef<Path>>,
65-
) -> crate::Result<Response> {
66-
open_dialog_internal(DialogType::MultipleFiles, filter_list, default_path)
67-
}
68-
69-
/// Open save dialog
70-
pub fn save_file(
71-
filter_list: Option<impl AsRef<str>>,
72-
default_path: Option<impl AsRef<Path>>,
73-
) -> crate::Result<Response> {
74-
open_dialog_internal(DialogType::SaveFile, filter_list, default_path)
75-
}
76-
77-
/// Open pick folder dialog
78-
pub fn pick_folder(default_path: Option<impl AsRef<Path>>) -> crate::Result<Response> {
79-
let filter: Option<String> = None;
80-
open_dialog_internal(DialogType::PickFolder, filter, default_path)
81-
}

tauri/examples/api/src-tauri/Cargo.lock

Lines changed: 22 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tauri/examples/api/src/components/Dialog.svelte

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,42 +27,58 @@
2727
2828
function openDialog() {
2929
open({
30-
defaultPath: defaultPath,
31-
filter: filter,
32-
multiple: multiple,
33-
directory: directory
30+
defaultPath,
31+
filters: filter ? [{
32+
name: 'Tauri Example',
33+
extensions: filter.split(',').map(f => f.trim())
34+
}] : [],
35+
multiple,
36+
directory
3437
}).then(function (res) {
35-
var pathToRead = res
36-
var isFile = pathToRead.match(/\S+\.\S+$/g)
37-
readBinaryFile(pathToRead).then(function (response) {
38-
if (isFile) {
39-
if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) {
40-
arrayBufferToBase64(new Uint8Array(response), function (base64) {
41-
var src = 'data:image/png;base64,' + base64
42-
onMessage('<img src="' + src + '"></img>')
43-
})
38+
if (Array.isArray(res)) {
39+
onMessage(res)
40+
} else {
41+
var pathToRead = res
42+
var isFile = pathToRead.match(/\S+\.\S+$/g)
43+
readBinaryFile(pathToRead).then(function (response) {
44+
if (isFile) {
45+
if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) {
46+
arrayBufferToBase64(new Uint8Array(response), function (base64) {
47+
var src = 'data:image/png;base64,' + base64
48+
onMessage('<img src="' + src + '"></img>')
49+
})
50+
} else {
51+
onMessage(res)
52+
}
4453
} else {
4554
onMessage(res)
4655
}
47-
} else {
48-
onMessage(res)
49-
}
50-
}).catch(onMessage(res))
56+
}).catch(onMessage(res))
57+
}
5158
}).catch(onMessage)
5259
}
5360
5461
function saveDialog() {
5562
save({
56-
defaultPath: defaultPath,
57-
filter: filter,
63+
defaultPath,
64+
filters: filter ? [{
65+
name: 'Tauri Example',
66+
extensions: filter.split(',').map(f => f.trim())
67+
}] : [],
5868
}).then(onMessage).catch(onMessage)
5969
}
6070
6171
</script>
6272

73+
<style>
74+
#dialog-filter {
75+
width: 260px;
76+
}
77+
</style>
78+
6379
<div style="margin-top: 24px">
6480
<input id="dialog-default-path" placeholder="Default path" bind:value={defaultPath} />
65-
<input id="dialog-filter" placeholder="Extensions filter" bind:value={filter} />
81+
<input id="dialog-filter" placeholder="Extensions filter, comma-separated" bind:value={filter} />
6682
<div>
6783
<input type="checkbox" id="dialog-multiple" bind:checked={multiple} />
6884
<label for="dialog-multiple">Multiple</label>

tauri/examples/communication/dist/__tauri.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tauri/examples/communication/dist/dialog.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,37 @@ document.getElementById("open-dialog").addEventListener("click", function () {
77
window.__TAURI__.dialog
88
.open({
99
defaultPath: defaultPathInput.value || null,
10-
filter: filterInput.value || null,
10+
filters: filterInput.value ? [{
11+
name: 'Tauri Example',
12+
extensions: filterInput.value.split(',').map(f => f.trim())
13+
}] : [],
1114
multiple: multipleInput.checked,
1215
directory: directoryInput.checked,
1316
})
1417
.then(function (res) {
15-
console.log(res);
16-
var pathToRead = res;
17-
var isFile = pathToRead.match(/\S+\.\S+$/g);
18-
window.__TAURI__.fs
19-
.readBinaryFile(pathToRead)
20-
.then(function (response) {
21-
if (isFile) {
22-
if (pathToRead.includes(".png") || pathToRead.includes(".jpg")) {
23-
arrayBufferToBase64(new Uint8Array(response), function (base64) {
24-
var src = "data:image/png;base64," + base64;
25-
registerResponse('<img src="' + src + '"></img>');
26-
});
18+
if (Array.isArray(res)) {
19+
registerResponse(res);
20+
} else {
21+
var pathToRead = res;
22+
var isFile = pathToRead.match(/\S+\.\S+$/g);
23+
window.__TAURI__.fs
24+
.readBinaryFile(pathToRead)
25+
.then(function (response) {
26+
if (isFile) {
27+
if (pathToRead.includes(".png") || pathToRead.includes(".jpg")) {
28+
arrayBufferToBase64(new Uint8Array(response), function (base64) {
29+
var src = "data:image/png;base64," + base64;
30+
registerResponse('<img src="' + src + '"></img>');
31+
});
32+
} else {
33+
registerResponse(res);
34+
}
2735
} else {
2836
registerResponse(res);
2937
}
30-
} else {
31-
registerResponse(res);
32-
}
33-
})
34-
.catch(registerResponse(res));
38+
})
39+
.catch(registerResponse(res));
40+
}
3541
})
3642
.catch(registerResponse);
3743
});
@@ -40,7 +46,10 @@ document.getElementById("save-dialog").addEventListener("click", function () {
4046
window.__TAURI__.dialog
4147
.save({
4248
defaultPath: defaultPathInput.value || null,
43-
filter: filterInput.value || null,
49+
filters: filterInput.value ? [{
50+
name: 'Tauri Example',
51+
extensions: filterInput.value.split(',').map(f => f.trim())
52+
}] : [],
4453
})
4554
.then(registerResponse)
4655
.catch(registerResponse);

0 commit comments

Comments
 (0)