気ままなタンス*プログラミングなどのノートブック

プログラミングやRPGツクール、DTM等について、学んだことや備忘録をアウトプットとして残し、情報を必要としている誰かにとって「かゆいところに手が届く」ブログとなることを願いながら記事を書いています。

【intra-mart】Electron+Angularで、IM-FormaDesignerのzipファイルのカスタムスクリプトを読み込み編集できるアプリ(FCS-Editor)を公開しました。

スポンサーリンク

こんにちは。

今回はintra-mart IM-FormaDesignerの補助エディタアプリを作りました。

TL;DR

  • IM-FormaDesignerのカスタムスクリプトを確認・修正する時に、1つ1つ設定をポチポチ開くのが辛かったのでアプリ作った
  • カスタムスクリプトを俯瞰的に編集できるので、自分の作業改善、作業効率化に繋がって嬉しい

目次

おさらい:IM-FormaDesignerとは

IM-FormaDesigner

intra-martのローコード開発機能の一つです。
いわゆるIDEのように 画面部品をドラッグドロップで配置することで、簡単にCRUD画面を作成することができます。 簡易的な画面を作成する場合は、大変重宝します。
僕の場合、自社とユーザの都合で頻繁にデプロイができないため、IM-FormaDesignerを結構重要な場面で活用しています。

Web画面上で入力部品やボタンの配置ができ、「カスタムスクリプト」として、jQueryを含むコードでカスタマイズできます。

f:id:rinne_grid2_1:20211007101304j:plain

概要:作成したアプリ

IM-FormaDesignerのエクスポートデータ(zipファイル)から
カスタムスクリプトのソースコードや説明、条件などを読み込み ツリービューでソースコードの確認や編集ができるエディタです。

IM-FormaDesignerで登録したカスタムスクリプト数が膨大になってくると、
調査時や改修時に一つ一つのWeb画面から定義を開いて 作業するのが辛いので、Electron + Angularの勉強がてら作成してみました。

作成したアプリのリポジトリ

github.com

Before(アプリ作成前)

  • こんな感じで鉛筆マークを一つ一つ開いて、確認しソースを書く必要がある
  • カスタムスクリプト設定画面は行数やシンタックスハイライトなし。
    • このままだと開発・保守しづらいので、現状は各カスタムスクリプトをファイルとして保存し、VSCode等で編集し、この画面に貼り付けていた

f:id:rinne_grid2_1:20211007093038j:plain

After(アプリ利用時)

IM-FormaDesignerのエクスポートデータさえ読み込めば、エディタ画面で編集可能になりました。
編集後は、もちろん新しいForma定義のzipファイルとして保存し、そのままintra-martにインポートすることができます。(カスタムスクリプト以外には影響を与えません)

アプリ画面

f:id:rinne_grid2_1:20211007093431p:plain

操作イメージ

FCS-Editor操作イメージ

アプリのダウンロード・インストール

リリースページ よりダウンロードできます。

  • Windows
    • fcs-editor.Setup.x.x.x.exeを見つけてダウンロードします
  • macOS
    • fcs-editor-x.x.x.dmgを見つけてダウンロードします

アプリ操作方法

sample_appフォルダにサンプルのForma定義を配置してますので、試しに使ってみる場合はこちらをご利用ください。

  1. [Forma定義を開く]ボタンもしくは[ファイル]->[zipファイルを開く]メニュークリックします
  2. IM-FormaDesignerからエクスポートしたzipファイルを選択します
  3. zip内容を元に、アプリにツリーが表示され、各カスタムスクリプトの編集ができます
  4. ツリーをたどり、編集したいカスタムスクリプトを開き、編集を行います
  5. 変更後、[Ctrl+S]もしくは[ファイル]->[zipファイルにエクスポート]メニューをクリックすると、変更内容を反映したzipファイルとして保存することができます
  6. 保存したzipファイルをIM-FormaDesignerでインポートします

Tips

  • [ツール]->[カスタムスクリプトをファイルとしてエクスポート]メニューをクリックすると、zipファイルに含まれる全てのカスタムスクリプト(ボタンイベントやスクリプトアイテム含む)をファイルとして出力できます。
  • 実験的機能として、カスタムスクリプト全体の検索・置換機能がついています
    • 検索: [Ctrl+Shift+F]
    • 置換: [Ctrl+Shift+H]

