Skip to content

Commit

Permalink
feat: add property customization to save dialogs
Browse files Browse the repository at this point in the history
  • Loading branch information
codebytere committed Aug 7, 2019
1 parent 67169a5 commit d51bbf8
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 50 deletions.
15 changes: 13 additions & 2 deletions docs/api/dialog.md
Expand Up @@ -169,6 +169,12 @@ dialog.showOpenDialog(mainWindow, {
displayed in front of the filename text field.
* `showsTagField` Boolean (optional) _macOS_ - Show the tags input box,
defaults to `true`.
* `properties` String[] (optional)
* `showHiddenFiles` - Show hidden files in dialog.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
as a directory instead of a file.
* `showOverwriteConfirmation` _Linux_ - Sets whether the user will be presented a confirmation dialog if the user types a file name that already exists.
* `securityScopedBookmarks` Boolean (optional) _macOS_ _mas_ - Create a [security scoped bookmark](https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16) when packaged for the Mac App Store. If this option is enabled and the file doesn't already exist a blank file will be created at the chosen path.

Returns `String | undefined`, the path of the file chosen by the user; if the dialog is cancelled it returns `undefined`.
Expand All @@ -191,8 +197,13 @@ The `filters` specifies an array of file types that can be displayed, see
* `message` String (optional) _macOS_ - Message to display above text fields.
* `nameFieldLabel` String (optional) _macOS_ - Custom label for the text
displayed in front of the filename text field.
* `showsTagField` Boolean (optional) _macOS_ - Show the tags input box,
defaults to `true`.
* `showsTagField` Boolean (optional) _macOS_ - Show the tags input box, defaults to `true`.
* `properties` String[] (optional)
* `showHiddenFiles` - Show hidden files in dialog.
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
as a directory instead of a file.
* `showOverwriteConfirmation` _Linux_ - Sets whether the user will be presented a confirmation dialog if the user types a file name that already exists.
* `securityScopedBookmarks` Boolean (optional) _macOS_ _mas_ - Create a [security scoped bookmark](https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16) when packaged for the Mac App Store. If this option is enabled and the file doesn't already exist a blank file will be created at the chosen path.

Returns `Promise<Object>` - Resolve with an object containing the following:
Expand Down
36 changes: 27 additions & 9 deletions lib/browser/api/dialog.js
Expand Up @@ -4,7 +4,19 @@ const { app, BrowserWindow, deprecate } = require('electron')
const binding = process.electronBinding('dialog')
const v8Util = process.electronBinding('v8_util')

const fileDialogProperties = {
const DialogType = {
OPEN: 'OPEN',
SAVE: 'SAVE'
}

const saveFileDialogProperties = {
createDirectory: 1 << 0,
showHiddenFiles: 1 << 1,
treatPackageAsDirectory: 1 << 2,
showOverwriteConfirmation: 1 << 3
}

const openFileDialogProperties = {
openFile: 1 << 0,
openDirectory: 1 << 1,
multiSelections: 1 << 2,
Expand Down Expand Up @@ -44,6 +56,16 @@ const checkAppInitialized = function () {
}
}

const setupDialogProperties = (type, properties) => {
const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties
let dialogProperties = 0
for (const prop in dialogPropertiesTypes) {
if (properties.includes(prop)) {
dialogProperties |= dialogPropertiesTypes[prop]
}
}
}

const saveDialog = (sync, window, options) => {
checkAppInitialized()

Expand All @@ -58,6 +80,7 @@ const saveDialog = (sync, window, options) => {
buttonLabel = '',
defaultPath = '',
filters = [],
properties = [],
title = '',
message = '',
securityScopedBookmarks = false,
Expand All @@ -72,6 +95,8 @@ const saveDialog = (sync, window, options) => {
if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string')

const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window }
settings.properties = setupDialogProperties(DialogType.SAVE, properties)

return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings)
}

Expand Down Expand Up @@ -102,20 +127,13 @@ const openDialog = (sync, window, options) => {

if (!Array.isArray(properties)) throw new TypeError('Properties must be an array')

let dialogProperties = 0
for (const prop in fileDialogProperties) {
if (properties.includes(prop)) {
dialogProperties |= fileDialogProperties[prop]
}
}

if (typeof title !== 'string') throw new TypeError('Title must be a string')
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string')
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string')
if (typeof message !== 'string') throw new TypeError('Message must be a string')

const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window }
settings.properties = dialogProperties
settings.properties = setupDialogProperties(DialogType.OPEN, properties)

return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings)
}
Expand Down
2 changes: 1 addition & 1 deletion shell/browser/common_web_contents_delegate.cc
Expand Up @@ -455,7 +455,7 @@ void CommonWebContentsDelegate::DevToolsAddFileSystem(
file_dialog::DialogSettings settings;
settings.parent_window = owner_window();
settings.force_detached = offscreen_;
settings.properties = file_dialog::FILE_DIALOG_OPEN_DIRECTORY;
settings.properties = file_dialog::OPEN_DIALOG_OPEN_DIRECTORY;
if (!file_dialog::ShowOpenDialogSync(settings, &paths))
return;

Expand Down
25 changes: 16 additions & 9 deletions shell/browser/ui/file_dialog.h
Expand Up @@ -24,15 +24,22 @@ namespace file_dialog {
typedef std::pair<std::string, std::vector<std::string>> Filter;
typedef std::vector<Filter> Filters;

enum FileDialogProperty {
FILE_DIALOG_OPEN_FILE = 1 << 0,
FILE_DIALOG_OPEN_DIRECTORY = 1 << 1,
FILE_DIALOG_MULTI_SELECTIONS = 1 << 2,
FILE_DIALOG_CREATE_DIRECTORY = 1 << 3,
FILE_DIALOG_SHOW_HIDDEN_FILES = 1 << 4,
FILE_DIALOG_PROMPT_TO_CREATE = 1 << 5,
FILE_DIALOG_NO_RESOLVE_ALIASES = 1 << 6,
FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY = 1 << 7,
enum OpenFileDialogProperty {
OPEN_DIALOG_OPEN_FILE = 1 << 0,
OPEN_DIALOG_OPEN_DIRECTORY = 1 << 1,
OPEN_DIALOG_MULTI_SELECTIONS = 1 << 2,
OPEN_DIALOG_CREATE_DIRECTORY = 1 << 3,
OPEN_DIALOG_SHOW_HIDDEN_FILES = 1 << 4,
OPEN_DIALOG_PROMPT_TO_CREATE = 1 << 5,
OPEN_DIALOG_NO_RESOLVE_ALIASES = 1 << 6,
OPEN_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY = 1 << 7,
};

enum SaveFileDialogProperty {
SAVE_DIALOG_CREATE_DIRECTORY = 1 << 0,
SAVE_DIALOG_SHOW_HIDDEN_FILES = 1 << 1,
SAVE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY = 1 << 2,
SAVE_DIALOG_SHOW_OVERWRITE_CONFIRMATION = 1 << 3,
};

struct DialogSettings {
Expand Down
29 changes: 21 additions & 8 deletions shell/browser/ui/file_dialog_gtk.cc
Expand Up @@ -103,15 +103,26 @@ class FileChooserDialog {
parent_->SetEnabled(true);
}

void SetupProperties(int properties) {
const auto hasProp = [properties](FileDialogProperty prop) {
void SetupOpenProperties(int properties) {
const auto hasProp = [properties](OpenFileDialogProperty prop) {
return gboolean((properties & prop) != 0);
};
auto* file_chooser = GTK_FILE_CHOOSER(dialog());
gtk_file_chooser_set_select_multiple(file_chooser,
hasProp(FILE_DIALOG_MULTI_SELECTIONS));
hasProp(OPEN_DIALOG_MULTI_SELECTIONS));
gtk_file_chooser_set_show_hidden(file_chooser,
hasProp(FILE_DIALOG_SHOW_HIDDEN_FILES));
hasProp(OPEN_DIALOG_SHOW_HIDDEN_FILES));
}

void SetupSaveProperties(int properties) {
const auto hasProp = [properties](SaveFileDialogProperty prop) {
return gboolean((properties & prop) != 0);
};
auto* file_chooser = GTK_FILE_CHOOSER(dialog());
gtk_file_chooser_set_show_hidden(file_chooser,
hasProp(SAVE_DIALOG_SHOW_HIDDEN_FILES));
gtk_file_chooser_set_do_overwrite_confirmation(
file_chooser, hasProp(SAVE_DIALOG_SHOW_OVERWRITE_CONFIRMATION));
}

void RunAsynchronous() {
Expand Down Expand Up @@ -267,10 +278,10 @@ void FileChooserDialog::OnUpdatePreview(GtkWidget* chooser) {
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths) {
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
if (settings.properties & OPEN_DIALOG_OPEN_DIRECTORY)
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
FileChooserDialog open_dialog(action, settings);
open_dialog.SetupProperties(settings.properties);
open_dialog.SetupOpenProperties(settings.properties);

gtk_widget_show_all(open_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog()));
Expand All @@ -284,15 +295,17 @@ bool ShowOpenDialogSync(const DialogSettings& settings,
void ShowOpenDialog(const DialogSettings& settings,
electron::util::Promise promise) {
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
if (settings.properties & OPEN_DIALOG_OPEN_DIRECTORY)
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
FileChooserDialog* open_dialog = new FileChooserDialog(action, settings);
open_dialog->SetupProperties(settings.properties);
open_dialog->SetupOpenProperties(settings.properties);
open_dialog->RunOpenAsynchronous(std::move(promise));
}

bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
save_dialog.SetupSaveProperties(settings.properties);

gtk_widget_show_all(save_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog()));
if (response == GTK_RESPONSE_ACCEPT) {
Expand Down
31 changes: 21 additions & 10 deletions shell/browser/ui/file_dialog_mac.mm
Expand Up @@ -191,19 +191,28 @@ void SetupDialog(NSSavePanel* dialog, const DialogSettings& settings) {
[dialog setNameFieldStringValue:default_filename];
}

void SetupDialogForProperties(NSOpenPanel* dialog, int properties) {
[dialog setCanChooseFiles:(properties & FILE_DIALOG_OPEN_FILE)];
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
void SetupOpenDialogForProperties(NSOpenPanel* dialog, int properties) {
[dialog setCanChooseFiles:(properties & OPEN_DIALOG_OPEN_FILE)];
if (properties & OPEN_DIALOG_OPEN_DIRECTORY)
[dialog setCanChooseDirectories:YES];
if (properties & FILE_DIALOG_CREATE_DIRECTORY)
if (properties & OPEN_DIALOG_CREATE_DIRECTORY)
[dialog setCanCreateDirectories:YES];
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
if (properties & OPEN_DIALOG_MULTI_SELECTIONS)
[dialog setAllowsMultipleSelection:YES];
if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
if (properties & OPEN_DIALOG_SHOW_HIDDEN_FILES)
[dialog setShowsHiddenFiles:YES];
if (properties & FILE_DIALOG_NO_RESOLVE_ALIASES)
if (properties & OPEN_DIALOG_NO_RESOLVE_ALIASES)
[dialog setResolvesAliases:NO];
if (properties & FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
if (properties & OPEN_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
[dialog setTreatsFilePackagesAsDirectories:YES];
}

void SetupSaveDialogForProperties(NSSavePanel* dialog, int properties) {
if (properties & SAVE_DIALOG_CREATE_DIRECTORY)
[dialog setCanCreateDirectories:YES];
if (properties & SAVE_DIALOG_SHOW_HIDDEN_FILES)
[dialog setShowsHiddenFiles:YES];
if (properties & SAVE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
[dialog setTreatsFilePackagesAsDirectories:YES];
}

Expand Down Expand Up @@ -278,7 +287,7 @@ bool ShowOpenDialogSync(const DialogSettings& settings,
NSOpenPanel* dialog = [NSOpenPanel openPanel];

SetupDialog(dialog, settings);
SetupDialogForProperties(dialog, settings.properties);
SetupOpenDialogForProperties(dialog, settings.properties);

int chosen = RunModalDialog(dialog, settings);
if (chosen == NSFileHandlingPanelCancelButton)
Expand Down Expand Up @@ -324,7 +333,7 @@ void ShowOpenDialog(const DialogSettings& settings,
NSOpenPanel* dialog = [NSOpenPanel openPanel];

SetupDialog(dialog, settings);
SetupDialogForProperties(dialog, settings.properties);
SetupOpenDialogForProperties(dialog, settings.properties);

// Capture the value of the security_scoped_bookmarks settings flag
// and pass it to the completion handler.
Expand Down Expand Up @@ -355,6 +364,7 @@ bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
NSSavePanel* dialog = [NSSavePanel savePanel];

SetupDialog(dialog, settings);
SetupSaveDialogForProperties(dialog, settings.properties);

int chosen = RunModalDialog(dialog, settings);
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
Expand Down Expand Up @@ -395,6 +405,7 @@ void ShowSaveDialog(const DialogSettings& settings,
NSSavePanel* dialog = [NSSavePanel savePanel];

SetupDialog(dialog, settings);
SetupSaveDialogForProperties(dialog, settings.properties);
[dialog setCanSelectHiddenExtension:YES];

// Capture the value of the security_scoped_bookmarks settings flag
Expand Down
15 changes: 9 additions & 6 deletions shell/browser/ui/file_dialog_win.cc
Expand Up @@ -226,13 +226,13 @@ bool ShowOpenDialogSync(const DialogSettings& settings,
return false;

DWORD options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST;
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
if (settings.properties & OPEN_DIALOG_OPEN_DIRECTORY)
options |= FOS_PICKFOLDERS;
if (settings.properties & FILE_DIALOG_MULTI_SELECTIONS)
if (settings.properties & OPEN_DIALOG_MULTI_SELECTIONS)
options |= FOS_ALLOWMULTISELECT;
if (settings.properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
if (settings.properties & OPEN_DIALOG_SHOW_HIDDEN_FILES)
options |= FOS_FORCESHOWHIDDEN;
if (settings.properties & FILE_DIALOG_PROMPT_TO_CREATE)
if (settings.properties & OPEN_DIALOG_PROMPT_TO_CREATE)
options |= FOS_CREATEPROMPT;
file_open_dialog->SetOptions(options);

Expand Down Expand Up @@ -292,8 +292,11 @@ bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
if (FAILED(hr))
return false;

file_save_dialog->SetOptions(FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST |
FOS_OVERWRITEPROMPT);
DWORD options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT;
if (settings.properties & SAVE_DIALOG_SHOW_HIDDEN_FILES)
options |= FOS_FORCESHOWHIDDEN;

file_save_dialog->SetOptions(options);
ApplySettings(file_save_dialog, settings);
hr = ShowFileDialog(file_save_dialog, settings);

Expand Down
10 changes: 5 additions & 5 deletions shell/browser/web_dialog_helper.cc
Expand Up @@ -301,17 +301,17 @@ void WebDialogHelper::RunFileChooser(
settings.default_path = params.default_file_name;
file_select_helper->ShowSaveDialog(settings);
} else {
int flags = file_dialog::FILE_DIALOG_CREATE_DIRECTORY;
int flags = file_dialog::OPEN_DIALOG_CREATE_DIRECTORY;
switch (params.mode) {
case FileChooserParams::Mode::kOpenMultiple:
flags |= file_dialog::FILE_DIALOG_MULTI_SELECTIONS;
flags |= file_dialog::OPEN_DIALOG_MULTI_SELECTIONS;
FALLTHROUGH;
case FileChooserParams::Mode::kOpen:
flags |= file_dialog::FILE_DIALOG_OPEN_FILE;
flags |= file_dialog::FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY;
flags |= file_dialog::OPEN_DIALOG_OPEN_FILE;
flags |= file_dialog::OPEN_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY;
break;
case FileChooserParams::Mode::kUploadFolder:
flags |= file_dialog::FILE_DIALOG_OPEN_DIRECTORY;
flags |= file_dialog::OPEN_DIALOG_OPEN_DIRECTORY;
break;
default:
NOTREACHED();
Expand Down

0 comments on commit d51bbf8

Please sign in to comment.