UserScriptでカイゼンを始めてみた

こんにちは、サイオステクノロジーの馬場です。

私の携わっているプロジェクトでは、
飲食店のオペレーション全般をサポートするシステムを、スクラッチから開発しています。

その中には、商品メニューを管理するためのウェブアプリもあるのですが、
このウェブアプリは、規定の寸法から外れる商品画像を「拒絶する」という硬派な仕様になっています。

商品メニュー編集を代行しているメンバー (私じゃない) は、この仕様がツラそうなんですが、
限られた予算の中で、このツラさが解決されるまでの道のりは遠く険しそう、という状況です。

この記事では、それを見た私がとった行動を時系列で晒しつつ、最後に、これによる成果を妄想します。

時系列

アイディア

以下のように考えました:

  • サイズが違ったら、自動リサイズしてあげれば良いだろう。
    • 「メニューに掲載するような写真を、システムで自動的に加工して良いの?」
      という懸念が、現状の硬派な仕様を生んでいると思われるが、
      そういうことを気にする場合は、きちんと手動リサイズしたものを上げれば良い。
      (サイズが合っていれば自動リサイズされないようにする)
  • そのリサイズは、クライアントサイドで済ませられるだろう。
    • JavaScript ライブラリがあるはず。
  • ブラウザの UserScript で試してみよう。
    • まずは、 “Proof of Concept” として、やる。
      • 本体に組み込むのは、担当エンジニアに任せる。
    • 何なら、「運用上の工夫」として、やる。
      • メニュー編集担当メンバーが UserScript を育て続けても良い。

つまり「 UserScript で試すというステップを設ければ、世界が拡がるかもしれない」と考えました。

JS の画像処理ライブラリを探す

GitHub でのスター数とかを参考に、これにしました:

Jimp:
https://github.com/oliver-moran/jimp

“Chrome Greasemonkey” でググる

チームの標準ブラウザは Chrome です。
ググってみると、Tampermonkey というものがあるようです:

Tampermonkey:
https://www.tampermonkey.net/

Greasemonkey の後継で、主要なブラウザには大体対応しています。Chrome にインストールしました。

Tampermonkey を試す

簡単に試したところ、
ブラウザで閲覧中の任意のウェブページを、JavaScript (UserScript) で
好きなように加工できることが分かりました。

早速、上記 Jimp を使ったコードを書き始めました。

UserScript を書く

方針としては、
最初に説明したとおり、アップロードされたファイルをチェック & 拒絶している箇所があるわけなので、
その部分を丸ごと差し替えるイメージで進めました。

その箇所は、 input[type=file] の onChange のハンドラーとして実装されていたので、
元のハンドラーを解除 (off) して、書き換えたほうのハンドラーを登録 (on) すれば良いはずです。

最終的には、こんな感じになりました:

/* 
 * コンセプトを伝えるために、簡略化しています。
 * コピペで動作するものではありません。 
 */

// ウェブページ側のブロックスコープで隠蔽されていて、 UserScript から参照できない変数は、
// UserScript 側でも定義。
const sizes = {
  small: {width: 120, height: 120},
  medium: {width: 510, height: 340},
  large: {width: 1200,height: 800},
}
const photoShadow = 'input[type=file]#photo-shadow';
$(document)
  .off('change', photoShadow) // 元のハンドラーを解除 (off)
  .on('change', photoShadow, function(){ // 新しいハンドラーを登録 (on) 
    const fileReader = new FileReader();
    fileReader.onload = function (event) {
      const data = event.target.result;
      const base64Prefix = 'data:image\\/jpeg;base64,';
      const dataBase64 = data.substring(base64Prefix.length - 1);
      if (data.match(base64Prefix)) {
        const img = new Image();
        img.onload = function() {
          EXIF.getData(img, function() {
            const colorSpace = EXIF.getTag(this, 'ColorSpace');
            if (dataBase64.slice(0, 4) !== "/9j/") {
              // JPEG じゃない!
              window.alert("JPEGファイルであることを確認してください");
            } else if (colorSpace && colorSpace != 1) {
              // カラープロファイルが sRGB じゃない!
              window.alert('カラープロファイルがsRGB(1)であることを確認してください');
            } else if (img.width !== sizes[size].width || img.height !== sizes[size].height) {
              // サイズが違う! (size はグローバル変数)

              /* ▼ ここから書き換えた部分 */
              Jimp.read(data)
                .then(function(image){
                  return image
                    .cover(sizes[size].width, sizes[size].height) // sizes[size] どおりにリサイズ
                    .getBase64(Jimp.MIME_JPEG, function(err, d){
                      $(dataForm).val(d.substring(base64Prefix.length - 1)); // リサイズ後のデータを保持
                      $(preview).attr('src', d); // プレビュー表示
                    });
                })
                .catch(function(err){
                  console.error(err);
                });
              /* ▲ ここまで書き換えた部分 */

            } else {
              // オールオッケー!
              $(dataForm).val(dataBase64);
              $(preview).attr('src', data);
            }
          });
        };
        img.src = data;
      }
    };
    fileReader.readAsDataURL(this.files[0]);
    
    // $(photoShadow) はリセットして使い回す。
    $(photoShadow).replaceWith($(photoShadow).val('').clone(true));
  });

 

(Tips 1) 変数のスコープについて

これはTampermonkey のエディタのスクリーンショットです。
warnings

UserScript からウェブページ側の変数を参照している箇所で、
エディタにとっては未定義の変数なので、エディタがその旨をワーニングしています。

これを見て「// @grant unsafeWindow が必要なのかな?」と思って、試したりしましたが、
結局は不要で、ウェブページ側のグローバル変数は普通に UserScript から参照できるようです。

unsafeWindow:
https://www.tampermonkey.net/documentation.php#unsafeWindow

それとは別に、
ウェブページ側で { let ... } { const ... } とブロック内で宣言されている変数は、
言語仕様上(ブロックスコープ)、ブロック外である UserScript からは見えません。
これは、いずれ本体(ウェブページ)側に組み込まれれば解決する問題なので、
素直に UserScript 側にコピペしました。

(Tips 2) @match について

Tampermonkey では、
どのウェブページで UserScript を有効にするかを指定する @match 記法が用意されています。

@match はワイルドカード記号 * をサポートしていて便利ですが、
この * は、正規表現とも(シェル, glob の)ワイルドカードとも違う独自のものなので、
注意が必要です: https://developer.chrome.com/extensions/match_patterns

チーム内でシェアして完了

ひとまず PoC としては完成したので、メニュー編集担当メンバーにシェアしました。
担当メンバーは「あの時、これがあれば…!」と言っていたので、PoC としては成功でしょう。

チーム内の課題管理システム上で、この件のチケットに UserScript を添付して、あとは託しました。

期待している成果

UserScript を活用することで、以下のような変化を期待しています:

  • 担当メンバーが「手元で工夫すれば業務改善できる」という認識をもつ。
  • 手元(こちら側)で試すというステップを経ることで、
    本体(あちら側)に取り込まれるプロセスがスムーズになる。

つまり、「こちら側」と「あちら側」の架け橋となって、
この手のカユいツラいところの解決への道筋を示すものになれば、と期待しています。

次回予告

また、そのうち、
何かやってみた時の行動を時系列で晒しつつ、最後に成果物も晒し、「あるある」的な共感を得る
というフォーマットで、書こうと思います。 (=「◯◯てみた」シリーズ)

 

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

1人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です