【Electron】画像を保存するだけで満足してしまうオタクのために最高の画像ビューアをつくった

みなさん、いかがオタクライフお過ごしでしょうか？
オタクの生活には萌え画像が欠かせませんね。
日々twitterを眺めてはTLに流れてくる画像をふぁぼり、pixivでお気に入りの絵師の画像を眺め、tumblrでアニメgifをリブログ、そして自分のPCのお宝フォルダに保存して完了。
だいたい、こんな感じでしょうか。
ですが、肝心なことを忘れてないでしょうか？

# *保存するだけ保存してそれで満足って、それ、画像を楽しんでないんじゃないの？*

手に入れた画像をどう楽しんでますか？
デスクトップの壁紙やターミナルの背景にする？
いいですね。ですが、それで満足してますか？

背景ではなく最前面に表示されていたら？
静止画だけじゃなくgif画像も表示されたら？

そこで、**作業用画像ビューア**です。

作業中ずっと好きな画像が表示され続けば...

1. *最高な画像が表示される!*
2. *テンションアップ!*
3. *作業効率アップ！*
4. *最高！*
5. 1に戻る

という最高のスパイラルが体験できることでしょう。
そんな最高の体験を可能にするための作業用画像ビューア **[yamada](https://github.com/akameco/yamada)** をつくりました。

## スクリーンショット

![gif](https://raw.githubusercontent.com/akameco/yamada/master/media/screenshot.gif)

## 特徴

- 常に最前面
- タイトルバーなし(邪魔だよね)
- 閉じるボタンなし(邪魔だよね)
- 起動するだけで使える
- どこをドラッグしても移動可能
- 簡単にリサイズ
- ディレクトリを監視し起動後に追加した画像も表示
- `⌘+o`でファイルダイアログを開く
- コンテキストメニュー
- png,jpg,gifに対応
- コマンドラインからの起動可能

## ダウンロード

ダウンロードは[ここ](https://github.com/akameco/yamada/releases)から。

<a href='https://github.com/akameco/yamada/releases'>
<img src='https://raw.githubusercontent.com/akameco/yamada/master/yamada.png' width=400>
</a>

zipを展開して起動できます。
mac対応のみ(windows環境ではコンテキストメニューに不具合があります)

## CLI

```sh
$ yamada .
$ yamada ~/Picture
```

## 技術的なこと

Electronを使いました。1つのjsとhtmlだけでも簡単なデスクトップアプリケーションがつくれるので手軽で楽しいです。

今回はメインプロセス側にmain.js、レンダラープロセス側にindex.jsをルートディレクトリにぽいっと配置したお手軽構成にしました。viewライブラリは使ってないです。(レンダラープロセス側はimgを書き換えてるだけなので)

### browserify
つくってるときはbrowserify必要なくねと思っていましたが、リリース用にパッケージ化したらサイズが大きくなったので、node_modulesを削るためにbrowserifyを使いました。

以下の記事のコメントでも言及されています。

> kobito for windows では開発環境下でのnode_modules以下が120MBあるのですが、これをbrowserifyで不要なファイルを除去することで3.2MBまで絞れました。ある程度以上のサイズがある環境なら必須だと思ってます。
[ぼくのかんがえたさいきょうのElectron - Qiita](http://qiita.com/Quramy/items/90d61ff37ca1b95a7f6d#browserify)

electronでbrowserifyを使う時の注意点として、mainプロセス側をブラウザリると`require('electron')`が迷子になる等の問題が起きるのでオプションをつけてあげる必要があります。

```
browserify main.js --im --no-detect-globals --node -o ./dist/main.js
```

`--im`はrequireが解決できなかったときにrequireを無視します。
`--no-detect-globals`は`__dirname`などがビルド環境のパスに置き換えられるのを防ぎます。
このオプションはbrowserifyのREADMEにのってないので調査にちょっと手こずりました。

- [Support for node-webkit · Issue #481 · substack/node-browserify](https://github.com/substack/node-browserify/issues/481)

browserifyは依存するnpmモジュールのpackage.jsonがbrowserフィールドを持っているとそちらを見に行ってしまいます。しかしmainプロセスはnode向けに動作してほしいため`--node`オプションを有効にします。
また、レンダラープロセスにおいてもelectronをrequireする必要があれば`--im`を有効にする必要があります。

### npm scripts
gulpなどのタスクランナーは使わずnpm scriptsを使いました。

```js
"scripts": {
  "start": "electron .",
  "clean": "rimraf dist",
  "copy": "cpx '{*.html,package.json,*.png}' dist",
  "build": "npm-run-all -s clean copy build:renderer build:main",
  "build:renderer": "browserify index.js --im -o ./dist/index.js",
  "build:main": "browserify main.js --im --no-detect-globals --node -o ./dist/main.js",
  "release:osx": "electron-packager ./dist yamada --out=./release --platform=darwin --arch=x64 --version=0.36.9 --overwrite --asar --icon=./res/yamada.icns",
  "release:win": "electron-packager ./dist yamada --out=./release --platform=win32 --arch=x64 --version=0.36.9 --overwrite --asar --icon=./res/yamada.icon",
  "release": "npm-run-all -s build release:*"
},
```

### es6
`process.versions`を確認してみたところElectronのnodeのバージョンは5.1.1なので、アロー関数やクラス等のes6の機能が使え環境も固定されているので、とりあえずアロー関数さえあればいいよという人はバベる必要もないかなと思います。(なお今回使用したelectron-prebuiltのバージョンは0.36.9です)

```js
{ 
  http_parser: '2.6.0',
  node: '5.1.1',
  v8: '4.7.80.27',
  uv: '1.7.5',
  zlib: '1.2.8',
  ares: '1.10.1-DEV',
  modules: '47',
  openssl: '1.0.2e',
  electron: '0.36.9',
  'atom-shell': '0.36.9',
  chrome: '47.0.2526.110'
}
```

### ファイル監視
基本的には、メインプロセスで[chokidar](https://github.com/paulmillr/chokidar)を使いディレクトリを監視し、5秒ごとにランダムで画像をipc通信でレンダラープロセスに送って画像を表示しています。chokidarはgulpやbrowserifyでも使われてるファイル監視ライブラリです。

### Menuとキーイベント
はじめキーイベントはレンダラープロセスで呼び出していましたが、menuに`accelerator`を書けばclickに定義した関数がキーイベントが発生すると呼び出せるようです。レンダラープロセスにもキーイベントのリスナを設定しておくと重複して呼び出されてしまうので注意が必要です。(あとからメニューを追加したらファイルダイアログが２連続で出るようになりハマりました)

### フレームレスウインドウとドラック移動
公式のドキュメントに全部書いてあります。

- [electron/frameless-window.md at master · atom/electron](https://github.com
/atom/electron/blob/master/docs/api/frameless-window.md)

フレームレスウインドウはBrowserWindowのオプションのframeをfalseにするだけです。

```js
const BrowserWindow = require('electron').BrowserWindow;
var win = new BrowserWindow({ width: 800, height: 600, frame: false });
```

ドラック移動を可能にするには、cssで以下を設定します。

```css
body {
  -webkit-app-region: drag;
}
```

### alwaysOnTop
フレームレスウインドウと同様にBrowserWindowのオプションで`alwaysOnTop`を有効にするだけで、常に最前面に表示させることができます。

```js
var win = new BrowserWindow({ width: 800, height: 600, alwaysOnTop: true });
```

しかし、ダイアログを開くと最前面でなくなることがあります。そこで、ダイアログを開いたあとなどに`setAlwaysOnTop`を実行し最前面に設定しなおす対策をしています。何かよい方法があれば教えて下さい。

```js
mainWindow.setAlwaysOnTop(true);
```

## おわりに
  
これを書きながらも常に画面右下にかわいい画像をスライドショーさせています。
思ったより邪魔になりませんしいい気分でこれを書いてます。

バグや機能追加等あればイシューやプルリクエストお願いします。
[github - akameco/yamada](https://github.com/akameco/yamada)

それでは、よいオタクライフを！

## 追記

browserifyではなく、webpackを使ったelectronアプリケーションの記事を書きました。
他にもreact+flowtypeを使っています。機会があれば見てください。

[Electron+react+flowtypeでtweetdeckライクなpixivクライアントPixivDeckをつくった - Qiita](http://qiita.com/akameco/items/fa80b9a325e3d1e8fd5b)