Tutorial de desarrollo del complemento JupyterLab

Introducción

JupyterLab es el proyecto de aplicación web de próxima generación de Jupyter. Se utiliza principalmente para el desarrollo interactivo y la visualización de proyectos de ciencia de datos. Lo utilizan principalmente desarrolladores de algoritmos y científicos de datos que escriben Python. Según lo observado, no hay muchas empresas que utilicen esta tecnología.

Un cuaderno integra el código y su salida en un solo documento, y combina visualización, texto narrativo, fórmulas matemáticas y otros medios enriquecidos. De hecho, utiliza código abierto y lo mejora. Puede ejecutar código en él, mostrar la salida codemirrory agregue explicaciones, fórmulas, diagramas y haga que su trabajo sea más transparente, comprensible, repetible y compartible. Puede entenderse simplemente como un editor de código mejorado .

En primer lugar, declaro que soy un desarrollador front-end. El siguiente contenido se basa en las notas recopiladas desde la perspectiva de un desarrollador front-end. Si hay algún problema, por favor deme más consejos. Tomemos como ejemplo el último JupyterLab , que contiene la función de cuaderno. Un cuaderno separado es solo una página de edición separada. JupyterLab tiene administración de recursos y puede personalizar complementos y otras funciones mejoradas. Para los desarrolladores, es una interfaz end y back-end Para proyectos que no están separados, el lenguaje de back-end es Python. Use Python para escribir algunas interfaces para que el front-end las llame a través de ajax. Yo uso la biblioteca axios.

Requisitos previos

  • Los conceptos básicos de programación, los desarrolladores front-end o los desarrolladores de Python comenzarán más rápido
  • Entorno de desarrollo nodejs (la versión no debe ser demasiado baja)
  • Entorno de desarrollo Python (en este artículo se utiliza python3)
  • anaconda (recomendado para principiantes, después de instalar esto, se integran cientos de bibliotecas de Python, incluidas Jupyter Notebooky JupyterLab, este artículo se basa en esto, el paquete de instalación es de solo unos pocos cientos de M, la instalación es relativamente lenta, solo espere tranquilamente hasta que instalación)
  • Texto mecanografiado de la interfaz de usuario (opcional, generalmente escrito usando ts, también puede usar js)

Notas de desarrollo

Es fácil equivocarse al desarrollar complementos. A continuación se detallan algunos problemas comunes.

  1. Al escribir el backend de Python, debe prestar atención al hecho de que las rutas de Windows y Linux son diferentes y es fácil causar problemas.
  2. Además, si desea que surta efecto después de escribir el código Python, es mejor ir al directorio donde se desarrolla el complemento pip install .y luego volver a ejecutar jupyterlab.
  3. Si pip install .falla, cambie el campo package.jsondel proyecto versionpara indicar que la versión del complemento se ha actualizado y luego npm run buildintente el paso 2 nuevamente, básicamente estará bien.
  4. En el entorno de desarrollo de Windows, si elimina el estilo CSS, el entorno de desarrollo no lo eliminará, solo podrá sobrescribirlo.
  5. Si el tiempo de desarrollo es largo, puede haber problemas con el entorno de desarrollo. Puede considerar crear un nuevo entorno conda, reinstalar el paquete o eliminar todos los entornos conda y python y reinstalarlos todos. Las pruebas personales son efectivas.
  6. Lo principal a tener en cuenta sobre la versión de jupyterlab es que la API del complemento de desarrollo puede ser diferente en diferentes versiones y, a veces, es necesario especificar qué versión instalar.
    conda install jupyterlab=3.5
  7. Aunque se admite la forma en que React desarrolla componentes, debe tenerse en cuenta que es solo un desarrollo similar a React, que solo convierte los componentes de React en HTML, y el ciclo de vida relacionado no se ejecutará lógicamente.

conocimiento básico

widget

El widget puede entenderse como la clase principal de todos los componentes. Si desea personalizar una vista o un botón, incluidos algunos botones de vista en el proyecto, debe heredar esta clase y tener algunos métodos públicos. Por ejemplo, puede mostrar, ocultar, etc. devolviendo widget.children()nodos secundarios , que generalmente son subtipos de widget.widget.show()widget.hide()

import {
    
     Widget } from '@lumino/widgets';

Rastreadores de widgets

A menudo, las extensiones necesitan interactuar con documentos y actividades creados por otras extensiones. Por ejemplo, es posible que una extensión desee insertar texto en una celda del cuaderno, establecer un mapa de teclas de acceso directo personalizado o cerrar todos los documentos de un determinado tipo. Estas operaciones suelen ser realizadas por rastreadores de widgets . Los módulos de extensión rastrean sus instancias activas en un WidgetTracker y luego las proporcionan como tokens para que otros módulos de extensión puedan solicitarlos. Por ejemplo, INotebookTracker puede realizar un seguimiento de las operaciones, adiciones y eliminaciones del cuaderno, etc.

Diseño de diseño

