*iroi*

mechairoi の Blog

Material Color Utilitiesの9つのDynamic Scheme

github.com

Material Color UtilitiesはMaterial 3のカラーシステムに関するアルゴリズムやユーティリティを含むライブラリです。 HCT色空間や 画像から代表色を抽出するquantizeなど様々なコンポーネントを含んでいます。最近リリースされたv0.3.0 *1で非推奨になったSchemeの代替として用意されているDynamic Schemeが面白かったので紹介します。

Dynamic Schemeはコンテンツから抽出した色や指定した色、コントラストレベル、variantなどのいくつかのパラメータからのカラースキーマを生成する仕組みです*2。 ライブラリには9つのvariantが用意されていて、例えばAndroidのMaterial Youのデフォルトと同じにするならSchemeTonalSpotというvariantを使います。詳しくはDynamic Schemeのドキュメントを参照。

それぞれのパラメータがどのような影響を与えるのか理解しやすいように、インタラクティブにパラメータを変更してカラースキーマを確認できるようにしてみました。

https://codepen.io/mechairoi/full/mdodrJj

シードカラーやコントラストレベルなどのパラメータを固定して、variantを切り替えると下のようになります。

グレースケールに近いものもあったりして面白いですね。最後に各variantの説明をソースコードから引用します。

SchemeContent

A scheme that places the source color in Scheme.primaryContainer.

Primary Container is the source color, adjusted for color relativity. It maintains constant appearance in light mode and dark mode. This adds ~5 tone in light mode, and subtracts ~5 tone in dark mode. Tertiary Container is the complement to the source color, using TemperatureCache. It also maintains constant appearance.

SchemeExpressive

A Dynamic Color theme that is intentionally detached from the source color.

SchemeFidelity

A scheme that places the source color in Scheme.primaryContainer.

Primary Container is the source color, adjusted for color relativity. It maintains constant appearance in light mode and dark mode. This adds ~5 tone in light mode, and subtracts ~5 tone in dark mode. Tertiary Container is the complement to the source color, using TemperatureCache. It also maintains constant appearance.

SchemeFruitSalad

A playful theme - the source color's hue does not appear in the theme.

SchemeMonochrome

A Dynamic Color theme that is grayscale.

SchemeNeutral

A Dynamic Color theme that is near grayscale.

SchemeRainbow

A playful theme - the source color's hue does not appear in the theme.

SchemeTonalSpot

A Dynamic Color theme with low to medium colorfulness and a Tertiary TonalPalette with a hue related to the source color.

The default Material You theme on Android 12 and 13.

SchemeVibrant

A Dynamic Color theme that maxes out colorfulness at each position in the Primary Tonal Palette.

*1:TypeScript 版のバージョン

*2: variant を使わず複数の色を指定する方法もあります

go-readabilityをwasmにしてCloudflare Workersで本文抽出する

先日Cloudflare Workersでウェブページの本文を抽出したくなったことがありました。本文抽出といえば、@mozilla/readabilityが使えそうです。しかし依存に含まれるnwsapiにはFunctionコンストラクタが多用されており*1、Cloudflare Workersでは動作しません。これを修正するのも大変そうです。

そこで、TinyGoを使用してgo-readabilityをwasmにコンパイルしてみることにしました。

TinyGo のドキュメントUsing WASM | TinyGo のままではCloudflare Workersでは動かなかったので、TinyGoに含まれるwasm_exec.jsを少し修正したり*2、 import側もwasmファイルをfetchしているのを変更したり*3すると無事動きました。

動作するコードは https://github.com/mechairoi/cloudflare-go-readability-demo

Cloudflareにデプロイするか、ローカルでnpm run devを実行してから、/?url=https://example.com/ のようにアクセスすると、本文がテキストで返ってくるはずです。ビルドサイズは圧縮後約850kBで、Cloudflare Workersのフリープランは1MBなのでセーフですね。golangのwasmはRustと比べるとJavaScriptからの利用に少し癖があるように感じましたが、ちゃんと動いてくれて便利ですね。

おまけ

