litegraph.js のコンテキストメニューで日本語を使う

[litegraph.js](https://github.com/jagenjo/litegraph.js)というUnreal BlueprintsのようなGUIが構築できるライブラリがあります。デモなどはコンテキストメニューが全て英語になっているので、日本語で利用していくにはどうしたらよいかちょっと調べてみました。まだ色々と調査の足りないところもあるので、誤りがあるかもしれません。

## 環境

このような環境で動作確認しています。

```json:package.json
"dependencies": {
  "@testing-library/jest-dom": "^4.2.4",
  "@testing-library/react": "^9.3.2",
  "@testing-library/user-event": "^7.1.2",
  "@types/jest": "^24.0.0",
  "@types/node": "^12.0.0",
  "@types/react": "^16.9.0",
  "@types/react-dom": "^16.9.0",
  "litegraph.js": "^0.7.2",
  "react": "^16.12.0",
  "react-dom": "^16.12.0",
  "react-scripts": "3.3.0",
  "typescript": "~3.7.2"
},
```

## litegraph.jsを起動する

litegraph.jsをどのように動作させるかは、githubのreadmeにも書かれているので、そちらを参考に以下の様なコンポーネントを作成します。

```typescript:WorkSpace.tsx
import React, {Component} from "react";
import {IContextMenuItem, LGraph, LGraphCanvas, LiteGraph} from "litegraph.js";
import 'litegraph.js/css/litegraph.css'
import MyNode from "../Nodes/MyNode";

const style = {
  border: '1px solid',
};

/**
 * コンテキストメニューの1要素
 */
class AddNodeMenuItem implements IContextMenuItem {
  content: string = "ノードの追加";
  has_submenu = true;
  callback = LGraphCanvas.onMenuAdd;
}

class WorkSpace extends Component {
  componentDidMount(): void {
    let graph = new LGraph();

    let canvas = new LGraphCanvas("#mycanvas", graph);
    let addNodeMenuItem = new AddNodeMenuItem();
    // デフォルトのコンテキストメニューを上書きする
    canvas.getMenuOptions = () => {
      return [addNodeMenuItem]
    };
    canvas.allow_searchbox = false;

    // デフォルトの登録済みノードをクリア
    LiteGraph.registered_node_types = {};
    LiteGraph.searchbox_extras = {};
    LiteGraph.registerNodeType("自作ノード/アイテム", MyNode);

    graph.start()
  }

  render() {
    return (
      <canvas id='mycanvas' width='1024' height='720' style={style}/>
    )
  }
}

export default WorkSpace;
```

DOMにアクセスできるようになってからでないと

```typescript:WorkSpace.tsx
let canvas = new LGraphCanvas("#mycanvas", graph);
```

が失敗するので、componentDidMount内で起動します。いくつか、readmeには記載のない処理を追加しているのでそれぞれ解説します。

```typescript:WorkSpace.tsx
/**
* コンテキストメニューの1要素
*/
class AddNodeMenuItem implements IContextMenuItem {
  content: string = "ノードの追加"; // メニューに表示される文字列
  has_submenu = true; // サブメニューを持つかどうか
  callback = LGraphCanvas.onMenuAdd; // クリックされたときの処理、この例では元々備わっているコールバックを呼び出すようにしています。
}

class WorkSpace extends Component {
  componentDidMount(): void {
    // 省略----------------------
    let addNodeMenuItem = new AddNodeMenuItem();
    // デフォルトのコンテキストメニューを上書きする
    canvas.getMenuOptions = () => {
      return [addNodeMenuItem]
    };
    // ダブルクリックで起動するサーチボックスを無効化
    canvas.allow_searchbox = false;
    // 省略----------------------
  }
}
```

右クリックで開くコンテキストメニューには、標準で様々なノードが登録されていますが、日本語で使用するにあたりそれらを一旦亡き者にします。IContextMenuItemを実装したクラスの配列を返す関数をgetMenuOptionsに登録してやります。これで、デフォルトのコンテキストメニューが上書きされ、自分のメニューアイテムが表示されるようになります。

## 自作のノードを登録する

このままだとコンテキストメニューから「ノードの追加」を選択してもなにも起こりません。READMEに従い、自前のノードをグラフに登録してやります。まずは、自前のノードを作ります。

```typescript:MyNode.ts
import {LGraphNode} from "litegraph.js";

class MyNode extends LGraphNode {
  constructor() {
    super("俺のノード!");
    this.addInput("入力", "number");
    this.addOutput("出力", "number");
  }
  onExecute(): void {
    let a = this.getInputData(0);
    if (a === undefined) {
      a = 0;
    }
    this.setOutputData(0, ++a);
  }
}

MyNode.title = "自作したノード"; // これがメニューに表示される文字列

export default MyNode;
```

LGraphNodeクラスを継承したクラスを作成することで、ノードが自作できます。この例では入力1つ、出力1つ。出力時に入力された値に1を加算した値を出すという特に意味のないノードになっています。コメントにもありますがクラス定義の外で

```typescript
MyNode.title = "自作したノード"; // これがメニューに表示される文字列
```

としてやることで、メニューに表示される項目が日本語になります。titleプロパティを設定しないとクラス名がそのまま表示されるようです。また、WorkSpace.tsxに戻ります。

```typescript:WorkSpace.tsx
// デフォルトの登録済みノードをクリア
LiteGraph.registered_node_types = {};
LiteGraph.searchbox_extras = {};
LiteGraph.registerNodeType("自作ノード/アイテム", MyNode);
```

この処理が自作ノードの登録を行っています。最初に登録済みのノードをクリアします。サーチボックスを使用しないのであれば特に必要ないと思われますが、サーチボックスの登録内容もクリアします。その後registerNodeTypeでノードを登録します。第一引数に指定した文字列の/より左側がコンテキストメニューに表示されるようです。

## まとめ

これで、日本語でコンテキストメニューを構築できるようになりました。後はデモのコードを参考にしながら機能を実装していくだけです。