JupyterLab tiene dos modos, modo de documento único (único) y modo de documento múltiple (múltiple). Todos los widgets se colocan en 4 áreas, a saber, izquierda (barra lateral izquierda), derecha (barra lateral derecha), espacio de trabajo principal (principal), área inferior, puede anular la ubicación predeterminada de los widgets a través de la configuración basada en el tipo de widgets, configurada en JupyterLab Shell. A continuación se muestra el área donde se encuentran todas las funciones (widgets).

Insertar descripción de la imagen aquíInsertar descripción de la imagen aquí

comandos

Pueden entenderse como comandos, que son algunas funciones encapsuladas que se activan en una escena determinada y luego realizan ciertas operaciones, como hacer clic derecho para realizar una operación o hacer clic en el menú para implementar una operación. Por ejemplo, cree una página de nueva pestaña, cree un nuevo archivo de texto, abra una terminal, etc. Por ejemplo, al usar código app.commands.execute("filebrowser:refresh")para realizar la función de actualizar el explorador de archivos local, todos los comandos se pueden app.commmandsencontrar imprimiendo la lista completa.

Lista de todos los comandos

paleta de comandos

Puede usar el método abreviado de teclado ctrl + shift + c para abrirlo, o la vista de menú => paleta de comandos activa para abrirlo, que es una colección de comandos.

configuración Configuración de interfaz y funciones

La configuración del widget de Jupyterlab, incluidos menús, barras de herramientas, etc., se puede configurar de tres maneras.

Método 1 : Menú->configuración->Editor de configuración avanzada, haga clic en Editor de configuración JSON en la esquina superior derecha, los usuarios pueden modificar json directamente para configurar la adición, modificación, eliminación, etc. de elementos de la interfaz.

Método 2 : el desarrollador define el esquema. En el proyecto del complemento, agregue esquema/plugin.json y defínalo en el archivo json. Puede settingRegistry.schemaobtener la configuración en el código.

Método 3 : definido por el desarrollador, definido en el código.

núcleo (núcleo)

En la arquitectura Jupyter, los kernels son procesos separados iniciados por el servidor que ejecutan código en diferentes lenguajes y entornos de programación. JupyterLab puede conectar cualquier archivo de texto abierto a la consola de código y al kernel, lo que facilita la ejecución interactiva del código en el archivo de texto en el kernel. Obtenga más información .

barras de herramientas (importante)

También hay 3 formas de configurarlo, la primera es relativamente sencilla. Hay lugares donde puede personalizar la barra de herramientas. Los campos de configuración específicos se pueden ver en el menú de configuración.

Cell: Cell Toolbar -> toolbar
CSV Viewer: CSV Viewer -> toolbar
File Browser: File Browser Widget -> toolbar
HTML Viewer: HTML Viewer -> toolbar
Notebook panel: Notebook Panel -> toolbar
Text Editor: Text Editor -> toolbar
TSV Viewer: TSV Viewer -> toolbar

Los siguientes son los datos json ubicados en el complemento /schema/plugin.json. Establecer deshabilitado en verdadero significa que está oculto.

  "jupyter.lab.toolbars": {
    
    
    "Notebook": [
      {
    
     "name": "save", "rank": 10},
      {
    
     "name": "insert", "command": "notebook:insert-cell-below", "rank": 20, "disabled": true},
      {
    
     "name": "cut", "command": "notebook:cut-cell", "rank": 21, "disabled": true },
      {
    
     "name": "copy", "command": "notebook:copy-cell", "rank": 22,"disabled": true  },
      {
    
     "name": "paste", "command": "notebook:paste-cell-below", "rank": 23,"disabled": true  },
      {
    
     "name": "run", "command": "runmenu:run", "rank": 30 },
      {
    
     "name": "interrupt", "command": "kernelmenu:interrupt", "rank": 31 },
      {
    
     "name": "restart", "command": "kernelmenu:restart", "rank": 32 },
      {
    
     "name": "restart-and-run", "command": "notebook:restart-run-all", "rank": 33},
      {
    
     "name": "cellType", "rank": 40 },
      {
    
     "name": "spacer", "type": "spacer", "rank": 100 },
      {
    
     "name": "kernelName", "rank": 1000 },
      {
    
     "name": "kernelStatus", "rank": 1001 }
    ]
  },

Menú contextual del botón derecho

JupyterLab tiene un menú contextual para toda la aplicación disponible como app.contextMenu. Cuando el usuario hace clic derecho, aparece el menú contextual de la aplicación y se completa con los elementos del menú más relevantes para lo que el usuario hizo clic. El sistema de menú contextual determina qué elementos mostrar en función de los selectores CSS. Aparece en el árbol DOM y prueba si el elemento HTML dado coincide con el selector CSS proporcionado por el comando dado. Puede utilizar archivos de configuración o código para agregar elementos de menú personalizados. Dirección del documento oficial
, como el uso de archivos de configuración.

