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)?

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

log:

```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)
## 簡化版


1. 準備 icon,並放到 [style](https://github.com/jupyterlab/extension-examples/tree/master/launcher/style)

- [線上轉檔](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)

## iconClass
### 內建
> 定義在:`packages/ui-components/style/deprecated.css`
- `jp-JupyterIcon` 
- packages/ui-components/style/deprecated.css:
`.jp-JupyterIcon {`
- packages/apputils-extension/src/workspacesplugin.ts:
`const ICON_NAME = 'jp-JupyterIcon';`
- `jp-LinkIcon` 
- `jp-AddIcon` 
- `jp-CodeIcon` 
### 自行實作
- `icon-ipynb.png`

- `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.

```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

```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)
> - 操作範例
> 
> - [InfuseAI //primehub-job](https://github.com/InfuseAI/primehub-job/tree/master/jupyterlab_primehub)

```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:

- `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/)


<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);
});
```