Move some plugins to the notebook extension

Jeremy Tuloup 5 years ago
parent 95b39d037e
commit ebfeaf35c7

@ -68,7 +68,11 @@ async function main() {
// TODO: formalize the way the set of initial extensions and plugins are specified
let mods = [
// @jupyterlab-classic plugins
require('@jupyterlab-classic/application-extension'),
require('@jupyterlab-classic/notebook-extension'),
// @jupyterlab plugins
require('@jupyterlab/apputils-extension').default.filter(({ id }) =>
[
'@jupyterlab/apputils-extension:palette',

@ -13,6 +13,7 @@
"@jupyterlab-classic/application": "^0.1.0",
"@jupyterlab-classic/application-extension": "^0.1.0",
"@jupyterlab-classic/filebrowser-extension": "^0.1.0",
"@jupyterlab-classic/notebook-extension": "^0.1.0",
"@jupyterlab-classic/ui-components": "^0.1.0",
"@jupyterlab/apputils-extension": "^3.0.0-rc.12",
"@jupyterlab/codemirror-extension": "^3.0.0-rc.12",

@ -1,5 +1,6 @@
@import url('~@jupyterlab-classic/application-extension/style/index.css');
@import url('~@jupyterlab-classic/filebrowser-extension/style/index.css');
@import url('~@jupyterlab-classic/notebook-extension/style/index.css');
@import url('~@jupyterlab-classic/ui-components/style/index.css');
/* TODO: check is the the extension package can be used directly */

@ -36,16 +36,14 @@
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyterlab-classic/application": "0.1.0",
"@jupyterlab-classic/ui-components": "0.1.0",
"@jupyterlab-classic/application": "^0.1.0",
"@jupyterlab-classic/ui-components": "^0.1.0",
"@jupyterlab/application": "^3.0.0-rc.12",
"@jupyterlab/apputils": "^3.0.0-rc.12",
"@jupyterlab/codeeditor": "^3.0.0-rc.12",
"@jupyterlab/codemirror": "^3.0.0-rc.12",
"@jupyterlab/docregistry": "^3.0.0-rc.12",
"@jupyterlab/docmanager": "^3.0.0-rc.12",
"@jupyterlab/mainmenu": "^3.0.0-rc.12",
"@jupyterlab/notebook": "^3.0.0-rc.12",
"@jupyterlab/settingregistry": "^3.0.0-rc.12",
"@jupyterlab/translation": "^3.0.0-rc.12",
"@lumino/widgets": "^1.14.0"

@ -11,19 +11,12 @@ import {
import {
sessionContextDialogs,
ISessionContextDialogs,
ISessionContext,
DOMUtils,
ICommandPalette
} from '@jupyterlab/apputils';
import { PageConfig, Text, Time } from '@jupyterlab/coreutils';
import { IDocumentManager, renameDialog } from '@jupyterlab/docmanager';
import { IMainMenu } from '@jupyterlab/mainmenu';
import { NotebookPanel } from '@jupyterlab/notebook';
import { ITranslator, TranslationManager } from '@jupyterlab/translation';
import {
@ -41,26 +34,6 @@ import { Widget } from '@lumino/widgets';
*/
const NOTEBOOK_FACTORY = 'Notebook';
/**
* The class for kernel status errors.
*/
const KERNEL_STATUS_ERROR_CLASS = 'jp-ClassicKernelStatus-error';
/**
* The class for kernel status warnings.
*/
const KERNEL_STATUS_WARN_CLASS = 'jp-ClassicKernelStatus-warn';
/**
* The class for kernel status infos.
*/
const KERNEL_STATUS_INFO_CLASS = 'jp-ClassicKernelStatus-info';
/**
* The class to fade out the kernel status.
*/
const KERNEL_STATUS_FADE_OUT_CLASS = 'jp-ClassicKernelStatus-fade';
/**
* The command IDs used by the application plugin.
*/
@ -76,168 +49,6 @@ namespace CommandIDs {
export const toggleZen = 'application:toggle-zen';
}
/**
* A plugin for the checkpoint indicator
*/
const checkpoints: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:checkpoints',
autoStart: true,
requires: [IDocumentManager],
optional: [IClassicShell],
activate: (
app: JupyterFrontEnd,
docManager: IDocumentManager,
classicShell: IClassicShell
) => {
const { shell } = app;
const widget = new Widget();
widget.id = DOMUtils.createDomID();
widget.addClass('jp-ClassicCheckpoint');
app.shell.add(widget, 'top', { rank: 100 });
const onChange = async () => {
const current = shell.currentWidget;
if (!current) {
return;
}
const context = docManager.contextForWidget(current);
context?.fileChanged.disconnect(onChange);
context?.fileChanged.connect(onChange);
const checkpoints = await context?.listCheckpoints();
if (!checkpoints) {
return;
}
const checkpoint = checkpoints[checkpoints.length - 1];
widget.node.textContent = `Last Checkpoint: ${Time.formatHuman(
new Date(checkpoint.last_modified)
)}`;
};
if (classicShell) {
classicShell.currentChanged.connect(onChange);
}
// TODO: replace by a Poll
onChange();
setInterval(onChange, 2000);
}
};
/**
* The kernel logo plugin.
*/
const kernelLogo: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:kernel-logo',
autoStart: true,
requires: [IClassicShell],
activate: (app: JupyterFrontEnd, shell: IClassicShell) => {
const { serviceManager } = app;
const baseUrl = PageConfig.getBaseUrl();
let widget: Widget;
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const onChange = async () => {
if (widget) {
widget.dispose();
widget.parent = null;
}
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
await current.sessionContext.ready;
current.sessionContext.kernelChanged.disconnect(onChange);
current.sessionContext.kernelChanged.connect(onChange);
const name = current.sessionContext.session?.kernel?.name ?? '';
const spec = serviceManager.kernelspecs?.specs?.kernelspecs[name];
if (!spec) {
return;
}
let kernelIconUrl = spec.resources['logo-64x64'];
if (!kernelIconUrl) {
return;
}
const index = kernelIconUrl.indexOf('kernelspecs');
kernelIconUrl = baseUrl + kernelIconUrl.slice(index);
const node = document.createElement('div');
const img = document.createElement('img');
img.src = kernelIconUrl;
img.title = spec.display_name;
node.appendChild(img);
widget = new Widget({ node });
widget.addClass('jp-ClassicKernelLogo');
app.shell.add(widget, 'top', { rank: 10_010 });
};
shell.currentChanged.connect(onChange);
}
};
/**
* A plugin to display the kernel status;
*/
const kernelStatus: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:kernel-status',
autoStart: true,
requires: [IClassicShell],
activate: (app: JupyterFrontEnd, shell: IClassicShell) => {
const widget = new Widget();
widget.addClass('jp-ClassicKernelStatus');
app.shell.add(widget, 'menu', { rank: 10_010 });
const removeClasses = () => {
widget.removeClass(KERNEL_STATUS_ERROR_CLASS);
widget.removeClass(KERNEL_STATUS_WARN_CLASS);
widget.removeClass(KERNEL_STATUS_INFO_CLASS);
widget.removeClass(KERNEL_STATUS_FADE_OUT_CLASS);
};
const onStatusChanged = (sessionContext: ISessionContext) => {
const status = sessionContext.kernelDisplayStatus;
let text = `Kernel ${Text.titleCase(status)}`;
removeClasses();
switch (status) {
case 'busy':
case 'idle':
text = '';
widget.addClass(KERNEL_STATUS_FADE_OUT_CLASS);
break;
case 'dead':
case 'terminating':
widget.addClass(KERNEL_STATUS_ERROR_CLASS);
break;
case 'unknown':
widget.addClass(KERNEL_STATUS_WARN_CLASS);
break;
default:
widget.addClass(KERNEL_STATUS_INFO_CLASS);
widget.addClass(KERNEL_STATUS_FADE_OUT_CLASS);
break;
}
widget.node.textContent = text;
};
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const onChange = async () => {
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
const sessionContext = current.sessionContext;
sessionContext.statusChanged.connect(onStatusChanged);
};
shell.currentChanged.connect(onChange);
}
};
/**
* A plugin to dispose the Tabs menu
*/
@ -270,17 +81,6 @@ const logo: JupyterFrontEndPlugin<void> = {
}
};
/**
* The main plugin.
*/
const main: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:main',
autoStart: true,
activate: () => {
console.log(main.id, 'activated');
}
};
/**
* The default paths for a JupyterLab Classic app.
*/
@ -365,56 +165,6 @@ const spacer: JupyterFrontEndPlugin<void> = {
}
};
/**
* A plugin to display the title of the notebook
*/
const title: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:title',
autoStart: true,
requires: [IClassicShell],
optional: [IDocumentManager, IRouter],
activate: (
app: JupyterFrontEnd,
shell: IClassicShell,
docManager: IDocumentManager | null,
router: IRouter | null
) => {
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const widget = new Widget();
widget.id = 'jp-title';
app.shell.add(widget, 'top', { rank: 10 });
shell.currentChanged.connect(async () => {
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
const h = document.createElement('h1');
h.textContent = current.title.label;
widget.node.appendChild(h);
widget.node.style.marginLeft = '10px';
if (docManager) {
widget.node.onclick = async () => {
const result = await renameDialog(
docManager,
current.sessionContext.path
);
if (result) {
h.textContent = result.path;
if (router) {
// TODO: better handle this
router.navigate(`/classic/notebooks/${result.path}`, {
skipRouting: true
});
}
}
};
}
});
}
};
/**
* Plugin to toggle the top header visibility.
*/
@ -555,18 +305,13 @@ const zen: JupyterFrontEndPlugin<void> = {
* Export the plugins as default.
*/
const plugins: JupyterFrontEndPlugin<any>[] = [
checkpoints,
kernelLogo,
kernelStatus,
logo,
main,
noTabsMenu,
paths,
router,
sessionDialogs,
shell,
spacer,
title,
topVisibility,
translator,
tree,

@ -8,77 +8,3 @@
flex-grow: 1;
flex-shrink: 1;
}
.jp-ClassicKernelLogo {
flex: 0 0 auto;
display: flex;
align-items: center;
text-align: center;
margin-right: 8px;
}
.jp-ClassicKernelLogo img {
max-width: 28px;
max-height: 28px;
display: flex;
}
.jp-ClassicKernelStatus {
font-size: 12px;
margin: 0;
font-weight: normal;
color: var(--jp-ui-font-color0);
font-family: var(--jp-ui-font-family);
line-height: var(--jp-private-title-panel-height);
padding-left: 5px;
padding-right: 5px;
}
.jp-ClassicKernelStatus-error {
background-color: var(--jp-error-color0);
}
.jp-ClassicKernelStatus-warn {
background-color: var(--jp-warn-color0);
}
.jp-ClassicKernelStatus-info {
background-color: var(--jp-info-color0);
}
.jp-ClassicKernelStatus-fade {
animation: 0.5s fade-out forwards;
}
@keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#jp-title h1 {
cursor: pointer;
font-size: 18px;
margin: 0;
font-weight: normal;
color: var(--jp-ui-font-color0);
font-family: var(--jp-ui-font-family);
line-height: calc(1.5 * var(--jp-private-title-panel-height));
}
#jp-title h1:hover {
background: var(--jp-layout-color2);
}
.jp-ClassicCheckpoint {
font-size: 14px;
margin-left: 5px;
margin-right: 5px;
font-weight: normal;
color: var(--jp-ui-font-color0);
font-family: var(--jp-ui-font-family);
line-height: calc(1.5 * var(--jp-private-title-panel-height));
}