masukomi/arc90-readability: a copy of the original arc90 repo with links to many of the current ports.によるとReadabilityライブラリには多くの実装があるようです。実は最初はRustで書かれたkumabook/readabilityをwasmにコンパイルして使っていました。 しかし、試しているといくつかのページうまく抽出できないケースがあったり、またmozilla/readabilityをrustで書き直すにkumabook/readabilityの実装はMozilla版と比べると古いという記述もあったので、今回はgo-readabilityを使用することにしました。

PlaywrightのレポートをCloudflare PagesにデプロイしてGitHub IdPでアクセス制限する

導入

PlaywrightやReg SuitなどのVRT(Visual Regression Testing)の結果は、HTMLで出力されます。Playwright のヘルプ1 でもレポートをダウンロードして確認する方法が記載されていますが毎回行うのは面倒です。また、レポートをGitHub Pagesにデプロイする方法もありますが、アクセス制限にはEnterpriseプランが必要だったり、Pull Requestごとの結果を保存するのに手間がかかるなどの問題があります 2 3Amazon S3にレポートを置く方法もアクセス制限が面倒なことが知られています。

そこでこのエントリでは、レポートをCloudflare Pagesにデプロイする方法を紹介します。Cloudflare PagesにはPreview deployments4があるため、Pull Requestごとのレポートをデプロイできます。また、Cloudflare Zero Trustの機能を使えば、アクセス制限も容易です。リポジトリの閲覧と同様のアクセス制限をかけるのが多くの場合で合理的と思われるので、GitHubのOrganizationやTeamでアクセス制限するのがおすすめです。

設定方法

まず、Zero TrustのIdentity ProviderとしてGitHubを追加します。手順はGitHub - IdP Integration · Cloudflare Zero Trust docsに従ってください。

次に、Cloudflare Pagesを作成します。「Upload Assets」を選び、名前を入力して「Create Project」をクリックします。Preview deploymentsしか使用しないので、アップロードは不要です。

Access Policyの設定

Pagesの「Manage」>「Access policy」から「Enable access policy」ボタンを押すと、Cloudflare AccessのApplicationが作成されます。「Manage Policies」を押して Cloudflare Access の Application 一覧へ

alt

Applicationを選び、「Edit」から「Authentication」タブでIdPをGitHubに設定し、保存します。

「Policies」タブでPolicyの「Edit」から、「Configure Rules」で「GitHub Organization」を選びOrganization(とTeam)を指定します。

GitHub Actionsでデプロイ

GitHub Actionsでcloudflare/pages-actionアクションを使ってPagesにデプロイします。APIトークンの作り方などは cloudflare/pages-action を参考にしてください。

    steps:
      ...
      - name: Publish test report to Cloudflare Pages
        id: publish-report
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: YOUR_ACCOUNT_ID
          projectName: YOUR_PROJECT_NAME
          directory: ./playwright-report/
          workingDirectory: .

レポートのURLを commit status に入れておくと便利です。

    permissions:
      statuses: "write"

    steps:
      ...
      - name: Create commit status
        uses: actions/github-script@v7
        env:
          TEST_STATE: ${{ steps.run-test.outcome }} # error, failure, pending, success のいずれか
          TARGET_URL: ${{ steps.publish-report.outputs.url }}
          SHA: ${{ github.event.pull_request.head.sha || github.event.after }}
        with:
          script: |
            github.rest.repos.createCommitStatus({
              state: process.env.TEST_STATE,
              context: 'playwright',
              description: 'playwright test results',
              owner: context.repo.owner,
              repo: context.repo.repo,
              sha: process.env.SHA,
              target_url: process.env.TARGET_URL,
            });

さらに Cloudflare Pagesの{name}.pages.devドメインに自分だけアクセスできるようにアクセス制限をつける手順 | Web Scratch のようにPreview用でないドメインもアクセス制限しておくと安心です。

まとめ

閲覧する時の認証が少し遅い気がしますがダウンロードしなくてよいのは便利です。GitHub Actions 以外のコストはほとんど無料である点も嬉しいですね。

iOS Safari でキーボード表示時にフッターを固定するもう1つの方法

