Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update new features #70

Merged
merged 4 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
pip uninstall -y "jupyter_app_launcher" jupyterlab

- name: Upload extension packages
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: extension-artifacts
path: dist/jupyter_app_launcher*
Expand All @@ -63,7 +63,7 @@ jobs:
with:
python-version: '3.9'
architecture: 'x64'
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: extension-artifacts
- name: Install and Test
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/check-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Upload Distributions
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: jupyter_app_launcher-releaser-dist-${{ github.run_number }}
path: .jupyter_releaser_checkout/dist
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
cd lite
jupyter lite build
- name: Upload (dist)
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: jupyter-app-launcher-demo-dist-${{ github.run_number }}
path: ./lite/_output
Expand All @@ -44,7 +44,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2.3.1
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
with:
name: jupyter-app-launcher-demo-dist-${{ github.run_number }}
path: ./dist
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

**jupyter_app_launcher** helps users customize the JupyterLab launcher with a simple YAML file. Users can add custom entries to the launcher to:

- Open a predefined notebook or markdown file.
- Open a predefined notebook with selected widget factory
- Open a markdown file.
- Render a notebook in dashboard mode
- Open a notebook with Voila
- Start a local web server and open the predefined URL.
- Open a remote URL (as widget tab in Jupyter or in new browser window).
- Open terminal and run a predefined command

https://user-images.githubusercontent.com/4451292/191499842-3b3aae7b-dd61-416b-9958-6490c1e220c7.mp4

Expand Down
29 changes: 28 additions & 1 deletion docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,16 @@ This launcher entry will create a new notebook in the current working directory

- title: Notebook example
description: Example of opening a notebook in dashboard mode without Voila
type: notebook
source: ../../samples/sample.ipynb
cwd: ../../samples
type: notebook
args:
widget-type: 'default'
catalog: Notebook catalog

- ``type`` = ``notebook``
- ``source``: Path to the notebook (can be stored anywhere) which will be copied to the current working directory of *JupyterLab*. It can be an absolute path or a relative path to the directory of the configuration file.
- ``args`` (Optional): By default the notebook will be opened with the default notebook view. Users can customize the document widget to open the notebook by setting the ``widget-type`` argument. For example, to open the notebook with the Voila preview widget, set ``widget-type`` to ``Voila Preview``.
- ``cwd``: Unused.

.. figure:: images/notebook.gif
Expand Down Expand Up @@ -309,6 +312,30 @@ This launcher entry will run predefined JupyterLab commands.

The execution of commands will be stopped if a command fails, and the error message will be shown in a dialog.


--------------------------------------
Open JupyterLab terminal.
--------------------------------------

This launcher entry will open JupyterLab terminal and execute the defined command.

.. code-block:: yaml

- title: Terminal example
description: Example of opening JupyterLab terminal
type: terminal
source: 'ls -l'
cwd: '../'
args:
reuse: true
catalog: Config 2

- ``type`` = ``terminal``
- ``source``: the command to be executed at startup
- ``cwd``: The CWD of the terminal. It can be an absolute path or a relative path to the CWD of JupyterLab.
- ``args`` (Optional): By default the terminal session will be created independently, set ``reuse`` to ``true`` to use the same terminal session.


Running subprocesses manager
======================================================

