フィルムの在庫を数えた

SARS-CoV-2 の影響やらで最近フィルムの消費が少ないので期限切れフィルムを洗い出すことと冷蔵庫の中の整理のためにフィルムを全部出して数えてみた。

続きを読む

Twitter ツイートにウェブアプリを埋め込む

Twitter Card の Player だと iframe を使って埋め込めることを知ったので埋め込んでみた。(ツイートの埋め込みの中で埋め込んだものを再生できないので一旦ツイートを開く必要がある。)

f:id:kakkun61:20200728163346p:plain

Player Card を使ってるのはよく見るのは YouTube だろう。WebGL を埋め込んでる人もいた。

実装

実装としてはメタタグを埋め込むだけ(diff)。

    <meta name="twitter:card" content="player" />
    <meta name="twitter:site" content="@kakkun61" />
    <meta name="twitter:title" content="Photo Film Dev" />
    <meta name="twitter:description" content="This is a timer app for developing monochrome negative photograph films." />
    <meta name="twitter:image" content="https://photo-film-dev.kakkun61.com/preview.png" />
    <meta name="twitter:player" content="https://photo-film-dev.kakkun61.com/index.html" />
    <meta name="twitter:player:width" content="400" />
    <meta name="twitter:player:height" content="650" />

埋め込むコンテンツにはルールがあるので注意しよう。

developer.twitter.com

Elm でマテリアルデザインなウェブアプリを作った

アプリ概要

アプリとしてはニッチな用途で、写真用のモノクロネガフィルムを自家現像するときに便利なタイマーアプリです。下記は埋め込まれた実際に動くアプリです。

Photo Film Dev で公開しています。

時間配分をレシピと呼んでいますが、レシピはサーバーに保存されるためログインすれば複数の端末でアプリを使用できます。

今はレシピは自分のアカウントでしか見れないですが別のアカウントに共有する機能が作れたらなと考えています。

技術構成

ソースコードGitHub に GPL3 で提供しています。

github.com

アプリの技術構成としては次の通りです。

Elm

Elm は言語としては Haskell 様の文法を採用した alternative HTML/CSS/JS です。The Elm Architecture がいい感じです。チュートリアルを読めば書けるようになります。

モジュール分割

最初は1ファイルに全部書いて始めたのですがわりとすぐにどの辺りに何を書いたか分からなくなったのと同じ名前を付けたいものが出てきたのでモジュール分割をしました。詳細は後述します。

モジュール 役割
Model Msg 以外のデータ型
データ同士の変換関数など
Model.Foo enum として定義する Foo
getset など関連する関数
Msg Msg のデータ型
Msg.Foo 入れ子になる Msg のデータ型
update 関数と対応する
Port port 関数とその引数になる型
その型と内部で使用する型との変換関数
Cmd Port で定義した関数を組み合わせた Cmd を生成する関数
Sub Port で定義した関数を組み合わせた Sub を生成する関数
Update 直和型として定義した Msg 型のそれぞれの値構築子に対応する update 関数
View 画面要素ごとに分割した view 関数
Text 多言語テキスト

Enum

Elm にはアドホック多相が組み込み関数にしかありません。例えば Step という段階を表す型を作って Haskell であれば Enum 型クラスにするところですができないのでモジュールを分けて toIntfromIntcompare などを定義することになります。標準ライブラリーの型でも Maybe など Monad になるものはモジュールごとに andThen を定義しているので正当な方法だと思います。

module Model.Step exposing (..)

type Step
    = Soak
    | Dev
    | Stop
    | …

toInt : Step -> Int
toInt step =
    case step of
        Soak -> 0
        Dev -> 1
        Stop -> 2
        …

fromInt : Int -> Maybe Step
fromInt value =
    case value of
        0 -> Just Soak
        1 -> Just Dev
        2 -> Just Stop
        …
        _ -> Nothing

compare : Step -> Step -> Order
compare step0 step1 = Basics.compare (toInt step0) (toInt step1)

Dict 様レコードでの DRY

フィールドの型が全部同じで辞書(マップ)として使用するような下記のようなレコードの場合フィールドごとに同じような処理を書こうとしてもそのままでは実装を複製するしかありません。Dict にする手もありますが存在することが分かっているのに不必要な Maybe だらけになってしまいます。

type alias TimeSpans =
    { soak : TimeSpan
    , dev : TimeSpan
    , stop : TimeSpan
    , fix : TimeSpan
    , rinse : TimeSpan
    , wet : TimeSpan
    }

そこで先の enum として作った型をキーとして getset 関数を作ります。

module Model.Step exposing (..)

…

get : Step -> { soak : a, dev : a, stop : a, fix : a, rinse : a, wet : a } -> a
get step =
    case step of
        Soak -> .soak
        Dev -> .dev
        Stop -> .stop
        …

set : Step -> a -> { soak : a, dev : a, stop : a, fix : a, rinse : a, wet : a } -> { soak : a, dev : a, stop : a, fix : a, rinse : a, wet : a }
set step value record =
    case step of
        Soak -> { record | soak = value }
        Dev -> { record | dev = value }
        Stop -> { record | stop = value }
        …