{
    
    
  "jupyter.lab.menus": {
    
    
  "context": [
    {
    
    
      "command": "my-command",
      "selector": ".jp-Notebook",
      "rank": 500
    }
  ]
}

Usar código

app.contextMenu.addItem({
    
    
  command: commandID,
  selector: '.jp-Notebook'
})

Ruta de almacenamiento de datos

Directorio de aplicaciones: JupyterLab almacena la compilación principal de JupyterLab y los datos relacionados, incluidas las extensiones integradas en JupyterLab.

Directorio de configuración de usuario: el directorio donde JupyterLab almacena la configuración a nivel de usuario para las extensiones de JupyterLab.

Directorio de espacios de trabajo: donde JupyterLab almacena espacios de trabajo

jupyter lab pathEl valor de 3 rutas se puede obtener mediante el comando.

Insertar descripción de la imagen aquí
JupyterLab también admite el directorio LabConfig para datos de configuración del subdirectorio LabConfig del directorio de configuración de Jupyter en la jerarquía de rutas de Jupyter.

Además, JupyterLab puede cargar extensiones sindicadas dinámicamente (prediseñadas), es decir, extensiones que agrupan sus dependencias, desde el subdirectorio LabExtensions del directorio de datos de Jupyter.

Entrada del complemento JupyterFrontEnd

import {
    
    
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';

const plugin: JupyterFrontEndPlugin<void> = {
    
    
  id: 'demo:plugin',
  autoStart: true,
  activate: (
    app: JupyterFrontEnd,
  ) => {
    
    
    console.log(app)
    // 禁止系统右键
    app.contextMenu.dispose()
  }
};

export default plugin;

Al imprimir en la consola, puede ver que el objeto en la figura siguiente
Insertar descripción de la imagen aquí
contiene casi todas las cosas de uso común. Por ejemplo, los comandos contienen todos los datos del comando, como guardar archivos, ejecutar archivos, pegar y copiar, etc., y conextMenu contiene todos Los datos para las operaciones de clic derecho están en él. El módulo de shell es muy importante. Las operaciones de interfaz comunes están en él.

Clasificación de desarrollo de complementos

Los complementos de JupyterLab son la unidad básica de extensibilidad en JupyterLab. JupyterLab admite varios tipos de complementos:

Complementos de aplicaciones : los complementos de aplicaciones son los componentes fundamentales de la funcionalidad de JupyterLab. Los complementos de aplicaciones interactúan con JupyterLab y otros complementos al requerir servicios proporcionados por otros complementos y, opcionalmente, brindar sus propios servicios al sistema. Los complementos de la aplicación en el núcleo de JupyterLab incluyen el sistema de menú principal, el explorador de archivos y los componentes del cuaderno, la consola y el editor de archivos.

Complemento de renderizado MIME : el complemento de renderizado MIME es una forma simplificada y restringida de extender JupyterLab para renderizar datos MIME personalizados en cuadernos y archivos. JupyterLab convierte automáticamente estos complementos en complementos de aplicación equivalentes cuando se cargan. Ejemplos de complementos de renderizado MIME en el núcleo de JupyterLab son PDF Viewer, JSON Viewer y Vega Viewer.

Complementos de tema : los complementos de tema brindan una forma de personalizar la apariencia de JupyterLab cambiando los valores de los temas (es decir, valores de variables CSS) y proporcionando fuentes y gráficos adicionales a JupyterLab. JupyterLab viene con complementos de temas claros y oscuros.

Proceso simple de desarrollo de complementos.

  1. crear ambiente

    conda create -n dev --override-channels --strict-channel-priority -c conda-forge -c nodefaults jupyterlab=3 cookiecutter nodejs jupyter-packaging git
    
  2. Activa el entorno

    conda activate dev
    
  3. construir proyecto

    cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts
    
  4. Después de la instalación depende de la instalación frontal.

    pip install -ve . 
    // 运行以下命令来安装初始项目依赖项,并将扩展安装到JupyterLab环境中。
    // 上面的命令将扩展的前端部分复制到JupyterLab中。我们可以在每次进行更改时再次运行此pip安装命令,以将更改复制到JupyterLab中。 
    // 或者直接链接过去
    jupyter labextension develop --overwrite . 
    
  5. ejecutar JupyterLab

    jupyter lab // 注意,在哪个目录运行,就会加载哪个目录的文件
    
    jupyter lab --notebook-dir=D:/myapp // 指定目录启动 
    
    
    jupyter lab --ServerApp.disable_check_xsrf=True --watch // 添加更多参数
    

    Para la interfaz, jlpm es algo similar al hilo integrado en JupyterLab. Después de escribir el código, si desea que surta efecto, ejecútelo inmediatamente npm run build. Por supuesto, puede ejecutar el comando watch.

  6. Publicar complementos desarrollados

  7. También puedes seguir el enlace original directamente.

Una colección de demostraciones de uso común para el desarrollo de complementos.

Función 1: crear un nuevo comando para agregar una página

import {
    
    
  JupyterFrontEnd,
  JupyterFrontEndPlugin,
} from '@jupyterlab/application';
import {
    
     ICommandPalette } from '@jupyterlab/apputils';
import {
    
     Widget } from '@lumino/widgets';

const extension: JupyterFrontEndPlugin<void> = {
    
    
  id: 'widgets-example',
  autoStart: true,
  requires: [ICommandPalette],
  activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
    
    
    const {
    
     commands, shell } = app;
    const command = 'widgets:open-tab';
    // 使用command打开一个tab页
    commands.addCommand(command, {
    
    
      label: 'Open a Tab Widget',
      caption: 'Open the Widgets Example Tab',
      execute: () => {
    
    
        const widget = new ExampleWidget();
        shell.add(widget, 'main');
      },
    });
    palette.addItem({
    
     command, category: 'Extension Examples' });
  },
};

export default extension;

class ExampleWidget extends Widget {
    
    
  constructor() {
    
    
    super();
    this.addClass('jp-example-view');
    this.id = 'simple-widget-example';
    // tab页的标题
    this.title.label = 'Widget Example View';
    this.title.closable = true;
  }
}

Función 2 - Agregar menú

  1. En package.json, agregue el campo esquemaDir para que apunte al directorio de configuración que definimos.
  "jupyterlab": {
    
    
    "extension": true,
    "outputDir": "jupyterlab_examples_main_menu/labextension",
    "schemaDir": "schema"
  }
  1. Cree un nuevo directorio de esquema para el proyecto (si no existe), cree un nuevo plugin.json, agregue el siguiente contenido y use el ID para identificar si desea crear un nuevo menú o usar el menú original. ID debajo del archivo, consulte el sitio web oficial para obtener más detalles jp-mainmenu-file.
{
    
    
  "title": "Main Menu Example",
  "description": "Main Menu Example settings.",
  "jupyter.lab.menus": {
    
    
    "main": [
      {
    
    
        "id": "jp-mainmenu-example-menu", 
        "label": "Main Menu Example",
        "rank": 80,
        "items": [
          {
    
    
            "command": "jlab-examples:main-menu",
            "args": {
    
    
              "origin": "from the menu"
            }
          }
        ]
      }
    ]
  },
  "additionalProperties": false,
  "type": "object"
}


  1. configurar comando
import {
    
    
  JupyterFrontEnd,
  JupyterFrontEndPlugin,
} from '@jupyterlab/application';

import {
    
     ICommandPalette } from '@jupyterlab/apputils';

const extension: JupyterFrontEndPlugin<void> = {
    
    
  id: 'main-menu',
  autoStart: true,
  requires: [ICommandPalette],
  activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
    
    
    const {
    
     commands } = app;

    const command = 'jlab-examples:main-menu';
    commands.addCommand(command, {
    
    
      label: 'Execute jlab-examples:main-menu Command',
      caption: 'Execute jlab-examples:main-menu Command',
      execute: (args: any) => {
    
    
        console.log(
          `jlab-examples:main-menu has been called ${
      
      args['origin']}.`
        );
        window.alert(
          `jlab-examples:main-menu has been called ${
      
      args['origin']}.`
        );
      },
    });

    const category = 'Extension Examples';
    palette.addItem({
    
    
      command,
      category,
      args: {
    
     origin: 'from the palette' },
    });
  },
};

