週間アラーム(早速のTauriの習作アプリケーション)

今更の話だけど、コロナ禍でリモートワークなど自分も含めて自宅で活動することが多くなり、自宅などでも適切なタイムスケジュールでメリハリを付ける必要が増えてきたと思います。

そのためには会社や学校と同じように指定した時間割でアラーム(チャイム)を鳴らす設備があると便利かと思い、既存のアプリケーションを探して使用しておりました(スマホ/タブレット用アプリ)。

使用したアプリはスケジューリングした指定時刻に好きな音源を鳴らすことが出来て便利だったが、カレンダーとの関連が無く、日祝日など休みの日にもチャイムが鳴ってしまうのでその度にOFFにするのが面倒でした。

ならば作ってみようネタにしようということで、構想から(・・・というのが10月くらいのお話)。

  1. 平日<休日<曜日個別<祝祭日<個別指定日 の優先度でスケジューリングしたアラームタイムテーブルで運用
  2. アプリケーションの見た目は時計表示機能をもつGUIでスケジューリング等の設定を行う
  3. デフォルト音源(フリーの音源素材)のほか任意のMP3音源をアラームとして使用可能
  4. ターゲット機器はPC。ただしアラーム用に占有出来るよう安価なもの

4のターゲット機器は、安価なものとしてRasberryPiにUSBスピーカーを接続した構成とした(今となってはRaspberryPi自体が入手困難な貴重なデバイスですが・・・w)

ターゲットはLinuxだけど、GUIなどデバッグはMacOSでやりたいのでマルチプラットフォームGUIのTauriが最適ということで、アプリケーションフレームワークとしてTauriを採用という、まさに取って付けたような結論ありきの流れ。。

TauriであればフロントエンドはWebと同等なので3の音源再生も造作も無しと。

で、一通りの機能を実装して完成したアプリケーションが左のレポジトリ。MacOSでの実行画面が下記になります。折角Bootstrapを使用したフロントエンドなのにこのデザインセンスよwww。とりあえずは実用的には動けばいいや的なものになっています。

スケジュール管理画面
タイムテーブル編集状態

アラームスケジュールの設定は左記のように、デフォルトで平日<休日といった優先度の低い日程が定められており、ユーザーはスケジュールを特に変更したい日を個別に追加する。

それぞれのスジュール日程に対してアラームセットと呼ばれるタイムテーブルを設定する。アラームセットは個別データとなっているので、別のスケジュール日程に同じアラームセットを適用することが可能(左下画面)。

画面は省略するが、祝祭について、2023年まではデフォルト設定してあるが、それ以降はユーザーで追加する(祝祭日でーたをCSVファイルで一括登録も可能)。

しかし、さすがに工夫無しで使用するとUIがWEBアプリのそのまんまになってしまいますね。もう少しデスクトップアプリっぽくしたかったのですが。。。

あと、マルチプラットフォームGUIで苦労した所として、Linux環境だけ音源再生出来ない問題がありました。

もともとWEBアプリでは、ページロード後にユーザー操作無しには音声再生出来ないような仕組みになっている(広告アプリなどが勝手に大音量で鳴らし始めたら大変なので)ので、同じエンジンを使用しているTauriでも初期には同様の問題があったらしいが、デスクトップアプリでそれでは不便だろうとのことで対策されているはず?なので、本家GithubのDiscussionに質問を投げているが、イマイチ反応は無いですねーΣ(゚д゚lll)

一応自前で解決しているが、何か原因が良く分からない解決方法でした(笑)

これをキッカケに本家のGithubに爪痕でも残せたら、、、と思ってたけど。うーん高い壁なのかなw
(機械翻訳のいい加減な英文投稿でうまく伝わらなかったのかもwww)

Tauriアプリケーション開発メモ(Webpack+Typescript+React)

いままで覚えるのが面倒とかいう理由でフロントエンドのフレームワークを使用せずに素のTypescriptでゴリゴリ書いていたが、そろそろ限界というかネタ不足と言うことで、TauriアプリケーションにおいてReactをフロント側のフレームワークとして使用することにしました。

ということで、Typescript+Reactをフロントエンドに使用したTauriアプリケーション開発準備メモ。

以下のような手順でアプリケーションを作成します。

  1. プロジェクトディレクトリの作成
  2. フロントエンドの準備
  3. Tauri (Rust)プロジェクトの作成
  4. コンパイル

プロジェクトディレクトリの作成

アプリケーション名に準ずるディレクトリを作成して、移動します。

フロントエンドの準備

npmの初期化を行い、Webpack、Typescript、 Sass、Reactをインストールします

$ npm init -y
$ npm install -D webpack webpack-cli webpack-dev-server
$ npm install -D typescript ts-loader sass style-loader css-loader postcss-loader sass-loader
$ nom install -D babel-loader @babel/core @babel/preset-env
$ npm install -S react react-dom @types/react @types/react-dom
$ npm install -D @babel/preset-react

package.jsonに デバッグ/ビルド script を追加

scripts: {
    "build": "webpack",
    "dev": "webpack serve"
},

typescriptの設定初期化

$ npx tsc --init --target es6 \
                --module es2022 \
                --lib es2022,DOM \
                --sourceMap true \
                --moduleResolution node \
                --allowSyntheticDefaultImports true \
                --resolveJsonModule true \
                --jsx react

webpack.config.js作成

webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/react']
            },
          },
          {
            loader: 'ts-loader',
            options: {
              configFile: path.resolve(__dirname, 'tsconfig.json'),
            },
          },
        ],
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [{
          loader: 'style-loader'
        }, {
          loader: 'css-loader'
        }, {
          loader: 'postcss-loader',
        }, {
          loader: 'sass-loader'
        }]
      },
      {
        test:/\.SVG$/,
        type: 'asset/source',
      },
      {
        test:/.(gif|svg|png|jpg|jpeg|JPG)$/,
        type: 'asset', /**  default <8kb: inline, >8kb: resource  */
        generator: {
          filename: 'image/[hash].[ext]',
        }
      },
      {
        test:/.(mp3|wav|aac)$/,
        type: 'asset/resource',
        generator: {
          filename: 'audio/[hash].[ext]'
        }
      },
      {
        test:/.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource',
        generator: {
          filename: 'font/[hash].[ext]'
        }
      }
    ]
  },
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
    port: 3000,
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json", ".wasm"]
  },
  experiments: {
    outputModule: true,
    asyncWebAssembly: true,
  },
  target: 'web',
};

ソースディレクトリ(src)と生成物ディレクトリ(dist)の作成とテンプレ作成

dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Tauri App Title</title>
  <script async type="module" src="main.js"></script>
</head>
<body>
  <div id="root"></div>
</body>
</html>
src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';

new Promise(resolv => window.onload = resolv).then(() => {
    const root: ReactDOM.Root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
    root.render(
        <div>sample contents</div>
    );
});

Tauriプロジェクトの作成

$ cargo tauri init

ターゲットディレクトリを../distとするほか、フロントエンドが先に作成されているのでデフォルト通りでOK。

コンパイル&デバッグ

$ cargo tauri dev

その他UI構築にbootstrapを使いたい場合は
$ npm install -D bootstrap @types/bootstrap react-bootstrap @types/react-bootstrap
と、当然Tauri APIを使用するなら
$ npm install -D @tauri-apps/api
など・・・・とな。