{"id":17797573,"url":"https://github.com/wolfadex/earl-grey","last_synced_at":"2025-04-02T03:21:44.726Z","repository":{"id":205192210,"uuid":"713650158","full_name":"wolfadex/earl-grey","owner":"wolfadex","description":null,"archived":false,"fork":false,"pushed_at":"2023-11-19T16:26:53.000Z","size":178,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-07T18:15:08.871Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elm","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wolfadex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-11-03T00:34:20.000Z","updated_at":"2023-11-03T00:34:27.000Z","dependencies_parsed_at":"2023-11-19T17:39:49.533Z","dependency_job_id":null,"html_url":"https://github.com/wolfadex/earl-grey","commit_stats":null,"previous_names":["wolfadex/earl-grey"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfadex%2Fearl-grey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfadex%2Fearl-grey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfadex%2Fearl-grey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wolfadex%2Fearl-grey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wolfadex","download_url":"https://codeload.github.com/wolfadex/earl-grey/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246747977,"owners_count":20827243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-27T11:56:22.096Z","updated_at":"2025-04-02T03:21:44.710Z","avatar_url":"https://github.com/wolfadex.png","language":"Elm","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Earl Grey\n\nAn **experimental** Elm package to make building _apps_ easier.\n\nInspired by projects like [Elm Land](https://elm.land/) and [React Router](https://reactrouter.com), my thought was \"what if everything was TEA?\". This has led me to the idea of a \"component\" setup in Elm where each component is a self-contained TEA app **with effects**.\n\n## The gist of it:\n\n```elm\nmodule MyComponent exposing (branch)\n\nimport Tea exposing (Tea)\n\n\nbranch : Tea.Branch Flags Model Msg Effect\nbranch =\n    { init = init\n    , subscriptions = subscriptions\n    , update = update\n    , urlChanged = urlChanged\n    , view = view\n    }\n\n\ninit : Tea.Context Flags -\u003e Tea Flags Model Msg Effect\n\n\nsubscriptions : Tea.Context Flags -\u003e Model -\u003e Sub Msg\n\n\nupdate : Tea.Context Flags -\u003e Msg -\u003e Model -\u003e Tea Flags Model Msg Effect\n\n\nurlChanged : Tea.Context Flags -\u003e Model -\u003e Tea Flags Model Msg Effect\n\n\nview : Tea.Context Flags -\u003e Model -\u003e Html Msg\n\n\n\ntype alias Flags = ...\n\ntype alias Model = ...\n\ntype Msg = ...\n\ntype Effect = ...\n```\n\n### Things to note about this:\n\n#### Repition\n\nThere's a lot of type repition going on here, so I have a module like so\n\n```elm\nmodule Context exposing\n    ( Branch\n    , Context\n    , Flags\n    , MyTea\n    , Route\n    )\n\nimport Tea exposing (Tea)\n\n\ntype alias Flags =\n    -- Whatever you want\n    {}\n\n\ntype alias Context =\n    Tea.Context Flags\n\n\ntype alias Branch model msg effect =\n    Tea.Branch Flags model msg effect\n\n{-| We haven't talked about this yet,\nbut it's coming up soon\n-}\ntype alias Route model msg effect =\n    Tea.Route Flags model msg effect\n\n\ntype alias MyTea model msg effect =\n    Tea Flags model msg effect\n\n```\n\n#### What's `urlChanged`?\n\n**NOTE:** This is likely to change to accomodate things beyond URLs changing. E.g. what if your app has dynamic feature flags and those change? It'd be nice to have anything in the `Context` trigger an update.\n\nThis gets called any time the URL has changed **\u0026** your component's `init` has already been called. It's a way for your component to react to the URL changing without having to re-initialize, and without defining an interal hook like:\n\n```elm\nmodule MyComponent epxosing (Msg, urlChanged)\n\ntype Msg = UrlChanged Url\n\nurlChanged : Url -\u003e Msg\nurlChange = UrlChanged\n```\n\n```elm\nmodule Parent epxosing (..)\n\nimport MyComponent\n\nupdate msg model =\n    case msg of\n        UrlChanged url -\u003e\n            MyComponent.update\n                (MyComponent.urlChanged url)\n                model.myComponent\n                |\u003e ...\n```\n\nThis is hard to remember and track, as well as reptitive to check on every time you want to use a child component.\n\n## Routing\n\n**NOTE:** This portion is the most experimental and likely to change\n\nRouting will look very similar, with a slight modification, a path is specified! Below is an example of the setup for a component that only renders when the URL is `*/login`\n\n```elm\nmodule MyComponent exposing (branch)\n\nimport Tea exposing (Tea)\n\n\nbranch : Context.Route InternalModel Msg Effect\nbranch =\n    Tea.branch\n        { path = [ \"login\" ]\n        }\n        { init = init\n        , subscriptions = subscriptions\n        , update = update\n        , urlChanged = urlChanged\n        , view = view\n        }\n\ntype alias Model =\n    Tea.RouteModel InternalModel\n\n\ninit : Tea.Context Flags -\u003e Tea Flags InternalModel Msg Effect\n\n\nsubscriptions : Tea.Context Flags -\u003e InternalModel -\u003e Sub Msg\n\n\nupdate : Tea.Context Flags -\u003e Msg -\u003e InternalModel -\u003e Tea Flags InternalModel Msg Effect\n\n\nurlChanged : Tea.Context Flags -\u003e InternalModel -\u003e Tea Flags InternalModel Msg Effect\n\n\nview : Tea.Context Flags -\u003e InternalModel -\u003e Html Msg\n\n\n\ntype alias Flags = ...\n\ntype alias InternalModel = ...\n\ntype Msg = ...\n\ntype Effect = ...\n```\n\nYou'll also notice that the `Model` is now a `Tea.RouteModel` type alias. This is because a route can handle caching itself and calling either `init` or `urlChanged` depending on cache state.\n\n## Structure\n\n### Main.elm\n\nThe current approach is to define an application like so:\n\n```elm\nmodule Main exposing (main)\n\nimport Api -- This assumes you have a module to define your API calls\nimport Base -- This is the initial \"branch\" of your app\nimport Browser\nimport Context\nimport Json.Decode\nimport Json.Encode\nimport Tea\n\n\nmain : Program Json.Encode.Value (Tea.Model Base.Model Context.Flags) (Tea.Msg Base.Msg)\nmain =\n    Tea.plant\n        { decodeFlags = Json.Decode.decodeValue Api.decodeUser \u003e\u003e Result.toMaybe\n        , root = Base.branch\n        , rootEffect = \\_ model -\u003e ( model, Cmd.none )\n        }\n        |\u003e Browser.application\n\n```\n\nI want to support non-applications as well, though I'm still figuring out what that looks like. E.g. if you don't have an application, can you have a `Tea.route`? How would a URL change be handled? And lots more.\n\n### Component with multuple Component children\n\n```elm\ninit context =\n    let\n        ( header, headerEffects ) =\n            Header.branch.init context\n                |\u003e Tea.extractModel\n\n        ( home, homeEffects ) =\n            Home.branch.init context\n                |\u003e Tea.extractModel\n\n        ( login, loginEffects ) =\n            Login.branch.init context\n                |\u003e Tea.extractModel\n\n        ( register, registerEffects ) =\n            Register.branch.init context\n                |\u003e Tea.extractModel\n    in\n    { header = header\n    , home = home\n    , login = login\n    , register = register\n    }\n        |\u003e Tea.save\n        |\u003e Tea.withChildEffects HeaderMsg applyHeaderEffects headerEffects\n        |\u003e Tea.withChildEffects HomeMsg applyHomeEffects homeEffects\n        |\u003e Tea.withChildEffects LoginMsg applyLoginEffects loginEffects\n        |\u003e Tea.withChildEffects RegisterMsg applyRegisterEffects registerEffects\n\n\nsubscriptions context model =\n    Sub.batch\n        [ Header.branch.subscriptions context model.header\n            |\u003e Sub.map HeaderMsg\n        , Home.branch.subscriptions context model.home\n            |\u003e Sub.map HomeMsg\n        , Login.branch.subscriptions context model.login\n            |\u003e Sub.map LoginMsg\n        , Register.branch.subscriptions context model.register\n            |\u003e Sub.map RegisterMsg\n        ]\n\n\nupdate context msg model =\n    case msg of\n        HeaderMsg headerMsg -\u003e\n            Header.branch.update context headerMsg model.header\n                |\u003e Tea.mapMsg HeaderMsg\n                |\u003e Tea.mapModel (\\header -\u003e { model | header = header })\n                |\u003e Tea.applyEffects applyHeaderEffects\n\n        HomeMsg homeMsg -\u003e\n            Home.branch.update context homeMsg model.home\n                |\u003e Tea.mapMsg HomeMsg\n                |\u003e Tea.mapModel (\\home -\u003e { model | home = home })\n                |\u003e Tea.applyEffects applyHomeEffects\n\n        LoginMsg loginMsg -\u003e\n            Login.branch.update context loginMsg model.login\n                |\u003e Tea.mapMsg LoginMsg\n                |\u003e Tea.mapModel (\\login -\u003e { model | login = login })\n                |\u003e Tea.applyEffects applyLoginEffects\n\n        RegisterMsg registerMsg -\u003e\n            Register.branch.update context registerMsg model.register\n                |\u003e Tea.mapMsg RegisterMsg\n                |\u003e Tea.mapModel (\\register -\u003e { model | register = register })\n                |\u003e Tea.applyEffects applyRegisterEffects\n\n\nurlChanged context model =\n    Header.branch.urlChanged context model.header\n        |\u003e Tea.mapMsg HeaderMsg\n        |\u003e Tea.mapModel (\\header -\u003e { model | header = header })\n        |\u003e Tea.applyEffects applyHeaderEffects\n        |\u003e Tea.andThen\n            (\\m -\u003e\n                Home.branch.urlChanged context m.home\n                    |\u003e Tea.mapMsg HomeMsg\n                    |\u003e Tea.mapModel (\\home -\u003e { m | home = home })\n                    |\u003e Tea.applyEffects applyHomeEffects\n            )\n        |\u003e Tea.andThen\n            (\\m -\u003e\n                Login.branch.urlChanged context m.login\n                    |\u003e Tea.mapMsg LoginMsg\n                    |\u003e Tea.mapModel (\\login -\u003e { m | login = login })\n                    |\u003e Tea.applyEffects applyLoginEffects\n            )\n        |\u003e Tea.andThen\n            (\\m -\u003e\n                Register.branch.urlChanged context m.register\n                    |\u003e Tea.mapMsg RegisterMsg\n                    |\u003e Tea.mapModel (\\register -\u003e { m | register = register })\n                    |\u003e Tea.applyEffects applyRegisterEffects\n            )\n\n\nview context model =\n    Html.div []\n        [ Header.branch.view context model.header\n            |\u003e Html.map HeaderMsg\n        , Home.branch.view context model.home\n            |\u003e Html.map HomeMsg\n        , Login.branch.view context model.login\n            |\u003e Html.map LoginMsg\n        , Register.branch.view context model.register\n            |\u003e Html.map RegisterMsg\n        , viewFooter\n        ]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwolfadex%2Fearl-grey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwolfadex%2Fearl-grey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwolfadex%2Fearl-grey/lists"}