export default extension;


Función 3: agregar pestañas, izquierda, derecha, medio

Insertar descripción de la imagen aquí

import {
    
     Widget} from '@lumino/widgets';

class ExampleWidget extends Widget {
    
    
  constructor() {
    
    
    super();
    this.addClass('jp-example-view');
    this.id = 'simple-widget-example';
    this.title.label = 'halo';
    this.title.closable = true;
  }
}

// 往右边增加内容
const widget = new ExampleWidget();
app.shell.add(widget, 'right');

Característica 4: uso de React

Primero, instale reaccionar usted mismo, de la siguiente manera, y luego podrá usarlo como un widget normal.

import React from "react"
import {
    
     ReactWidget} from '@jupyterlab/apputils';
class MyComponent extends React.Component {
    
    

  click = (): void => {
    
    
    console.log(1111)
  }

  render(): React.ReactNode {
    
    
    return (
      <div>
        <button onClick={
    
    this.click}>hello</button>
      </div>
    )
  }
}

// 简单创建使用
const myWidget: Widget = ReactWidget.create(<MyComponent />);

// 或者是 
class DatasetWidget extends ReactWidget {
    
    

  constructor() {
    
    
    super();
    this.addClass("jp-dataset-widget-view");
    this.id = "widget-dataset";
    this.title.label = ""
    this.title.icon = dataIcon
    this.title.caption = "数据集"
    this.title.closable = false;
  }

  private child: DatasetComponent | undefined;

  // 插件显示,要重新请求数据
  protected onAfterShow() {
    
    
    this.child?.loadData();
  }

  setChild = (v:DatasetComponent) => {
    
    
    this.child = v;
  }

  render() {
    
    
    return (
      <DatasetComponent setChild={
    
    this.setChild} />
    )
  }
}

Función 5: agregar un efecto similar a un complemento, agregar pestañas y contenido a la izquierda


import {
    
     LabIcon } from "@jupyterlab/ui-components";
// 某段svg的代码
const pySvg =""
const pyIcon = new LabIcon({
    
    
  name: "py",
  svgstr: pySvg
});

// 定义这个要添加的组件
class ExampleWidget extends Widget {
    
    
  constructor() {
    
    
    super();
    this.addClass('jp-example-view');
    this.id = 'simple-widget-example';
    this.title.label = '';
    // 定义图标
    this.title.icon = pyIcon;
    this.title.caption = 'Python';
    this.title.closable = true;
    this.node.textContent = "this is tab content"
  }
}