@ -36,7 +36,7 @@
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyterlab-classic/application": "0.1.0",
"@jupyterlab-classic/application": "^0.1.0",
"@jupyterlab/application": "^3.0.0-rc.13",
"@jupyterlab/apputils": "^3.0.0-rc.13",
"@jupyterlab/coreutils": "^5.0.0-rc.13",

@ -0,0 +1,56 @@
{
"name": "@jupyterlab-classic/notebook-extension",
"version": "0.1.0",
"description": "JupyterLab Classic - Notebook Extension",
"homepage": "https://github.com/jtpio/jupyterlab-classic",
"bugs": {
"url": "https://github.com/jtpio/jupyterlab-classic/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/jtpio/jupyterlab-classic.git"
},
"license": "BSD-3-Clause",
"author": "Project Jupyter",
"sideEffects": [
"style/**/*.css"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"directories": {
"lib": "lib/"
},
"files": [
"lib/*.d.ts",
"lib/*.js.map",
"lib/*.js",
"schema/*.json",
"style/**/*.css"
],
"scripts": {
"build": "tsc -b",
"clean": "rimraf lib && rimraf tsconfig.tsbuildinfo",
"docs": "typedoc src",
"prepublishOnly": "npm run build",
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyterlab-classic/application": "^0.1.0",
"@jupyterlab/application": "^3.0.0-rc.12",
"@jupyterlab/apputils": "^3.0.0-rc.12",
"@jupyterlab/docmanager": "^3.0.0-rc.12",
"@jupyterlab/notebook": "^3.0.0-rc.12",
"@lumino/widgets": "^1.14.0"
},
"devDependencies": {
"rimraf": "~3.0.0",
"typescript": "~4.0.2"
},
"publishConfig": {
"access": "public"
},
"jupyterlab": {
"extension": true
}
}

@ -0,0 +1,298 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import {
IRouter,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ISessionContext, DOMUtils } from '@jupyterlab/apputils';
import { PageConfig, Text, Time } from '@jupyterlab/coreutils';
import { IDocumentManager, renameDialog } from '@jupyterlab/docmanager';
import { NotebookPanel } from '@jupyterlab/notebook';
import {
App,
ClassicShell,
IClassicShell
} from '@jupyterlab-classic/application';
import { Widget } from '@lumino/widgets';
/**
* The class for kernel status errors.
*/
const KERNEL_STATUS_ERROR_CLASS = 'jp-ClassicKernelStatus-error';
/**
* The class for kernel status warnings.
*/
const KERNEL_STATUS_WARN_CLASS = 'jp-ClassicKernelStatus-warn';
/**
* The class for kernel status infos.
*/
const KERNEL_STATUS_INFO_CLASS = 'jp-ClassicKernelStatus-info';
/**
* The class to fade out the kernel status.
*/
const KERNEL_STATUS_FADE_OUT_CLASS = 'jp-ClassicKernelStatus-fade';
/**
* A plugin for the checkpoint indicator
*/
const checkpoints: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:checkpoints',
autoStart: true,
requires: [IDocumentManager],
optional: [IClassicShell],
activate: (
app: JupyterFrontEnd,
docManager: IDocumentManager,
classicShell: IClassicShell
) => {
const { shell } = app;
const widget = new Widget();
widget.id = DOMUtils.createDomID();
widget.addClass('jp-ClassicCheckpoint');
app.shell.add(widget, 'top', { rank: 100 });
const onChange = async () => {
const current = shell.currentWidget;
if (!current) {
return;
}
const context = docManager.contextForWidget(current);
context?.fileChanged.disconnect(onChange);
context?.fileChanged.connect(onChange);
const checkpoints = await context?.listCheckpoints();
if (!checkpoints) {
return;
}
const checkpoint = checkpoints[checkpoints.length - 1];
widget.node.textContent = `Last Checkpoint: ${Time.formatHuman(
new Date(checkpoint.last_modified)
)}`;
};
if (classicShell) {
classicShell.currentChanged.connect(onChange);
}
// TODO: replace by a Poll
onChange();
setInterval(onChange, 2000);
}
};
/**
* The kernel logo plugin.
*/
const kernelLogo: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:kernel-logo',
autoStart: true,
requires: [IClassicShell],
activate: (app: JupyterFrontEnd, shell: IClassicShell) => {
const { serviceManager } = app;
const baseUrl = PageConfig.getBaseUrl();
let widget: Widget;
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const onChange = async () => {
if (widget) {
widget.dispose();
widget.parent = null;
}
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
await current.sessionContext.ready;
current.sessionContext.kernelChanged.disconnect(onChange);
current.sessionContext.kernelChanged.connect(onChange);
const name = current.sessionContext.session?.kernel?.name ?? '';
const spec = serviceManager.kernelspecs?.specs?.kernelspecs[name];
if (!spec) {
return;
}
let kernelIconUrl = spec.resources['logo-64x64'];
if (!kernelIconUrl) {
return;
}
const index = kernelIconUrl.indexOf('kernelspecs');
kernelIconUrl = baseUrl + kernelIconUrl.slice(index);
const node = document.createElement('div');
const img = document.createElement('img');
img.src = kernelIconUrl;
img.title = spec.display_name;
node.appendChild(img);
widget = new Widget({ node });
widget.addClass('jp-ClassicKernelLogo');
app.shell.add(widget, 'top', { rank: 10_010 });
};
shell.currentChanged.connect(onChange);
}
};
/**
* A plugin to display the kernel status;
*/
const kernelStatus: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:kernel-status',
autoStart: true,
requires: [IClassicShell],
activate: (app: JupyterFrontEnd, shell: IClassicShell) => {
const widget = new Widget();
widget.addClass('jp-ClassicKernelStatus');
app.shell.add(widget, 'menu', { rank: 10_010 });
const removeClasses = () => {
widget.removeClass(KERNEL_STATUS_ERROR_CLASS);
widget.removeClass(KERNEL_STATUS_WARN_CLASS);
widget.removeClass(KERNEL_STATUS_INFO_CLASS);
widget.removeClass(KERNEL_STATUS_FADE_OUT_CLASS);
};
const onStatusChanged = (sessionContext: ISessionContext) => {
const status = sessionContext.kernelDisplayStatus;
let text = `Kernel ${Text.titleCase(status)}`;
removeClasses();
switch (status) {
case 'busy':
case 'idle':
text = '';
widget.addClass(KERNEL_STATUS_FADE_OUT_CLASS);
break;
case 'dead':
case 'terminating':
widget.addClass(KERNEL_STATUS_ERROR_CLASS);
break;
case 'unknown':
widget.addClass(KERNEL_STATUS_WARN_CLASS);
break;
default:
widget.addClass(KERNEL_STATUS_INFO_CLASS);
widget.addClass(KERNEL_STATUS_FADE_OUT_CLASS);
break;
}
widget.node.textContent = text;
};
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const onChange = async () => {
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
const sessionContext = current.sessionContext;
sessionContext.statusChanged.connect(onStatusChanged);
};
shell.currentChanged.connect(onChange);
}
};
/**
* The default paths for a JupyterLab Classic app.
*/
const paths: JupyterFrontEndPlugin<JupyterFrontEnd.IPaths> = {
id: '@jupyterlab-classic/application-extension:paths',
activate: (app: JupyterFrontEnd): JupyterFrontEnd.IPaths => {
if (!(app instanceof App)) {
throw new Error(`${paths.id} must be activated in JupyterLab Classic.`);
}
return app.paths;
},
autoStart: true,
provides: JupyterFrontEnd.IPaths
};
/**
* The default JupyterLab Classic application shell.
*/
const shell: JupyterFrontEndPlugin<IClassicShell> = {
id: '@jupyterlab-classic/application-extension:shell',
activate: (app: JupyterFrontEnd) => {
if (!(app.shell instanceof ClassicShell)) {
throw new Error(`${shell.id} did not find a ClassicShell instance.`);
}
return app.shell;
},
autoStart: true,
provides: IClassicShell
};
/**
* A plugin to display the title of the notebook
*/
const title: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:title',
autoStart: true,
requires: [IClassicShell],
optional: [IDocumentManager, IRouter],
activate: (
app: JupyterFrontEnd,
shell: IClassicShell,
docManager: IDocumentManager | null,
router: IRouter | null
) => {
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const widget = new Widget();
widget.id = 'jp-title';
app.shell.add(widget, 'top', { rank: 10 });
shell.currentChanged.connect(async () => {
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
const h = document.createElement('h1');
h.textContent = current.title.label;
widget.node.appendChild(h);
widget.node.style.marginLeft = '10px';
if (docManager) {
widget.node.onclick = async () => {
const result = await renameDialog(
docManager,
current.sessionContext.path
);
if (result) {
h.textContent = result.path;
if (router) {
// TODO: better handle this
router.navigate(`/classic/notebooks/${result.path}`, {
skipRouting: true
});
}
}
};
}
});
}
};
/**
* Export the plugins as default.
*/
const plugins: JupyterFrontEndPlugin<any>[] = [
checkpoints,
kernelLogo,
kernelStatus,
title
];
export default plugins;