Expand Down
3 changes: 2 additions & 1 deletion jupyter_app_launcher/schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"markdown",
"local-server",
"url",
"jupyterlab-commands"
"jupyterlab-commands",
"terminal"
]
},
"catalog": {
Expand Down
2 changes: 1 addition & 1 deletion jupyter_app_launcher/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def validate_data(data: Dict, cwd: str) -> bool:
else:
if source_config is not None:
data["sourceCode"] = path_to_res(source_config, cwd)
if type_config not in ["url", "local-server"]:
if type_config not in ["url", "local-server", "terminal"]:
data["source"] = create_abs_path(source_config, cwd)
if type_config == "url":
data["source"] = os.path.expandvars(source_config)
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@jupyterlab/notebook": "^4.0.0",
"@jupyterlab/running": "^4.0.0",
"@jupyterlab/services": "^7.0.0",
"@jupyterlab/terminal": "^4.2.5",
"gridstack": "^6.0.1"
},
"devDependencies": {
Expand Down Expand Up @@ -135,6 +136,7 @@
"@typescript-eslint"
],
"rules": {
"no-constant-condition": "off",
"@typescript-eslint/naming-convention": [
"error",
{
Expand Down
13 changes: 12 additions & 1 deletion samples/jp_app_launcher_config_2.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
- title: Notebook example in config 2
description: Example of opening a notebook in dashboard mode without Voila
description: Example of opening a notebook with Voila Preview
source: ./sample.ipynb
args:
widget-type: 'Voila Preview'
cwd: '.'
type: notebook
catalog: Config 2
Expand All @@ -22,3 +24,12 @@
path: sample.ipynb
cwd: '.'
catalog: Config 2

- title: Terminal
description: Example of opening the terminal
source: 'ls -l'
args:
reuse: false
cwd: '../'
type: terminal
catalog: Config 2
2 changes: 1 addition & 1 deletion src/factories/commands/commands_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class CommandsFactory implements IPanelFactory {
config: ILauncherConfiguration,
args: IDict
): Promise<ILauncherApp | void> {
const source = config.source as any as ICommandSchema[];
const source = config.source as ICommandSchema[];
for (const cmd of source) {
try {
await this._commands.execute(cmd.id, cmd.args);
Expand Down
1 change: 1 addition & 0 deletions src/factories/commands/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CommandsFactory } from './commands_factory';
1 change: 1 addition & 0 deletions src/factories/local_server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LocalServerFactory } from './local_server_factory';
1 change: 1 addition & 0 deletions src/factories/markdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MarkdownFactory } from './markdown_factory';
1 change: 1 addition & 0 deletions src/factories/notebook/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NotebookFactory } from './notebook_factory';
57 changes: 30 additions & 27 deletions src/factories/notebook/notebook_factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { JupyterFrontEnd } from '@jupyterlab/application';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
import { NotebookPanel } from '@jupyterlab/notebook';
import { Contents } from '@jupyterlab/services';

import { ILauncherConfiguration } from '../../schema';
import { IDict, IPanelFactory } from '../../token';
Expand All @@ -11,42 +12,44 @@ export class NotebookFactory implements IPanelFactory {
if (!config.sourceCode) {
return;
}
const cwd = args['cwd'];
const app = this.options.app;
const model: Contents.IModel = await app.commands.execute(
'docmanager:new-untitled',
{
path: cwd,
type: 'notebook',
ext: '.ipynb'
}
);
const doc: NotebookPanel = await app.commands.execute('docmanager:open', {
path: model.path
});
await doc.context.ready;
doc.context.model.fromString(config.sourceCode);
// doc.context.model.initialize();
let renamed = false;
const { documentManager, fileBrowser } = this.options;

let found = false;
let index = 0;
while (!renamed) {
try {
await doc.context.rename(
`${config.title}${index === 0 ? '' : '-' + index}.ipynb`
);
renamed = true;
} catch {
let newName = config.title;
const allFiles = new Set([...fileBrowser.model.items()].map(it => it.name));
while (!found) {
newName = `${config.title}${index === 0 ? '' : '-' + index}.ipynb`;
if (!allFiles.has(newName)) {
found = true;
} else {
index += 1;
}
}
const fileBlob = new File([config.sourceCode], newName);
await fileBrowser.model.upload(fileBlob);
const configArgs: IDict = config.args ?? {};
const factory = configArgs['widget-type'] ?? 'default';
const doc = await documentManager.openOrReveal(newName, factory);
if (!doc) {
console.error(`Failed to create widget with ${factory} factory`);
return;
}
await doc.context.ready;
doc.context.model.fromString(config.sourceCode);

await doc.context.save();
await doc.sessionContext.ready;
doc.content.activeCellIndex = 1;
if (doc instanceof NotebookPanel) {
await doc.sessionContext.ready;
doc.content.activeCellIndex = 1;
}
}
}

export namespace NotebookFactory {
export interface IOptions {
app: JupyterFrontEnd;
documentManager: IDocumentManager;
fileBrowser: IDefaultFileBrowser;
}
}
1 change: 1 addition & 0 deletions src/factories/notebook_grid/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NotebookGridFactory } from './notebook_grid_factory';
1 change: 1 addition & 0 deletions src/factories/notebook_voila/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NotebookVoilaFactory } from './notebook_voila_factory';
1 change: 1 addition & 0 deletions src/factories/terminal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TerminalFactory } from './terminal_factory';
65 changes: 65 additions & 0 deletions src/factories/terminal/terminal_factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { JupyterFrontEnd } from '@jupyterlab/application';
import { IThemeManager } from '@jupyterlab/apputils';
import { TerminalAPI } from '@jupyterlab/services';
import { Terminal } from '@jupyterlab/terminal';

import { ILauncherConfiguration } from '../../schema';
import { IDict, ILauncherApp, IPanelFactory } from '../../token';

export class TerminalFactory implements IPanelFactory<Terminal> {
constructor(private options: TerminalFactory.IOptions) {}
async create(
config: ILauncherConfiguration,
args: IDict
): Promise<ILauncherApp<Terminal>> {
const cmd = config.source as string;
const name = config.title;
const reuse = (config.args as IDict)?.reuse;
const cwd = config.cwd;
const { themeManager, app } = this.options;
const manager = app.serviceManager.terminals;
const models = await TerminalAPI.listRunning();
let session;
const modelNames = models.map(d => d.name);
if (reuse === true) {
if (modelNames.includes(name)) {
session = manager.connectTo({ model: { name } });
} else {
session = await manager.startNew({ name, cwd });
}
} else {
let idx = 1;
let newName = '';
while (true) {
newName = `${name}${idx}`;
if (modelNames.includes(newName)) {
idx += 1;
} else {
break;
}
}
session = await manager.startNew({ name: newName, cwd });
}

const terminal = new Terminal(session, {
initialCommand: cmd,
theme: 'inherit'
});
terminal.title.closable = true;
const themeCb = () => {
terminal.setOption('theme', 'inherit');
};
themeManager?.themeChanged.connect(themeCb);
terminal.disposed.connect(() => {
themeManager?.themeChanged.disconnect(themeCb);
});
return { panel: terminal };
}
}

export namespace TerminalFactory {
export interface IOptions {
app: JupyterFrontEnd;
themeManager?: IThemeManager;
}
}
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ async function activate(
const appName = PageConfig.getOption('appName');
const widgetTracker = new AppTracker({ namespace }, appName);
const launcherData = await fetchLauncherData(appName);

function createCommand(config: ILauncherConfiguration, idx: number): void {
let icon: LabIcon;
if (config.icon) {
Expand Down
Loading
Loading