const plugin: JupyterFrontEndPlugin<void> = {
    
    
  id: 'cybercube:plugin',
  autoStart: true,
  activate: (app: JupyterFrontEnd) => {
    
    
    console.log('JupyterLab extension cybercube is activated!');
    const widget: any = new ExampleWidget();
    app.shell.add(widget, 'left');
  }
};

Característica 6: uso de iconos basados ​​en SVG en widgets

Puede encontrar el svg apropiado en iconfont y copiar el código.

import {
    
     LabIcon } from "@jupyterlab/ui-components";

const pySvg =
  `<svg t="1679991149151" 
        class="icon" 
        viewBox="0 0 1024 1024" 
        version="1.1" 
        xmlns="http://www.w3.org/2000/svg"
        xmlns:xlink="http://www.w3.org/1999/xlink" 
        width="64" 
        height="64">
    <path d="M943.58718 401c-15.4-61.8-44.6-108.4-106.8-108.4h-80.2v94.8c0 73.6-62.4 135.6-133.6 135.6H409.38718c-58.4 0-106.8 50-106.8 108.6v203.6c0 58 50.4 92 106.8 108.6 67.6 19.8 132.6 23.4 213.6 0 53.8-15.6 106.8-47 106.8-108.6v-81.4H516.38718v-27.2h320.4c62.2 0 85.2-43.4 106.8-108.4 22.4-67 21.4-131.4 0-217.2zM636.38718 808c22.2 0 40.2 18.2 40.2 40.6 0 22.6-18 40.8-40.2 40.8-22 0-40.2-18.4-40.2-40.8 0.2-22.6 18.2-40.6 40.2-40.6zM399.58718 496.2h213.6c59.4 0 106.8-49 106.8-108.6V183.8c0-58-48.8-101.4-106.8-111.2-71.6-11.8-149.4-11.2-213.6 0.2-90.4 16-106.8 49.4-106.8 111.2v81.4h213.8v27.2h-294c-62.2 0-116.6 37.4-133.6 108.4-19.6 81.4-20.4 132.2 0 217.2 15.2 63.2 51.4 108.4 113.6 108.4H265.98718v-97.6c0-70.6 61-132.8 133.6-132.8z m-13.4-285.2c-22.2 0-40.2-18.2-40.2-40.6 0.2-22.6 18-40.8 40.2-40.8 22 0 40.2 18.4 40.2 40.8s-18 40.6-40.2 40.6z">
    </path>
</svg>`;

function iconFactory(name: string, svgstr: string): LabIcon{
    
    
  return new LabIcon({
    
    
    name,
    svgstr
  });
}

export const pyIcon = iconFactory("py", pySvg)

Función 7: agregue un encabezado personalizado al encabezado sobre el cuaderno, que se encuentra debajo de una fila de botones de operación.

import {
    
     IDisposable, DisposableDelegate } from '@lumino/disposable';

import {
    
     Widget } from '@lumino/widgets';

import {
    
    
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';

import {
    
    
  DocumentRegistry
} from '@jupyterlab/docregistry';

import {
    
     NotebookPanel, INotebookModel } from '@jupyterlab/notebook';

const plugin: JupyterFrontEndPlugin<void> = {
    
    
  activate,
  id: 'my-extension-name:widgetPlugin',
  autoStart: true
};

export class WidgetExtension  implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>{
    
    
  createNew(
    panel: NotebookPanel,
    context: DocumentRegistry.IContext<INotebookModel>
  ): IDisposable {
    
    
    const widget = new Widget({
    
     node: Private.createNode() });
    widget.addClass('jp-myextension-myheader');
    panel.contentHeader.insertWidget(0, widget);
    return new DisposableDelegate(() => {
    
    
      widget.dispose();
    });
  }
}

function activate(app: JupyterFrontEnd): void {
    
    
  app.docRegistry.addWidgetExtension('Notebook', new WidgetExtension());
}
export default plugin;

namespace Private {
    
    
  export function createNode(): HTMLElement {
    
    
    const span = document.createElement('span');
    span.textContent = 'My custom header';
    return span;
  }
}

estilo css

.jp-myextension-myheader {
  min-height: 20px;
  background-color: lightsalmon;
}

Función 8: agregar una función de entrada a la página del Iniciador

import {
    
    JupyterFrontEnd,JupyterFrontEndPlugin,} from '@jupyterlab/application';
import {
    
     MainAreaWidget } from '@jupyterlab/apputils';
import {
    
     ILauncher } from '@jupyterlab/launcher';
import {
    
     reactIcon } from '@jupyterlab/ui-components';
import {
    
     ReactWidget } from "@jupyterlab/apputils";
import React, {
    
     useState } from "react";
namespace CommandIDs {
    
    
  export const create = 'create-react-widget';
}
const CounterComponent = (): JSX.Element => {
    
    
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <p>You clicked {
    
    counter} times!</p>
      <button
        onClick={
    
    (): void => {
    
    
          setCounter(counter + 1);
        }}
      >
        Increment
      </button>
    </div>
  );
};

class CounterWidget extends ReactWidget {
    
    
  constructor() {
    
    
    super();
    this.addClass("jp-ReactWidget");
  }
  render(): JSX.Element {
    
    
    return <CounterComponent />;
  }
}


