余白

https://blog.lacolaco.net/ に移転しました

Angular CDK drag-and-drop の紹介

こんにちは。

この記事ではAngular CDKの次期アップデートで提供される、 drag-and-drop 機能を紹介します。 執筆時点ではまだnpmパッケージとして公開されていないので、一般に利用できるまでにはもうしばらくかかりますが、 もし早く使いたい方は、次のコマンドで開発版ビルドをインストールしましょう。 なお、開発版ビルドですので自己責任でお願いします。

$ yarn add angular/cdk-builds

CDK drag-and-drop

drag-and-dropはその名のとおり、UI上でのドラッグアンドドロップ操作をサポートするものです。

@angular/cdk/drag-drop パッケージから提供される DragDropModule をインポートすると、次の2つのディレクティブ、コンポーネントが利用できます。

cdkDrag ディレクティブ

cdkDragディレクティブは、ドラッグされる要素を指定するディレクティブです。このディレクティブを付けられた要素は画面上で自由に位置を変えられます。

たとえば ng new 直後のテンプレートHTMLで、 li要素に cdkDragディレクティブを付与すると、次のようになります。 (わかりやすさのために li要素にCSSでスタイルを付与しています)

<ul>
  <li cdkDrag>
    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
  </li>
  <li cdkDrag>
    <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
  </li>
  <li cdkDrag>
    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
  </li>
</ul>

f:id:lacolaco:20180829200010g:plain

cdkDrag ディレクティブだけを使うと、何の制約もなく自由に移動することができました。

cdk-dropコンポーネント

このままでは動いて面白い以上の意味がないので、cdk-dropコンポーネントを使います。 cdk-dropコンポーネントは、cdkDragディレクティブをグルーピングし、動きに制限をつけて、限られた領域内でだけ移動できるようにします。

たとえば、ul要素の外側に <cdk-drop> コンポーネントを配置すると、ulの内部でだけ移動できるようになり、移動中は並び替えが行われるようになります。

<cdk-drop>
<ul>
  <li cdkDrag>
    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
  </li>
  <li cdkDrag>
    <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
  </li>
  <li cdkDrag>
    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
  </li>
</ul>
</cdk-drop>

f:id:lacolaco:20180829200735g:plain

見てのとおり、 <cdk-drop>タグの内側でだけ並べ替えが行われるようになりましたが、ドロップしてしまうともとの状態に戻ります。 これはcdk-dropコンポーネントの仕様で、ドラッグアンドドロップが終了すると、その内部のcdkDragの順序は復元されます。 ただし、ドラッグアンドドロップ終了時にはdroppedイベントが発行されていて、このイベントをもとにコンポーネント側からデータモデルを更新することで、 ドラッグアンドドロップによる並べ替えを実現できます。

並べ替え

並べ替えをおこなうためには、リストをコンポーネント側で管理する必要があります。これまでは適当なli要素を使っていましたが、AppComponentに次のようなlistプロパティをもたせます。

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  list = ['まぐろ', 'サーモン', 'えび'];
}

そしてテンプレートを次のように変更し、listプロパティの要素を繰り返し表示します。先程までと同じように、<cdk-drop>タグのなかで繰り返される並べ替えの対象にcdkDragディレクティブを付与します。

<h2>好きなネタ</h2>
<cdk-drop [data]="list" (dropped)="drop($event)">
  <ul>
    <li *ngFor="let item of list" cdkDrag>
      <h2>{{item}}</h2>
    </li>
  </ul>
</cdk-drop>

ポイントは <cdk-drop [data]="list" (dropped)="drop($event)"> です。[data]プロパティには並べ替えの対象となるデータモデルを渡します。 次に、(dropped)="drop($event)"では、droppedイベントハンドラdropメソッドを呼び出しています。 dropメソッドは次のように記述します。

import {
  CdkDragDrop,
  moveItemInArray,
} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  list = ['まぐろ', 'サーモン', 'えび'];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(
      event.container.data,
      event.previousIndex,
      event.currentIndex
    );
  }
}

