Skip to content

Commit

Permalink
Draft implementation of custom kernel dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
krassowski committed Apr 22, 2024
1 parent 09b6f3d commit b1c739b
Show file tree
Hide file tree
Showing 10 changed files with 655 additions and 308 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
"outputDir": "jupyterlab_new_launcher/labextension",
"schemaDir": "schema",
"disabledExtensions": [
"@jupyterlab/launcher-extension"
"@jupyterlab/launcher-extension",
"@jupyterlab/apputils-extension:sessionDialogs"
]
},
"eslintIgnore": [
Expand Down
61 changes: 61 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { IStateDB } from '@jupyterlab/statedb';
import { ReadonlyPartialJSONValue, PromiseDelegate } from '@lumino/coreutils';
import { ILauncher } from '@jupyterlab/launcher';
import {
ILastUsedDatabase,
IFavoritesDatabase,
ILauncherDatabase
} from './types';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';

abstract class Database<V extends ReadonlyPartialJSONValue, K> {
ready: Promise<void>;
Expand Down Expand Up @@ -56,3 +65,55 @@ export abstract class ItemDatabase<
return item.command + '_' + JSON.stringify(item.args);
}
}

export class LastUsedDatabase
extends ItemDatabase<string>
implements ILastUsedDatabase
{
protected _stateDBKey = 'new-launcher:last-used';

get(item: ILauncher.IItemOptions) {
const date = super._get(item);
return date ? new Date(date) : null;
}

async recordAsUsedNow(item: ILauncher.IItemOptions) {
this._set(item, new Date().toUTCString());
}
}

export class FavoritesDatabase
extends ItemDatabase<boolean>
implements IFavoritesDatabase
{
protected _stateDBKey = 'new-launcher:favorites';

get(item: ILauncher.IItemOptions) {
return super._get(item) ?? null;
}

async set(item: ILauncher.IItemOptions, isFavourite: boolean) {
this._set(item, isFavourite);
}
}

/**
* Initialization data for the jupyterlab-new-launcher extension.
*/
export const databasePlugin: JupyterFrontEndPlugin<ILauncherDatabase> = {
id: 'jupyterlab-new-launcher:database',
description: 'A redesigned JupyterLab launcher databases',
provides: ILauncherDatabase,
autoStart: true,
requires: [IStateDB],
activate: (app: JupyterFrontEnd, stateDB: IStateDB) => {
const databaseOptions = {
stateDB,
fetchInterval: 10000
};
return {
lastUsed: new LastUsedDatabase(databaseOptions),
favorites: new FavoritesDatabase(databaseOptions)
};
}
};
228 changes: 228 additions & 0 deletions src/dialogs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import type { CommandRegistry } from '@lumino/commands';
import {
SessionContextDialogs,
ISessionContextDialogs,
ISessionContext,
SessionContext,
Dialog,
ReactWidget
} from '@jupyterlab/apputils';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import {
ITranslator,
nullTranslator,
TranslationBundle
} from '@jupyterlab/translation';

import { KernelTable } from './launcher';
import {
IItem,
ILastUsedDatabase,
IFavoritesDatabase,
ILauncherDatabase,
MAIN_PLUGIN_ID
} from './types';
import { Item } from './item';
import type { ILauncher } from '@jupyterlab/launcher';
import { JSONExt, ReadonlyJSONValue } from '@lumino/coreutils';
import * as React from 'react';

