|
@@ -10,7 +10,11 @@ import {
|
|
|
} from '@phosphor/algorithm';
|
|
|
|
|
|
import {
|
|
|
- Token
|
|
|
+ CommandRegistry
|
|
|
+} from '@phosphor/commands';
|
|
|
+
|
|
|
+import {
|
|
|
+ Token, ReadonlyJSONObject
|
|
|
} from '@phosphor/coreutils';
|
|
|
|
|
|
import {
|
|
@@ -47,17 +51,6 @@ const KNOWN_CATEGORIES = ['Notebook', 'Console', 'Other'];
|
|
|
const KERNEL_CATEGORIES = ['Notebook', 'Console'];
|
|
|
|
|
|
|
|
|
-/**
|
|
|
- * The command IDs used by the launcher plugin.
|
|
|
- */
|
|
|
-export
|
|
|
-namespace CommandIDs {
|
|
|
- export
|
|
|
- const show: string = 'launcher:show';
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
/* tslint:disable */
|
|
|
/**
|
|
|
* The launcher token.
|
|
@@ -82,91 +75,7 @@ interface ILauncher {
|
|
|
* re-render event for parent widget.
|
|
|
*
|
|
|
*/
|
|
|
- add(options: ILauncherItem): IDisposable;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-/**
|
|
|
- * The specification for a launcher item.
|
|
|
- */
|
|
|
-export
|
|
|
-interface ILauncherItem {
|
|
|
- /**
|
|
|
- * The display name for the launcher item.
|
|
|
- */
|
|
|
- displayName: string;
|
|
|
-
|
|
|
- /**
|
|
|
- * The callback invoked to launch the item.
|
|
|
- *
|
|
|
- * The callback is invoked with a current working directory and the
|
|
|
- * name of the selected launcher item. When the function returns
|
|
|
- * the launcher will close.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * The callback must return the widget that was created so the launcher
|
|
|
- * can replace itself with the created widget.
|
|
|
- */
|
|
|
- callback: (cwd: string, name: string) => Widget | Promise<Widget>;
|
|
|
-
|
|
|
- /**
|
|
|
- * The icon class for the launcher item.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This class name will be added to the icon node for the visual
|
|
|
- * representation of the launcher item.
|
|
|
- *
|
|
|
- * Multiple class names can be separated with white space.
|
|
|
- *
|
|
|
- * The default value is an empty string.
|
|
|
- */
|
|
|
- iconClass?: string;
|
|
|
-
|
|
|
- /**
|
|
|
- * The icon label for the launcher item.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This label will be added as text to the icon node for the visual
|
|
|
- * representation of the launcher item.
|
|
|
- *
|
|
|
- * The default value is an empty string.
|
|
|
- */
|
|
|
- iconLabel?: string;
|
|
|
-
|
|
|
- /**
|
|
|
- * The identifier for the launcher item.
|
|
|
- *
|
|
|
- * The default value is the displayName.
|
|
|
- */
|
|
|
- name?: string;
|
|
|
-
|
|
|
- /**
|
|
|
- * The category for the launcher item.
|
|
|
- *
|
|
|
- * The default value is the an empty string.
|
|
|
- */
|
|
|
- category?: string;
|
|
|
-
|
|
|
- /**
|
|
|
- * The rank for the launcher item.
|
|
|
- *
|
|
|
- * The rank is used when ordering launcher items for display. After grouping
|
|
|
- * into categories, items are sorted in the following order:
|
|
|
- * 1. Rank (lower is better)
|
|
|
- * 3. Display Name (locale order)
|
|
|
- *
|
|
|
- * The default rank is `Infinity`.
|
|
|
- */
|
|
|
- rank?: number;
|
|
|
-
|
|
|
- /**
|
|
|
- * For items that have a kernel associated with them, the URL of the kernel
|
|
|
- * icon.
|
|
|
- *
|
|
|
- * This is not a CSS class, but the URL that points to the icon in the kernel
|
|
|
- * spec.
|
|
|
- */
|
|
|
- kernelIconUrl?: string;
|
|
|
+ add(options: ILauncher.IItemOptions): IDisposable;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -193,7 +102,7 @@ class LauncherModel extends VDomModel implements ILauncher {
|
|
|
* re-render event for parent widget.
|
|
|
*
|
|
|
*/
|
|
|
- add(options: ILauncherItem): IDisposable {
|
|
|
+ add(options: ILauncher.IItemOptions): IDisposable {
|
|
|
// Create a copy of the options to circumvent mutations to the original.
|
|
|
let item = Private.createItem(options);
|
|
|
|
|
@@ -209,11 +118,11 @@ class LauncherModel extends VDomModel implements ILauncher {
|
|
|
/**
|
|
|
* Return an iterator of launcher items.
|
|
|
*/
|
|
|
- items(): IIterator<ILauncherItem> {
|
|
|
+ items(): IIterator<ILauncher.IItemOptions> {
|
|
|
return new ArrayIterator(this._items);
|
|
|
}
|
|
|
|
|
|
- private _items: ILauncherItem[] = [];
|
|
|
+ private _items: ILauncher.IItemOptions[] = [];
|
|
|
}
|
|
|
|
|
|
|
|
@@ -225,10 +134,11 @@ class Launcher extends VDomRenderer<LauncherModel> {
|
|
|
/**
|
|
|
* Construct a new launcher widget.
|
|
|
*/
|
|
|
- constructor(options: Launcher.IOptions) {
|
|
|
+ constructor(options: ILauncher.IOptions) {
|
|
|
super();
|
|
|
this._cwd = options.cwd;
|
|
|
this._callback = options.callback;
|
|
|
+ this._commands = options.commands;
|
|
|
this.addClass(LAUNCHER_CLASS);
|
|
|
}
|
|
|
|
|
@@ -273,7 +183,10 @@ class Launcher extends VDomRenderer<LauncherModel> {
|
|
|
});
|
|
|
// Within each category sort by rank
|
|
|
for (let cat in categories) {
|
|
|
- categories[cat] = categories[cat].sort(Private.sortCmp);
|
|
|
+ categories[cat] = categories[cat]
|
|
|
+ .sort((a: ILauncher.IItemOptions, b: ILauncher.IItemOptions) => {
|
|
|
+ return Private.sortCmp(a, b, this._cwd, this._commands);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
// Variable to help create sections
|
|
@@ -293,8 +206,10 @@ class Launcher extends VDomRenderer<LauncherModel> {
|
|
|
}
|
|
|
|
|
|
// Now create the sections for each category
|
|
|
- each(orderedCategories, cat => {
|
|
|
- let iconClass = `${(categories[cat][0] as ILauncherItem).iconClass} ` +
|
|
|
+ orderedCategories.forEach(cat => {
|
|
|
+ const item = categories[cat][0] as ILauncher.IItemOptions;
|
|
|
+ let iconClass =
|
|
|
+ `${this._commands.iconClass(item.command, {...item.args, cwd: this.cwd})} ` +
|
|
|
'jp-Launcher-sectionIcon jp-Launcher-icon';
|
|
|
let kernel = KERNEL_CATEGORIES.indexOf(cat) > -1;
|
|
|
if (cat in categories) {
|
|
@@ -305,8 +220,8 @@ class Launcher extends VDomRenderer<LauncherModel> {
|
|
|
<h2 className='jp-Launcher-sectionTitle'>{cat}</h2>
|
|
|
</div>
|
|
|
<div className='jp-Launcher-cardContainer'>
|
|
|
- {toArray(map(categories[cat], (item: ILauncherItem) => {
|
|
|
- return Card(kernel, item, this, this._callback);
|
|
|
+ {toArray(map(categories[cat], (item: ILauncher.IItemOptions) => {
|
|
|
+ return Card(kernel, item, this, this._commands, this._callback);
|
|
|
}))}
|
|
|
</div>
|
|
|
</div>
|
|
@@ -328,6 +243,7 @@ class Launcher extends VDomRenderer<LauncherModel> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ private _commands: CommandRegistry;
|
|
|
private _callback: (widget: Widget) => void;
|
|
|
private _pending = false;
|
|
|
private _cwd = '';
|
|
@@ -335,10 +251,10 @@ class Launcher extends VDomRenderer<LauncherModel> {
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * The namespace for `Launcher` class statics.
|
|
|
+ * The namespace for `ILauncher` class statics.
|
|
|
*/
|
|
|
export
|
|
|
-namespace Launcher {
|
|
|
+namespace ILauncher {
|
|
|
/**
|
|
|
* The options used to create a Launcher.
|
|
|
*/
|
|
@@ -349,11 +265,75 @@ namespace Launcher {
|
|
|
*/
|
|
|
cwd: string;
|
|
|
|
|
|
+ /**
|
|
|
+ * The command registry used by the launcher.
|
|
|
+ */
|
|
|
+ commands: CommandRegistry;
|
|
|
+
|
|
|
/**
|
|
|
* The callback used when an item is launched.
|
|
|
*/
|
|
|
callback: (widget: Widget) => void;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The options used to create a launcher item.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ interface IItemOptions {
|
|
|
+ /**
|
|
|
+ * The command ID for the launcher item.
|
|
|
+ *
|
|
|
+ * #### Notes
|
|
|
+ * The command's `execute` method should return
|
|
|
+ * the widget that was created so that the launcher
|
|
|
+ * can replace itself with the widget in the correct
|
|
|
+ * location.
|
|
|
+ */
|
|
|
+ command: string;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The arguments given to the command for
|
|
|
+ * creating the launcher item.
|
|
|
+ *
|
|
|
+ * ### Notes
|
|
|
+ * The launcher will also add the current working
|
|
|
+ * directory of the filebrowser in the `cwd` field
|
|
|
+ * of the args, which a command may use to create
|
|
|
+ * the activity with respect to the right directory.
|
|
|
+ */
|
|
|
+ args?: ReadonlyJSONObject;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The category for the launcher item.
|
|
|
+ *
|
|
|
+ * The default value is the an empty string.
|
|
|
+ */
|
|
|
+ category?: string;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The rank for the launcher item.
|
|
|
+ *
|
|
|
+ * The rank is used when ordering launcher items for display. After grouping
|
|
|
+ * into categories, items are sorted in the following order:
|
|
|
+ * 1. Rank (lower is better)
|
|
|
+ * 3. Display Name (locale order)
|
|
|
+ *
|
|
|
+ * The default rank is `Infinity`.
|
|
|
+ */
|
|
|
+ rank?: number;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * For items that have a kernel associated with them, the URL of the kernel
|
|
|
+ * icon.
|
|
|
+ *
|
|
|
+ * This is not a CSS class, but the URL that points to the icon in the kernel
|
|
|
+ * spec.
|
|
|
+ */
|
|
|
+ kernelIconUrl?: string;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
|
|
|
|
|
@@ -370,7 +350,12 @@ namespace Launcher {
|
|
|
*
|
|
|
* @returns a vdom `VirtualElement` for the launcher card.
|
|
|
*/
|
|
|
-function Card(kernel: boolean, item: ILauncherItem, launcher: Launcher, launcherCallback: (widget: Widget) => void): React.ReactElement<any> {
|
|
|
+function Card(kernel: boolean, item: ILauncher.IItemOptions, launcher: Launcher, commands: CommandRegistry, launcherCallback: (widget: Widget) => void): React.ReactElement<any> {
|
|
|
+ // Get some properties of the command
|
|
|
+ const command = item.command;
|
|
|
+ const args = {...item.args, cwd: launcher.cwd};
|
|
|
+ const label = commands.label(command, args);
|
|
|
+
|
|
|
// Build the onclick handler.
|
|
|
let onclick = () => {
|
|
|
// If an item has already been launched,
|
|
@@ -379,11 +364,13 @@ function Card(kernel: boolean, item: ILauncherItem, launcher: Launcher, launcher
|
|
|
return;
|
|
|
}
|
|
|
launcher.pending = true;
|
|
|
- let callback = item.callback as any;
|
|
|
- let value = callback(launcher.cwd, item.name);
|
|
|
+ let value = commands.execute(command, {
|
|
|
+ ...item.args,
|
|
|
+ cwd: launcher.cwd
|
|
|
+ });
|
|
|
Promise.resolve(value).then(widget => {
|
|
|
if (!widget) {
|
|
|
- throw new Error('Launcher callbacks must resolve with a widget');
|
|
|
+ throw new Error('Launcher commands must resolve with a widget');
|
|
|
}
|
|
|
launcherCallback(widget);
|
|
|
launcher.dispose();
|
|
@@ -396,7 +383,7 @@ function Card(kernel: boolean, item: ILauncherItem, launcher: Launcher, launcher
|
|
|
// Return the VDOM element.
|
|
|
return (
|
|
|
<div className='jp-LauncherCard'
|
|
|
- title={item.displayName}
|
|
|
+ title={label}
|
|
|
onClick={onclick}
|
|
|
data-category={item.category || 'Other'}
|
|
|
key={Private.keyProperty.get(item)}>
|
|
@@ -404,14 +391,14 @@ function Card(kernel: boolean, item: ILauncherItem, launcher: Launcher, launcher
|
|
|
{(item.kernelIconUrl && kernel) &&
|
|
|
<img src={item.kernelIconUrl} className='jp-Launcher-kernelIcon' />}
|
|
|
{(!item.kernelIconUrl && !kernel) &&
|
|
|
- <div className={`${item.iconClass} jp-Launcher-icon`} />}
|
|
|
+ <div className={`${commands.iconClass(command, args)} jp-Launcher-icon`} />}
|
|
|
{(!item.kernelIconUrl && kernel) &&
|
|
|
<div className='jp-LauncherCard-noKernelIcon'>
|
|
|
- {item.displayName[0].toUpperCase()}
|
|
|
+ {label[0].toUpperCase()}
|
|
|
</div>}
|
|
|
</div>
|
|
|
- <div className='jp-LauncherCard-label' title={item.displayName}>
|
|
|
- {item.displayName}
|
|
|
+ <div className='jp-LauncherCard-label' title={label}>
|
|
|
+ {label}
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
@@ -431,22 +418,19 @@ namespace Private {
|
|
|
* An attached property for an item's key.
|
|
|
*/
|
|
|
export
|
|
|
- const keyProperty = new AttachedProperty<ILauncherItem, number>({
|
|
|
+ const keyProperty = new AttachedProperty<ILauncher.IItemOptions, number>({
|
|
|
name: 'key',
|
|
|
create: () => id++
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
- * Create an item given item options.
|
|
|
+ * Create a fully specified item given item options.
|
|
|
*/
|
|
|
export
|
|
|
- function createItem(options: ILauncherItem): ILauncherItem {
|
|
|
+ function createItem(options: ILauncher.IItemOptions): ILauncher.IItemOptions {
|
|
|
return {
|
|
|
...options,
|
|
|
category: options.category || '',
|
|
|
- name: options.name || options.name,
|
|
|
- iconClass: options.iconClass || '',
|
|
|
- iconLabel: options.iconLabel || '',
|
|
|
rank: options.rank !== undefined ? options.rank : Infinity
|
|
|
};
|
|
|
}
|
|
@@ -455,7 +439,7 @@ namespace Private {
|
|
|
* A sort comparison function for a launcher item.
|
|
|
*/
|
|
|
export
|
|
|
- function sortCmp(a: ILauncherItem, b: ILauncherItem): number {
|
|
|
+ function sortCmp(a: ILauncher.IItemOptions, b: ILauncher.IItemOptions, cwd: string, commands: CommandRegistry): number {
|
|
|
// First, compare by rank.
|
|
|
let r1 = a.rank;
|
|
|
let r2 = b.rank;
|
|
@@ -464,6 +448,8 @@ namespace Private {
|
|
|
}
|
|
|
|
|
|
// Finally, compare by display name.
|
|
|
- return a.displayName.localeCompare(b.displayName);
|
|
|
+ const aLabel = commands.label(a.command, { ...a.args, cwd });
|
|
|
+ const bLabel = commands.label(a.command, { ...b.args, cwd });
|
|
|
+ return aLabel.localeCompare(bLabel);
|
|
|
}
|
|
|
}
|