---
# System prepended metadata

title: Notebook / Extension / Misc
tags: [Notebook, Elyra, Extension, JupyterLab, Jupyter]

---

Notebook / Extension / Misc
===
> #ipython, jupyter notebook, python notebook, jupyterlab, elyra
###### tags: `Jupyter`
###### tags: `Jupyter`, `JupyterLab`, `Notebook`, `Elyra`, `Extension`

:::success
:bulb: **官方範例**：[[github] jupyterlab / extension-examples](https://github.com/jupyterlab/extension-examples)
- [hello-world](https://github.com/jupyterlab/extension-examples/tree/master/hello-world)
:::

<br>

[TOC]

<br>

<hr>

<br>

# 官方範例
> [[github] jupyterlab / extension-examples](https://github.com/jupyterlab/extension-examples)
> 
> - [hello-world](https://github.com/jupyterlab/extension-examples/tree/master/hello-world)
>   - [Commands](https://github.com/jupyterlab/extension-examples/blob/master/commands)
>     - [Command Palette](https://github.com/jupyterlab/extension-examples/blob/master/command-palette/README.md)
>     - [Main Menu](https://github.com/jupyterlab/extension-examples/blob/master/main-menu/README.md)
>       - [Settings](https://github.com/jupyterlab/extension-examples/blob/master/settings)
>     - [Launcher](https://github.com/jupyterlab/extension-examples/blob/master/launcher/README.md)
>   - [Widgets](https://github.com/jupyterlab/extension-examples/blob/master/widgets)
>     - [Main Widget / Content Header](https://github.com/jupyterlab/extension-examples/tree/master/contentheader)
>     - [Datagrid](https://github.com/jupyterlab/extension-examples/blob/master/datagrid/README.md)
>     - [React components](https://github.com/jupyterlab/extension-examples/blob/master/react-widget/README.md)
>     - [Kernel Messaging](https://github.com/jupyterlab/extension-examples/blob/master/kernel-messaging/README.md)
>       - [Kernel Output](https://github.com/jupyterlab/extension-examples/tree/master/kernel-output)
>     - [Toolbar Button](https://github.com/jupyterlab/extension-examples/blob/master/toolbar-button/README.md)
>   - [signals example](https://github.com/jupyterlab/extension-examples/blob/master/signals)
>
> - More
>   - [Context Menu](https://github.com/jupyterlab/extension-examples/blob/master/context-menu)
>   - [Custom Log Console](https://github.com/jupyterlab/extension-examples/blob/master/custom-log-console)
>     - [Log Messages](https://github.com/jupyterlab/extension-examples/blob/master/log-messages)
>   - [Documents](https://github.com/jupyterlab/extension-examples/blob/master/documents)
>   - [State](https://github.com/jupyterlab/extension-examples/blob/master/state)
>   - [Completer](https://github.com/jupyterlab/extension-examples/blob/master/completer)
>   
> - Backend
>   - [Server Hello World](https://github.com/jupyterlab/extension-examples/blob/master/server-extension)

## [hello-world](https://github.com/jupyterlab/extension-examples/tree/master/hello-world), 2022/02/22 done
- 介紹 cookiecutter
- python 名稱：不能有 `-` (dash)
- 專案架構說明
- 編譯
    - `pip install -e .` 等同包含：
        - `jlpm install`
        - `jlpm build` (`src/*.ts` -> `lib/*.js` -> final code)
- 安裝
    - `jupyter labextension develop . --overwrite`
        單純建立 symbolic link
- 持續編譯
    - `jlpm watch` 包含：
        1. `jupyter labextension develop . --overwrite` 
        2. `jlpm build`
- 開啟 jupyterlab
    - `jupyter lab --watch`
        `--watch` 感覺可有可無

<br>

## [Commands](https://github.com/jupyterlab/extension-examples/blob/master/commands), 2022/02/22 done
- 帶參數
    - 呼叫
        ```typescript
        commands.execute(command, { origin: 'init' })
        ```
    - 接收
        ```typescript
        execute: (args: any) => {
           ...
        },
        ```

<br>

## [Command Palette](https://github.com/jupyterlab/extension-examples/blob/master/command-palette/README.md), 2022/02/22 done

| Object | API |
| ------ | ----|
| ICommandPalette | [addItem({command, arg, category, rank})](https://jupyterlab.github.io/jupyterlab/interfaces/_apputils_src_index_.ipaletteitem.html) |

<br>

<hr>

<br>

# CSS
- 引用範例
    - [jupyterlab/packages/apputils/style/base.css](https://github.com/jupyterlab/jupyterlab/blob/master/packages/apputils/style/base.css)
        ```
        ...
        @import './dialog.css';
        ...
        ```
        
<br>

<hr>

<br>

# CommandPalette
## 如何顯示左側欄的 指令面板(Command Palette)？
![](https://i.imgur.com/AiXB0hr.jpg)

- 啟用左側欄選項：
    1. 進入 Menu: Settings > Command Palette
    2. 將 model 設為 ==**false**==
- 說明：
    JupyterLab 3.0 預設不顯示左側欄；
    可透過快捷鍵 Ctrl + Shift + C 來顯示小視窗

## 範例
![](https://i.imgur.com/HRyZGTf.png)

log:
![](https://i.imgur.com/q6FojZd.png)

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


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

const plugin: JupyterFrontEndPlugin<void> = {
  id: 'my_test1:plugin',
  autoStart: true,
  requires: [ICommandPalette], 
  activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
    console.log('my_test1 is activated!');
    
    app.commands.addCommand('say hello', {
    	label: 'Say Hello',
    	execute: () => {
    		console.log('Hello');
    	}
    });

    palette.addItem({
    	command: 'say hello', 
    	category: 'My Category'
    });
  }
};

export default plugin;
```
- command ID 為字串，可以包含空白字元
- command ID 如果不存在，palette UI 就不會出現該 command 選項
- 取得頁面焦點 (focus)  是透過：
    ```
    // 設成啟用中的頁面
    app.shell.activateById(widget.id)
    ```

<br>

<hr>

<br>

# Launcher
## 官方範例
- ### [jupyterlab / extension-examples / launcher](https://github.com/jupyterlab/extension-examples/tree/master/launcher)
    - [schema/plugin.json](https://github.com/jupyterlab/extension-examples/blob/master/launcher/schema/plugin.json)
    - [style/Python-logo-notext.svg](https://github.com/jupyterlab/extension-examples/tree/master/launcher/style)
    - [src](https://github.com/jupyterlab/extension-examples/tree/master/launcher/src)

## 簡化版
![](https://i.imgur.com/do2yhoK.png)

![](https://i.imgur.com/hCv4t0M.png)

1. 準備 icon，並放到 [style](https://github.com/jupyterlab/extension-examples/tree/master/launcher/style)
    ![](https://i.imgur.com/eqdaOFG.png =20%x)
    - [線上轉檔](https://www.aconvert.com/tw/image/png-to-svg/)：png -> svg (png2svg, png to svg)
2. [`src/svg.d.ts`](https://github.com/jupyterlab/extension-examples/blob/master/launcher/src/svg.d.ts) (沒有變更)
    ```typescript=
    declare module '*.svg' {
      const value: string;
      export default value;
    }
    ```
3. `src/index.ts`
    ```typescript=
    import {
      JupyterFrontEnd,
      JupyterFrontEndPlugin
    } from '@jupyterlab/application';

    import { ILauncher } from '@jupyterlab/launcher';
    // also:
    // $ jlpm add @jupyterlab/launcher

    import { LabIcon } from '@jupyterlab/ui-components';
    import iconStr from '../style/parabricks-logo.svg';

    const PLUGIN_ID = 'launcher:plugin';
    const COMMAND_ID = 'launcher:launch-parabricks';

    /**
     * Initialization data for the launcher extension.
     */
    const plugin: JupyterFrontEndPlugin<void> = {
      id: PLUGIN_ID,
      autoStart: true,
      requires: [ILauncher],
      activate: (app: JupyterFrontEnd, launcher: ILauncher) => {
        console.log('launcher is activated!');

        // Parabicks icon
        const icon = new LabIcon({
          name: 'launcher:parabricks-icon',
          svgstr: iconStr,
        });

        // 向系統註冊指令
        app.commands.addCommand(COMMAND_ID, {
          label: 'Parabricks',
          caption: 'Launch Parabricks',
          icon: icon,
          execute: async (args) => {
            alert('Parabricks was launched.')
          },
        });

        // 在 launcher 頁面長出選項，點了 icon 後執行 command
        // 限定變數只能是 `command` ?
        const command = COMMAND_ID;
        if (launcher) {
          launcher.add({
            command,
            category: 'Nvidia',
            rank: 1,
          });
        }
      }
    };

    export default plugin;
    ```

<br>

<hr>

<br>

# Widget
> [[doc]](https://jupyterlab.github.io/lumino/widgets/classes/widget.html)
> - [IOptions](https://jupyterlab.github.io/lumino/widgets/interfaces/widget.ioptions.html)

:::warning
:warning: **注意事項**
- 建立 widget
    ```javascript
     if (!widget || widget.isDisposed) {
        // create a new widget
     }
    ```
- 更新 widget
    [update()](https://jupyterlab.github.io/lumino/widgets/classes/widget.html#update) -> [onUpdateRequest()](https://jupyterlab.github.io/lumino/widgets/classes/widget.html#onupdaterequest)
    - 首次建立 widget, 
        `onUpdateRequest()` 自動會被呼叫 3 次 (不清楚原因)
:::

## 官方範例
- ### [jupyterlab / extension-examples / widgets](https://github.com/jupyterlab/extension-examples/tree/master/widgets)
    ![](https://i.imgur.com/VALK6JU.png)

## iconClass
### 內建
> 定義在：`packages/ui-components/style/deprecated.css`
- `jp-JupyterIcon` ![](https://i.imgur.com/wHgHKb4.png)
    - packages/ui-components/style/deprecated.css:
        `.jp-JupyterIcon {`
    - packages/apputils-extension/src/workspacesplugin.ts:
        `const ICON_NAME = 'jp-JupyterIcon';`
- `jp-LinkIcon` ![](https://i.imgur.com/G7o6huG.png)
- `jp-AddIcon` ![](https://i.imgur.com/STN5btc.png)
- `jp-CodeIcon` ![](https://i.imgur.com/LqzUYWH.png)

### 自行實作
- `icon-ipynb.png`
    ![](https://i.imgur.com/L9TPolz.png =25%x)
- `base.css`
    ```
    .jp-ipynb-icon {
      background-image: url(icon-ipynb.png);
    }
    ```
- index.ts
    ```
    widget.title.iconClass = 'jp-ipynb-icon';
    ```

## localization
- [Internationalization and Localization](https://jupyterlab.readthedocs.io/en/stable/extension/internationalization.html)

<br>

<hr>

<br>

# Dialog
## 官方原始碼
### apputils/src/dialog.tsx
- [Dialog](https://github.com/jupyterlab/jupyterlab/blob/59c1ee11b0443c407c155b8a2cee72728221e12c/packages/apputils/src/dialog.tsx#L79)
- [showDialog](https://github.com/jupyterlab/jupyterlab/blob/59c1ee11b0443c407c155b8a2cee72728221e12c/packages/apputils/src/dialog.tsx#L25)

### apputils/src/inputdialog.ts 
- [InputPasswordDialog](https://github.com/jupyterlab/jupyterlab/blob/59c1ee11b0443c407c155b8a2cee72728221e12c/packages/apputils/src/inputdialog.ts#L333)

## [User Interface Helpers](https://jupyterlab.readthedocs.io/en/stable/extension/ui_helpers.html)
> 官方提供的簡易 dialog API

### getText
> request a short text.
![](https://i.imgur.com/v7TRPfO.png)

```typescript=
import {
  InputDialog
} from '@jupyterlab/apputils'

  ...
  activate: (app: JupyterFrontEnd) => {
    console.log('JupyterLab extension myextension3 is activated!');
    
    // Request a text
    InputDialog.getText({ title: 'Provide your email' })
      .then(value => {
        console.log('text ' + value.value);
      });
  ...
```

### show hello
![](https://i.imgur.com/VkT2HZA.png)
```typescript=
import {
  showDialog,
  Dialog,
} from '@jupyterlab/apputils'

  ...
  activate: (app: JupyterFrontEnd) => {
    const title = 'tj_tsai';
    const body = 'hello, my name is TJ';
    const buttons = [Dialog.okButton({ label: 'Dismiss' })];
    showDialog({
      title: title,
      body: body,
      buttons: buttons
    });
  }
  ...
```

### show error
```
import { showErrorMessage } from '@jupyterlab/apputils';

  ...
  showErrorMessage('Error', reason);
```
- [doc](https://jupyterlab.readthedocs.io/en/stable/api/modules/apputils.html#showerrormessage)

### custom layout
> 需求來源：[[PrimeHub] Submit a Notebook as a Job](https://docs.primehub.io/docs/zh-tw/notebook-as-job-cht)
> - 操作範例
>   ![](https://i.imgur.com/HJrTilm.png)
> - [InfuseAI //primehub-job](https://github.com/InfuseAI/primehub-job/tree/master/jupyterlab_primehub)

![](https://i.imgur.com/SFZUhO6.png)
```typescript=
import {
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';


import {
  showDialog,
  Dialog,
} from '@jupyterlab/apputils'

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

class FormDialogBase extends Widget implements Dialog.IBodyWidget<string> {

  constructor() {
    super();
    const form = document.createElement('div');
    form.innerHTML = `
    <label>Instance type</label>:
    <br><input type='text' value='vCPUx1'>
    <br>
    <br><label>Image</label>:
    <br><input type='text' value='ml-sklearn'>
    <br>
    <br><label>Job name</label>:
    <br><input type='text' value='penguin-job'>
    `;
    this.node.appendChild(form);
  }
}



/**
 * Initialization data for the myextension3 extension.
 */
const plugin: JupyterFrontEndPlugin<void> = {
  id: 'myextension3:plugin',
  autoStart: true,
  activate: (app: JupyterFrontEnd) => {
    console.log('JupyterLab extension myextension3 is activated!');
    
    const title = 'Submit Notebook as Job';
    const body = new FormDialogBase();
    const buttons = [Dialog.okButton({ label: 'Submit' })];
    showDialog({
      title: title,
      body: body,
      buttons: buttons
    });
  }
};

export default plugin;
```

<br>

<hr>

<br>

# Event
- ### [@lumino/signaling](https://jupyterlab.github.io/lumino/signaling/classes/signal.html)
    > emit, connect

- ### [How to use events in jupyterlab extensions?](https://stackoverflow.com/questions/69657393/)

- ### [jupyterlab-latex / src / index.ts](https://github.com/jupyterlab/jupyterlab-latex/blob/master/src/index.ts)
    ```typescript=
    // Hook up an event listener for when the '.tex' file is saved.
    const onFileChanged = () => {

    };

    texContext.fileChanged.connect(onFileChanged);
    ```

<br>

<hr>

<br>

# Toolbar (未消化)

## 官方範例
- ### [jupyterlab / extension-examples / toolbar-button](https://github.com/jupyterlab/extension-examples/tree/master/toolbar-button)

<br>

<hr>

<br>

# Log console (未消化)

## 官方範例
- ### [jupyterlab / extension-examples / custom-log-console](https://github.com/jupyterlab/extension-examples/tree/master/custom-log-console)

## 其他資料
- ### [How do I create a Jupyter Lab extension, that adds a custom button to the toolbar of a Jupyter Lab notebook?](https://stackoverflow.com/questions/63065310/)

<br>

<hr>

<br>

# Status Bar (未消化)

## 官方範例
- ### [jupyterlab / jupyterlab-statusbar](https://github.com/jupyterlab/jupyterlab-statusbar)

<br>

<hr>

<br>

# Token (未消化)
## [State Database](https://jupyterlab.readthedocs.io/en/stable/extension/extension_points.html#state-database)
```typescript=
const id = 'foo-extension:IFoo';

const IFoo = new Token<IFoo>(id);

interface IFoo {}

class Foo implements IFoo {}

const plugin: JupyterFrontEndPlugin<IFoo> = {
  id,
  autoStart: true,
  requires: [IStateDB],
  provides: IFoo,
  activate: (app: JupyterFrontEnd, state: IStateDB): IFoo => {
    const foo = new Foo();
    const key = `${id}:some-attribute`;

    // Load the saved plugin state and apply it once the app
    // has finished restoring its former layout.
    Promise.all([state.fetch(key), app.restored])
      .then(([saved]) => { /* Update `foo` with `saved`. */ });

    // Fulfill the plugin contract by returning an `IFoo`.
    return foo;
  }
};
```

<br>

<hr>

<br>


# Native editor
## [Letex](https://github.com/jupyterlab/jupyterlab-latex) 
> 有很多概念值得研究 :+1: :100: 
![](https://i.imgur.com/ttSguvu.png)
- `pip install -U jupyterlab_latex`
- `jupyter serverextension enable --sys-prefix jupyterlab_latex`

### [simple.tex](http://www.maths.liv.ac.uk/~rakow/LatexGuide/very_simple.html)

<br>

<hr>

<br>


# Input/Output file
## [martinRenou / jupyterlab-fileopen](https://github.com/martinRenou/jupyterlab-fileopen) 

## 參考資料
- ### [How To Write Text In Jupyter Notebook And IPython To File](https://www.dev2qa.com/how-to-write-text-in-jupyter-notebook-and-ipython-to-file/)
- ### [How to read a local text file?](https://stackoverflow.com/questions/14446447)

<br>

<hr>

<br>

# right sidebar (未消化)
## [jupyterlab-contrib / jupyter-videochat](https://github.com/jupyterlab-contrib/jupyter-videochat/)
![](https://i.imgur.com/KSpEmPq.png)
![](https://i.imgur.com/UFmcyrE.png)


<br>

<hr>

<br>

# 與 OS 互動
- ### [martinRenou / jupyterlab-fileopen](https://github.com/martinRenou/jupyterlab-fileopen)



<br>

<hr>

<br>

# webpack.config.js 範例
- [jupyter-widgets / widget-ts-cookiecutter](https://github.com/jupyter-widgets/widget-ts-cookiecutter/blob/master/%7B%7Bcookiecutter.github_project_name%7D%7D/webpack.config.js)

<br>

<hr>

<br>

## 代碼示例 - 純淨天空 (待研究)
- [TypeScript apputils.ToolbarButton類代碼示例](https://vimsky.com/zh-tw/examples/detail/typescript-ex-%40jupyterlab.apputils-ToolbarButton---class.html)
    - event 處理方式
        ```
        node.addEventListener('contextmenu', (event: MouseEvent) => {
          event.preventDefault();
          const model = widget.modelForClick(event);
          const menu = createContextMenu(model, commands, registry);
          menu.open(event.clientX, event.clientY);
        });
        ```