const extension: JupyterFrontEndPlugin<void> = {
    
    
  id: 'react-widget',
  autoStart: true,
  optional: [ILauncher],
  activate: (app: JupyterFrontEnd, launcher: ILauncher) => {
    
    
    const {
    
     commands } = app;
    const command = CommandIDs.create;
    commands.addCommand(command, {
    
    
      caption: 'Create a new React Widget',
      label: 'React Widget',
      // @ts-ignore
      icon: (args) => (args['isPalette'] ? null : reactIcon),
      execute: () => {
    
    
        const content = new CounterWidget();
        const widget = new MainAreaWidget<CounterWidget>({
    
     content });
        widget.title.label = 'React Widget';
        widget.title.icon = reactIcon;
        app.shell.add(widget, 'main');
      },
    });
    if (launcher) {
    
    
      launcher.add({
    
    
        command,
      });
    }
  },
};
export default extension;

Función 9: activa una determinada función (como la función de la columna izquierda o la columna derecha)

// 通过组件功能的id来启动即可
const plugin: JupyterFrontEndPlugin<void> = {
    
    
  id: 'demo:plugin',
  autoStart: true,
  requires: [ILayoutRestorer],
  activate: (
    app: JupyterFrontEnd,
    restorer: ILayoutRestorer,
  ) => {
    
    
    app.shell.activateById("widget-model-info-libs");
  }
};

Función 10: supervisar los cambios en la ruta de edición, los cambios en el contenido del archivo y los cambios en el archivo actualmente editado

import {
    
     FileBrowserModel, IFileBrowserFactory } from '@jupyterlab/filebrowser';
import {
    
     IChangedArgs } from '@jupyterlab/coreutils';

const plugin: JupyterFrontEndPlugin<void> = {
    
    
  id: 'demo:plugin',
  autoStart: true,
  requires: [
    ILayoutRestorer,
    IFileBrowserFactory
  ],
  activate: (
    app: JupyterFrontEnd,
    restorer: ILayoutRestorer,
    fileBrowserFactory: IFileBrowserFactory,
  ) => {
    
    
   
       // 监听路径改变
    const onPathChanged = (
      model: FileBrowserModel,
      change: IChangedArgs<string>
    ) => {
    
    
      console.log(model)
      console.log(change)
    };
    fileBrowserFactory.defaultBrowser.model.pathChanged.connect(onPathChanged);

    // 监听文件内容改变
    const fileChange = (model: FileBrowserModel, b: any) => {
    
    
      console.log(model,b)
    }
    fileBrowserFactory.defaultBrowser.model.fileChanged.connect(fileChange);

    // 监听当前编辑文件变化
    const handler = (shell: JupyterFrontEnd.IShell, data: {
    
    newValue: string, oldValue: string}) => {
    
    
      console.log(shell,data)
    }
    
    // @ts-ignore
    app.shell.currentPathChanged.connect(handler)
  }
}

Fragmento de código de entrada de extensión

Varias bibliotecas de componentes, métodos de uso básicos y comentarios.

import React from "react"
import {
    
    
  ILayoutRestorer,
  JupyterFrontEnd,
  JupyterFrontEndPlugin,
  ILabStatus 
  // IConnectionLost,
  // IInfo
  // IRouter
} from '@jupyterlab/application';
import {
    
     Widget} from '@lumino/widgets';

// import { requestAPI } from './handler';
import {
    
    
  ICommandPalette,
  ISplashScreen, 
  IThemeManager, 
  IToolbarWidgetRegistry,
  MainAreaWidget,
  WidgetTracker,
  ReactWidget
} from '@jupyterlab/apputils';

import {
    
    IDocumentManager} from "@jupyterlab/docmanager"
import {
    
    IFileBrowserFactory} from "@jupyterlab/filebrowser"
import {
    
    IEditorTracker} from "@jupyterlab/fileeditor"
import {
    
    IHTMLViewerTracker} from "@jupyterlab/htmlviewer"
import {
    
    ILauncher} from "@jupyterlab/launcher"
import {
    
    IMainMenu} from "@jupyterlab/mainmenu"
import {
    
    ISettingEditorTracker} from "@jupyterlab/settingeditor"
import {
    
    ISettingRegistry} from "@jupyterlab/settingregistry"
import {
    
    IStateDB} from "@jupyterlab/statedb"
import {
    
    IStatusBar} from "@jupyterlab/statusbar"
import {
    
    ITerminalTracker} from "@jupyterlab/terminal"
import {
    
    ITooltipManager} from "@jupyterlab/tooltip"
import {
    
    INotebookTools, INotebookTracker, INotebookWidgetFactory} from "@jupyterlab/notebook"

interface APODResponse {
    
    
  copyright: string;
  date: string;
  explanation: string;
  media_type: 'video' | 'image';
  title: string;
  url: string;
}

/**
 * Initialization data for the cybercube extension.
 */
