Skip to content

Commit

Permalink
Draft of initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
krassowski committed Apr 15, 2024
1 parent 62162d7 commit 34bbc9b
Show file tree
Hide file tree
Showing 9 changed files with 623 additions and 24 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# jupyterlab_new_launcher
# jupyterlab-new-launcher

[![Github Actions Status](https://github.com/nebari-dev/jupyterlab-new-launcher/workflows/Build/badge.svg)](https://github.com/nebari-dev/jupyterlab-new-launcher/actions/workflows/build.yml)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/nebari-dev/jupyterlab-new-launcher/main?urlpath=lab)

A redesigned JupyterLab launcher

## Requirements
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
},
"dependencies": {
"@jupyterlab/application": "^4.0.0",
"@jupyterlab/launcher": "^4.0.0",
"@jupyterlab/settingregistry": "^4.0.0"
},
"devDependencies": {
Expand Down Expand Up @@ -98,7 +99,10 @@
"jupyterlab": {
"extension": true,
"outputDir": "jupyterlab_new_launcher/labextension",
"schemaDir": "schema"
"schemaDir": "schema",
"disabledExtensions": [
"@jupyterlab/launcher-extension"
]
},
"eslintIgnore": [
"node_modules",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["hatchling>=1.5.0", "jupyterlab>=4.0.0,<5", "hatch-nodejs-version>=0
build-backend = "hatchling.build"

[project]
name = "jupyterlab_new_launcher"
name = "jupyterlab-new-launcher"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.8"
Expand Down
183 changes: 167 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,183 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import {
ILabShell,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';

import { ICommandPalette, MainAreaWidget } from '@jupyterlab/apputils';
import { FileBrowserModel, IDefaultFileBrowser } from '@jupyterlab/filebrowser';
import { ILauncher, LauncherModel } from '@jupyterlab/launcher';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ITranslator } from '@jupyterlab/translation';
import { addIcon, launcherIcon } from '@jupyterlab/ui-components';
import { find } from '@lumino/algorithm';
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
import { DockPanel, TabBar, Widget } from '@lumino/widgets';
import { NewLauncher as Launcher } from './launcher';
import { LastUsedDatabase } from './last_used';

/**
* The command IDs used by the launcher plugin.
*/
namespace CommandIDs {
export const create = 'launcher:create';
}

/**
* Initialization data for the jupyterlab-new-launcher extension.
*/
const plugin: JupyterFrontEndPlugin<void> = {
const plugin: JupyterFrontEndPlugin<ILauncher> = {
id: 'jupyterlab-new-launcher:plugin',
description: 'A redesigned JupyterLab launcher',
provides: ILauncher,
autoStart: true,
optional: [ISettingRegistry],
activate: (app: JupyterFrontEnd, settingRegistry: ISettingRegistry | null) => {
console.log('JupyterLab extension jupyterlab-new-launcher is activated!');

if (settingRegistry) {
settingRegistry
.load(plugin.id)
.then(settings => {
console.log('jupyterlab-new-launcher settings loaded:', settings.composite);
})
.catch(reason => {
console.error('Failed to load settings for jupyterlab-new-launcher.', reason);
requires: [ITranslator],
optional: [ILabShell, ICommandPalette, IDefaultFileBrowser, ISettingRegistry],
activate
};

export default plugin;

/**
* Activate the launcher.
*/
function activate(
app: JupyterFrontEnd,
translator: ITranslator,
labShell: ILabShell | null,
palette: ICommandPalette | null,
defaultBrowser: IDefaultFileBrowser | null,
settingRegistry: ISettingRegistry | null
): ILauncher {
const { commands, shell } = app;
const trans = translator.load('jupyterlab-new-launcher');
const model = new LauncherModel();

console.log('JupyterLab extension jupyterlab-new-launcher is activated!');

if (settingRegistry) {
settingRegistry
.load(plugin.id)
.then(settings => {
console.log(
'jupyterlab-new-launcher settings loaded:',
settings.composite
);
})
.catch(reason => {
console.error(
'Failed to load settings for jupyterlab-new-launcher.',
reason
);
});
}

const lastUsedDatabase = new LastUsedDatabase();

commands.addCommand(CommandIDs.create, {
label: trans.__('New Launcher'),
icon: args => (args.toolbar ? addIcon : undefined),
execute: (args: ReadonlyPartialJSONObject) => {
const cwd = (args['cwd'] as string) ?? defaultBrowser?.model.path ?? '';
const id = `launcher-${Private.id++}`;

Check failure on line 83 in src/index.ts

View workflow job for this annotation

GitHub Actions / check_release

Cannot assign to 'id' because it is a read-only property.
const callback = (item: Widget) => {
// If widget is attached to the main area replace the launcher
if (find(shell.widgets('main'), w => w === item)) {
shell.add(item, 'main', { ref: id });
launcher.dispose();
}
};
const launcher = new Launcher({
model,
cwd,
callback,
commands,
translator,
lastUsedDatabase
});

launcher.model = model;
launcher.title.icon = launcherIcon;
launcher.title.label = trans.__('Launcher');

const main = new MainAreaWidget({ content: launcher });

// If there are any other widgets open, remove the launcher close icon.
main.title.closable = !!Array.from(shell.widgets('main')).length;
main.id = id;

shell.add(main, 'main', {
activate: args['activate'] as boolean,
ref: args['ref'] as string
});

if (labShell) {
labShell.layoutModified.connect(() => {
// If there is only a launcher open, remove the close icon.
main.title.closable = Array.from(labShell.widgets('main')).length > 1;
}, main);
}

if (defaultBrowser) {
const onPathChanged = (model: FileBrowserModel) => {
launcher.cwd = model.path;
};
defaultBrowser.model.pathChanged.connect(onPathChanged);
launcher.disposed.connect(() => {
defaultBrowser.model.pathChanged.disconnect(onPathChanged);
});
}

return main;
}
});

if (labShell) {
void Promise.all([app.restored, defaultBrowser?.model.restored]).then(
() => {
function maybeCreate() {
// Create a launcher if there are no open items.
if (labShell!.isEmpty('main')) {
void commands.execute(CommandIDs.create);
}
}
// When layout is modified, create a launcher if there are no open items.
labShell.layoutModified.connect(() => {
maybeCreate();
});
}
);
}
};

export default plugin;
if (palette) {
palette.addItem({
command: CommandIDs.create,
category: trans.__('Launcher')
});
}

if (labShell) {
labShell.addButtonEnabled = true;
labShell.addRequested.connect((sender: DockPanel, arg: TabBar<Widget>) => {
// Get the ref for the current tab of the tabbar which the add button was clicked
const ref =
arg.currentTitle?.owner.id ||
arg.titles[arg.titles.length - 1].owner.id;

return commands.execute(CommandIDs.create, { ref });
});
}

return model;
}

/**
* The namespace for module private data.
*/
namespace Private {
/**
* The incrementing id used for launcher widgets.
*/
export const id = 0;
}
24 changes: 24 additions & 0 deletions src/last_used.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ILauncher } from '@jupyterlab/launcher';

export interface ILastUsedDatabase {
get(item: ILauncher.IItemOptions): Date | null;
recordAsUsedNow(item: ILauncher.IItemOptions): void;
}

export class LastUsedDatabase {
constructor() {
// TODO: use settings registry, or state db, or server to persist this info
this._db = new Map();
}
get(item: ILauncher.IItemOptions) {
const date = this._db.get(this._itemKey(item));
return date ? new Date(date) : null;
}
recordAsUsedNow(item: ILauncher.IItemOptions) {
this._db.set(this._itemKey(item), new Date().toUTCString());
}
private _itemKey(item: ILauncher.IItemOptions): string {
return item.command + '_' + JSON.stringify(item.args);
}
private _db: Map<string, string>;
}
Loading

0 comments on commit 34bbc9b

Please sign in to comment.