Polymer.dart でアプリ作って感じたことなど

![0a92f6f7-9959-4d76-843a-47dd0df6797e.png](https://qiita-image-store.s3.amazonaws.com/0/755/58f6bf64-cfb3-3f38-cdfa-a2757bccbbcc.png)

宣伝も兼ねて、作る時に頭抱えたポイントなど書く。

* [ANSI Pixels][ap]

今回作った [ANSI Pixels][ap] は、ターミナルで表示するドット絵を編集するためのアプリ。[ANSI Art](http://en.wikipedia.org/wiki/ANSI_art) というらしいです。

特色・機能・制限
========

* gh-pagesで動いてる: [ANSI Pixels][ap]
* Dart で作った
* `curl` や python スクリプトを使ってターミナルに自分が書いたドット絵を表示できる。
    * 設定画面で上のサンプルコマンド切り替えられる。
* 画像としてダウンロードできる。
* ドット絵の情報は、URLのハッシュフラグメントに base64 + zip で保存されてる。
    * なので Undo/Redo はブラウザの戻る/進むボタンで制御できる（※要設定「Stack into history」）
    * リンクをコピペすれば他の人と共有できる。
    * 参考: 同ページの Samples
* Firefox と Google Chrome で動く
* 16 色以上の code 8〜15 を使う場合は、ターミナルの対応が必要
* 256 色の code 16 以上を使う場合は、また別にターミナルの対応が必要

まだ表示や英語が適当なところがあったり、そもそも文字でしかボタン作ってない。

手元に IE も Safari も無いので、動作確認済みなのは Firefox と Google Chrome だけです。Opera? 知らない子です[ね。](https://code.google.com/p/dart/issues/detail?id=17459 "Issue 17459 - dart - Polymer broken on Opera - Dart - Structured Web Programming - Google Project Hosting")

端末での実行例
--------------

```bash
# 8色+curl 実行例
curl -s 'http://ansipixels.k-ui.jp/eAHNlD0LwjAQhv-K3KQQJLUSpZs6uNvBoXTwo2qhWLEOovjfTWnSpElOCyrI2-HNXXkuXK93h1N6TbIwvSUQDCiB9X6WZ_kZAljMp5MuJR3x9Mc9ILCr08tDekl4ZFOew9NqwwEw5oFjvj-n2wKC3SorElIV4McoouRniklN92u53_WEMBYTsum-JTcZq8AM6XSb3eSbbJNvsmt-C7pHRlySqrxilzFJVV7vO84fCUm29NTIS7b0za9q16hiQy5J1D018iVR9za9WaPqC0b3RF8wOmtxdw_tu6RjfWct-u6aF5OO6f3M_Dv91b_6DTq-ZxTJ5fTt4nDOibR3DO6otlcs14L-geL48QSYojuO'

#256色+pythonスクリプト実行例:
python -c "$(curl -s https://raw.githubusercontent.com/kui/ansi_pixels/master/tool/ansi-pixels.py)" "eAGVlMtOwzAQRX8FeQWShRonTersgAV7umARdVFKWiJFTdWChED8Ox6_MhlPEZWch-05947Hcb7Fofts-2X31Yo6K6R42T0M_XAUtXh6vL-7nskr324XN0KKbZx-fuveWzOygf7ysN4YAaHmpRnaD7tj93oS9Xbdn1rpLEy3afYffS_JLSuZxgXCbSWbhCwKe80X0FCPlwkKZ0mVZ_E906kKErCh5qFUEEA6wGq6HOpehrBJJ5KzPNAIdEtjuGDu-pbl6lUpXLkSlcs3zYOwzHHR_G6l2do8dICtuQ6DsT5ciaKAg62AT30ioNEuMeyYuG8jWUVFmrP3yasAwdvoaqZtFVg4uAGCrymusARvDfM2ZkpTfwMbQBc-VXjxcAA15mIZHQcxmI2NjBCvGOCVITf3sYb1xiBaW_K5IIEyBdNdZf4e-Kzg_P8roPHZ4_9AZxTwIXAqxPkvBcLggl_sj4Uu4FM59km0Vj-_i96k9Q=="
```

描画データ全部ハッシュフラグメントに載せてるので長い。

コンポーネント
================

ANSI Pixels を作るにあたって、4 つのカスタム要素を作った:

* [pixel_canvas](http://kui.github.io/pixel_canvas/)
    * `<pixel-canvas>`
    * canvas 部分。フロートレイヤや選択範囲、カット/コピー＆ペーストまでやるAPIある。
    * ちょっと1要素に機能盛りすぎたので、canvas 機能だけの要素と編集するだけの不可視要素で分けたい
* [color_palette](http://kui.github.io/color_palette/)
    * `<color-palette>`, `<color-palette-cell>`
    * 色選択するパレット部分。`.color` で現在選択している色が取れる。
* [ansi_color_palette](https://kui.github.io/ansi_color_palette/)
    * `<ansi-color-palette>`
    * color_palette から ANSI Color だけ使えるようにしたもの。
    * 0-255 のカラーコードとしても取得できるように機能を追加している。
* [fold_button](https://kui.github.io/fold_button/)
    * `<fold-button>`, `<on-folding>`, `<on-unfolding>`
    * パレットや設定画面をしまうのに作ったカスタム要素
    * transform, transition でちょっとアニメーションする
    * スクリプト無し要素 `<on-folding>`, `<on-unfolding>` で閉じてる時と開いている時の文言を切り替えられる。
        * 不可視 `<content>` 要素でコンテンツ奪って残りを表示してる。[ソースコード](https://github.com/kui/fold_button/blob/master/lib/fold_button.html#L19-L26)
        * `<button>` の中に `<content>` や `<template>` 入れると、マウスイベントの伝播が止まるのが辛い。

こうやって機能分離して整理しながら開発できるの楽しかった。fold_button がお気に入り。

Polymer.dart (Dart) で苦労したところ
========================

Dart, Polymer.dart に関わる部分で、苦労したり試行錯誤したところを書く。

* 参考になるものが少ない
    * チュートリアルにもっとバリエーションほしい
    * [paper-elements](https://github.com/dart-lang/paper-elements) をちょっと参考にした。
        * Google が発表した Material Design のやつ
    * [ドキュメント](http://www.dartdocs.org/documentation/polymer/0.13.0/index.html#polymer) かソースコード読もう。
* スタイルのカプセル化とは・・・
    * スタイルをカプセル化したら、外からイジる(`::shadow`, `/deep/` など)のは行儀が悪いってことなる。でも、アプリごとにルック・アンド・フィール変わるだろうし、スタイルのカプセル化って必要なの？
    * paper-elements のように片っ端から必要になりそうな要素つくるのも簡単じゃないし。
    * アプリごとにその見た目にあったカスタム要素作りまくるの？
    * 機能をカプセル化は分かる。 fold_button が多分それ。
    * 機能をカプセル化したカスタム要素と、スタイルをカプセル化したカスタム要素は分けたほうが良さそう。
    * fold_button は button 要素で見た目っぽいの持ってしまっているけれど、これはどうするの？
    * セマンティック的には button 要素はあったほうがいいと思う
    * `<button is="fold_button" ...` みたいに button 要素継承して外からスタイルを触れるようにするべきだった？
        * 継承するとソースコードの見た目がカスタム要素感なくなってあんまり好きじゃない。
    * この辺よくわからないまま作ってしまった。多分これ勉強するのにそれなりに時間割く必要ありそう。
* Firefox 対応
    * さんざん指摘されていますが Shadow DOM に関わるスタイルシートやセレクタは機能しない。
        * Shadow DOM 内で一意な `id` でも、Firefox に Shadow DOM が無いので、よく `id` 重複してスタイルが意図したとおりに行かない。
        * `::shadow` ダメ
            * [fold_button](https://kui.github.io/fold_button/) の三番目のボタンを Firefox と Google Chrome の両方で見るとよく分かる。
        * `/deep/` もダメ
        * [HTML5 - Polymer の Shadow DOM Polyfill - Qiita](http://qiita.com/hokaccha/items/9975a9183d93f8d3d65d)
    * `<style>` 内で polymer expression 使うのもダメ。
        * 例: `<style>div { background-color: {{bgColor}} }</style>`
    * 逆にこのスタイル関連だけで、dart2js に関わる部分ではほとんど問題にであわなかった。
* Polymer Expression の仕様
    * Mustache ベースの文法らしいが、詳しいことがいまいち纏まってなかったのでソースコード読んだ
    * どこかに纏まってそう
    * `{{foo is SomeClass}}` はダメ。dart2js すると、実行時には `SomeClass` は別の短い名前に書き換えられていて、この Polymer Expression はその書き換えを追尾していないため。
* `@publish` つらい: http://qiita.com/k_ui/items/b5b7a3635e05d58bdc19
* `@published`, `@PublishedProperty(reflect: true)`, `@reflectable`, `@observable` の使い分けがうまくできてない
    * カスタム要素の属性として使いたいなら、`@published` か `@PublishedProperty(reflect: true)`
    * `@reflectable` は使っていてよく理解できた。
    * 他がよくわからない。
* メソッドでジェネリック型の定義ができない
    * [Issue 254 - dart - Support declaration of generic type for methods - Dart - Structured Web Programming - Google Project Hosting](https://code.google.com/p/dart/issues/detail?id=254)
    * `List` などで `.map` につないでメソッドチェーンするとそれ以降は全部 `dynamic` になってしまうのもつらい。
* `void someMethod(void callback())` ではなく `Future someMethod()` のほうが取り回し効く
* ビルドに時間かかる
    * 今回の [ANSI Pixels][ap] でだいたい 21 秒くらい。
    * Firefox 対応の際のトライアンドエラーがつらい
    * 主に dart2js に時間かかってるっぽい
* バンドルされている DartEditor が時々 Ubuntu Unity 巻き込んで止まる
    * Eclipse や IDEA でも起きてたので Java のせいかも
    * `Ctrl+Alt+F1` 押してログインして `$ killall java` して対処

思い出したら追記する。

振り返り
=====

スタイルのカプセル化に関してきちんと理解してから手を付けたほうが実りあったかもしれない。

[ap]: https://kui.github.io/ansi_pixels/#eAGVlMtOwzAQRX8FeQWShRonTersgAV7umARdVFKWiJFTdWChED8O36NMxlPEZWch-05947Hcb7Fofts-2X31Yo601K87B6GfjiKWjw93t9dz-RVaLeLGyHFNk4_v3XvrRnZ2P7ysN4YAaHmpRnaD7tj93oS9Xbdn1rpLUy3afYffS_JLSuZxgXa20o2CVkU7povbEM9XgYUzpIqz-K7qUiiggRcqHkoBQJIx7KaLoe6lxA26URylgONQL80hgNz33csV69K4cqVqFyhaR60yxwXze9Wmq3LQwPszDUMxvpwJYoCHnYCIfWJgEa7xLBj4qGNZBUVac7BJ68Asm-jq5l2VWBhcLMIvqa4whK8tZ13MVOa-hvYALoIqdqXAAOoMRfL6Dkbg9nYyAjxigFB2ebmP1ZYbwyitSWfCxIoUzDdVebvgc8Kzv-_AhqfPf4PdEYBHwKvQpz_UiAMLvjF_ljoAj6VY59Ea_XzC6qrpPo=