如何在JupyterLab中获取当前活动笔记本的名称?

5
我正在创建一个 JupyterLab 的服务器端扩展,并且已经搜索了相当长的时间以获取在我的 index.ts 文件中获取当前活动笔记本名称的方法。我找到了这个现有的扩展,使用 ILabShell 获取当前活动标签页名称,并设置浏览器标签页具有相同的名称。(扩展链接) 在我的情况下,我将通过笔记本工具栏上的按钮触发进程,因此活动选项卡始终是笔记本。然而,当我尝试在扩展的 activate 部分中使用代码时,我会得到 "TypeError: labShell.currentChanged is undefined" 错误,其中 labShell 是 ILabShell 的实例。那个扩展不支持 JupyterLab 3.0+,所以我认为这是问题的一部分。但是,ILabShell 的 currentChanged 在这里明确定义:(链接)。我该如何更改才能使其正常工作呢?还有其他方法可以实现我想要做的事吗?我知道像这样的方法可以在笔记本中获取笔记本名称,但那不是我想做的事情。
Windows 10, Node v14.17.0, npm 6.14.13, jlpm 1.21.1, jupyter lab 3.0.14
我正在使用这个示例服务器扩展作为模板:(链接) 获取当前选项卡名称的现有扩展的 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;

1个回答

3

有两种方法可以修复它,一种方法可以改进它。我推荐使用 (2) 和 (3)。

  1. Your order of arguments in activate is wrong. There is no magic matching of argument types to signature function; instead arguments are passed in the order given in requires and then optional. This means that you will receive:

    ...[JupyterFrontEnd, ICommandPalette, ILabShell, ILauncher]
    

    but what you are expecting is:

    ...[JupyterFrontEnd, ICommandPalette, ILauncher, ILabShell]
    

    In other words, optionals are always at the end. There is no static type check so this is a common source of mistakes - just make sure you double check the order next time (or debug/console.log to see what you are getting).

  2. Actually, don't require ILabShell as a token. Use the ILabShell that comes in app.shell instead. This way your extension will be also compatible with other frontends built using JupyterLab components.

    shell = app.shell as ILabShell
    
  3. (optional improvement) install RetroLab as a development-only requirement and use import type (this way it is not a runtime requirement) to ensure compatibility with RetroLab:

    import type { IRetroShell } from '@retrolab/application';
    // ... and then in `activate()`:
    shell = app.shell as ILabShell | IRetroShell
    

    to be clear: not doing so would not make your extension incompatible; what it does is ensures you do not make it incompatible by depending on lab-specific behaviour of the ILabShell in the future.

总体来说,它应该是这个样子:

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
  ) => {
    let shell = app.shell as ILabShell | IRetroShell ;
    shell.currentChanged.connect((_, change) => {
        console.log(change);
        // ...
    });
  }
};

export default extension;

#2和#3修复得非常好,非常感谢! - Trevor Villwock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接