CdkDragDrop<string[]>は、dropイベントの引数の型です。ジェネリックstring[]は並べ替え対象の配列の型を表しています。

moveItemInArray関数は、基本的な配列の並べ替えを行ってくれるCDKの機能です。中身は単なるJavaScriptの配列の並べ替えですが、Angularチームによる実装にまかせておくのが安心だと思います。

export function moveItemInArray<T = any>(array: T[], fromIndex: number, toIndex: number): void {
  const from = clamp(fromIndex, array.length - 1);
  const to = clamp(toIndex, array.length - 1);

  if (from === to) {
    return;
  }

  const target = array[from];
  const delta = to < from ? -1 : 1;

  for (let i = from; i !== to; i += delta) {
    array[i] = array[i + delta];
  }

  array[to] = target;
}

f:id:lacolaco:20180829210528g:plain

これで、dropイベントによって配列を並べ替えられるようになりました。

複数のcdk-dropでグルーピングをおこなう

複数のグループを跨いだ並べ替えも可能です。先程のAppComponentを次のように変更します。listプロパティをlikeプロパティに改名し、新しくunlikeプロパティを追加します。

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  like = ['まぐろ', 'サーモン', 'えび'];
  unlike = ['数の子', 'たくあん'];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(
      event.container.data,
      event.previousIndex,
      event.currentIndex
    );
  }
}

テンプレートでは、likeプロパティとunlikeプロパティの両方で同じようにcdk-dropによる並べ替えができるようにします。

<h2>好きなネタ</h2>
<cdk-drop [data]="like" (dropped)="drop($event)">
  <ul>
    <li *ngFor="let item of like" cdkDrag>
      <h2>{{item}}</h2>
    </li>
  </ul>
</cdk-drop>

<h2>好きじゃないネタ</h2>
<cdk-drop [data]="unlike" (dropped)="drop($event)">
  <ul>
    <li *ngFor="let item of unlike" cdkDrag>
      <h2>{{item}}</h2>
    </li>
  </ul>
</cdk-drop>

ここまでは先程と変わりません。ここから、この2つのグループを結合します。 並べ替えグループを結合するには、cdk-dropconnectToプロパティを使います。このプロパティに結合の対象となるグループの参照を渡します。

<h2>好きなネタ</h2>
<cdk-drop #dropLike [data]="like" (dropped)="drop($event)" [connectedTo]="[dropUnlike]">
  <ul>
    <li *ngFor="let item of like" cdkDrag>
      <h2>{{item}}</h2>
    </li>
  </ul>
</cdk-drop>

<h2>好きじゃないネタ</h2>
<cdk-drop #dropUnlike [data]="unlike" (dropped)="drop($event)" [connectedTo]="[dropLike]">
  <ul>
    <li *ngFor="let item of unlike" cdkDrag>
      <h2>{{item}}</h2>
    </li>
  </ul>
</cdk-drop>

さらに、AppComponentのdropメソッドで、グループを跨いでいた場合の処理を追加します。 この場合も汎用的なグループ移動機能をサポートするtransferArrayItem関数が提供されているので、それを使います。

グループを跨いだ移動かどうかは event.previousContainerevent.container を比較して判定できます。 次のように書けば、一致する場合は配列内での移動を、一致しない場合はグループを越えた移動をおこないます。

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
    }
  }

f:id:lacolaco:20180829212028g:plain

これで複数のグループを跨いだドラッグアンドドロップによる並べ替えができるようになりました。

CSSによるスタイリング

最後に、CDKのdrag-dropが提供するスタイリングのためのCSSクラスを紹介します。

.cdk-drag.placeholder

.cdk-drag.placeholderクラスは、ドラッグされている要素のプレースホルダ部分につけられるCSSクラスです。たとえばここを次のように見えなくすることで自然な挿入を演出できます。

.cdk-drag-placeholder {
  opacity: 0;
}

f:id:lacolaco:20180829212513g:plain

.cdk-drag-preview

.cdk-drag-previewクラスは、ドラッグされている要素のプレビュー部分(動かしている部分)につけられるCSSクラスです。たとえば次のように半透明にすることで自然な挿入を演出できます。