class CustomSessionContextDialogs extends SessionContextDialogs {
constructor(protected options: CustomSessionContextDialogs.IOptions) {
super(options);
const translator = options.translator ?? nullTranslator;
this.trans = translator.load('jupyterlab');
}
/**
* Select a kernel for the session.
*/
async selectKernel(sessionContext: ISessionContext): Promise<void> {
const trans = this.trans;
if (sessionContext.isDisposed) {
return Promise.resolve();
}

// If there is no existing kernel, offer the option
// to keep no kernel.
let label = trans.__('Cancel');
if (sessionContext.hasNoKernel) {
label = sessionContext.kernelDisplayName;
}
const buttons = [
Dialog.cancelButton({
label
}),
Dialog.okButton({
label: trans.__('Select'),
ariaLabel: trans.__('Select Kernel')
})
];

const autoStartDefault = sessionContext.kernelPreference.autoStartDefault;
const hasCheckbox = typeof autoStartDefault === 'boolean';
const settings = await this.options.settingRegistry.load(MAIN_PLUGIN_ID);

const dialog = new Dialog({
title: trans.__('Select Kernel'),
body: new KernelSelector({
data: {
specs: sessionContext.specsManager.specs,
sessions: sessionContext.sessionManager.running(),
preference: sessionContext.kernelPreference
},
commands: this.options.commands,
favoritesDatabase: this.options.database.favorites,
lastUsedDatabase: this.options.database.lastUsed,
settings,
trans
}),
buttons,
checkbox: hasCheckbox
? {
label: trans.__('Always start the preferred kernel'),
caption: trans.__(
'Remember my choice and always start the preferred kernel'
),
checked: autoStartDefault
}
: null
});

const result = await dialog.launch();

if (sessionContext.isDisposed || !result.button.accept) {
return;
}

if (hasCheckbox && result.isChecked !== null) {
sessionContext.kernelPreference = {
...sessionContext.kernelPreference,
autoStartDefault: result.isChecked
};
}

const model = result.value;
if (model === null && !sessionContext.hasNoKernel) {
return sessionContext.shutdown();
}
if (model) {
await sessionContext.changeKernel(model);
}
}
private trans: TranslationBundle;
}

export namespace CustomSessionContextDialogs {
export interface IOptions extends ISessionContext.IDialogsOptions {
database: ILauncherDatabase;
commands: CommandRegistry;
settingRegistry: ISettingRegistry;
}
}

/**
* Initialization data for the jupyterlab-new-launcher session dialogs.
*/
export const sessionDialogsPlugin: JupyterFrontEndPlugin<ISessionContextDialogs> =
{
id: 'jupyterlab-new-launcher:dialogs',
description: 'Session dialogs for redesigned JupyterLab launcher',
provides: ISessionContextDialogs,
autoStart: true,
requires: [ITranslator, ILauncherDatabase, ISettingRegistry],
activate: (
app: JupyterFrontEnd,
translator: ITranslator,
database: ILauncherDatabase,
settingRegistry: ISettingRegistry
) => {
return new CustomSessionContextDialogs({
translator: translator,
database: database,
commands: app.commands,
settingRegistry: settingRegistry
});
}
};

export class KernelSelector extends ReactWidget {
constructor(protected options: KernelSelector.IOptions) {
super();
this.commands = options.commands;
this._lastUsedDatabase = options.lastUsedDatabase;
this._favoritesDatabase = options.favoritesDatabase;
this._settings = options.settings;
this.trans = options.trans;
}
private _lastUsedDatabase: ILastUsedDatabase;
private _favoritesDatabase: IFavoritesDatabase;
trans: TranslationBundle;

renderKernelCommand = (item: ILauncher.IItemOptions): IItem => {
return new Item({
item,
cwd: '',
commands: this.commands,
lastUsedDatabase: this._lastUsedDatabase,
favoritesDatabase: this._favoritesDatabase
});
};

/**
* Render the launcher to virtual DOM nodes.
*/
protected render(): React.ReactElement<any> | null {
const items: ILauncher.IItemOptions[] = [];

for (const [_, spec] of Object.entries(

Check warning on line 181 in src/dialogs.tsx

View workflow job for this annotation

GitHub Actions / build

'_' is assigned a value but never used
this.options.data.specs!.kernelspecs!
)) {
if (!spec) {
continue;
}
const kernelIconUrl =
spec.resources['logo-svg'] || spec.resources['logo-64x64'];
items.push({
command: 'notebook:create-new',
args: {
isLauncher: true,
kernelName: spec.name
},
kernelIconUrl,
metadata: {
kernel: JSONExt.deepCopy(spec.metadata || {}) as ReadonlyJSONValue
},
category: this.trans.__('Notebook')
});
}
const notebookItems = items.map(this.renderKernelCommand);

return (
<KernelTable
trans={this.trans}
commands={this.commands}
items={notebookItems}
settings={this._settings}
query={''}
showSearchBox={true}
/>
);
}
protected commands: CommandRegistry;
private _settings: ISettingRegistry.ISettings;
}

export namespace KernelSelector {
export interface IOptions {
lastUsedDatabase: ILastUsedDatabase;
favoritesDatabase: IFavoritesDatabase;
settings: ISettingRegistry.ISettings;
commands: CommandRegistry;
trans: TranslationBundle;
data: SessionContext.IKernelSearch;
}
}
22 changes: 0 additions & 22 deletions src/favorites.ts

This file was deleted.

Loading

0 comments on commit b1c739b

Please sign in to comment.