const plugin: JupyterFrontEndPlugin<void> = {
    
    
  id: 'cybercube:plugin',
  autoStart: true,
  requires: [
    ICommandPalette,
    ISplashScreen,
    IThemeManager,
    IToolbarWidgetRegistry,
    IDocumentManager,
    IFileBrowserFactory,
    IEditorTracker,
    IHTMLViewerTracker,
    ILauncher,
    IMainMenu,
    INotebookTools,
    INotebookTracker,
    INotebookWidgetFactory,
    ISettingEditorTracker,
    ISettingRegistry,
    IStateDB,
    IStatusBar,
    ITerminalTracker,
    ITooltipManager,
    ILabStatus
  ],
  optional: [ILayoutRestorer],
  activate
};

export default plugin;

// @ts-ignore
// @ts-ignore
function activate(
  app: JupyterFrontEnd,
  palette: ICommandPalette,
  splashScreen: ISplashScreen,
  themeManager: IThemeManager,
  // 工具栏小部件的注册表,如果要从数据定义(例如存储在设置中)动态生成工具栏,则需要此选项
  toolbarWidgetRegistry: IToolbarWidgetRegistry,
  // 操作文件系统,文件增删
  documentManager: IDocumentManager,
  // 可以自定义文件浏览器
  fileBrowserFactory: IFileBrowserFactory,
  // 如果希望能够循环访问由应用程序创建的文件编辑器并与之交互,请使用此选项
  editorTracker: IEditorTracker,
  // 处理HTML documents的交互
  htmlViewerTracker: IHTMLViewerTracker,
  // 添加东西到launcher
  launcher: ILauncher,
  mainMenu: IMainMenu,
  // 在右侧边栏中notebook工具面板的服务。使用此选项可将您自己的功能添加到面板。
  notebookTools: INotebookTools,
  // 一种用于notebook的部件跟踪器。如果您希望能够循环访问应用程序创建的notebook并与之交互,请使用此选项。
  notebookTracker: INotebookTracker,
  // @ts-ignore 可以自行创建notebook
  notebookWidgetFactory: INotebookWidgetFactory,
  // 处理编辑器设置
  settingEditorTracker:ISettingEditorTracker,
  // jupyterlab 设置系统,可以存储应用的存储设置
  settingRegistry: ISettingRegistry,
  // jupyterlab的状态数据库
  stateDB: IStateDB,
  // 状态栏的操作
  statusBar: IStatusBar,
  // 控制台的操作
  terminalTracker: ITerminalTracker,
  tooltipManager: ITooltipManager,
  labStatus: ILabStatus,
  restorer: ILayoutRestorer | null
) {
    
    
  console.log('JupyterLab extension jupyterlab_apod is activated!');

  // Declare a widget variable
  let widget: any;
  // console.log(app);
  // console.log(splashScreen);
  // console.log(themeManager);
  // splashScreen.show(true)
  // toolbarWidgetRegistry.createWidget
  // console.log(documentManager)


  /**
   * @title 添加普通节点到 文件浏览器 toolbar
   */
  // const t = fileBrowserFactory.defaultBrowser.toolbar;
  // const w: any = new Widget();
  // w.node.textContent = "haha"
  // t.addItem("haha", w);

  /**
   * @title 添加react节点到 文件浏览器 toolbar
   */
  // const t = fileBrowserFactory.defaultBrowser.toolbar;


  // class MyComponent extends React.Component {
    
    

  //   click = (): void => {
    
    
  //     console.log(1111)
  //   }

  //   render(): React.ReactNode {
    
    
  //     return (
  //       <div>
  //         <button onClick={this.click}>hello</button>
  //       </div>
  //     )
  //   }
  // }

  // // @ts-ignore
  // const myWidget: Widget = ReactWidget.create(<MyComponent />);
  // // @ts-ignore
  // t.addItem("ff", myWidget);

  /**
   * @title fileBrowserFactory
   */
  // const fb = fileBrowserFactory.createFileBrowser("custom-browser")
  // console.log(fb)
  // ----- fileBrowserFactory ------
    // 默认文件浏览器的dom
    // console.log(fileBrowserFactory.defaultBrowser.node)
    // 默认文件浏览器上边那三个按钮
    // console.log(fileBrowserFactory.defaultBrowser.toolbar.node)
    // 文件列表的dom
    // console.log(fileBrowserFactory.defaultBrowser.listing.node)
    // 文件列表上边的path的dom
    // console.log(fileBrowserFactory.defaultBrowser.crumbs.node)
  // ----- fileBrowserFactory ------

  // console.log(editorTracker);
  // console.log(htmlViewerTracker);
  // console.log(launcher);
  // console.log(notebookTools);
  // console.log(notebookTracker);
  // console.log(notebookWidgetFactory);
  // console.log(settingEditorTracker);
  // console.log(settingEditorTracker.currentWidget);
  // console.log(stateDB);
  // stateDB.save("myid", "cube").then(r => {
    
    
  //   console.log(r);
  // }).catch(e => {
    
    
  //   console.log(e);
  // })
  // stateDB.fetch("myid").then(r => {
    
    
  //   console.log(r);
  // })

  // console.log(terminalTracker);
  // terminalTracker.forEach(e => {
    
    
  //   console.log(e);
  // })

  // console.log(tooltipManager.invoke());

  // 自定义添加右键菜单,根据css选择器匹配在什么地方出现,甚至可以自定义右键菜单
  // console.log(app.contextMenu)
  // console.log(app)

  // 可以添加widgets到应用中
  // A top area for things like top-level toolbars and information.
  //  A menu area for top-level menus, which is collapsed into the top area in multiple-document mode and put below it in single-document mode.
  //  left and right sidebar areas for collapsible content.
  //  A main work area for user activity.
  //  A down area for information content; like log console, contextual help.
  //  A bottom area for things like status bars.
  //  A header area for custom elements.
  // console.log(app.shell.add());


  // 添加快捷键
  // Accel 就是 ctrl
  // app.commands.addKeyBinding({
    
    
  //   command: commandID,
  //   args: {},
  //   keys: ['Accel T'],
  //   selector: '.jp-Notebook'
  // });

  // launcher.add({
    
    
  //   command: 'apod:open',
  //   category: 'Tutorial',
  //   rank: 0
  // });

  // setTimeout(() => {
    
    
  //   app.commands
  //     .execute('terminal:create-new')
  //     .then((terminal: any) => {
    
    
  //       app.shell.add(terminal, 'right');
  //     });
  // }, 2000)

  const command: string = 'open-picture';

  /**
   * @title 菜单操作,暂没作用
   */
  // console.log(mainMenu.addMenu);
  /// @ts-ignore
  // const menu = new Menu({ "commands": app.commands });
  // menu.addItem({
    
    
  //   command,
  //   args: {},
  // });
  // mainMenu.addMenu(menu as any, { rank: 40 });

  // 成功了,可以跑,在文件菜单下增加子项
  mainMenu.fileMenu.addGroup([ {
    
    command}], 40);

  /**
   * @title 状态栏的操作
   */
  // console.log(statusBar);
  const statusWidget = new Widget();
  labStatus.busySignal.connect(() => {
    
    
    statusWidget.node.textContent = labStatus.isBusy ? 'Busy' : 'Idle';
  });
  statusBar.registerStatusItem('lab-status', {
    
    
    align: 'middle',
    // @ts-ignore
    item: statusWidget
  });


  // console.log(notebookTracker)

  app.commands.addCommand(command, {
    
    
    // command 的标题
    label: 'Astronomy Picture',
    execute: () => {
    
    
      if (!widget || widget.isDisposed) {
    
    
        const content: any = new APODWidget();
        widget = new MainAreaWidget({
    
    content});
        widget.id = 'apod-jupyterlab';
        // 页面tab的标题
        widget.title.label = '太空图片';
        widget.title.closable = true;
      }
      if (!tracker.has(widget)) {
    
    
        // Track the state of the widget for later restoration
        tracker.add(widget);
      }
      if (!widget.isAttached) {
    
    
        // Attach the widget to the main work area if it's not there
        app.shell.add(widget, 'main');
      }
      widget.content.updateAPODImage();

      // Activate the widget
      app.shell.activateById(widget.id);
    }
  });

  // Add the command to the palette.
  palette.addItem({
    
     command, category: 'Tutorial' });

  // Track and restore the widget state
  let tracker: any = new WidgetTracker({
    
    
    namespace: 'apod'
  });
  if (restorer) {
    
    
    restorer.restore(tracker, {
    
    
      command,
      name: () => 'apod'
    });
  }
}



