Skip to content

Commit

Permalink
refactor(core): use nfd for file dialogs, closes #1251 (#1257)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Feb 18, 2021
1 parent e7bd8c5 commit 2326bcd
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 176 deletions.
6 changes: 6 additions & 0 deletions .changes/file-dialog-refactor.md
@@ -0,0 +1,6 @@
---
"tauri-api": minor
"api": minor
---

The file dialog API now uses [rfd](https://github.com/PolyMeilex/rfd). The filter option is now an array of `{ name: string, extensions: string[] }`.
15 changes: 10 additions & 5 deletions api/src/dialog.ts
@@ -1,16 +1,21 @@
import { invoke } from './tauri'

export interface DialogFilter {
name: string
extensions: string[]
}

export interface OpenDialogOptions {
filter?: string
filters?: DialogFilter[]
defaultPath?: string
multiple?: boolean
directory?: boolean
}

export type SaveDialogOptions = Pick<
OpenDialogOptions,
'filter' | 'defaultPath'
>
export interface SaveDialogOptions {
filters?: DialogFilter[]
defaultPath?: string
}

/**
* @name openDialog
Expand Down
2 changes: 1 addition & 1 deletion tauri-api/Cargo.toml
Expand Up @@ -27,7 +27,7 @@ tar = "0.4"
flate2 = "1.0"
thiserror = "1.0.23"
rand = "0.8"
nfd = "0.0.4"
rfd = "0.2.1"
tinyfiledialogs = "3.3"
reqwest = { version = "0.11", features = [ "json", "multipart" ] }
bytes = { version = "1", features = ["serde"] }
Expand Down
99 changes: 46 additions & 53 deletions tauri-api/src/dialog.rs
@@ -1,10 +1,52 @@
use std::path::Path;

pub use nfd::Response;
use nfd::{open_dialog, DialogType};
use std::path::{Path, PathBuf};

use rfd::FileDialog;
use tinyfiledialogs::{message_box_ok, message_box_yes_no, MessageBoxIcon, YesNo};

/// The file dialog builder.
/// Constructs file picker dialogs that can select single/multiple files or directories.
#[derive(Default)]
pub struct FileDialogBuilder(FileDialog);

impl FileDialogBuilder {
/// Gets the default file dialog builder.
pub fn new() -> Self {
Default::default()
}

/// Add file extension filter. Takes in the name of the filter, and list of extensions
pub fn add_filter(mut self, name: impl AsRef<str>, extensions: &[&str]) -> Self {
self.0 = self.0.add_filter(name.as_ref(), extensions);
self
}

/// Set starting directory of the dialog.
pub fn set_directory<P: AsRef<Path>>(mut self, directory: P) -> Self {
self.0 = self.0.set_directory(&directory);
self
}

/// Pick one file.
pub fn pick_file(self) -> Option<PathBuf> {
self.0.pick_file()
}

/// Pick multiple files.
pub fn pick_files(self) -> Option<Vec<PathBuf>> {
self.0.pick_files()
}

/// Pick one folder.
pub fn pick_folder(self) -> Option<PathBuf> {
self.0.pick_folder()
}

/// Opens save file dialog.
pub fn save_file(self) -> Option<PathBuf> {
self.0.save_file()
}
}

/// Response for the ask dialog
pub enum AskResponse {
/// User confirmed.
Expand All @@ -30,52 +72,3 @@ pub fn ask(title: impl AsRef<str>, message: impl AsRef<str>) -> AskResponse {
pub fn message(title: impl AsRef<str>, message: impl AsRef<str>) {
message_box_ok(title.as_ref(), message.as_ref(), MessageBoxIcon::Info);
}

fn open_dialog_internal(
dialog_type: DialogType,
filter: Option<impl AsRef<str>>,
default_path: Option<impl AsRef<Path>>,
) -> crate::Result<Response> {
let response = open_dialog(
filter.map(|s| s.as_ref().to_string()).as_deref(),
default_path
.map(|s| s.as_ref().to_string_lossy().to_string())
.as_deref(),
dialog_type,
)
.map_err(|e| crate::Error::Dialog(e.to_string()))?;
match response {
Response::Cancel => Err(crate::Error::DialogCancelled),
_ => Ok(response),
}
}

/// Open single select file dialog
pub fn select(
filter_list: Option<impl AsRef<str>>,
default_path: Option<impl AsRef<Path>>,
) -> crate::Result<Response> {
open_dialog_internal(DialogType::SingleFile, filter_list, default_path)
}

/// Open multiple select file dialog
pub fn select_multiple(
filter_list: Option<impl AsRef<str>>,
default_path: Option<impl AsRef<Path>>,
) -> crate::Result<Response> {
open_dialog_internal(DialogType::MultipleFiles, filter_list, default_path)
}

/// Open save dialog
pub fn save_file(
filter_list: Option<impl AsRef<str>>,
default_path: Option<impl AsRef<Path>>,
) -> crate::Result<Response> {
open_dialog_internal(DialogType::SaveFile, filter_list, default_path)
}

/// Open pick folder dialog
pub fn pick_folder(default_path: Option<impl AsRef<Path>>) -> crate::Result<Response> {
let filter: Option<String> = None;
open_dialog_internal(DialogType::PickFolder, filter, default_path)
}
38 changes: 22 additions & 16 deletions tauri/examples/api/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 36 additions & 20 deletions tauri/examples/api/src/components/Dialog.svelte
Expand Up @@ -27,42 +27,58 @@
function openDialog() {
open({
defaultPath: defaultPath,
filter: filter,
multiple: multiple,
directory: directory
defaultPath,
filters: filter ? [{
name: 'Tauri Example',
extensions: filter.split(',').map(f => f.trim())
}] : [],
multiple,
directory
}).then(function (res) {
var pathToRead = res
var isFile = pathToRead.match(/\S+\.\S+$/g)
readBinaryFile(pathToRead).then(function (response) {
if (isFile) {
if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) {
arrayBufferToBase64(new Uint8Array(response), function (base64) {
var src = 'data:image/png;base64,' + base64
onMessage('<img src="' + src + '"></img>')
})
if (Array.isArray(res)) {
onMessage(res)
} else {
var pathToRead = res
var isFile = pathToRead.match(/\S+\.\S+$/g)
readBinaryFile(pathToRead).then(function (response) {
if (isFile) {
if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) {
arrayBufferToBase64(new Uint8Array(response), function (base64) {
var src = 'data:image/png;base64,' + base64
onMessage('<img src="' + src + '"></img>')
})
} else {
onMessage(res)
}
} else {
onMessage(res)
}
} else {
onMessage(res)
}
}).catch(onMessage(res))
}).catch(onMessage(res))
}
}).catch(onMessage)
}
function saveDialog() {
save({
defaultPath: defaultPath,
filter: filter,
defaultPath,
filters: filter ? [{
name: 'Tauri Example',
extensions: filter.split(',').map(f => f.trim())
}] : [],
}).then(onMessage).catch(onMessage)
}
</script>

<style>
#dialog-filter {
width: 260px;
}
</style>

<div style="margin-top: 24px">
<input id="dialog-default-path" placeholder="Default path" bind:value={defaultPath} />
<input id="dialog-filter" placeholder="Extensions filter" bind:value={filter} />
<input id="dialog-filter" placeholder="Extensions filter, comma-separated" bind:value={filter} />
<div>
<input type="checkbox" id="dialog-multiple" bind:checked={multiple} />
<label for="dialog-multiple">Multiple</label>
Expand Down
2 changes: 1 addition & 1 deletion tauri/examples/communication/dist/__tauri.js

Large diffs are not rendered by default.

47 changes: 28 additions & 19 deletions tauri/examples/communication/dist/dialog.js
Expand Up @@ -7,31 +7,37 @@ document.getElementById("open-dialog").addEventListener("click", function () {
window.__TAURI__.dialog
.open({
defaultPath: defaultPathInput.value || null,
filter: filterInput.value || null,
filters: filterInput.value ? [{
name: 'Tauri Example',
extensions: filterInput.value.split(',').map(f => f.trim())
}] : [],
multiple: multipleInput.checked,
directory: directoryInput.checked,
})
.then(function (res) {
console.log(res);
var pathToRead = res;
var isFile = pathToRead.match(/\S+\.\S+$/g);
window.__TAURI__.fs
.readBinaryFile(pathToRead)
.then(function (response) {
if (isFile) {
if (pathToRead.includes(".png") || pathToRead.includes(".jpg")) {
arrayBufferToBase64(new Uint8Array(response), function (base64) {
var src = "data:image/png;base64," + base64;
registerResponse('<img src="' + src + '"></img>');
});
if (Array.isArray(res)) {
registerResponse(res);
} else {
var pathToRead = res;
var isFile = pathToRead.match(/\S+\.\S+$/g);
window.__TAURI__.fs
.readBinaryFile(pathToRead)
.then(function (response) {
if (isFile) {
if (pathToRead.includes(".png") || pathToRead.includes(".jpg")) {
arrayBufferToBase64(new Uint8Array(response), function (base64) {
var src = "data:image/png;base64," + base64;
registerResponse('<img src="' + src + '"></img>');
});
} else {
registerResponse(res);
}
} else {
registerResponse(res);
}
} else {
registerResponse(res);
}
})
.catch(registerResponse(res));
})
.catch(registerResponse(res));
}
})
.catch(registerResponse);
});
Expand All @@ -40,7 +46,10 @@ document.getElementById("save-dialog").addEventListener("click", function () {
window.__TAURI__.dialog
.save({
defaultPath: defaultPathInput.value || null,
filter: filterInput.value || null,
filters: filterInput.value ? [{
name: 'Tauri Example',
extensions: filterInput.value.split(',').map(f => f.trim())
}] : [],
})
.then(registerResponse)
.catch(registerResponse);
Expand Down

0 comments on commit 2326bcd

Please sign in to comment.