-
Notifications
You must be signed in to change notification settings - Fork 0
[package] Chapter APIを導入し、elm-emakiをより楽に記述できるようにする #34
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Main.elmがEmakiモジュールの新APIで期待通りに動きさえすれば、ドキュメントや細かい調整は別PRでも良いですよ!
新しいAPIはexampleをほぼ全部rewriteすることになりそうで難しそう。小さいPRで徐々に移行するプランとして
|
4までは完遂させたい |
This comment was marked as resolved.
This comment was marked as resolved.
📝 いい感じの実装ができたからこのブランチのコードを分割してreviewに回す |
module Emaki exposing (Model, Msg, runChapter) | ||
|
||
import Browser | ||
import Emaki.Chapter as Chapter exposing (Chapter) | ||
import Html.Styled as Styled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
今はrunChapter
しかないけど、いつかrunChapters
みたいな感じのものができるはず
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
トップレベルのAPIについては、おそらくはBrowser.sandboxと対応させてsandboxにするのが良いなぁと思っています。実装するタイミングで適切なほうを選べば良いけど。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
入力となる型が { init: model, view : model -> viewLike, update : msg -> model -> model }
な構造でしたら名前をsandboxにしても良いと思うのですが、emakiを使う人がやりたいのは組み立てたEmakiを実行できるProgramに変換することなので、runChapter
もしくはbuild
あたりが適切かなぁと思いました
逆にsandboxだとBrowser.sandboxと入力が全然違うのになんでsandboxという名前なのかで混乱しちゃいそうな気がしています
@@ -0,0 +1,68 @@ | |||
module Emaki.Chapter exposing (Chapter, Model, Msg, chapter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
emakiで閲覧したいview関数と、それの入力を操作するためのパーツを組み合わせたデータをChapterと呼ぶことにしました。ChapterもTEAっぽくなっています
type alias Chapter props = | ||
ChapterInternal.Chapter props | ||
|
||
|
||
type alias Model props = | ||
ChapterInternal.Model props | ||
|
||
|
||
type alias Msg = | ||
ChapterInternal.Msg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
回りくどい実装ですが、Internalモジュールにしてelm.jsonでexposeしないようにするとライブラリ利用者がInternalモジュールを使って悪さできないようになります
中身の構造を利用者に見せないことで、emakiライブラリ開発者は好きに内部構造をリファクタできるようしつつ、利用者は中身の構造に誤って依存するようなことがなくなります
type Control props | ||
= Control | ||
{ init : Value | ||
, view : Value -> Html Value | ||
, updateProp : Value -> props -> props | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
全てのコントロールは、
- コントロールの状態として
Value
を持っていて - 状態からコントロールの表示する方法を知っていて
- コントロールの状態が viewの引数をどう変化させるべきかを知っている
という定義にしました。
その結果、Controlは全て同じ型になるため、ChapterでList (Control msg)として持たせられるようになるため、利用者がコントロールを追加/削除する時にコードのあちこちを修正する必要はなくなります
package/src/Emaki/Chapter.elm
Outdated
chapterView : Model props -> Styled.Html Msg | ||
chapterView { viewState, controlsState } = | ||
Styled.div [] | ||
[ Styled.fromUnstyled (view viewState) | ||
, controlsState | ||
|> JD.decodeValue (JD.list JD.value) | ||
|> Result.withDefault controlInits | ||
|> List.map2 (<|) controlViews | ||
|> List.map (Styled.li [] << List.singleton) | ||
|> List.indexedMap (\i -> Styled.map (ChapterInternal.UpdateControlAt i)) | ||
|> Styled.ul [] | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
雑にdivで作っちゃっていますが、コントロール対象のviewとコントロールをそれぞれ表示しているような実装です
update (ChapterInternal.UpdateControlAt idx newValue) { viewState, controlsState } = | ||
{ viewState = Maybe.withDefault (always identity) (Array.get idx controlOnChanges) newValue viewState | ||
, controlsState = | ||
controlsState | ||
|> JD.decodeValue (JD.array JD.value) | ||
|> Result.map (Array.set idx newValue >> JE.array identity) | ||
|> Result.withDefault controlsState | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
idx番目のコントロールが更新されたことがmsgからわかるので、jsonデコードして該当箇所を更新します。
この辺りはライブラリ利用者が直接Chapterの状態をいじると平気で壊れる危ない実装なのですが、ライブラリからexportしないことで利用者に触らせないようにします
計画が壮大すぎたので、壮大な計画はissue側に移し、このPRでやったことについての説明をPRのdescriptionに記載します〜 |
, controlsState | ||
|> JD.decodeValue (JD.list JD.value) | ||
|> Result.withDefault controlInits | ||
|> List.map2 (<|) controlViews |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
かっこいいね😎
import Html exposing (Html) | ||
import Html.Styled as Styled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
どっちが良いというものではないんだけど、現状はこのままが良いと思う反面、気持ちの面では将来的にas Html
にしたくなるかもしれない点だけメモしておきます。
```elm | ||
import Emaki exposing (runChapter) | ||
import Emaki.Chapter exposing (chapter) | ||
import Emaki.Chapter.Control as Control |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
現時点ではこれでOK。
ただし、ChapterとControlの結びつきが少し強すぎるようにも見える。
ControlはChapterに対して作用するものではないので。
ここの関係性は今後の開発に応じて変化しそうだなと。
ちなみに、現時点では将来的に↓のような階層構造になると考えています。
Emakiアプリケーション
> List Chapter
> List Section
> コンテンツ(Markdownテキストや、現playgroundに相当する小さいアプリケーションなど)
> List Control
- 小さいアプリケーションの層には他のアプリ(例えばキーノートなど)が入る可能性がある
- 敢えてplaygroundのみに機能を限定する可能性はあるが、その場合にも
Emaki.Chapter.Control
だと若干不自然?
(細かく見ていくと「Sectionの名前が他と被りやすいので採用しない」などもあり得ますが、いったん気にしない)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type alias Emaki =
{ chapters : List Chapters
, sections : List Section
, content : MarkdownText | PlaygroundApp
, control : List Control
}
ということでしょうか? あんまりピンとこなかったのでオフラインで会うときにしっかり認識合わせしたいです!
今回の自分の実装はEmaki.Chapter名前空間っぽく作ったので、最終的なモジュール構造が決まり次第これをバラして埋め込んでいければ良いかなと思っているので、このままmainに取り込ませていただきます 🚀
Co-authored-by: Yoshitaka Totsuka <y047aka@users.noreply.github.com>
なぜやるのか
既存の
Emaki.Control
は、uiをコントロールするためのviewは提供していますが、コントロールするviewの状態と、コントロールする対象のviewのモデルの統合やデータの結合をユーザが記述しなければならず、利用者にとって負荷が高い状態になっています。このPRで作成したChapter APIは、コントロールの集まりとコントロール対象のviewを組にした
Chapter
という概念を導入し、controlの状態管理のためのコードをユーザが自前実装をしなくて良くなります。壮大な全体の計画は下記issue参照
このPRで実現したことサマリ
Emaki
モジュールがチャプターを実行するためのrunChapter
をexportEmaki.Chapter
が、viewとコントロールのリストを組みにした構造Chapter
データ型と、それを作るためのchapter
関数をexportEmaki.Chapter.Control
chapterに渡すためのコントロールたちがたくさんあるモジュールにする予定。今はtext inputの雑実装のみある詳細はコメントに記述しました!
このPRがmergeされた後にやる予定