Skip to content

Commit

Permalink
Launcher first version
Browse files Browse the repository at this point in the history
  • Loading branch information
fcollonval committed Dec 23, 2019
1 parent cbff547 commit be84853
Show file tree
Hide file tree
Showing 12 changed files with 465 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- basics/signals
- command-palette
- commands
- launcher
- main-menu
- react/react-widget
- settings
Expand Down
6 changes: 6 additions & 0 deletions launcher/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.bundle.*
lib/
node_modules/
*.egg-info/
.ipynb_checkpoints
*.tsbuildinfo
30 changes: 30 additions & 0 deletions launcher/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
BSD 3-Clause License

Copyright (c) 2019, Jeremy Tuloup
Copyright (c) 2019, Jupyter Development Team
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
110 changes: 110 additions & 0 deletions launcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Start your extension from the launcher

In this example, you will learn how to start your extension from the launcher and how to have optional
dependencies to JupyterLab features.

![Launcher example](preview.png)

> Acknowledgement: This example is copied from Jeremy Tuloup [Python file extension](https://github.com/jtpio/jupyterlab-python-file).
In this example, you will add the ability to create an empty Python file. To do so,
your extension will use two commands defined by the [documents manager](https://github.com/jupyterlab/jupyterlab/blob/master/packages/docmanager-extension/src/index.ts#L47-L75) of JupyterLab:

- `'docmanager:new-untitled'`: Create new untitled document
- `'docmanager:open'`: Open a document

The command will create a new Python file and then open it:

```ts
// src/index.ts#L36-L58

commands.addCommand(CommandIDs.createNew, {
label: args => (args['isPalette'] ? 'New Python File' : 'Python File'),
caption: 'Create a new Python file',
iconClass: args => (args['isPalette'] ? '' : ICON_CLASS),
execute: async args => {
// Get the directory in which the Python file must be created;
// otherwise take the current filebrowser directory
let cwd = args['cwd'] || browserFactory.defaultBrowser.model.path;

// Create a new untitled python file
const model = await commands.execute('docmanager:new-untitled', {
path: cwd,
type: 'file',
ext: 'py'
});

// Open the newly created file with the 'Editor'
return commands.execute('docmanager:open', {
path: model.path,
factory: FACTORY
});
}
});
```

To link that command to the JupyterLab launcher, the `ILauncher` interface needs to be passed to the `activate`
extension function. As that interface is provided by the `@jupyterlab/launcher` package, it needs first to be installed:

```bash
jlpm add @jupyterlab/launcher
```

Then you can use it in the extension by importing it:

```ts
// src/index.ts#L10-L10

import { ILauncher } from '@jupyterlab/launcher';
```

And finally you can request it as extension dependency:

```ts
// src/index.ts#L22-L33

const extension: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-examples/launcher',
autoStart: true,
optional: [ILauncher, IMainMenu, ICommandPalette],
requires: [IFileBrowserFactory],
activate: (
app: JupyterFrontEnd,
browserFactory: IFileBrowserFactory,
launcher: ILauncher | null,
menu: IMainMenu | null,
palette: ICommandPalette | null
) => {
```
In this example, the `ILauncher` interface is requested as optional dependency and not as classical dependency. This allow other application without launcher to be able
to use your extension.
If the application is unable to provide an optional interface, it will take a `null`
value.
Therefore before adding the command to the launcher, you need to check if the `launcher`
variable is not `null`:
```ts
// src/index.ts#L60-L67

// Add the command to the launcher
if (launcher) {
launcher.add({
command: CommandIDs.createNew,
category: 'Other',
rank: 1
});
}
```
## Where to Go Next
This example uses a _command_. This is an essential concept of JupyterLab. To know more about it
have a look at the [command example](../commands/README.md).
As seen in this example too, an user can execute a command from other UI elements than the launcher. To
know more about those other possible, you could look at the following examples:
- Add the command to the [command palette](../command-palette/README.md)
- Add the command to a [menu](../main-menu/README.md)
- Add the command to a [context menu](../context-menu/README.md)
56 changes: 56 additions & 0 deletions launcher/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "@jupyterlab-examples/launcher",
"version": "0.1.0",
"description": "A minimal JupyterLab example using the launcher.",
"keywords": [
"jupyter",
"jupyterlab",
"jupyterlab-extension"
],
"homepage": "https://github.com/my_name/jupyterlab_myextension",
"bugs": {
"url": "https://github.com/my_name/jupyterlab_myextension/issues"
},
"license": "BSD-3-Clause",
"author": "my_name",
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"repository": {
"type": "git",
"url": "https://github.com/my_name/jupyterlab_myextension.git"
},
"scripts": {
"build": "tsc",
"clean": "rimraf lib tsconfig.tsbuildinfo",
"link": "jupyter labextension link . --no-build",
"prepare": "jlpm run clean && jlpm run build",
"tslint": "tslint --fix -c tslint.json --project tsconfig.json \"**/*{.ts,.tsx}\"",
"tslint:check": "tslint -c tslint.json --project tsconfig.json \"**/*{.ts,.tsx}\"",
"watch": "tsc -w"
},
"dependencies": {
"@jupyterlab/application": "^1.0.0",
"@jupyterlab/filebrowser": "^1.2.1",
"@jupyterlab/launcher": "^1.2.1",
"@jupyterlab/mainmenu": "^1.2.1"
},
"devDependencies": {
"rimraf": "^2.6.1",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.0.1",
"tslint-react": "^4.1.0",
"typescript": "~3.5.2"
},
"sideEffects": [
"style/*.css"
],
"jupyterlab": {
"extension": true
}
}
85 changes: 85 additions & 0 deletions launcher/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';

import { ICommandPalette } from '@jupyterlab/apputils';

import { IFileBrowserFactory } from '@jupyterlab/filebrowser';

import { ILauncher } from '@jupyterlab/launcher';

import { IMainMenu } from '@jupyterlab/mainmenu';

const FACTORY = 'Editor';
const ICON_CLASS = 'jp-example-PythonIcon';
const PALETTE_CATEGORY = 'Text Editor';

namespace CommandIDs {
export const createNew = 'launcher:create-new-python-file';
}

const extension: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-examples/launcher',
autoStart: true,
optional: [ILauncher, IMainMenu, ICommandPalette],
requires: [IFileBrowserFactory],
activate: (
app: JupyterFrontEnd,
browserFactory: IFileBrowserFactory,
launcher: ILauncher | null,
menu: IMainMenu | null,
palette: ICommandPalette | null
) => {
const { commands } = app;

commands.addCommand(CommandIDs.createNew, {
label: args => (args['isPalette'] ? 'New Python File' : 'Python File'),
caption: 'Create a new Python file',
iconClass: args => (args['isPalette'] ? '' : ICON_CLASS),
execute: async args => {
// Get the directory in which the Python file must be created;
// otherwise take the current filebrowser directory
let cwd = args['cwd'] || browserFactory.defaultBrowser.model.path;

// Create a new untitled python file
const model = await commands.execute('docmanager:new-untitled', {
path: cwd,
type: 'file',
ext: 'py'
});

// Open the newly created file with the 'Editor'
return commands.execute('docmanager:open', {
path: model.path,
factory: FACTORY
});
}
});

// Add the command to the launcher
if (launcher) {
launcher.add({
command: CommandIDs.createNew,
category: 'Other',
rank: 1
});
}

// Add the command to the palette
if (palette) {
palette.addItem({
command: CommandIDs.createNew,
args: { isPalette: true },
category: PALETTE_CATEGORY
});
}

// Add the command to the menu
if (menu) {
menu.fileMenu.newMenu.addGroup([{ command: CommandIDs.createNew }], 30);
}
}
};

export default extension;
31 changes: 31 additions & 0 deletions launcher/style/Python-logo-notext.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions launcher/style/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.jp-example-PythonIcon {
background-image: url('Python-logo-notext.svg');
}
24 changes: 24 additions & 0 deletions launcher/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"composite": true,
"declaration": true,
"esModuleInterop": true,
"incremental": true,
"jsx": "react",
"module": "esnext",
"moduleResolution": "node",
"noEmitOnError": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"preserveWatchOutput": true,
"resolveJsonModule": true,
"outDir": "lib",
"rootDir": "src",
"strict": true,
"strictNullChecks": false,
"target": "es2017",
"types": []
},
"include": ["src/*"]
}
Loading

0 comments on commit be84853

Please sign in to comment.