これで下記のようにアクセスできます。

timeSpans |> get step
timeSpans |> set step value

これで don't repeat yourself にのっとって処理をまとめることができます。

Msgupdate の分割

Msgupdate の分割のしかたは対応させています。

-- Msg.elm
type Msg
    = Drawer Visible
    | SelectRecipe Recipe
    | GoRun
    …

-- Main.elm
update : Msg -> Model Msg -> ( Model Msg, Cmd Msg )
update msg model =
    case msg of
        Msg.Drawer visible -> Update.drawer visible model
        Msg.SelectRecipe recipe -> Update.selectRecipe recipe model
        Msg.GoRun -> Update.goRun model
        …

Msg.Drawer には Update.drawer というように対応させています。入れ子になったメッセージである VisibleMsg.Visible モジュールに定義してあります。Model の状態による条件分岐は各々の update の中で行っています。

PortCmdSub

Model モジュールに Recipe は下記のような型で定義しているのですがこれは port 関数で JavaScript に渡したり JavaScript から渡されたりすることができません。なので Port モジュールに同名の Recipe を作り変換関数も Port に定義しました。

-- Model.elm
type alias Recipe =
    { id : UUID
    , name : String
    , timeInputs : TimeInputs
    }

-- Port.elm
type alias Recipe =
    { id : String
    , name : String
    , timeInputs : Model.TimeInputs
    }

Port.Recipe から Model.Recipe への変換には elm-form-decoder を使いました。フォームからの変換以外でも外界からデータを取り込む場合に便利です。

Port.setRecipeCmdPort.Recipe に依存していますが Cmd.setRecipeModel.Recipe に依存するように Port の変換関数を使って変換しています。Sub.changeRecipe も同じく Port.changeRecipeSub から変換していますが、msg の構築子が2つあるのは変換の成功時・失敗時を表現するためです。

-- Port.elm
port setRecipeCmd : Recipe -> Cmd msg -- ここの Recipe は Port.Recipe

-- Cmd.elm
setRecipe : Recipe -> Cmd msg -- ここの Recipe は Model.Recipe
-- Port.elm
port changeRecipesSub : (Array Recipe -> msg) -> Sub msg -- ここの Recipe は Port.Recipe

-- Sub.elm
changeRecipes : (List RecipeDecoderError -> msg) -> (Dict String Recipe -> msg) -> Sub msg -- ここの Recipe は Model.Recipe

Material Design

Material Design を採用しライブラリーとしては elm-mdc を使用しました。UI センスがなくても Material Designガイドラインにしたがえばそれっぽくなるので便利です。

github.com

Elm のライブラリーは Elm しか内包できません。このライブラリーは Elm の他に JavaScript なども含むため Git リポジトリーとして公開され、サブモジュールとして依存します。

elm.jsonpackage.json は下記のようになります。

// elm.json
{
    "type": "application",
    "source-directories": [
        "src",
        "elm-mdc/src"
    ],
    "elm-version": "0.19.1",
    "dependencies": {},
    …
}
// package.json
{
  "dependencies": {
    "elm-mdc": "file:elm-mdc",
    …
  },
  …
}

ビルドタスクは GNU Make で管理しているのですが Parcel を使用しているにもかかわらず上記事情のため elm-mdc の面倒も見ているので少々複雑になっています。

多言語テキスト

Text モジュールでは下記の型の値を列挙しています。といっても今のところ日本語のみですが。

type alias Text = Language -> String

モジュール依存関係

モジュールの依存関係を下図に示します。Text は図から省略しています。

f:id:kakkun61:20200720040856p:plain
モジュール依存関係

Text は他のいずれにも依存しません。Model グループは Text にのみ依存します。黄色で ModelMsg を囲っているものは単に矢印の数を削減するためのもので、黄色枠への矢印もしくは黄色枠からの矢印はその中の1個以上への矢印もしくはその中の1個以上からの矢印と読んでください。MsgModelText にのみ依存します。他に特記すべきは PortCmdSub のみから依存され必ずラップされたものが他から参照されます。

Firebase

サーバーは自前で書くつもりだったのですが Firebase をさわってみたら認証とストレージだけで十分だったのでサーバーは自前では立てないことになりました。local storage を使ってスタンドアローンで動くところまで作っていましたが Firestore がオフラインの面倒も見てくれるので local storage の実装は消しました。

先に書いたように Elm のライブラリーは Elm のみに限定されるので、必然的に JavaScript に依存する Firebase のラッパーライブラリーはありません。とはいえ書くのはグルーコードなので大した問題ではありません。

クレジットページ

頒布をともなう場合、多くのオープンソースライセンスはコピーライト表記とライセンス表記を求めます。なのでクレジットページを作成しました。これは自動生成されるようにしています。NPM License CheckerElm License Checker を使用して生成しています。Elm License Checker はなかったので作りました。(Elm License Checker は PureScript 製です。作成時 Elm で CLI を作れることを知らなかったので。)

