我正在 JupyterLab 中创建服务器端扩展,并且一直在寻找一种方法来在我的 index.ts 文件中获取当前活动的笔记本名称。我发现 this existing extension 使用 ILabShell
获取当前活动的标签名称并将浏览器标签设置为具有相同的名称。在我的情况下,我将使用笔记本工具栏上的按钮触发该过程,因此活动选项卡将始终是笔记本。但是,当我尝试使用扩展程序的 activate
部分中的代码时,我得到 TypeError: labShell.currentChanged is undefined
,其中 labShell
是 ILabShell
的一个实例。该扩展不支持 JupyterLab 3.0+,所以我相信这是问题的一部分。但是,currentChanged
的 ILabShell
被明确定义为 here。如果我可以更改任何内容以使其正常工作怎么办?有没有另一种方法来完成我想要做的事情?我知道 this 之类的东西可以在笔记本中获取笔记本名称,但这并不是我想要做的。
Windows 10,
节点 v14.17.0,
npm 6.14.13,
jlpm 1.21.1,
jupyter 实验室 3.0.14
我使用这个示例服务器扩展作为模板:https://github.com/jupyterlab/extension-examples/tree/master/server-extension
index.ts 文件从现有扩展中获取当前选项卡名称:
import {
ILabShell,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { Title, Widget } from '@lumino/widgets';
/**
* Initialization data for the jupyterlab-active-as-tab-name extension.
*/
const extension: JupyterFrontEndPlugin<void> = {
id: 'jupyterlab-active-as-tab-name',
autoStart: true,
requires: [ILabShell],
activate: (app: JupyterFrontEnd, labShell: ILabShell) => {
const onTitleChanged = (title: Title<Widget>) => {
console.log('the JupyterLab main application:', title);
document.title = title.label;
};
// Keep the session object on the status item up-to-date.
labShell.currentChanged.connect((_, change) => {
const { oldValue, newValue } = change;
// Clean up after the old value if it exists,
// listen for changes to the title of the activity
if (oldValue) {
oldValue.title.changed.disconnect(onTitleChanged);
}
if (newValue) {
newValue.title.changed.connect(onTitleChanged);
}
});
}
};
export default extension;
我的 index.ts 文件:
import {
ILabShell,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ICommandPalette } from '@jupyterlab/apputils';
import { ILauncher } from '@jupyterlab/launcher';
import { requestAPI } from './handler';
import { ToolbarButton } from '@jupyterlab/apputils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { INotebookModel, NotebookPanel } from '@jupyterlab/notebook';
import { IDisposable } from '@lumino/disposable';
import { Title, Widget } from '@lumino/widgets';
export class ButtonExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
constructor(app: JupyterFrontEnd) {
this.app = app;
}
readonly app: JupyterFrontEnd
createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
// dummy json data to test post requests
const data2 = {"test message" : "message"}
const options = {
method: 'POST',
body: JSON.stringify(data2),
headers: {'Content-Type': 'application/json'
}
};
// Create the toolbar button
let mybutton = new ToolbarButton({
label: 'Measure Energy Usage',
onClick: async () => {
// POST request to Jupyter server
const dataToSend = { file: 'nbtest.ipynb' };
try {
const reply = await requestAPI<any>('hello', {
body: JSON.stringify(dataToSend),
method: 'POST'
});
console.log(reply);
} catch (reason) {
console.error(
`Error on POST /jlab-ext-example/hello ${dataToSend}.\n${reason}`
);
}
// sample POST request to svr.js
fetch('http://localhost:9898/api', options);
}
});
// Add the toolbar button to the notebook toolbar
panel.toolbar.insertItem(10, 'MeasureEnergyUsage', mybutton);
console.log("MeasEnerUsage activated");
// The ToolbarButton class implements `IDisposable`, so the
// button *is* the extension for the purposes of this method.
return mybutton;
}
}
/**
* Initialization data for the server-extension-example extension.
*/
const extension: JupyterFrontEndPlugin<void> = {
id: 'server-extension-example',
autoStart: true,
optional: [ILauncher],
requires: [ICommandPalette, ILabShell],
activate: async (
app: JupyterFrontEnd,
palette: ICommandPalette,
launcher: ILauncher | null,
labShell: ILabShell
) => {
console.log('JupyterLab extension server-extension-example is activated!');
const your_button = new ButtonExtension(app);
app.docRegistry.addWidgetExtension('Notebook', your_button);
// sample GET request to jupyter server
try {
const data = await requestAPI<any>('hello');
console.log(data);
} catch (reason) {
console.error(`Error on GET /jlab-ext-example/hello.\n${reason}`);
}
// get name of active tab
const onTitleChanged = (title: Title<Widget>) => {
console.log('the JupyterLab main application:', title);
document.title = title.label;
};
// Keep the session object on the status item up-to-date.
labShell.currentChanged.connect((_, change) => {
const { oldValue, newValue } = change;
// Clean up after the old value if it exists,
// listen for changes to the title of the activity
if (oldValue) {
oldValue.title.changed.disconnect(onTitleChanged);
}
if (newValue) {
newValue.title.changed.connect(onTitleChanged);
}
});
}
};
export default extension;
答案 0 :(得分:2)
有两种方法可以修复它,一种方法是改进它。我建议使用 (2) 和 (3)。
您在 activate
中的参数顺序是错误的。参数类型与签名函数没有神奇的匹配;而是按照 requires
和 optional
中给出的顺序传递参数。这意味着您将收到:
...[JupyterFrontEnd, ICommandPalette, ILabShell, ILauncher]
但您期望的是:
...[JupyterFrontEnd, ICommandPalette, ILauncher, ILabShell]
换句话说,可选项总是在最后。没有静态类型检查,因此这是一个常见的错误来源 - 只需确保您下次仔细检查订单(或调试/console.log
以查看您得到了什么)。
实际上,不需要 ILabShell
作为令牌。请改用 ILabShell
中的 app.shell
。这样,您的扩展程序也将与使用 JupyterLab 组件构建的其他前端兼容。
shell = app.shell as ILabShell
(可选改进)安装 RetroLab 作为仅开发要求并使用 import type
(这样它不是运行时要求)以确保与 RetroLab
兼容:< /p>
import type { IRetroShell } from '@retrolab/application';
// ... and then in `activate()`:
shell = app.shell as ILabShell | IRetroShell
要明确:不这样做不会使您的扩展不兼容;它的作用是确保您不会通过依赖于未来 ILabShell
的实验室特定行为而使其不兼容。
所以总的来说它看起来像:
import {
ILabShell,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import type { IRetroShell } from '@retrolab/application';
// ...
const extension: JupyterFrontEndPlugin<void> = {
id: 'server-extension-example',
autoStart: true,
optional: [ILauncher],
requires: [ICommandPalette],
activate: async (
app: JupyterFrontEnd,
palette: ICommandPalette,
launcher: ILauncher | null
) => {
shell = app.shell as ILabShell | IRetroShell
shell.currentChanged.connect((_, change) => {
console.log(change);
// ...
});
}
};
export default extension;