zenn.dev

エディタを作っていると iOS Safari でキーボードを開いたときの挙動は本当に大変ですよね。 この記事では、キーボードを開いたままスクロールしてもフッターを固定できるようなワークアラウンドを最近見つけたので紹介します。 Safari 16 以降が必要。キーボード開閉時に追従が遅れる挙動は改善しません。

デモは https://mechairoi.github.io/ios-safari-virtual-keyboard/iPhoneiOS Simulator でお試しください。

GitHub - mechairoi/ios-safari-virtual-keyboard

Virtual Keyboard の挙動

Virtual Keyboard を開いたときには以下のようなことが起こっていると推測しています。

  • Visual_Viewport_API で取得できる Visual Viewport のサイズがキーボードを除いた領域のサイズまで小さくなります。
  • レイアウトの計算をするときにはキーボード表示前の Visual Viewport が使われます。vh 単位や fixed, sticky 要素の位置などは表示前と変わりません。
  • スクロールしたときに、キーボード表示前の Visual Viewport のうち画面に表示されている位置が変わることがあり、fixed 要素が動いて見えます。
    • Layout Viewport の上に大小二つの Visual Viewport が重なっていてそこから見ている感じです、ややこしいですね。
  • Layout Viewport の下?に謎の余白が追加されます。下にスクロールし続けると余白が表示されます。
  • キーボードと重なる位置にある入力要素をタップしてキーボードを開いたとき、キーボードに隠れないようにするためかスクロールが発生します。
    • また、キーボードを開いている状態で他の入力要素等にフォーカスが移動したときにもスクロールが発生します。

ワークアラウンド

まず画面全体にスクロール可能な要素を表示します。さらにSafari 16 から使えるようになった overscroll-behavior: contain をすることでスワイプやフリック操作による window のスクロールを防ぎます。window がスクロールしなければ固定した要素が動いて見えることはありません。

#root {
  overflow-y: scroll;
  overscroll-behavior: contain;
  width: 100svw;
  height: 100svh;
}

キーボード開閉時などどうしてもスクロールが発生してしまうタイミングでは JavaScript で計算して要素を移動したりするのはこれまでの方法と変わらないです。

ダイアログなど他の要素を画面に固定したいときは position:sticky を使うか、position:fixedposition:absolute で固定した要素の内側もスクロール可能にしてoverscroll-behavior: contain します。overscroll-behavior: contain しないと固定した要素上でスワイプしたときにwindow がスクロールしてしまいます。また選択範囲のすぐ隣にツールバー出すときはコンテンツに対して固定できれば良いので position:relativeposition:absolute が使えます。

他にも以下のような工夫をしてます。

  • コンテンツが少ないときも overscroll-behavior を効かせるために min-height を指定して微妙にスクロールできるようにします。
  • overscroll-behavior-y:contain があると Safari では pull to refresh できなくなってしまいます。キーボードを閉じているときは pull to refresh できるように overscroll-behavior-y:contain はキーボードが開いている間だけ有効にします。
  • maximum-scale=1ツールバーを非表示機能を使ったタイミングで微妙に拡大されてしまうのを防ぎます。指定していても最近のブラウザではピンチアウトで拡大できると思います…
  • キーボードを開いた時にカーソルがヘッダやフッタの下に隠れてしまうことがあるので調整が必要ですが、少し頑張ればなんとかなるでしょう。

この方法でも実現できるUIに制限があり現実的にはある程度妥協することになりそうです。Safariinteractive-widget=resizes-content に対応してくれるとかなり解決する気がするので、なんとかお願いします。

参考文献

SQLiteでLinderaを使った日本語全文検索

これは はてなエンジニアアドベントカレンダー2023 3日目の記事です。

昨日は id:pokutuna さんの

blog.pokutuna.com

でした。私も若い頃に同僚とGitHub上で白熱してしまい観光名所になってしまっていたような気がします。気を付けていきましょう。


さて、この記事では SQLiteでLinderaを使った日本語全文検索をする話を紹介します。

モチベーション

laiso.hatenablog.com