.cdk-drag-preview {
  box-sizing: border-box;
  opacity: 0.5;
}

f:id:lacolaco:20180829212904g:plain

この他にもいくつかCSSクラスがあります。詳しくはスタイリングに関するドキュメントを参照してください。

まとめ

CDKのアップデートはAngular v7のリリースと合わせておこなわれるだろうと見られています。 楽しみに待ちましょう。

ng-sake #12 を開催しましたレポート

8月6日にng-sake #12を開催しました!

ng-sake.connpass.com

f:id:lacolaco:20180807220110p:plain

数字

今回の出席者数は 12人 + スタッフ 3人 という構成でした。

参加費は¥1,500、今回もKyashによる集金をオプションで用意しましたが、6人がKyashで送金してもらえました。ご協力ありがとうございました!

内容

会場

株式会社トレタさんのオフィスを使わせていただきました。初めてのトレタでしたが広くてよかったです。さすが五反田バレー。

jp.techcrunch.com

フード・ドリンク

お酒はいつもどおりカクヤスでプレミアムモルツと氷結やほろよいなどを用意しました。

フードは、今回は宅配ピザをやめて、サンドイッチを提供してみました。 みなさんピザはもう飽きてると思うんですよ。

写真撮ろうと思ったらいくつか食べられてました。 f:id:lacolaco:20180807221851p:plain

サンドイッチはフレッシュデリというお店のデリバリーを使いました。おいしかったです。

www.freshdeli.jp

ライトニングトーク

今回は3人がライトニングトークを発表しました。

  • kouさん: ngrx/effectsの話
  • t2さん: Angularをlit-htmlで書けるようにする
  • laco: jsx-to-ivyの続報

いつもよりもさらに一般受けしない話題で、ng-sakeらしいLTでした。

ディスカッション

今回のディスカッションボードはこちら

app.sli.do

反省点

  • YouTube配信しようと思ったけど機材の準備に失敗した
    • 家で練習したので次回はうまくいけばいいな…
  • ソフトドリンクを用意してなかった
    • カクヤスの注文にいれておきたい

最近ng-sakeの開催頻度が低下しているので、もともとの隔月くらいに戻したいなと思っています。 できれば10月〜11月くらいにまたやりますので、興味があれば ng-japan Slackの #ng-sake チャンネルで更新情報をウォッチしてください!

Join ng-japan on Slack!

Angularコンポーネントのスタイルにemotionを使う

追記

型安全にCSSのオブジェクトを書きたいというだけならNgStyleとcsstypeを使うだけでもよさそうだ。

github.com

f:id:lacolaco:20180728090201p:plain

emotionを使うことによる利点は、

  • CSSクラスにシリアライズされるので、テンプレート中で評価対象が文字列となり、Change Detectionのパフォーマンス上で有利

くらいなものか。


今日の境界遊び。CSS in JSをAngularでやりたかった。 常識のある方は真似しないほうがよい。

f:id:lacolaco:20180728002419p:plain

今回使ったのは https://emotion.sh/.

Angularで時々困るのはstylesの中にデータバインディングを置きたいケース。 たとえば、フォームの入力に応じて動的にフォントサイズを変えるようなケースを考える。

image

emotionの css 関数は、与えたCSSスタイルシートシリアライズしてユニークなCSSクラス名に変換してくれる。 AngularのコンポーネントはHTML要素と1:1に対応するので、 [className] プロパティにバインディングすればemotionで生成したクラスを適用できる。

helloClassName$ プロパティは、フォントサイズに応じたCSSスタイルシートCSSクラス名に変換したObservableである。

  helloClassName$ = this.form.valueChanges.pipe(
    map(({ fontSize }) => css({ fontSize }))
  );

これをテンプレート中で次のように使えば、emotionによって動的に生成されたクラスを任意の要素に適用できる。

<hello [className]="helloClassName$ | async" [name]="name"></hello>

ところで、Angularの CommonModule (@angular/common) は、 NgClassNgStyle という2つのディレクティブを提供している。

https://angular.io/api/common/NgStyle

https://angular.io/api/common/NgClass