@ -0,0 +1,79 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
|
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
.jp-ClassicKernelLogo {
flex: 0 0 auto;
display: flex;
align-items: center;
text-align: center;
margin-right: 8px;
}
.jp-ClassicKernelLogo img {
max-width: 28px;
max-height: 28px;
display: flex;
}
.jp-ClassicKernelStatus {
font-size: 12px;
margin: 0;
font-weight: normal;
color: var(--jp-ui-font-color0);
font-family: var(--jp-ui-font-family);
line-height: var(--jp-private-title-panel-height);
padding-left: 5px;
padding-right: 5px;
}
.jp-ClassicKernelStatus-error {
background-color: var(--jp-error-color0);
}
.jp-ClassicKernelStatus-warn {
background-color: var(--jp-warn-color0);
}
.jp-ClassicKernelStatus-info {
background-color: var(--jp-info-color0);
}
.jp-ClassicKernelStatus-fade {
animation: 0.5s fade-out forwards;
}
@keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#jp-title h1 {
cursor: pointer;
font-size: 18px;
margin: 0;
font-weight: normal;
color: var(--jp-ui-font-color0);
font-family: var(--jp-ui-font-family);
line-height: calc(1.5 * var(--jp-private-title-panel-height));
}
#jp-title h1:hover {
background: var(--jp-layout-color2);
}
.jp-ClassicCheckpoint {
font-size: 14px;
margin-left: 5px;
margin-right: 5px;
font-weight: normal;
color: var(--jp-ui-font-color0);
font-family: var(--jp-ui-font-family);
line-height: calc(1.5 * var(--jp-private-title-panel-height));
}

@ -0,0 +1 @@
@import url('./base.css');

@ -0,0 +1,13 @@
{
"extends": "../../tsconfigbase",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": ["src/**/*"],
"references": [
{
"path": "../application"
}
]
}
Loading…
Cancel
Save