class APODWidget extends Widget {
    
    
  /**
   * Construct a new APOD widget.
   */
  constructor() {
    
    
    super();

    this.addClass('my-apodWidget');

    // Add an image element to the panel
    this.img = document.createElement('img');
    this.node.appendChild(this.img);

    // Add a summary element to the panel
    this.summary = document.createElement('p');
    this.node.appendChild(this.summary);
  }

  /**
   * The image element associated with the widget.
   */
  readonly img: HTMLImageElement;

  /**
   * The summary text element associated with the widget.
   */
  readonly summary: HTMLParagraphElement;

  /**
   * Handle update requests for the widget.
   */
  async updateAPODImage(): Promise<void> {
    
    

    const response = await fetch(`https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=${
      
      this.randomDate()}`);

    if (!response.ok) {
    
    
      const data = await response.json();
      if (data.error) {
    
    
        this.summary.innerText = data.error.message;
      } else {
    
    
        this.summary.innerText = response.statusText;
      }
      return;
    }

    const data = await response.json() as APODResponse;

    if (data.media_type === 'image') {
    
    
      // Populate the image
      this.img.src = data.url;
      this.img.title = data.title;
      this.summary.innerText = data.title;
      if (data.copyright) {
    
    
        this.summary.innerText += ` (Copyright ${
      
      data.copyright})`;
      }
    } else {
    
    
      this.summary.innerText = 'Random APOD fetched was not an image.';
    }
  }

  /**
   * Get a random date string in YYYY-MM-DD format.
   */
  randomDate(): string {
    
    
    const start = new Date(2010, 1, 1);
    const end = new Date();
    const randomDate = new Date(start.getTime() + Math.random()*(end.getTime() - start.getTime()));
    return randomDate.toISOString().slice(0, 10);
  }
}

enlaces relacionados

Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_29334605/article/details/129500252
Recomendado
Clasificación