上の記事でも話題になっているように個人開発ではDBのコストは問題です。同様に全文検索したいときにもコストに頭を悩ませているのではないでしょうか?

たとえば Amazon OpenSearch Service は t2.micro でも東京リージョンでは $20.44 /月/台 から 、冗長化には最低で3台必要です。他にもマネージドなDBで全文検索するにもプラグインや拡張が自由に入れられず日本語の検索に向いたトークナイザが使えなかったりと、安価で楽に運用できる方法はあまりありません。

そこで fly.io の LiteFS Cloud なら10GBまでは $5/月/組織です。fly.io の請求は月 $5未満? *1*2 は $0 になるようなので実質無料(?)*3で冗長構成です 。あとはいい感じに全文検索ができるようになれば解決です。Litestream も良さそうですね。

SQLite のカスタムトークナイザ

SQLite には SQLite FTS5 Extension という全文検索用の Extension があり、これを使うとSQL全文検索できます。

CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
SELECT * FROM email WHERE email MATCH ? ORDER BY bm25(fts);

デフォルトではあまり日本語の検索に向いたトークナイザはありませんがカスタムトークナイザを load して使うことができます。

既存の日本語向けのカスタムトークナイザは手元でうまく動かせなかったりしたので、signalapp/Signal-FTS5-Extension を fork してみました。オリジナルでは Unicode Text Segmentation を使ってますが、日本語を検索したいので lindera-morphology/lindera を使うように書き換えていきます。Lindera は Meilisearch などでも使われている Rust で書かれた形態素解析ライブラリです。動詞の活用や漢数字の正規化もやってくれます。

できたら .load してテーブル作成時にtokenizerを指定して使います

.load "./lib/libsignal_tokenizer" "signal_fts5_tokenizer_init"
CREATE VIRTUAL TABLE email USING fts5(sender, title, body, tokenize="signal_tokenizer");

signalapp/Signal-FTS5-Extension のライセンスは AGPL 3.0 なので忘れず公開しましょう。

https://github.com/mechairoi/Signal-FTS5-Extension

デモ

試しに簡単なWebアプリケーションを書いて、fly.ioの一番小さいマシン(shared 1 cpu, 256MB)にデプロイしてみます。

https://wispy-wildflower-1272.fly.dev/

アクセスが無いときは0台にスケールする設定にしているのでコールドスタートは遅いです。 辞書は lindera-ipadic、データセットは日本語 Wikipedia のサブセット(134万件、335MB) を使っていて、DBのファイルサイズは1021 MBになりました。 アプリケーションのソースコードはこちら

https://github.com/mechairoi/litefs-fts-demo

マッチする記事数が多くなるクエリでは少し遅くなりますが、インクリメンタルサーチでも違和感なく使える速度が出ていると思います。


この記事は はてなエンジニア Advent Calendar 2023 3日目の記事でした。 次は id:mizdra さんです。楽しみですね。

*1:https://community.fly.io/t/questions-on-100-billing-discount/11133

*2:先月はダッシュボードでは $5.06 でしたが 100% Discount でした

*3:無料で使うにはインスタンスのストレージがトータル3GBまで、詳しくは https://fly.io/docs/about/pricing/

ghq/fzf で選んだリポジトリに対応する tmux の session を作ったり探したりするスクリプト

gist41201c4579d17253b7bd26e699c6dccb

  1. リポジトリghq | fzf で選ぶ
  2. リポジトリに対応する session が tmux になければ作る
  3. 対応する session が存在する場合はそれを前面に

といったことを行うスクリプトです。

f:id:mechairoi:20171226225115g:plain

session の current directory がリポジトリのルートに設定されるので、別のシェルを使いたいときは tmux new-window などで新しい端末を開けば cd する必要もありません。 作業リポジトリを切り替えたいときも、このスクリプトを起動して選ぶだけで、以前の session があれば記憶とともに蘇ることでしょう。

gist41201c4579d17253b7bd26e699c6dccb

Wikipedia の作り方 / How to make Wikipedia