classNameを使わずとも、次のように書くこともできる。NgClassは文字列以外にも文字列の配列やオブジェクトを受け取れる以外には、本質的にclassNameと何も違いはない。

<hello [ngClass]="helloClassName$ | async" [name]="name"></hello>

NgStyleを使う場合は、emotionではなく生のスタイルシートっぽいオブジェクトを渡すことになる。 本来AngularだけでCSS-in-JSやろうとするとこのAPIになるわけだが、emotionだとcss関数の引数オブジェクトにTypeScript型定義もあるし嬉しいのでは?という目論見がある。 あとemotionなら同じスタイルなら同じクラスになり、キャッシュの仕組みが強いっぽいので、パフォーマンス良くなるかもしれない。

https://emotion.sh/docs/typescript

  helloStyle$ = this.form.valueChanges.pipe(
    map(({ fontSize }) => ({ fontSize: `${fontSize}px` }))
  );

<hello [ngStyle]="helloStyle$ | async" [name]="name"></hello>

すべて同じ動きとなるので好みで選べばよいが、個人的にはReactとの対称性を考えて[className]でよいのでは?と感じる。

実際に動くサンプルは次の通り。

stackblitz.com

Reactの場合、classNameはHTML要素に対応するコンポーネントにしか使えないが、Angularの場合すべてのコンポーネントはHTML要素に対応付けられるので、テンプレート中で親からclassNameプロパティにバインディングするだけで子コンポーネント側でなにもしなくてもよいのは、比較的楽だなと思った。 しかしemotionで一番やりたいstyled-componentがReactしか使えないので、これをどうにかしてみたい。

GraphQLとRESTfulについて今日考えてたこと Backend for Usecase/Resourceについて

DISCLAIMER: これは本当にただのメモ書きで、これがベストプラクティスだとかいう話ではないので、同じようなことを考えてる人いたら今度議論しましょうよ、って程度の話の種。

GraphQLを使うべきスポット、RESTfulが好ましいスポットについて今日ぼんやり考えていて、なんとなく言語化ができる気がするので文字起こししてみる。

Backend for UsecaseとBackend for Resource

バックエンドのAPIには2種類あって、

  • 「データ」を構成する「リソース」を提供するもの
  • アプリケーションの「ユースケース」がもつシナリオのなかで登場する「データ」部分を埋めるためのもの

を区別することが必要そう、と思っている。

まず前者を Backend for Resource (BFR)と呼ぶことにする。これはわかりやすくて、これはまさしくRESTfulそのもの。 RDBやそうじゃないDB、あるいはファイルストレージかもしれない永続化されたリソースにURIを付与し、外部からアクセス可能にするのが役目。 BFRの設計はリソースのスキーマに依存する。

次の後者を Backend for Usecase (BFU)と呼ぶことにする。これがGraphQLが向いていそうなところ。 フロントエンドのユースケースのなかで、永続化されたデータが必要になった時の問い合わせ先。 つまり、BFUの設計はフロントエンドのユースケースに依存する。

f:id:lacolaco:20180712230727p:plain

なんで分けたいのか

BFFの文脈でいろんな理由が挙げられているとは思うが、個人的にはそのシステム自体のライフサイクルの違いに合わせた分割が、柔軟な開発サイクルを支えてくれるんじゃないかと思っている。

フロントエンドはユーザーが触れる最前線にあるので、UXの改善、ユースケースの再設計は成長段階のプロダクトにおいて頻繁に発生する。 フロントエンドについては一旦諦めるとして、バックエンドの中には、そうしたユースケースの再設計に巻き込まれてもしょうがない部分とそうでもなさそうな部分がある。 前者が、「ユースケースに対するデータプロバイダー」としてのバックエンド、つまり上述のBFUで、後者が「リソースコンテナ」としてのバックエンド、つまりBFRじゃないかと思う。

ユースケースが再設計されたとしても、リソース自体に変化があるわけじゃないなら、その部分は残したい。ユースケースに依存する範囲を明確に分けたい、というのが僕のBFU/BFRに対するモチベーションの大きな部分。 その境界でバックエンドのサーバーごと分けてしまうというのは、昨今のコンテナ化の流れにもマッチしてそうな気がする。