感想など

ngrxを利用していたのですが、Electron上でReduxの拡張機能やAngularの拡張機能がうまく動かず、ステートやコンポーネントの状態確認に苦労しました。

最初はテストコードを頑張って書いたり、CSSにBEM記法を適用してみたりと、挑戦していたのですが…。

途中から、まずは動くものを完成させることを優先してしまい、案の定テストコードを書かなくなりました。

個人の趣味プロジェクトであるため、これでも問題にはならないのですが、やはりきちんとテストコードを書くことは、スキルとして必要である痛感します。
(ElectronやAngularのホットデプロイ機能があるとはいえ、Electron側のコードを修正すると、プロセス終了してやり直さないと変更が反映されない)
(GUI上でポチポチ動作確認する行為、とても時間がかかって途中、めげそうになった)

なお、今回angular-electronとmonaco-editorを利用したのですが、Electron上で動かす部分で色々ハマりました。 その他、ハマったことがいくつもあったので、書き出してみようと思います。

ハマりポイント

Angular Electron上でmonaco-editorを動かす

  • monaco-editorのimportScriptsでworkerMain.jsをインポートして、MonacoEnvironmentに設定する必要があった
    • (window as any).MonacoEnvironment = の部分
import { Component, AfterViewInit, ViewChild, ElementRef } from "@angular/core";
import * as monaco from "monaco-editor";
@Component({
  selector: "app-main-edit",
  templateUrl: "./main-edit.component.html",
  styleUrls: ["./main-edit.component.scss"],
})
export class MainEditComponent implements AfterViewInit {
  ngAfterViewInit() {
    (window as any).MonacoEnvironment = {
      getWorkerUrl: function (workerId, label) {
        return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
              self.MonacoEnvironment = { baseUrl: '${window.location.origin}/' };
              importScripts('${window.location.origin}/vs/base/worker/workerMain.js');
          `)}`;
      },
    };
    this.initMonaco();
  }
  initMonaco() {
    const editor = monaco.editor.create(document.getElementById("editor"), {
      value: "",
      language: "javascript",
      // theme: "vs-dark",
    });
  }
}
  • angular.jsonに設定追記が必要
    • projects.angular-electron.architect.build.assetsの配列に追記が必要
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "cli": {
    "analytics": false
  },
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "angular-electron": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "tsConfig": "src/tsconfig.app.json",
            "polyfills": "src/polyfills.ts",
            "assets": [
              "src/assets",
              {
                "glob": "**/*",
                "input": "node_modules/monaco-editor/min/vs",
                "output": "./vs"
              }

バイナリ作成後に、Cannot find module 'xxx'が発生

  • ipcRendererで定義した関数やクラスをipcMain側で利用していると発生する
  • もしくは、angular-electronにおいて、app/package.jsonに、モジュールを追記していないと発生する
    • 今回、unzipperやxml2jsをnpmでインストールしており、ルートのpackage.jsonには追記されていたが、app/package.json側にも必要だった

AngularからElectronへのipc通信方法

  • ElectronServiceをDIして利用する
    • electronService.ipcRenderer.onでMainプロセス(Electron側)からのイベントを処理
    • electronService.ipcRenderer.invokeでRendererプロセス(Angular側)からイベントを送信
import { ElectronService } from '../../../core/services';
import { OnInit } from '@angular/core';
@Component({
  selector: 'app-container',
  templateUrl: './container.component.html',
  styleUrls: ['./container.component.scss'],
})
export class ContainerComponent implements OnInit {
  constructor(private electronService: ElectronService) {}
  ngOnInit(): void {
    this.electronService.ipcRenderer.on('ipc_channel_name1', (event, value) => { });
  }

  anyMethod() {
    this.electronService.ipcRenderer
      .invoke('ipc_channel_name2', file.path)
      .then((data) => {})
      .catch((error) => {});
  }

ElectronからAngularへのipc通信方法

  • BrowserWindowのインスタンスメソッドwebContents.send利用する
  • browserWindow.webContents.send("ipc_channel_name1", value)

今後について

感想などあれば、気軽にこのブログのコメントやGithubにissueを上げていただければ嬉しいです。 すべてに返信できるかはわかりません。バグは許してください。

それでは、皆様も良いintra-martライフを!