www.npmjs.com

謝辞

Discord の Elm-jp サーバーのみなさまにはお世話になりました。

Windows Terminal に MSYS2/MinGW64 を追加する

Windows 10 2004 にしたので Windows Terminal をさわってみた。自分の用途だと Cmder はもう使わなくていいなと思う。

さて、Windows Terminal に MSYS2/MinGW64 を追加したのでその設定を示す。

{
    …,

    "profiles":
    {
        …,

        "list": [
            …,

            {
                "guid": "{43257a9a-8d31-4208-8ed4-3d4365d44bd1}",
                "name": "MSYS2/MinGW64",
                "commandline": "%ChocolateyToolsLocation%\\msys64\\msys2_shell.cmd -defterm -no-start -mingw64 --login -i",
                "icon": "%ChocolateyToolsLocation%\\msys64\\mingw64.png"
            }
        ]
    }
}

profiles.list にレコードを追加する。

GUID は適当に生成した GUID を設定する。

commandlinemsys2_shell.cmd を経由してシェルを起動するようにする。msys2_shell.cmd を使用することで初期設定や環境変数の設定などをコマンドライン引数で行える。

アイコンは mingw64.exe から抽出したものを使用している。

Windows のホームフォルダーにドットファイルを作らないでほしい

Windows ではファイル名の先頭にドットを付けるだけでは隠しファイルにならないのでユーザーのホームフォルダーを汚染することになる。

だいたいの場合では LOCALAPPDATA 環境変数の示す場所にソフトウェア名のフォルダーを作ってその下を自由に使えばいい。

LOCALAPPDATA の示す場所は普通 C:\Users\kazuki\AppData\Local といったぐあいだが、ユーザーが変えている場合もあるので固定パスにするのはよろしくない。

また、隠しファイルを作りたい場合はファイルシステムの隠し属性を有効にしてほしい。そうするとデフォルト状態ではそのファイルは見えなくなる。

LOCALAPPDATA 以外の似たようなフォルダーはWindowsのディレクトリ構成ガイドライン - torutkのブログの記事がくわしい。

torutk.hatenablog.jp

Haskell 環境構築ツールフローチャートを作りました

Haskell 環境構築ツールフローチャート

前に環境構築についての記事を書いたのですが、初学者向けにパッと見て分かるようにフローチャートにしました。

kakkun61.hatenablog.com

Google ドライブ 図形描画のファイルはこちらです。コメントを付けることができます。

docs.google.com

新しいディスプレー

f:id:kakkun61:20200307011401j:plain

会社が 4K ディスプレーなのもあって、家でプログラミングしてたら画面が窮屈に感じてきてうちも 4K ディスプレーを導入しました。

特に Elm の標準のフォーマットが密度低いのでフル HD だと不便だったのが最後の引き金でした。

4K かつ HDR にしました。

KEIAN 28インチ4K対応LEDモニター KWIN28 ゲーミングモニター

KEIAN 28インチ4K対応LEDモニター KWIN28 ゲーミングモニター

  • 発売日: 2019/04/20
  • メディア: Personal Computers

4K で High DPI 設定による高精細は言わずもがな HDR も綺麗ですね。

綺麗なんだけど WindowsHDR 出力をオンにしたときの SDR(今までのダイナミックレンジを表すレトロニム)コンテンツが白くかすんでしまって WindowsHDR オンにするのは時期尚早な感じがします。

自分は Windows では HDR はオフにしました。

PS4 をせっかく Pro 買ったのに今まで full HD の SDR でしか出力してなくもったいなかったのがやっともったいなくなくなり、Windows がいまいちでもとりあえず満足です。

HDMI にバージョンがあることを知らず PS4 Pro から 4K 60Hz が出ず後悔しそうでしたが、ディスプレーの HDMI 入力端子によってバージョン 1.4 と 2.0 が違うらしく挿し直したらちゃんと出力してくれました よかった。

ディスプレー買って初めて輝点があったのでちょっと凹んだんですが、グレー一色表示とかしなければ意外とめだたなくてよかったです。 でもやっぱり今度から輝点黒点保証付けて買おうかな。

……

…………

と、これだけでは終わらず右下のディスプレーも新しく買ってて、こっちはフル HD だけど Adobe RGB カバー率 99% のカラーキャリブレーションディスプレーです。

10bit カラーでもあります。

デジカメで Adobe RGB で撮ってましたがこれまで sRGB でしか見れてなく、見れないものを画像編集しててモヤモヤしてたのが、やっと本当の色を見ることができます。

特に緑の鮮やかさに差が大きいらしいのでわざとそういうの撮って比較したいですね。

カラーキャリブレーションの機能はこれ単体ではできなくカラーセンサーがまた高いのだけどそれは先の話ということで……

4K かつ Adobe RGB にすればよかったじゃんという意見はごもっともなんだけど14万円とかして用途別に2台買う方がやすいんですよね……