なんでBFUはGraphQLがよさそうか

フロントエンドのユースケースの中では目的が必ず存在してデータ要求が生まれる。 すごく単純化したら「ID=2のUserのデータがほしい」ではなく「ID=2のユーザーの名前を表示するためのデータがほしい」という話で、そのユースケースにとってはユーザーの名前がUserテーブルのNameカラムに保存されているとかそんなことはどうでもよく、ただユーザーの名前がほしい、というニーズだけがある。

BFUがない場合は、フロントエンドのなかでBFRから受け取ったデータをユースケースに合った形に自分で整形することになる。あるいはBFRが拡張されてユースケースを受け入れはじめるかのどちらか。

BFUがある場合は、フロントエンドはBFUにユースケースを投げつける。ユースケースを定義するのはフロントエンドの仕事なので、GraphQLのようにフロントエンドでほしいデータの形を定義するのがマッチするはず。

フロントエンド、BFU、BFRが分かれたとき、それぞれのシステムのライフサイクルは次のようになる。

f:id:lacolaco:20180713000156p:plain

悲しいことにフロントエンドはまっさきに壊されるが、それは宿命として諦めるとして、BFUがあることでBFRは長生きすることができる。

やっちゃいけないこと

BFUは「ユースケースに対するデータプロバイダー」としてのバックエンドであるから、常に実装の要件はフロントエンドのユースケースによって決定される。 そのため、次のことはやってはいけない。

  • 「DBからデータが取れるから」というボトムアップで実装すること
    • 本来依存しなくてもよい部分にBFRへの依存が発生する。
  • 複数のユースケースをひとつのqueryで対応しようとする
    • たまたまスキーマが似ているだけでDRY的にまとめるのは本来関係をもつはずのないユースケース間の関係を生んでしまう

BFUは常にアプリケーションのために存在するが、BFRはアプリケーションが無くても成立する。


とりあえずここまで。今日10分くらい会社でryopekoさんと雑談してたネタなのであまりしっかり練った考えではないけど、こんな感じのシステムを作って試してみたい。

Kaizen Platformに入社して1年が経ちました(在職エントリ)

Inspired by

punchdrunker.hatenablog.jp

おはようございます、lacoです。 早いもので、Kaizen Platformに入社してから1年になります。

これは退職したときの記事です。

lacolaco.hatenablog.com

そしてこれが入社後3ヶ月の記事ですね。こんな記事書いてたこと完全に忘れてました。

lacolaco.hatenablog.com

1年間なにやったか

AngularJSアプリケーションのチューニング

入社後の初めてのプルリクエストはAngularJSのチューニングで、めっちゃ遅かったドロップダウンリストの描画を7倍くらい速くしました。

f:id:lacolaco:20180626233219p:plain

その後もしばらく、パフォーマンスに問題がある画面の計測と改修に手を付けていました。

フロントエンド開発チームの体制づくり

僕が入って4人になったフロントエンドチームですが、新規の小規模アプリ開発をいくつか並行して進めることにしました。 4人がそれぞれ個人戦のようになる上で、守るべき約束事を決めようということで、「フロントエンドマイクロアプリケーション憲章」というものを制定しました。

その中で定めたコードフォーマットの決まりのために、Prettierの設定を共通化して公開しました。 いまでも大活躍しています。

lacolaco.hatenablog.com

この憲章は4月に行ったフロントエンドチームの福岡合宿で半年ぶりにアップデートされました。その記事はKaizen Platformのエンジニアブログでまとめられています。

developer.kaizenplatform.com

憲章があることで、逆に心理的に安全に冒険できる環境が作れているんじゃないかと思っています。「公園には柵があるから自由に遊べる」、というのが最近の口癖。

AngularでSPA開発

当然といえば当然ですが僕もSPA開発やってまして、さっき書いた 新規の小規模アプリ開発 ってやつをAngular使って2つほど開発してます。 設計を試行錯誤したり、アプリケーションの要件が変わったりいろいろありますが、TypeScriptでよかった〜となる場面がとても多いですね。 コンパイラによる静的検査がなければ安心してリファクタリングできません。

