Skip to content

Commit

Permalink
Merge pull request #6366 from fcollonval/open-dialog
Browse files Browse the repository at this point in the history
Open dialogs
  • Loading branch information
blink1073 committed Aug 27, 2019
2 parents 3e4351b + 6b69db3 commit 5a8ceaa
Show file tree
Hide file tree
Showing 5 changed files with 644 additions and 1 deletion.
42 changes: 42 additions & 0 deletions docs/source/developer/ui_helpers.rst
Expand Up @@ -65,3 +65,45 @@ a ``Promise`` resolving in a ``Dialog.IResult`` object.
InputDialog.getText({ title: 'Provide a text' }).then(value => {
console.log('text ' + value.value);
});
File Dialogs
''''''''''''

Two helper functions to ask a user to open a file or a directory are
available in the ``filebrowser`` package under the namespace ``FileDialog``.

Here is an example to request a file.

.. code:: typescript
const dialog = FileDialog.getExistingDirectory({
iconRegistry, // IIconRegistry
manager, // IDocumentManager
filter: model => model.type == 'notebook' // optional (model: Contents.IModel) => boolean
});
const result = await dialog;
if(result.button.accept){
let files = result.value;
}
And for a folder.

.. code:: typescript
const dialog = FileDialog.getExistingDirectory({
iconRegistry, // IIconRegistry
manager // IDocumentManager
});
const result = await dialog;
if(result.button.accept){
let folders = result.value;
}
.. note:: The document manager and the icon registry can be obtained in a plugin by
requesting ``IFileBrowserFactory`` token. The ``manager`` will be accessed through
``factory.defaultBrowser.model.manager`` and the ``iconRegistry`` through
``factory.defaultBrowser.model.iconRegistry``.
1 change: 1 addition & 0 deletions packages/filebrowser/src/index.ts
Expand Up @@ -6,5 +6,6 @@ export * from './crumbs';
export * from './tokens';
export * from './listing';
export * from './model';
export * from './opendialog';
export * from './upload';
export * from './uploadstatus';
46 changes: 45 additions & 1 deletion packages/filebrowser/src/model.ts
Expand Up @@ -23,7 +23,8 @@ import {
find,
IIterator,
IterableOrArrayLike,
ArrayExt
ArrayExt,
filter
} from '@phosphor/algorithm';

import { PromiseDelegate, ReadonlyJSONObject } from '@phosphor/coreutils';
Expand Down Expand Up @@ -672,6 +673,49 @@ export namespace FileBrowserModel {
}
}

/**
* File browser model with optional filter on element.
*/
export class FilterFileBrowserModel extends FileBrowserModel {
constructor(options: FilterFileBrowserModel.IOptions) {
super(options);

this._filter = options.filter ? options.filter : model => true;
}

/**
* Create an iterator over the filtered model's items.
*
* @returns A new iterator over the model's items.
*/
items(): IIterator<Contents.IModel> {
return filter(super.items(), (value, index) => {
if (value.type === 'directory') {
return true;
} else {
return this._filter(value);
}
});
}

private _filter: (value: Contents.IModel) => boolean;
}

/**
* Namespace for the filtered file browser model
*/
export namespace FilterFileBrowserModel {
/**
* Constructor options
*/
export interface IOptions extends FileBrowserModel.IOptions {
/**
* Filter function on file browser item model
*/
filter?: (value: Contents.IModel) => boolean;
}
}

/**
* The namespace for the file browser model private data.
*/
Expand Down
216 changes: 216 additions & 0 deletions packages/filebrowser/src/opendialog.ts
@@ -0,0 +1,216 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { toArray } from '@phosphor/algorithm';
import { PanelLayout, Widget } from '@phosphor/widgets';
import { PathExt } from '@jupyterlab/coreutils';

import { Dialog } from '@jupyterlab/apputils';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { Contents } from '@jupyterlab/services';
import { IIconRegistry } from '@jupyterlab/ui-components';

import { FileBrowser } from './browser';
import { FilterFileBrowserModel } from './model';
import { IFileBrowserFactory } from './tokens';

/**
* The class name added to open file dialog
*/
const OPEN_DIALOG_CLASS = 'jp-Open-Dialog';