こんにちは。アプリケーションエンジニアの id:mechairoi です。
この記事は はてなエンジニアアドベントカレンダー2014 の10日目です。
昨日は id:hatz48 さんの Mackerel と fluentd でサービスの状態を可視化する - Hatena Developer Blog でした。


今日は Wikipedia の作り方について紹介します。

完成イメージ

http://en.wikipedia.org/wiki/Logo_of_Wikipedia#mediaviewer/File:Wikipedia-logo-v2.svg

必要なもの

  • プリンタ、 白の塗料、 セロテープ、 やわらかい鉛筆、 はさみ、のり、お休みの日

作り方

なんとたった7ステップで完成です。

1. くっつける

450mmの半球が届くと想像より大きくてテンションがあがってしまいますが落ち着きます。輪っかにしたセロテープで2つの半球を貼りあわせて球にします。あとで剥がすのでさらっと貼るとよいでしょう。

2. 補助線をひく

くっつけた球にやわらかい鉛筆で補助線をひきます。

f:id:mechairoi:20141210014124j:plain

Wikimedia official marks/About the official Marks - Wikimedia Foundation のいろんな方向からみたWikipediaの様子が参考になります。

f:id:mechairoi:20141210114142j:plain

「W」と「И」の間を通っている線(上図の赤線)が赤道だとすると、南極と北極を結ぶ線を等間隔に10本ひいてから、赤道に平行な線を北半球と南半球に3本づつひきましょう。半球のつなぎ目(上図の青線)と赤道を直交させるとよいでしょう。

やわらかいメジャーやIKEAでもらえる紙の定規があると捗ります。

3. 下書き

スチロールカッターで切断する線を下書きします。

f:id:mechairoi:20141210014128j:plain

ステップ2と同じように Wikimedia official marks/About the official Marks - Wikimedia Foundation をみながら手で雑に書いていきます。ついでにピースに対応する文字も書いておきます。リンク先は各方向から見た様子と展開後とで文字の配置が微妙の異なるので嵌らないように気をつけましょう。

もちろんピースが無いところは自由に想像を膨らませて書きます。

4. スチロールカッターで切る

仮止めしたセロテープを剥がし、スチロールカッターで切ります。

f:id:mechairoi:20141210014132j:plain

一定のペースでカッターを動かすとなめらかに仕上がります。下書きから多少ずれても問題ありません。球の中心から出るレーザーで切るようなイメージでカッターを動かすとやりやすいかもしれません。スチロールの厚みでピースの噛み合わせがうまくいかないところもあるので断面の内側を削って調整します。削り過ぎると安定しなくなるのでほどほどに。半球の境界と重なったピースはセロテープでくっつけます。

ここまでくればパズルを組み立てて遊ぶのもよいでしょう。

f:id:mechairoi:20141210014136j:plain

5. 白く塗る

下書きで汚れたピースの表面を塗料で白く塗って綺麗にします。塗る前にピースの裏に対応する文字を書いておくと後で楽です。

f:id:mechairoi:20141210014140j:plain

6. 文字を入れる

Wikimedia official marks/About the official Marks - Wikimedia Foundation にある文字のSVGInkscape とかで適当に並べて印刷します。印刷した文字をはさみで切ってのりで浮かないように貼ります。文字のまわりに白い部分が残ってもほとんど目立たないのでシュッとやるとよいでしょう。

7. 装う

かぶって仮装パーティに向かうもよし、分解してコンパクトなまま持ち込んで現地で組み立てるもよしです。お気に入りのTシャツとコーディネートするのもオススメです。

f:id:mechairoi:20141031105523j:plain

おわりに

はてなでは、パーティを一緒に盛り上げてくれる方やScalaエンジニアも募集しています。

Wikipedia への寄付もお願いします。

Ways to Give - Wikimedia Foundation

明日は id:shimobayashi さんの予定です。よろしくお願いします!

追記

Wikipedia の作り方 - *iroi*

塗装の塗料なにつかったんだろう。ポスカ?

2014/12/10 13:40

最初は

のマット ホワイトつや消しでやってたんですが、量が足りなかったので2年前に使った

の残りを使いました!