ワークフローづくり

2つのうち今年の頭から始まったものは、実験的にバックエンドとフロントエンドの間の通信にGraphQLを採用しています。 API Spec中心開発と銘打って、リモートワークを前提とした非同期的な分業をスムーズにするためのワークフローを考えて、実践してみたのは楽しかったし学びも多かったです。

developer.kaizenplatform.com

同じような文脈で、フロントエンドとバックエンドの開発者間だけじゃなく、デザイナーとの分業においても仕事を型化してワークフローを定義するとよいんじゃないか、ということでそういったことも今年に入って取り組んでいます。 デザイナーの中に閉じてブラックボックス化していたデザインプロセスを分解、整理して、プロダクト設計原則という形に再構成しました。

developer.kaizenplatform.com

ここ1ヶ月くらいは、この設計原則に従って、いままでだったらデザイナーに頼んでいたタスクを、僕が代わりに原則に沿って進めることを試しています。 ステップごとに進めばいいのでデザイン初心者でも安心して仕事ができます。やはり型があると安心。

ver 1.0としているので、うまくいくところ、いかないところをもとにバージョンアップを続けていくつもりです。

オープンコミュニティ活動

おそらくお気づきかとは思いますが1年間まったく滞ることなくAngular日本ユーザー会をはじめとしたいろんなオープンコミュニティの活動つづけてました。 大きめのイベントだけ拾ってみてもこんだけあちこちいろいろやってて、露出の多い1年でした。

f:id:lacolaco:20180627001517p:plain

本の監修なんかも経験しました。

lacolaco.hatenablog.com

やりたかったこと

1年前の記事で、今後やりたいことを書いていました。

Angular日本ユーザー会をはじめとしたコミュニティ活動は今後も継続したい

できていますね

OSSコントリビューション増やしていきたい

できているんじゃないかと思います

JavaScriptを駆逐したい

きびしい

Flutterに夢を見たい

リリースプレビューおめでとう!!!!!!!!!!🎉🎉🎉🎉

ということで概ね達成している気がします。

1年やってきて

思ってた以上にKaizen Platformという会社は馴染んでいて、日々楽しく仕事をしています。 プログラミングするのも好きなんだけど、どちらかというとエンジニアリングというか、どうやったら生産性を向上できるのかっていうのを考えるのが好きなんだなーというのに、この1年で気づきました。 知らない自分を発見できた感じ。

これはチームのマネージャーになりたいとかそういうわけじゃないんですよね。 むしろデベロッパーがどうすれば生産性上がるかってことは、デベロッパー自身も当事者として考えなきゃいけないところだと思うし、考えなきゃいけないっていうか、考えると楽しいですよ。 自分を歯車に見立てて、うまい回し方や他の歯車との噛み合わせ方を考える、パズルゲームみたいな感じがします。

って感じで、脳みその使い方が1年間でだいぶ変わったなーと、自分なりに思います。

これから

特になにか節目ということでもないので今やってることは引き続きやっていきます。 あと、自分の働き方をいろいろ実験してみようと思ってて、有り体に言えば副業っぽいんですが、今年のテーマとして「OSSのように働く」ってのを目指してます。

OSSにコントリビューションするのって、面白そうなリポジトリを見つけて、自分が貢献できそうな部分を見つけて、そこにアプローチしていくじゃないですか。 そんな感じで、面白そうな事業を見つけて、自分が貢献できそうかどうか自分からアプローチして、コントリビューションするように仕事ができたら楽しいんじゃないかって考えてます。

せっかく新しい自由な働き方を推進してる会社に所属しているので、自分自身で試行錯誤していきます。

まだ実際にできているわけじゃないんですけど、もしそういうことができたら楽しいなーと思ってるし、続報があればまたブログ書きます。

ところで、試しにPatreon登録してみました。$1から投げられるのでぜひよろしくおねがいします。

www.patreon.com

はじめた経緯などはこちらの記事に書いています。

lacolaco.hatenablog.com

それでは。