/**
* Namespace for file dialog
*/
export namespace FileDialog {
/**
* Options for the open directory dialog
*/
export interface IDirectoryOptions
extends Partial<
Pick<
Dialog.IOptions<Promise<Contents.IModel[]>>,
Exclude<
keyof Dialog.IOptions<Promise<Contents.IModel[]>>,
'body' | 'buttons' | 'defaultButton'
>
>
> {
/**
* An icon registry instance.
*/
iconRegistry: IIconRegistry;

/**
* Document manager
*/
manager: IDocumentManager;
}

/**
* Options for the open file dialog
*/
export interface IFileOptions extends IDirectoryOptions {
/**
* Filter function on file browser item model
*/
filter?: (value: Contents.IModel) => boolean;
}

/**
* Create and show a open files dialog.
*
* Note: if nothing is selected when `getValue` will return the browser
* model current path.
*
* @param options - The dialog setup options.
*
* @returns A promise that resolves with whether the dialog was accepted.
*/
export function getOpenFiles(
options: IFileOptions
): Promise<Dialog.IResult<Contents.IModel[]>> {
let dialogOptions: Partial<Dialog.IOptions<Contents.IModel[]>> = {
title: options.title,
buttons: [
Dialog.cancelButton(),
Dialog.okButton({
label: 'Select'
})
],
focusNodeSelector: options.focusNodeSelector,
host: options.host,
renderer: options.renderer,
body: new OpenDialog(
options.iconRegistry,
options.manager,
options.filter
)
};
let dialog = new Dialog(dialogOptions);
return dialog.launch();
}

/**
* Create and show a open directory dialog.
*
* Note: if nothing is selected when `getValue` will return the browser
* model current path.
*
* @param options - The dialog setup options.
*
* @returns A promise that resolves with whether the dialog was accepted.
*/
export function getExistingDirectory(
options: IDirectoryOptions
): Promise<Dialog.IResult<Contents.IModel[]>> {
return getOpenFiles({
...options,
filter: model => false
});
}
}

/**
* Open dialog widget
*/
class OpenDialog extends Widget
implements Dialog.IBodyWidget<Contents.IModel[]> {
constructor(
iconRegistry: IIconRegistry,
manager: IDocumentManager,
filter?: (value: Contents.IModel) => boolean
) {
super();
this.addClass(OPEN_DIALOG_CLASS);

this._browser = Private.createFilteredFileBrowser(
'filtered-file-browser-dialog',
iconRegistry,
manager,
filter
);

// Build the sub widgets
let layout = new PanelLayout();
layout.addWidget(this._browser);

// Set Widget content
this.layout = layout;
}

/**
* Get the selected items.
*/
getValue(): Contents.IModel[] {
const selection = toArray(this._browser.selectedItems());
if (selection.length === 0) {
// Return current path
return [
{
path: this._browser.model.path,
name: PathExt.basename(this._browser.model.path),
type: 'directory',
content: undefined,
writable: false,
created: 'unknown',
last_modified: 'unknown',
mimetype: 'text/plain',
format: 'text'
}
];
} else {
return selection;
}
}

private _browser: FileBrowser;
}

namespace Private {
/**
* Create a new file browser instance.
*
* @param id - The widget/DOM id of the file browser.
*
* @param iconRegistry - An icon registry instance.
*
* @param manager - A document manager instance.
*
* @param filter - function to filter file browser item.
*
* @param options - The optional file browser configuration object.
*
* #### Notes
* The ID parameter is used to set the widget ID. It is also used as part of
* the unique key necessary to store the file browser's restoration data in
* the state database if that functionality is enabled.
*
* If, after the file browser has been generated by the factory, the ID of the
* resulting widget is changed by client code, the restoration functionality
* will not be disrupted as long as there are no ID collisions, i.e., as long
* as the initial ID passed into the factory is used for only one file browser
* instance.
*/
export const createFilteredFileBrowser = (
id: string,
iconRegistry: IIconRegistry,
manager: IDocumentManager,
filter?: (value: Contents.IModel) => boolean,
options: IFileBrowserFactory.IOptions = {}
) => {
const model = new FilterFileBrowserModel({
iconRegistry,
manager,
filter,
driveName: options.driveName,
refreshInterval: options.refreshInterval
});
const widget = new FileBrowser({
id,
model
});

return widget;
};
}

0 comments on commit 5a8ceaa

Please sign in to comment.