mirror of
https://github.com/ryannhg/elm-spa.git
synced 2024-11-22 17:52:33 +03:00
a fresh start :)
This commit is contained in:
parent
d94b630c8e
commit
fe048e5d83
866
README.md
866
README.md
@ -1,857 +1,53 @@
|
||||
# ryannhg/elm-app
|
||||
> a way to build single page apps with Elm.
|
||||
|
||||
## installing
|
||||
|
||||
```
|
||||
elm install ryannhg/elm-app
|
||||
```
|
||||
|
||||
## motivation
|
||||
|
||||
Every time I try and create a single page application from scratch in Elm, I end up repeating a few first steps from scratch:
|
||||
|
||||
- __Routing__ - Implementing `onUrlChange` and `onUrlRequest` for my `update` function.
|
||||
|
||||
- __Page Transitions__ - Fading in the whole app on load and fading in/out the pages (not the persistent stuff) on route change.
|
||||
|
||||
- __Wiring up pages__ - Every page has it's own `Model`, `Msg`, `init`, `update`, `view`, and `subscriptions`. I need to bring those together at the top level.
|
||||
|
||||
- __Sharing app model__ - In addition to updating the _page model_, I need a way for pages to update the _shared app model_ used across pages (signed in users).
|
||||
|
||||
This package is an attempt to create a few abstractions on top of `Browser.application` to make creating single page applications focus on what makes __your app unique__.
|
||||
> an experiment for creating single page apps with Elm!
|
||||
|
||||
|
||||
## is it real?
|
||||
### try it out
|
||||
|
||||
- A working demo is available online here: [https://elm-app-demo.netlify.com/](https://elm-app-demo.netlify.com/)
|
||||
1. `npm install`
|
||||
|
||||
- And you can play around with an example yourself in the repo: [https://github.com/ryannhg/elm-app/tree/master/examples/basic](https://github.com/ryannhg/elm-app/tree/master/examples/basic) around with the files in `examples/basic/src` and change things!
|
||||
|
||||
---
|
||||
|
||||
## examples are helpful!
|
||||
|
||||
Let's walk through the package together, at a high-level, with some code!
|
||||
1. `npm run dev`
|
||||
|
||||
|
||||
### src/Main.elm
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm ✨
|
||||
```
|
||||
|
||||
This is the __entrypoint__ to the application, and connects all the parts of our `Application` together:
|
||||
### overview
|
||||
|
||||
```elm
|
||||
module Main exposing (main)
|
||||
|
||||
import Application
|
||||
import Application exposing (Application)
|
||||
import Generated.Pages as Pages
|
||||
import Generated.Route as Route
|
||||
import Layout as Layout
|
||||
|
||||
|
||||
main : Application () Pages.Model Pages.Msg
|
||||
main =
|
||||
Application.create
|
||||
{ routing = -- TODO
|
||||
, layout = -- TODO
|
||||
, pages = -- TODO
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, `Application.create` is a function that takes in a `record` with three properties:
|
||||
|
||||
1. __routing__ - handles URLs and page transitions
|
||||
|
||||
2. __layout__ - the app-level `init`, `update`, `view`, etc.
|
||||
|
||||
3. __pages__ - the page-level `init`, `update`, `view`, etc.
|
||||
|
||||
|
||||
#### routing
|
||||
|
||||
```elm
|
||||
module Main exposing (main)
|
||||
|
||||
import Application
|
||||
import Route ✨
|
||||
|
||||
main =
|
||||
Application.create
|
||||
{ routing =
|
||||
{ fromUrl = Route.fromUrl ✨
|
||||
, toPath = Route.toPath ✨
|
||||
, transitionSpeed = 200 ✨
|
||||
Application.create
|
||||
{ routing =
|
||||
{ fromUrl = Route.fromUrl
|
||||
, toPath = Route.toPath
|
||||
}
|
||||
, layout =
|
||||
{ view = Layout.view
|
||||
}
|
||||
, pages =
|
||||
{ init = Pages.init
|
||||
, update = Pages.update
|
||||
, bundle = Pages.bundle
|
||||
}
|
||||
}
|
||||
, layout = -- TODO
|
||||
, pages = -- TODO
|
||||
}
|
||||
```
|
||||
|
||||
The record for `routing` only has three properties:
|
||||
#### supporting code
|
||||
|
||||
1. __fromUrl__ - a function that turns a `Url` into a `Route`
|
||||
- [`Generated.Route`](./src/Generated/Route.elm)
|
||||
|
||||
2. __toPath__ - a function that turns a `Route` into a `String` used for links.
|
||||
- [`Generated.Pages`](./src/Generated/Pages.elm)
|
||||
|
||||
3. __transitionSpeed__ - number of __milliseconds__ it takes to fade in/out pages.
|
||||
- [`Layout`](./src/Layout.elm)
|
||||
|
||||
The implementation for `fromUrl` and `toPath` don't come from the `src/Main.elm`. Instead we create a new file called `src/Route.elm`, which handles all this for us in one place!
|
||||
- [`Pages.Homepage`](./src/Pages/Homepage.elm) (a static page)
|
||||
|
||||
We'll link to that in a bit!
|
||||
- [`Pages.Counter`](./src/Pages/Counter.elm) (a sandbox page)
|
||||
|
||||
#### layout
|
||||
|
||||
```elm
|
||||
module Main exposing (main)
|
||||
|
||||
import Application
|
||||
import Route
|
||||
import Components.Layout as Layout ✨
|
||||
|
||||
main =
|
||||
Application.create
|
||||
{ routing =
|
||||
{ fromUrl = Route.fromUrl
|
||||
, toPath = Route.toPath
|
||||
, transitionSpeed = 200
|
||||
}
|
||||
, layout =
|
||||
{ init = Layout.init ✨
|
||||
, update = Layout.update ✨
|
||||
, view = Layout.view ✨
|
||||
, subscriptions = Layout.subscriptions ✨
|
||||
}
|
||||
, pages = -- TODO
|
||||
}
|
||||
```
|
||||
|
||||
The `layout` property introduces four new pieces:
|
||||
|
||||
1. __init__ - how to initialize the shared state.
|
||||
|
||||
2. __update__ - how to update the app-level state (and routing commands).
|
||||
|
||||
3. __view__ - the app-level view (and where to render our page view)
|
||||
|
||||
4. __subscriptions__ - app-level subscriptions (regardless of which page we're on)
|
||||
|
||||
Just like before, a new file `src/Components/Layout.elm` will contain all the functions we'll need for the layout, so that `Main.elm` is relatively focused.
|
||||
|
||||
#### pages
|
||||
|
||||
|
||||
```elm
|
||||
module Main exposing (main)
|
||||
|
||||
import Application
|
||||
import Route
|
||||
import Components.Layout as Layout
|
||||
import Pages ✨
|
||||
|
||||
main =
|
||||
Application.create
|
||||
{ routing =
|
||||
{ fromUrl = Route.fromUrl
|
||||
, toPath = Route.toPath
|
||||
, transitionSpeed = 200
|
||||
}
|
||||
, layout =
|
||||
{ init = Layout.init
|
||||
, update = Layout.update
|
||||
, view = Layout.view
|
||||
, subscriptions = Layout.subscriptions
|
||||
}
|
||||
, pages =
|
||||
{ init = Pages.init ✨
|
||||
, update = Pages.update ✨
|
||||
, bundle = Pages.bundle ✨
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Much like the last property, `pages` is just a few functions.
|
||||
|
||||
The `init` and `update` parts are fairly the same, but there's a new property that might look strange: `bundle`.
|
||||
|
||||
The "bundle" is a combination of `view`, `title`, `subscriptions` that allows our new `src/Pages.elm` file to reduce a bit of boilerplate! (There's a better explanation in the `src/Pages.elm` section of the guide.)
|
||||
|
||||
#### that's it for Main.elm!
|
||||
|
||||
As the final touch, we can update our import statements to add in a type annotation for the `main` function:
|
||||
|
||||
```elm
|
||||
module Main exposing (main)
|
||||
|
||||
import Application exposing (Application) ✨
|
||||
import Flags exposing (Flags) ✨
|
||||
import Global ✨
|
||||
import Route exposing (Route) ✨
|
||||
import Components.Layout as Layout
|
||||
import Pages
|
||||
|
||||
main : Application Flags Route Global.Model Global.Msg Pages.Model Pages.Msg ✨
|
||||
main =
|
||||
Application.create
|
||||
{ routing =
|
||||
{ fromUrl = Route.fromUrl
|
||||
, toPath = Route.toPath
|
||||
, transitionSpeed = 200
|
||||
}
|
||||
, layout =
|
||||
{ init = Layout.init
|
||||
, update = Layout.update
|
||||
, view = Layout.view
|
||||
, subscriptions = Layout.subscriptions
|
||||
}
|
||||
, pages =
|
||||
{ init = Pages.init
|
||||
, update = Pages.update
|
||||
, bundle = Pages.bundle
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Instead of main being the traditional `Program Flags Model Msg` type, here we use `Application Flags Route Global.Model Global.Msg Pages.Model Pages.Msg`, which is very long and spooky!
|
||||
|
||||
This is caused by the fact that our `Application.create` needs to know more about the `Flags`, `Route`, `Global`, and `Pages` types so it can do work for us.
|
||||
|
||||
But enough of that– let's move on to routing next!
|
||||
|
||||
---
|
||||
|
||||
### src/Route.elm
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm
|
||||
Route.elm ✨
|
||||
```
|
||||
|
||||
in our new file, we need to create a [custom type](#custom-type) to handle all the possible routes.
|
||||
|
||||
```elm
|
||||
module Route exposing (Route(..))
|
||||
|
||||
type Route
|
||||
= Homepage
|
||||
| SignIn
|
||||
```
|
||||
|
||||
For now, there is only two routes: `Homepage` and `SignIn`.
|
||||
|
||||
We also need to make `fromUrl` and `toPath` so our application handles routing and page navigation correctly!
|
||||
|
||||
For that, we need to install the official `elm/url` package:
|
||||
|
||||
```
|
||||
elm install elm/url
|
||||
```
|
||||
|
||||
And use the newly installed `Url` and `Url.Parser` modules like this:
|
||||
|
||||
```elm
|
||||
module Route exposing
|
||||
( Route(..)
|
||||
, fromUrl ✨
|
||||
, toPath ✨
|
||||
)
|
||||
|
||||
import Url exposing (Url) ✨
|
||||
import Url.Parser as Parser exposing (Parser) ✨
|
||||
|
||||
type Route
|
||||
= Homepage
|
||||
| SignIn
|
||||
|
||||
fromUrl : Url -> Route ✨
|
||||
-- TODO
|
||||
|
||||
toPath : Route -> String ✨
|
||||
-- TODO
|
||||
```
|
||||
|
||||
#### fromUrl
|
||||
|
||||
Let's get started on implementing `fromUrl` by using the `Parser` module:
|
||||
|
||||
```elm
|
||||
type Route
|
||||
= Homepage
|
||||
| SignIn
|
||||
| NotFound ✨ -- see note #2
|
||||
|
||||
fromUrl : Url -> Route
|
||||
fromUrl url =
|
||||
let
|
||||
router =
|
||||
Parser.oneOf ✨ -- see note #1
|
||||
[ Parser.map Homepage Parser.top
|
||||
, Parser.map SignIn (Parser.s "sign-in")
|
||||
]
|
||||
in
|
||||
Parser.parse router url
|
||||
|> Maybe.withDefault NotFound ✨ -- see note #2
|
||||
```
|
||||
|
||||
__Notes__
|
||||
|
||||
1. With `Parser.oneOf`, we match `/` to `Homepage` and `/sign-in` to `SignIn`.
|
||||
|
||||
2. `Parser.parse` returns a `Maybe Route` because it not find a match in our `router`. That means we need to add a `NotFound` case (good catch, Elm!)
|
||||
|
||||
|
||||
#### toPath
|
||||
|
||||
It turns out `toPath` is really easy, its just a case expression:
|
||||
|
||||
```elm
|
||||
toPath : Route -> String ✨
|
||||
toPath route =
|
||||
case route of
|
||||
Homepage -> "/"
|
||||
SignIn -> "/sign-in"
|
||||
NotFound -> "/not-found"
|
||||
```
|
||||
|
||||
#### that's it for Route.elm!
|
||||
|
||||
here's the complete file we made.
|
||||
|
||||
```elm
|
||||
module Route exposing
|
||||
( Route(..)
|
||||
, fromUrl
|
||||
, toPath
|
||||
)
|
||||
|
||||
import Url exposing (Url)
|
||||
import Url.Parser as Parser exposing (Parser)
|
||||
|
||||
type Route
|
||||
= Homepage
|
||||
| SignIn
|
||||
| NotFound
|
||||
|
||||
fromUrl : Url -> Route
|
||||
fromUrl url =
|
||||
let
|
||||
router =
|
||||
Parser.oneOf
|
||||
[ Parser.map Homepage Parser.top
|
||||
, Parser.map SignIn (Parser.s "sign-in")
|
||||
]
|
||||
in
|
||||
Parser.parse router url
|
||||
|> Maybe.withDefault NotFound
|
||||
|
||||
toPath : Route -> String
|
||||
toPath route =
|
||||
case route of
|
||||
Homepage -> "/"
|
||||
SignIn -> "/sign-in"
|
||||
NotFound -> "/not-found"
|
||||
```
|
||||
|
||||
You can learn how to add more routes by looking at:
|
||||
|
||||
1. __the `elm/url` docs__ - https://package.elm-lang.org/packages/elm/url/latest
|
||||
|
||||
2. __the example in this repo__ -https://github.com/ryannhg/elm-app/blob/master/examples/basic/src/Route.elm
|
||||
|
||||
---
|
||||
|
||||
### src/Flags.elm
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm
|
||||
Route.elm
|
||||
Flags.elm ✨
|
||||
```
|
||||
|
||||
For this app, we don't actually have flags, so we return an empty tuple.
|
||||
|
||||
```elm
|
||||
module Flags exposing (Flags)
|
||||
|
||||
type alias Flags = ()
|
||||
```
|
||||
|
||||
So let's move onto something more interesting!
|
||||
|
||||
---
|
||||
|
||||
### src/Global.elm
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm
|
||||
Route.elm
|
||||
Flags.elm
|
||||
Global.elm
|
||||
Ports.elm ✨
|
||||
```
|
||||
|
||||
The purpose of `Global.elm` is to define the `Model` and `Msg` types we'll share across pages and use in our layout functions:
|
||||
|
||||
```elm
|
||||
module Global exposing ( Model, Msg(..) )
|
||||
|
||||
type alias Model =
|
||||
{ isSignedIn : Bool
|
||||
}
|
||||
|
||||
type Msg
|
||||
= SignIn
|
||||
| SignOut
|
||||
| Log String
|
||||
```
|
||||
|
||||
Here we create a simple record to keep track of the user's sign in status.
|
||||
|
||||
Let's see an example of `Global.Model` and `Global.Msg` being used in our layout:
|
||||
|
||||
#### src/Ports.elm
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm
|
||||
Route.elm
|
||||
Flags.elm
|
||||
Global.elm
|
||||
Ports.elm ✨
|
||||
```
|
||||
|
||||
If you need to use ports, create a file called `src/Ports.elm`
|
||||
|
||||
```elm
|
||||
port module Ports exposing (log)
|
||||
|
||||
port log : String -> Cmd msg
|
||||
```
|
||||
|
||||
We'll use them in the layouts component up next!
|
||||
|
||||
|
||||
### src/Components/Layout.elm
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm
|
||||
Route.elm
|
||||
Flags.elm
|
||||
Global.elm
|
||||
Ports.elm
|
||||
Components/
|
||||
Layout.elm ✨
|
||||
```
|
||||
|
||||
To implement an app-level layout, we'll need a new file:
|
||||
|
||||
```elm
|
||||
module Components.Layout exposing (init, update, view, subscriptions)
|
||||
|
||||
import Global
|
||||
import Route exposing (Route)
|
||||
|
||||
-- ...
|
||||
```
|
||||
|
||||
This file needs to export the following four functions:
|
||||
|
||||
#### init
|
||||
|
||||
```elm
|
||||
init :
|
||||
{ flags : Flags
|
||||
, route : Route
|
||||
, navigateTo : Route -> Cmd msg
|
||||
}
|
||||
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
|
||||
init _ =
|
||||
( { isSignedIn = False }
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
```
|
||||
|
||||
Initially, our layout has access to a record with three fields:
|
||||
|
||||
- __flags__ - the initial JSON passed in with the app.
|
||||
|
||||
- __route__ - the current route
|
||||
|
||||
- __navigateTo__ - allows programmatic navigation to other pages.
|
||||
|
||||
For our example, we set `isSignedIn` to `False`, don't perform any `Global.Msg` side effects, nor use `messages.navigateTo` to change to another page.
|
||||
|
||||
#### update
|
||||
|
||||
```elm
|
||||
import Ports ✨
|
||||
|
||||
update :
|
||||
{ flags : Flags
|
||||
, route : Route
|
||||
, navigateTo : Route -> Cmd msg
|
||||
}
|
||||
-> Global.Msg
|
||||
-> Global.Model
|
||||
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
|
||||
update { navigateTo } msg model =
|
||||
case msg of
|
||||
Global.SignIn ->
|
||||
( { model | isSignedIn = True }
|
||||
, Cmd.none
|
||||
, navigateTo Route.Homepage
|
||||
)
|
||||
|
||||
Global.SignOut ->
|
||||
( { model | isSignedIn = False }
|
||||
, Cmd.none
|
||||
, navigateTo Route.SignIn
|
||||
)
|
||||
|
||||
Log message ->
|
||||
( model
|
||||
, Cmd.none
|
||||
, Ports.log message
|
||||
)
|
||||
```
|
||||
|
||||
In addition to the record we saw earlier with `init`, our layout's `update` function take a `Global.Msg` and `Global.Model`:
|
||||
|
||||
That allows us to return an updated state of the app, and programmatically navigate to different pages!
|
||||
|
||||
#### view
|
||||
|
||||
```elm
|
||||
view :
|
||||
{ flags : Flags
|
||||
, route : Route
|
||||
, viewPage : Html msg
|
||||
, toMsg : Global.Msg -> msg
|
||||
}
|
||||
-> Global.Model
|
||||
-> Html msg
|
||||
view { viewPage, toMsg } model =
|
||||
div [ class "layout" ]
|
||||
[ Html.map toMsg (viewNavbar model)
|
||||
, viewPage
|
||||
, viewFooter
|
||||
]
|
||||
```
|
||||
|
||||
Instead of `navigateTo`, our `view` function takes in a record with two other properties:
|
||||
|
||||
2. __viewPage__ - where we want the rendered page to show up in our layout
|
||||
|
||||
3. __toMsg__ - a way to convert from `Global.Msg` to `msg`, so that components can send global messages, but still return `Html msg`.
|
||||
|
||||
The `viewNavbar` function is an example of where we would use `Html.map toMsg` to turn `Html Global.Msg` into `Html msg`:
|
||||
|
||||
```elm
|
||||
viewNavbar : Global.Model -> Html Global.Msg
|
||||
viewNavbar model =
|
||||
header
|
||||
[ class "navbar" ]
|
||||
[ a [ href (Route.toPath Route.Homepage) ]
|
||||
[ text "Home" ]
|
||||
, if model.isSignedIn then
|
||||
button
|
||||
[ Events.onClick SignOut ]
|
||||
[ text "Sign out" ]
|
||||
else
|
||||
button
|
||||
[ Events.onClick SignIn ]
|
||||
[ text "Sign in" ]
|
||||
]
|
||||
```
|
||||
|
||||
The `viewFooter` function doesn't send messages, so `Html.map toMsg` isn't necessary!
|
||||
|
||||
```elm
|
||||
viewFooter : Html msg
|
||||
viewFooter =
|
||||
footer
|
||||
[ class "footer" ]
|
||||
[ text "Build with Elm in 2019" ]
|
||||
```
|
||||
|
||||
If you'd like, you can update the view to use components in folders like this:
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm
|
||||
Route.elm
|
||||
Flags.elm
|
||||
Global.elm
|
||||
Ports.elm
|
||||
Components/
|
||||
Layout.elm
|
||||
Navbar.elm ✨
|
||||
Footer.elm ✨
|
||||
```
|
||||
|
||||
```elm
|
||||
import Components.Navbar as Navbar ✨
|
||||
import Components.Footer as Footer ✨
|
||||
|
||||
-- ...
|
||||
|
||||
view :
|
||||
{ flags : Flags
|
||||
, route : Route
|
||||
, viewPage : Html msg
|
||||
, toMsg : Global.Msg -> msg
|
||||
}
|
||||
-> Global.Model
|
||||
-> Html msg
|
||||
view { viewPage, toMsg } model =
|
||||
div [ class "layout" ]
|
||||
[ Html.map toMsg (Navbar.view model) ✨
|
||||
, viewPage
|
||||
, Footer.view ✨
|
||||
]
|
||||
```
|
||||
|
||||
Moving `Components.Layout.viewNavbar` into `Components.Navbar.view`
|
||||
|
||||
#### subscriptions
|
||||
|
||||
```elm
|
||||
subscriptions :
|
||||
{ navigateTo : Route -> Cmd msg
|
||||
, route : Route
|
||||
, flags : Flags
|
||||
}
|
||||
-> Global.Model
|
||||
-> Html Global.Msg
|
||||
subscriptions _ model =
|
||||
Sub.none
|
||||
```
|
||||
|
||||
That's the entire file! Here it is
|
||||
|
||||
---
|
||||
|
||||
### src/Pages.elm
|
||||
|
||||
```
|
||||
our-project/
|
||||
elm.json
|
||||
src/
|
||||
Main.elm
|
||||
Route.elm
|
||||
Flags.elm
|
||||
Global.elm
|
||||
Ports.elm
|
||||
Pages.elm ✨
|
||||
Components/
|
||||
Layout.elm
|
||||
Navbar.elm
|
||||
Footer.elm
|
||||
```
|
||||
|
||||
```elm
|
||||
module Pages exposing (init, update, bundle)
|
||||
|
||||
import Pages.Homepage
|
||||
import Pages.SignIn
|
||||
import Pages.NotFound
|
||||
|
||||
type Model
|
||||
= HomepageModel ()
|
||||
| SignInModel Pages.SignIn.Model
|
||||
| NotFoundModel ()
|
||||
|
||||
type Msg
|
||||
= HomepageMsg Never
|
||||
| SignInMsg Pages.SignIn.Msg
|
||||
| NotFoundMsg Never
|
||||
|
||||
pages = -- TODO
|
||||
|
||||
init = -- TODO
|
||||
|
||||
update = -- TODO
|
||||
|
||||
bundle = -- TODO
|
||||
```
|
||||
|
||||
Here we define a top level `Model` and `Msg`, so we can easily implement `init`, `update`, and `bundle`.
|
||||
|
||||
### pages
|
||||
|
||||
```elm
|
||||
import Application.Page as Page ✨
|
||||
|
||||
pages =
|
||||
{ homepage =
|
||||
Page.static
|
||||
{ title = Pages.Homepage.title
|
||||
, view = Pages.Homepage.view
|
||||
, toModel = HomepageModel
|
||||
}
|
||||
, signIn =
|
||||
Page.page
|
||||
{ title = Pages.SignIn.title
|
||||
, init = Pages.SignIn.init
|
||||
, update = Pages.SignIn.update
|
||||
, subscriptions = Pages.SignIn.subscriptions
|
||||
, view = Pages.SignIn.view
|
||||
, toModel = SignInModel
|
||||
, toMsg = SignInMsg
|
||||
}
|
||||
, notFound =
|
||||
Page.static
|
||||
{ title = Pages.NotFound.title
|
||||
, view = Pages.NotFound.view
|
||||
, toModel = NotFoundModel
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Page` type is the important abstraction that allows us to make our `init` function take in the same shape.
|
||||
|
||||
#### init
|
||||
|
||||
```elm
|
||||
import Application ✨
|
||||
import Flags exposing (Flags) ✨
|
||||
import Global ✨
|
||||
import Route exposing (Route) ✨
|
||||
|
||||
init :
|
||||
Route
|
||||
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
|
||||
init route =
|
||||
case route of
|
||||
Route.Homepage ->
|
||||
Application.init
|
||||
{ page = pages.homepage
|
||||
}
|
||||
|
||||
Route.SignIn ->
|
||||
Application.init
|
||||
{ page = pages.signIn
|
||||
}
|
||||
|
||||
Route.NotFound ->
|
||||
Application.init
|
||||
{ page = pages.notFound
|
||||
}
|
||||
```
|
||||
|
||||
#### update
|
||||
|
||||
```elm
|
||||
update :
|
||||
Msg
|
||||
-> Model
|
||||
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
|
||||
update appMsg appModel =
|
||||
case ( appModel, appMsg ) of
|
||||
( HomepageModel model, HomepageMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.homepage
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( HomepageModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
( SignInModel model, SignInMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.signIn
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( SignInModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
( NotFoundModel model, NotFoundMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.notFound
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( NotFoundModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
```
|
||||
|
||||
#### bundle
|
||||
|
||||
```elm
|
||||
bundle :
|
||||
Model
|
||||
-> Application.Bundle Flags Route Global.Model Msg
|
||||
bundle appModel =
|
||||
case appModel of
|
||||
HomepageModel model ->
|
||||
Application.bundle
|
||||
{ page = pages.homepage
|
||||
, model = model
|
||||
}
|
||||
|
||||
SignInModel model ->
|
||||
Application.bundle
|
||||
{ page = pages.signIn
|
||||
, model = model
|
||||
}
|
||||
|
||||
NotFoundModel model ->
|
||||
Application.bundle
|
||||
{ page = pages.notFound
|
||||
, model = model
|
||||
}
|
||||
```
|
||||
|
||||
Like with the last two examples, `Application.bundle` makes our case expression consistent. Behind the scenes, `bundle` is used to provide `view`, `subscriptions`, and `title`.
|
||||
|
||||
The alternative would look super repetitive:
|
||||
|
||||
```elm
|
||||
-- AN IMPROVEMENT ON
|
||||
view appModel =
|
||||
case appModel of
|
||||
HomepageModel model -> Application.view { ... }
|
||||
SignInModel model -> Application.view { ... }
|
||||
NotFoundModel model -> Application.view { ... }
|
||||
|
||||
title appModel =
|
||||
case appModel of
|
||||
HomepageModel model -> Application.title { ... }
|
||||
SignInModel model -> Application.title { ... }
|
||||
NotFoundModel model -> Application.title { ... }
|
||||
|
||||
subscriptions appModel =
|
||||
case appModel of
|
||||
HomepageModel model -> Application.subscriptions { ... }
|
||||
SignInModel model -> Application.subscriptions { ... }
|
||||
NotFoundModel model -> Application.subscriptions { ... }
|
||||
```
|
||||
|
||||
The `bundle` abstraction gives us the ability to only write __one case expression__ at the top level for all three of these things 😎
|
||||
|
||||
You can find `src/Pages/*.elm` examples in the [basic example]. All those pages are really just normal Elm `init/update/view` things!
|
||||
|
||||
## that's it!
|
||||
|
||||
Thanks for reading this huge README, I hope this package helps you build great single page apps with Elm! 😄
|
||||
- [`Pages.Random`](./src/Pages/Random.elm) (a static page)
|
37
elm.json
37
elm.json
@ -1,19 +1,28 @@
|
||||
{
|
||||
"type": "package",
|
||||
"name": "ryannhg/elm-app",
|
||||
"summary": "an experiment for making single page apps with Elm",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "1.0.0",
|
||||
"exposed-modules": [
|
||||
"Application",
|
||||
"Application.Page"
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"package"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"elm-version": "0.19.0",
|
||||
"dependencies": {
|
||||
"elm/browser": "1.0.0 <= v < 2.0.0",
|
||||
"elm/core": "1.0.0 <= v < 2.0.0",
|
||||
"elm/html": "1.0.0 <= v < 2.0.0",
|
||||
"elm/url": "1.0.0 <= v < 2.0.0"
|
||||
"direct": {
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/http": "2.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/url": "1.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {}
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>ryannhg/elm-app</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
@ -1,6 +0,0 @@
|
||||
# examples
|
||||
> some examples of how to use this package.
|
||||
|
||||
- [basic](./basic)
|
||||
|
||||
- [elm-ui](./elm-ui)
|
@ -1,53 +0,0 @@
|
||||
# ryannhg/elm-app
|
||||
> an experiment for creating single page apps with Elm!
|
||||
|
||||
|
||||
### try it out
|
||||
|
||||
1. `npm install`
|
||||
|
||||
1. `npm run dev`
|
||||
|
||||
|
||||
### overview
|
||||
|
||||
```elm
|
||||
module Main exposing (main)
|
||||
|
||||
import Application exposing (Application)
|
||||
import Generated.Pages as Pages
|
||||
import Generated.Route as Route
|
||||
import Layout as Layout
|
||||
|
||||
|
||||
main : Application () Pages.Model Pages.Msg
|
||||
main =
|
||||
Application.create
|
||||
{ routing =
|
||||
{ fromUrl = Route.fromUrl
|
||||
, toPath = Route.toPath
|
||||
}
|
||||
, layout =
|
||||
{ view = Layout.view
|
||||
}
|
||||
, pages =
|
||||
{ init = Pages.init
|
||||
, update = Pages.update
|
||||
, bundle = Pages.bundle
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### supporting code
|
||||
|
||||
- [`Generated.Route`](./src/Generated/Route.elm)
|
||||
|
||||
- [`Generated.Pages`](./src/Generated/Pages.elm)
|
||||
|
||||
- [`Layout`](./src/Layout.elm)
|
||||
|
||||
- [`Pages.Homepage`](./src/Pages/Homepage.elm) (a static page)
|
||||
|
||||
- [`Pages.Counter`](./src/Pages/Counter.elm) (a sandbox page)
|
||||
|
||||
- [`Pages.Random`](./src/Pages/Random.elm) (a static page)
|
@ -1,28 +0,0 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"package"
|
||||
],
|
||||
"elm-version": "0.19.0",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/http": "2.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/url": "1.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
# examples/basic
|
||||
> an intro into an app using `ryannhg/elm-app`
|
||||
|
||||
## how to run
|
||||
|
||||
1. `npm install`
|
||||
|
||||
1. `npm run dev`
|
@ -1,26 +0,0 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"../../src"
|
||||
],
|
||||
"elm-version": "0.19.0",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/url": "1.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="./main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,79 +0,0 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100%;
|
||||
padding: 2rem 1rem;
|
||||
box-sizing: border-box;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbar__links {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.navbar__links > *:first-child {
|
||||
font-size: 20px;
|
||||
}
|
||||
.navbar__links > :not(:first-child) {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: solid 1px #ccc;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: solid 1px #ccc;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background: #06f;
|
||||
color: white;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
label div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.layout > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout__page {
|
||||
flex: 1 0 auto;
|
||||
padding: 2rem 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding-bottom: 1rem;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { Elm } from './src/Main.elm'
|
||||
|
||||
Elm.Main.init({
|
||||
node: document.getElementById('app')
|
||||
})
|
7585
examples/complete/package-lock.json
generated
7585
examples/complete/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "application",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "parcel build index.html",
|
||||
"dev": "parcel index.html"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"elm": "^0.19.0-no-deps",
|
||||
"elm-format": "^0.8.2",
|
||||
"elm-hot": "^1.1.2",
|
||||
"node-elm-compiler": "^5.0.4",
|
||||
"parcel-bundler": "^1.12.3"
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
module Components.Footer exposing (view)
|
||||
|
||||
import Data.User exposing (User)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
|
||||
|
||||
type alias Options =
|
||||
{ user : Maybe User
|
||||
}
|
||||
|
||||
|
||||
view : Options -> Html msg
|
||||
view { user } =
|
||||
footer [ Attr.class "footer" ]
|
||||
[ user
|
||||
|> Maybe.map Data.User.username
|
||||
|> Maybe.withDefault "not signed in"
|
||||
|> (++) "Current user: "
|
||||
|> text
|
||||
]
|
@ -1,88 +0,0 @@
|
||||
module Components.Layout exposing
|
||||
( init
|
||||
, subscriptions
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Components.Footer
|
||||
import Components.Navbar
|
||||
import Flags exposing (Flags)
|
||||
import Global
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (class)
|
||||
import Route exposing (Route)
|
||||
import Utils.Cmd
|
||||
|
||||
|
||||
init :
|
||||
{ navigateTo : Route -> Cmd msg
|
||||
, route : Route
|
||||
, flags : Flags
|
||||
}
|
||||
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
|
||||
init _ =
|
||||
( { user = Nothing }
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update :
|
||||
{ navigateTo : Route -> Cmd msg
|
||||
, route : Route
|
||||
, flags : Flags
|
||||
}
|
||||
-> Global.Msg
|
||||
-> Global.Model
|
||||
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
|
||||
update { navigateTo } msg model =
|
||||
case msg of
|
||||
Global.SignIn (Ok user) ->
|
||||
( { model | user = Just user }
|
||||
, Cmd.none
|
||||
, navigateTo Route.Homepage
|
||||
)
|
||||
|
||||
Global.SignIn (Err _) ->
|
||||
Utils.Cmd.pure model
|
||||
|
||||
Global.SignOut ->
|
||||
( { model | user = Nothing }
|
||||
, Cmd.none
|
||||
, navigateTo Route.SignIn
|
||||
)
|
||||
|
||||
|
||||
view :
|
||||
{ flags : Flags
|
||||
, route : Route
|
||||
, toMsg : Global.Msg -> msg
|
||||
, viewPage : Html msg
|
||||
}
|
||||
-> Global.Model
|
||||
-> Html msg
|
||||
view { route, toMsg, viewPage } model =
|
||||
div [ class "layout" ]
|
||||
[ Html.map toMsg
|
||||
(Components.Navbar.view
|
||||
{ currentRoute = route
|
||||
, user = model.user
|
||||
, signOut = Global.SignOut
|
||||
}
|
||||
)
|
||||
, div [ class "layout__page" ] [ viewPage ]
|
||||
, Html.map toMsg
|
||||
(Components.Footer.view { user = model.user })
|
||||
]
|
||||
|
||||
|
||||
subscriptions :
|
||||
{ navigateTo : Route -> Cmd msg
|
||||
, route : Route
|
||||
, flags : Flags
|
||||
}
|
||||
-> Global.Model
|
||||
-> Sub Global.Msg
|
||||
subscriptions _ model =
|
||||
Sub.none
|
@ -1,69 +0,0 @@
|
||||
module Components.Navbar exposing (view)
|
||||
|
||||
import Data.User exposing (User)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr exposing (class)
|
||||
import Html.Events as Events
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
type alias Options msg =
|
||||
{ currentRoute : Route
|
||||
, user : Maybe User
|
||||
, signOut : msg
|
||||
}
|
||||
|
||||
|
||||
view : Options msg -> Html msg
|
||||
view { currentRoute, user, signOut } =
|
||||
header [ class "navbar" ]
|
||||
[ div [ class "navbar__links" ]
|
||||
(List.map
|
||||
(viewLink currentRoute)
|
||||
[ Route.Homepage
|
||||
, Route.Counter
|
||||
, Route.Random
|
||||
]
|
||||
)
|
||||
, case user of
|
||||
Just _ ->
|
||||
button [ Events.onClick signOut ] [ text <| "Sign out" ]
|
||||
|
||||
Nothing ->
|
||||
a [ Attr.href "/sign-in" ] [ text "Sign in" ]
|
||||
]
|
||||
|
||||
|
||||
viewLink : Route -> Route -> Html msg
|
||||
viewLink currentRoute route =
|
||||
a
|
||||
[ class "navbar__link-item"
|
||||
, Attr.href (Route.toPath route)
|
||||
, Attr.style "font-weight"
|
||||
(if route == currentRoute then
|
||||
"bold"
|
||||
|
||||
else
|
||||
"normal"
|
||||
)
|
||||
]
|
||||
[ text (linkLabel route) ]
|
||||
|
||||
|
||||
linkLabel : Route -> String
|
||||
linkLabel route =
|
||||
case route of
|
||||
Route.Homepage ->
|
||||
"Home"
|
||||
|
||||
Route.Counter ->
|
||||
"Counter"
|
||||
|
||||
Route.SignIn ->
|
||||
"Sign In"
|
||||
|
||||
Route.Random ->
|
||||
"Random"
|
||||
|
||||
Route.NotFound ->
|
||||
"Not found"
|
@ -1,28 +0,0 @@
|
||||
module Data.User exposing (User, signIn, username)
|
||||
|
||||
import Utils.Cmd
|
||||
|
||||
|
||||
type User
|
||||
= User String
|
||||
|
||||
|
||||
username : User -> String
|
||||
username (User username_) =
|
||||
username_
|
||||
|
||||
|
||||
signIn :
|
||||
{ username : String
|
||||
, password : String
|
||||
, msg : Result String User -> msg
|
||||
}
|
||||
-> Cmd msg
|
||||
signIn options =
|
||||
(Utils.Cmd.toCmd << options.msg) <|
|
||||
case ( options.username, options.password ) of
|
||||
( _, "password" ) ->
|
||||
Ok (User options.username)
|
||||
|
||||
_ ->
|
||||
Err "Sign in failed."
|
@ -1,5 +0,0 @@
|
||||
module Flags exposing (Flags)
|
||||
|
||||
|
||||
type alias Flags =
|
||||
()
|
@ -1,16 +0,0 @@
|
||||
module Global exposing
|
||||
( Model
|
||||
, Msg(..)
|
||||
)
|
||||
|
||||
import Data.User exposing (User)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ user : Maybe User
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SignIn (Result String User)
|
||||
| SignOut
|
@ -1,31 +0,0 @@
|
||||
module Main exposing (main)
|
||||
|
||||
import Application
|
||||
import Components.Layout as Layout
|
||||
import Flags exposing (Flags)
|
||||
import Global
|
||||
import Pages
|
||||
import Route
|
||||
|
||||
|
||||
main : Application.Program Flags Global.Model Global.Msg Pages.Model Pages.Msg
|
||||
main =
|
||||
Application.start <|
|
||||
Application.create
|
||||
{ routing =
|
||||
{ transition = 200
|
||||
, fromUrl = Route.fromUrl
|
||||
, toPath = Route.toPath
|
||||
}
|
||||
, layout =
|
||||
{ init = Layout.init
|
||||
, update = Layout.update
|
||||
, view = Layout.view
|
||||
, subscriptions = Layout.subscriptions
|
||||
}
|
||||
, pages =
|
||||
{ init = Pages.init
|
||||
, update = Pages.update
|
||||
, bundle = Pages.bundle
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
module Pages exposing
|
||||
( Model
|
||||
, Msg
|
||||
, bundle
|
||||
, init
|
||||
, update
|
||||
)
|
||||
|
||||
import Application
|
||||
import Application.Page as Page
|
||||
import Flags exposing (Flags)
|
||||
import Global
|
||||
import Html exposing (Html)
|
||||
import Pages.Counter
|
||||
import Pages.Homepage
|
||||
import Pages.NotFound
|
||||
import Pages.Random
|
||||
import Pages.SignIn
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
type Model
|
||||
= HomepageModel ()
|
||||
| CounterModel Pages.Counter.Model
|
||||
| RandomModel Pages.Random.Model
|
||||
| SignInModel Pages.SignIn.Model
|
||||
| NotFoundModel ()
|
||||
|
||||
|
||||
type Msg
|
||||
= HomepageMsg Never
|
||||
| CounterMsg Pages.Counter.Msg
|
||||
| RandomMsg Pages.Random.Msg
|
||||
| SignInMsg Pages.SignIn.Msg
|
||||
| NotFoundMsg Never
|
||||
|
||||
|
||||
app =
|
||||
{ bundle = Application.bundle Html.map
|
||||
}
|
||||
|
||||
|
||||
pages =
|
||||
{ homepage =
|
||||
Page.static
|
||||
{ title = Pages.Homepage.title
|
||||
, view = Pages.Homepage.view
|
||||
, toModel = HomepageModel
|
||||
, fromNever = Html.map never
|
||||
}
|
||||
, counter =
|
||||
Page.sandbox
|
||||
{ title = Pages.Counter.title
|
||||
, init = Pages.Counter.init
|
||||
, update = Pages.Counter.update
|
||||
, view = Pages.Counter.view
|
||||
, toModel = CounterModel
|
||||
, toMsg = CounterMsg
|
||||
}
|
||||
, random =
|
||||
Page.element
|
||||
{ title = Pages.Random.title
|
||||
, init = Pages.Random.init
|
||||
, update = Pages.Random.update
|
||||
, subscriptions = Pages.Random.subscriptions
|
||||
, view = Pages.Random.view
|
||||
, toModel = RandomModel
|
||||
, toMsg = RandomMsg
|
||||
}
|
||||
, signIn =
|
||||
Page.page
|
||||
{ title = Pages.SignIn.title
|
||||
, init = Pages.SignIn.init
|
||||
, update = Pages.SignIn.update
|
||||
, subscriptions = Pages.SignIn.subscriptions
|
||||
, view = Pages.SignIn.view
|
||||
, toModel = SignInModel
|
||||
, toMsg = SignInMsg
|
||||
}
|
||||
, notFound =
|
||||
Page.static
|
||||
{ title = Pages.NotFound.title
|
||||
, view = Pages.NotFound.view
|
||||
, toModel = NotFoundModel
|
||||
, fromNever = Html.map never
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Route
|
||||
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
|
||||
init route =
|
||||
case route of
|
||||
Route.Homepage ->
|
||||
Application.init
|
||||
{ page = pages.homepage
|
||||
}
|
||||
|
||||
Route.Counter ->
|
||||
Application.init
|
||||
{ page = pages.counter
|
||||
}
|
||||
|
||||
Route.Random ->
|
||||
Application.init
|
||||
{ page = pages.random
|
||||
}
|
||||
|
||||
Route.SignIn ->
|
||||
Application.init
|
||||
{ page = pages.signIn
|
||||
}
|
||||
|
||||
Route.NotFound ->
|
||||
Application.init
|
||||
{ page = pages.notFound
|
||||
}
|
||||
|
||||
|
||||
update :
|
||||
Msg
|
||||
-> Model
|
||||
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
|
||||
update appMsg appModel =
|
||||
case ( appModel, appMsg ) of
|
||||
( HomepageModel model, HomepageMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.homepage
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( HomepageModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
( CounterModel model, CounterMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.counter
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( CounterModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
( RandomModel model, RandomMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.random
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( RandomModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
( SignInModel model, SignInMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.signIn
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( SignInModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
( NotFoundModel model, NotFoundMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.notFound
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( NotFoundModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
|
||||
bundle :
|
||||
Model
|
||||
-> Application.Bundle Flags Route Global.Model Msg (Html Msg)
|
||||
bundle appModel =
|
||||
case appModel of
|
||||
HomepageModel model ->
|
||||
app.bundle
|
||||
{ page = pages.homepage
|
||||
, model = model
|
||||
}
|
||||
|
||||
CounterModel model ->
|
||||
app.bundle
|
||||
{ page = pages.counter
|
||||
, model = model
|
||||
}
|
||||
|
||||
RandomModel model ->
|
||||
app.bundle
|
||||
{ page = pages.random
|
||||
, model = model
|
||||
}
|
||||
|
||||
SignInModel model ->
|
||||
app.bundle
|
||||
{ page = pages.signIn
|
||||
, model = model
|
||||
}
|
||||
|
||||
NotFoundModel model ->
|
||||
app.bundle
|
||||
{ page = pages.notFound
|
||||
, model = model
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
module Pages.Counter exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, title
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Events as Events
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Increment
|
||||
| Decrement
|
||||
|
||||
|
||||
title : Model -> String
|
||||
title model =
|
||||
"Counter: " ++ String.fromInt model.counter ++ " | elm-app"
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ counter = 0
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
Decrement ->
|
||||
{ model | counter = model.counter - 1 }
|
||||
|
||||
Increment ->
|
||||
{ model | counter = model.counter + 1 }
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div []
|
||||
[ h1 [] [ text "Counter!" ]
|
||||
, p [] [ text "Even the browser tab updates!" ]
|
||||
, div []
|
||||
[ button [ Events.onClick Decrement ] [ text "-" ]
|
||||
, text (String.fromInt model.counter)
|
||||
, button [ Events.onClick Increment ] [ text "+" ]
|
||||
]
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
module Pages.Homepage exposing
|
||||
( title
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (..)
|
||||
|
||||
|
||||
title : String
|
||||
title =
|
||||
"Homepage"
|
||||
|
||||
|
||||
view : Html Never
|
||||
view =
|
||||
div []
|
||||
[ h1 [] [ text "Homepage!" ]
|
||||
, p [] [ text "It's boring, but it works!" ]
|
||||
]
|
@ -1,22 +0,0 @@
|
||||
module Pages.NotFound exposing
|
||||
( title
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (..)
|
||||
|
||||
|
||||
title : String
|
||||
title =
|
||||
"Not found."
|
||||
|
||||
|
||||
view : Html Never
|
||||
view =
|
||||
div []
|
||||
[ h1 [] [ text "Page not found!" ]
|
||||
, p []
|
||||
[ text "Is this space? Am I in "
|
||||
, em [] [ text "space?" ]
|
||||
]
|
||||
]
|
@ -1,77 +0,0 @@
|
||||
module Pages.Random exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, subscriptions
|
||||
, title
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Events as Events
|
||||
import Random
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ roll : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Roll
|
||||
| GotOutcome Int
|
||||
|
||||
|
||||
title : Model -> String
|
||||
title model =
|
||||
"Random | elm-app"
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init _ =
|
||||
( { roll = Nothing }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
rollDice : Model -> ( Model, Cmd Msg )
|
||||
rollDice model =
|
||||
( model
|
||||
, Random.generate GotOutcome (Random.int 1 6)
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Roll ->
|
||||
rollDice model
|
||||
|
||||
GotOutcome value ->
|
||||
( { model | roll = Just value }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div []
|
||||
[ h1 [] [ text "Random!" ]
|
||||
, p [] [ text "Did somebody say 'random numbers pls'?" ]
|
||||
, div []
|
||||
[ button [ Events.onClick Roll ] [ text "Roll" ]
|
||||
, p []
|
||||
[ model.roll
|
||||
|> Maybe.map String.fromInt
|
||||
|> Maybe.withDefault "Click the button!"
|
||||
|> text
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.none
|
@ -1,136 +0,0 @@
|
||||
module Pages.SignIn exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, subscriptions
|
||||
, title
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Application exposing (Context)
|
||||
import Data.User as User exposing (User)
|
||||
import Flags exposing (Flags)
|
||||
import Global
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Route exposing (Route)
|
||||
import Utils.Cmd
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ username : String
|
||||
, password : String
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Update Field String
|
||||
| AttemptSignIn
|
||||
|
||||
|
||||
type Field
|
||||
= Username
|
||||
| Password
|
||||
|
||||
|
||||
title : Context Flags Route Global.Model -> Model -> String
|
||||
title { context } model =
|
||||
case context.user of
|
||||
Just user ->
|
||||
"Sign out " ++ User.username user ++ " | elm-app"
|
||||
|
||||
Nothing ->
|
||||
"Sign in | elm-app"
|
||||
|
||||
|
||||
init :
|
||||
Context Flags Route Global.Model
|
||||
-> ( Model, Cmd Msg, Cmd Global.Msg )
|
||||
init _ =
|
||||
Utils.Cmd.pure { username = "", password = "" }
|
||||
|
||||
|
||||
update :
|
||||
Context Flags Route Global.Model
|
||||
-> Msg
|
||||
-> Model
|
||||
-> ( Model, Cmd Msg, Cmd Global.Msg )
|
||||
update _ msg model =
|
||||
case msg of
|
||||
Update Username value ->
|
||||
Utils.Cmd.pure { model | username = value }
|
||||
|
||||
Update Password value ->
|
||||
Utils.Cmd.pure { model | password = value }
|
||||
|
||||
AttemptSignIn ->
|
||||
( model
|
||||
, Cmd.none
|
||||
, User.signIn
|
||||
{ username = model.username
|
||||
, password = model.password
|
||||
, msg = Global.SignIn
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
view :
|
||||
Context Flags Route Global.Model
|
||||
-> Model
|
||||
-> Html Msg
|
||||
view _ model =
|
||||
div []
|
||||
[ h1 [] [ text "Sign in" ]
|
||||
, p [] [ text "and update some user state!" ]
|
||||
, Html.form [ Events.onSubmit AttemptSignIn ]
|
||||
[ viewInput
|
||||
{ label = "Username"
|
||||
, fieldType = "text"
|
||||
, value = model.username
|
||||
, onInput = Update Username
|
||||
}
|
||||
, viewInput
|
||||
{ label = "Password"
|
||||
, fieldType = "password"
|
||||
, value = model.password
|
||||
, onInput = Update Password
|
||||
}
|
||||
, p []
|
||||
[ button
|
||||
[ Attr.class "button"
|
||||
, Attr.type_ "submit"
|
||||
]
|
||||
[ text "Sign in"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewInput :
|
||||
{ label : String
|
||||
, fieldType : String
|
||||
, value : String
|
||||
, onInput : String -> msg
|
||||
}
|
||||
-> Html msg
|
||||
viewInput options =
|
||||
label []
|
||||
[ div [] [ text options.label ]
|
||||
, input
|
||||
[ Attr.value options.value
|
||||
, Attr.type_ options.fieldType
|
||||
, Events.onInput options.onInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
subscriptions :
|
||||
Context Flags Route Global.Model
|
||||
-> Model
|
||||
-> Sub Msg
|
||||
subscriptions _ model =
|
||||
Sub.none
|
@ -1,4 +0,0 @@
|
||||
port module Ports exposing (log)
|
||||
|
||||
|
||||
port log : String -> Cmd msg
|
@ -1,44 +0,0 @@
|
||||
module Route exposing (Route(..), fromUrl, toPath)
|
||||
|
||||
import Url exposing (Url)
|
||||
import Url.Parser as Parser
|
||||
|
||||
|
||||
type Route
|
||||
= Homepage
|
||||
| SignIn
|
||||
| Counter
|
||||
| Random
|
||||
| NotFound
|
||||
|
||||
|
||||
fromUrl : Url -> Route
|
||||
fromUrl =
|
||||
Parser.parse
|
||||
(Parser.oneOf
|
||||
[ Parser.map Homepage Parser.top
|
||||
, Parser.map SignIn (Parser.s "sign-in")
|
||||
, Parser.map Counter (Parser.s "counter")
|
||||
, Parser.map Random (Parser.s "random")
|
||||
]
|
||||
)
|
||||
>> Maybe.withDefault NotFound
|
||||
|
||||
|
||||
toPath : Route -> String
|
||||
toPath route =
|
||||
case route of
|
||||
Homepage ->
|
||||
"/"
|
||||
|
||||
SignIn ->
|
||||
"/sign-in"
|
||||
|
||||
Counter ->
|
||||
"/counter"
|
||||
|
||||
Random ->
|
||||
"/random"
|
||||
|
||||
NotFound ->
|
||||
"/not-found"
|
@ -1,19 +0,0 @@
|
||||
module Utils.Cmd exposing
|
||||
( pure
|
||||
, toCmd
|
||||
)
|
||||
|
||||
import Task
|
||||
|
||||
|
||||
pure : model -> ( model, Cmd a, Cmd b )
|
||||
pure model =
|
||||
( model
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
toCmd : msg -> Cmd msg
|
||||
toCmd msg =
|
||||
Task.perform identity (Task.succeed msg)
|
@ -1,8 +0,0 @@
|
||||
# examples/elm-ui
|
||||
> an example of how to use ryannhg/elm-app with Elm UI.
|
||||
|
||||
### try it out
|
||||
|
||||
1. `npm install`
|
||||
|
||||
1. `npm run dev`
|
@ -1,26 +0,0 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"../../src"
|
||||
],
|
||||
"elm-version": "0.19.0",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"mdgriffith/elm-ui": "1.1.5"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Elm UI Example | ryannhg/elm-app</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,3 +0,0 @@
|
||||
import { Elm } from './src/Main.elm'
|
||||
|
||||
Elm.Main.init({ node: document.getElementById('app') })
|
7098
examples/elm-ui/package-lock.json
generated
7098
examples/elm-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "@ryannhg/elm-app-examples-elm-ui",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "parcel index.html"
|
||||
},
|
||||
"author": "Ryan Haskell-Glatz",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"elm": "0.19.0-no-deps",
|
||||
"elm-hot": "^1.1.2",
|
||||
"node-elm-compiler": "^5.0.4",
|
||||
"parcel-bundler": "1.12.4"
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
module Components.Layout exposing (init, subscriptions, update, view)
|
||||
|
||||
import Element exposing (Element)
|
||||
import Flags exposing (Flags)
|
||||
import Global
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
init :
|
||||
{ navigateTo : Route -> Cmd msg
|
||||
, route : Route
|
||||
, flags : Flags
|
||||
}
|
||||
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
|
||||
init _ =
|
||||
( {}
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update :
|
||||
{ navigateTo : Route -> Cmd msg
|
||||
, route : Route
|
||||
, flags : Flags
|
||||
}
|
||||
-> Global.Msg
|
||||
-> Global.Model
|
||||
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
|
||||
update _ msg model =
|
||||
case msg of
|
||||
Global.NoOp ->
|
||||
( model, Cmd.none, Cmd.none )
|
||||
|
||||
|
||||
view :
|
||||
{ flags : Flags
|
||||
, route : Route
|
||||
, toMsg : Global.Msg -> msg
|
||||
, viewPage : Element msg
|
||||
}
|
||||
-> Global.Model
|
||||
-> Element msg
|
||||
view { viewPage } _ =
|
||||
viewPage
|
||||
|
||||
|
||||
subscriptions :
|
||||
{ navigateTo : Route -> Cmd msg
|
||||
, route : Route
|
||||
, flags : Flags
|
||||
}
|
||||
-> Global.Model
|
||||
-> Sub Global.Msg
|
||||
subscriptions _ _ =
|
||||
Sub.none
|
@ -1,27 +0,0 @@
|
||||
module Components.LinkPage exposing (view)
|
||||
|
||||
import Element exposing (..)
|
||||
import Element.Font as Font
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
view :
|
||||
{ title : String
|
||||
, link : { label : String, route : Route }
|
||||
}
|
||||
-> Element msg
|
||||
view options =
|
||||
column [ centerX, centerY, Font.center, spacing 24 ]
|
||||
[ el [ Font.bold, Font.size 48, centerX ] (text options.title)
|
||||
, link
|
||||
[ Font.underline
|
||||
, Font.color (rgb 0 0.5 0.85)
|
||||
, centerX
|
||||
, mouseOver
|
||||
[ alpha 0.75
|
||||
]
|
||||
]
|
||||
{ url = Route.toPath options.link.route
|
||||
, label = text options.link.label
|
||||
}
|
||||
]
|
@ -1,5 +0,0 @@
|
||||
module Flags exposing (Flags)
|
||||
|
||||
|
||||
type alias Flags =
|
||||
()
|
@ -1,9 +0,0 @@
|
||||
module Global exposing (Model, Msg(..))
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= NoOp
|
@ -1,41 +0,0 @@
|
||||
module Main exposing (main)
|
||||
|
||||
import Application
|
||||
import Components.Layout as Layout
|
||||
import Element
|
||||
import Flags exposing (Flags)
|
||||
import Global
|
||||
import Pages
|
||||
import Route
|
||||
|
||||
|
||||
main : Application.Program Flags Global.Model Global.Msg Pages.Model Pages.Msg
|
||||
main =
|
||||
Application.createWith
|
||||
{ toLayout =
|
||||
Element.layout
|
||||
[ Element.height Element.fill
|
||||
, Element.width Element.fill
|
||||
]
|
||||
, fromAttribute = Element.htmlAttribute
|
||||
, map = Element.map
|
||||
, node = Element.column
|
||||
}
|
||||
{ routing =
|
||||
{ transition = 200
|
||||
, fromUrl = Route.fromUrl
|
||||
, toPath = Route.toPath
|
||||
}
|
||||
, layout =
|
||||
{ init = Layout.init
|
||||
, update = Layout.update
|
||||
, view = Layout.view
|
||||
, subscriptions = Layout.subscriptions
|
||||
}
|
||||
, pages =
|
||||
{ init = Pages.init
|
||||
, update = Pages.update
|
||||
, bundle = Pages.bundle
|
||||
}
|
||||
}
|
||||
|> Application.start
|
@ -1,110 +0,0 @@
|
||||
module Pages exposing
|
||||
( Model
|
||||
, Msg
|
||||
, bundle
|
||||
, init
|
||||
, update
|
||||
)
|
||||
|
||||
import Application
|
||||
import Application.Page
|
||||
import Element exposing (Element)
|
||||
import Flags exposing (Flags)
|
||||
import Global
|
||||
import Pages.Homepage
|
||||
import Pages.NotFound
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
type Model
|
||||
= HomepageModel ()
|
||||
| NotFoundModel ()
|
||||
|
||||
|
||||
type Msg
|
||||
= HomepageMsg Never
|
||||
| NotFoundMsg Never
|
||||
|
||||
|
||||
pages =
|
||||
{ homepage =
|
||||
Application.Page.static
|
||||
{ title = "examples/elm-ui"
|
||||
, view = Pages.Homepage.view
|
||||
, toModel = HomepageModel
|
||||
, fromNever = Element.map never
|
||||
}
|
||||
, notFound =
|
||||
Application.Page.static
|
||||
{ title = "Page not found"
|
||||
, view = Pages.NotFound.view
|
||||
, toModel = NotFoundModel
|
||||
, fromNever = Element.map never
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
app =
|
||||
{ bundle = Application.bundle Element.map
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Route
|
||||
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
|
||||
init route =
|
||||
case route of
|
||||
Route.Homepage ->
|
||||
Application.init
|
||||
{ page = pages.homepage
|
||||
}
|
||||
|
||||
Route.NotFound ->
|
||||
Application.init
|
||||
{ page = pages.notFound
|
||||
}
|
||||
|
||||
|
||||
update :
|
||||
Msg
|
||||
-> Model
|
||||
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
|
||||
update appMsg appModel =
|
||||
case ( appModel, appMsg ) of
|
||||
( HomepageModel model, HomepageMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.homepage
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( HomepageModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
( NotFoundModel model, NotFoundMsg msg ) ->
|
||||
Application.update
|
||||
{ page = pages.notFound
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
|
||||
( NotFoundModel _, _ ) ->
|
||||
Application.keep appModel
|
||||
|
||||
|
||||
bundle :
|
||||
Model
|
||||
-> Application.Bundle Flags Route Global.Model Msg (Element Msg)
|
||||
bundle appModel =
|
||||
case appModel of
|
||||
HomepageModel model ->
|
||||
app.bundle
|
||||
{ page = pages.homepage
|
||||
, model = model
|
||||
}
|
||||
|
||||
NotFoundModel model ->
|
||||
app.bundle
|
||||
{ page = pages.notFound
|
||||
, model = model
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
module Pages.Homepage exposing (view)
|
||||
|
||||
import Components.LinkPage
|
||||
import Element exposing (..)
|
||||
import Route
|
||||
|
||||
|
||||
view : Element Never
|
||||
view =
|
||||
Components.LinkPage.view
|
||||
{ title = "a homepage!"
|
||||
, link =
|
||||
{ label = "Go somewhere cool"
|
||||
, route = Route.NotFound
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
module Pages.NotFound exposing (view)
|
||||
|
||||
import Components.LinkPage
|
||||
import Element exposing (..)
|
||||
import Route
|
||||
|
||||
|
||||
view : Element Never
|
||||
view =
|
||||
Components.LinkPage.view
|
||||
{ title = "yea, sorry that's it."
|
||||
, link =
|
||||
{ label = "back to homepage"
|
||||
, route = Route.Homepage
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
module Route exposing (Route(..), fromUrl, toPath)
|
||||
|
||||
import Url exposing (Url)
|
||||
import Url.Parser as Parser
|
||||
|
||||
|
||||
type Route
|
||||
= Homepage
|
||||
| NotFound
|
||||
|
||||
|
||||
fromUrl : Url -> Route
|
||||
fromUrl =
|
||||
let
|
||||
routes =
|
||||
Parser.oneOf
|
||||
[ Parser.map Homepage Parser.top
|
||||
]
|
||||
in
|
||||
Parser.parse routes >> Maybe.withDefault NotFound
|
||||
|
||||
|
||||
toPath : Route -> String
|
||||
toPath route =
|
||||
case route of
|
||||
Homepage ->
|
||||
"/"
|
||||
|
||||
NotFound ->
|
||||
"/not-found"
|
@ -1,8 +1,3 @@
|
||||
[build]
|
||||
base = "examples/basic/"
|
||||
publish = "dist/"
|
||||
command = "npm run build"
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
|
@ -1,492 +0,0 @@
|
||||
module Application exposing
|
||||
( Application
|
||||
, Bundle
|
||||
, Config
|
||||
, Context
|
||||
, Program
|
||||
, Update
|
||||
, bundle
|
||||
, create
|
||||
, createWith
|
||||
, init
|
||||
, keep
|
||||
, start
|
||||
, update
|
||||
)
|
||||
|
||||
import Browser
|
||||
import Browser.Navigation as Nav
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attr
|
||||
import Internals.Context as Context exposing (Context)
|
||||
import Internals.Page as Page exposing (Page)
|
||||
import Internals.Transitionable as Transitionable exposing (Transitionable)
|
||||
import Process
|
||||
import Task
|
||||
import Url exposing (Url)
|
||||
|
||||
|
||||
type Application flags route contextModel contextMsg model msg appElement element appAttribute
|
||||
= Application
|
||||
{ adapters : Adapters appElement appAttribute element contextMsg msg
|
||||
, config : Config flags route contextModel contextMsg model msg appElement element
|
||||
}
|
||||
|
||||
|
||||
type alias Program flags contextModel contextMsg model msg =
|
||||
Platform.Program flags (Model flags contextModel model) (Msg contextMsg msg)
|
||||
|
||||
|
||||
type alias Adapters appElement appAttribute element contextMsg msg =
|
||||
{ toLayout : appElement -> Html (Msg contextMsg msg)
|
||||
, fromAttribute : Html.Attribute (Msg contextMsg msg) -> appAttribute
|
||||
, map : (msg -> Msg contextMsg msg) -> element -> appElement
|
||||
, node : List appAttribute -> List appElement -> appElement
|
||||
}
|
||||
|
||||
|
||||
type alias LayoutContext route flags msg =
|
||||
{ navigateTo : route -> Cmd msg
|
||||
, route : route
|
||||
, flags : flags
|
||||
}
|
||||
|
||||
|
||||
type alias Config flags route contextModel contextMsg model msg appElement element =
|
||||
{ layout :
|
||||
{ init :
|
||||
LayoutContext route flags (Msg contextMsg msg)
|
||||
-> ( contextModel, Cmd contextMsg, Cmd (Msg contextMsg msg) )
|
||||
, update :
|
||||
LayoutContext route flags (Msg contextMsg msg)
|
||||
-> contextMsg
|
||||
-> contextModel
|
||||
-> ( contextModel, Cmd contextMsg, Cmd (Msg contextMsg msg) )
|
||||
, subscriptions :
|
||||
LayoutContext route flags (Msg contextMsg msg)
|
||||
-> contextModel
|
||||
-> Sub contextMsg
|
||||
, view :
|
||||
{ flags : flags
|
||||
, route : route
|
||||
, toMsg : contextMsg -> Msg contextMsg msg
|
||||
, viewPage : appElement
|
||||
}
|
||||
-> contextModel
|
||||
-> appElement
|
||||
}
|
||||
, pages :
|
||||
{ init :
|
||||
route
|
||||
-> Context flags route contextModel
|
||||
-> ( model, Cmd msg, Cmd contextMsg )
|
||||
, update :
|
||||
msg
|
||||
-> model
|
||||
-> Context flags route contextModel
|
||||
-> ( model, Cmd msg, Cmd contextMsg )
|
||||
, bundle :
|
||||
model
|
||||
-> Context flags route contextModel
|
||||
-> TitleViewSubs msg element
|
||||
}
|
||||
, routing :
|
||||
{ transition : Float
|
||||
, fromUrl : Url -> route
|
||||
, toPath : route -> String
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type alias Context flags route contextModel =
|
||||
Context.Context flags route contextModel
|
||||
|
||||
|
||||
create :
|
||||
Config flags route contextModel contextMsg model msg (Html (Msg contextMsg msg)) (Html msg)
|
||||
-> Application flags route contextModel contextMsg model msg (Html (Msg contextMsg msg)) (Html msg) (Html.Attribute (Msg contextMsg msg))
|
||||
create =
|
||||
createWith
|
||||
{ toLayout = identity
|
||||
, fromAttribute = identity
|
||||
, map = Html.map
|
||||
, node = Html.div
|
||||
}
|
||||
|
||||
|
||||
createWith :
|
||||
Adapters appElement appAttribute element contextMsg msg
|
||||
-> Config flags route contextModel contextMsg model msg appElement element
|
||||
-> Application flags route contextModel contextMsg model msg appElement element appAttribute
|
||||
createWith adapters config =
|
||||
Application
|
||||
{ adapters = adapters
|
||||
, config = config
|
||||
}
|
||||
|
||||
|
||||
start :
|
||||
Application flags route contextModel contextMsg model msg appElement element appAttribute
|
||||
-> Program flags contextModel contextMsg model msg
|
||||
start (Application { adapters, config }) =
|
||||
Browser.application
|
||||
{ init = initWithConfig config
|
||||
, update = updateWithConfig config
|
||||
, view = viewWithConfig adapters config
|
||||
, subscriptions = subscriptionsWithConfig config
|
||||
, onUrlChange = UrlChanged
|
||||
, onUrlRequest = UrlRequested
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ACTUAL STUFF
|
||||
|
||||
|
||||
type alias Model flags contextModel model =
|
||||
{ key : Nav.Key
|
||||
, url : Url
|
||||
, flags : flags
|
||||
, context : contextModel
|
||||
, page : Transitionable model
|
||||
}
|
||||
|
||||
|
||||
type Msg contextMsg msg
|
||||
= UrlChanged Url
|
||||
| UrlRequested Browser.UrlRequest
|
||||
| PageLoaded Url
|
||||
| ContextMsg contextMsg
|
||||
| PageMsg msg
|
||||
|
||||
|
||||
initWithConfig :
|
||||
Config flags route contextModel contextMsg model msg appElement element
|
||||
-> flags
|
||||
-> Url
|
||||
-> Nav.Key
|
||||
-> ( Model flags contextModel model, Cmd (Msg contextMsg msg) )
|
||||
initWithConfig config flags url key =
|
||||
let
|
||||
route =
|
||||
config.routing.fromUrl url
|
||||
|
||||
( contextModel, contextCmd, globalCmd ) =
|
||||
config.layout.init
|
||||
{ navigateTo = navigateTo config url
|
||||
, route = route
|
||||
, flags = flags
|
||||
}
|
||||
|
||||
( pageModel, pageCmd, pageContextCmd ) =
|
||||
config.pages.init
|
||||
route
|
||||
{ route = route
|
||||
, flags = flags
|
||||
, context = contextModel
|
||||
}
|
||||
in
|
||||
( { url = url
|
||||
, key = key
|
||||
, flags = flags
|
||||
, context = contextModel
|
||||
, page = Transitionable.FirstLoad pageModel
|
||||
}
|
||||
, Cmd.batch
|
||||
[ globalCmd
|
||||
, delay config.routing.transition (PageLoaded url)
|
||||
, Cmd.map ContextMsg contextCmd
|
||||
, Cmd.map ContextMsg pageContextCmd
|
||||
, Cmd.map PageMsg pageCmd
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
delay : Float -> msg -> Cmd msg
|
||||
delay ms msg =
|
||||
Task.perform (\_ -> msg) (Process.sleep ms)
|
||||
|
||||
|
||||
updateWithConfig :
|
||||
Config flags route contextModel contextMsg model msg appElement element
|
||||
-> Msg contextMsg msg
|
||||
-> Model flags contextModel model
|
||||
-> ( Model flags contextModel model, Cmd (Msg contextMsg msg) )
|
||||
updateWithConfig config msg model =
|
||||
case msg of
|
||||
UrlRequested urlRequest ->
|
||||
case urlRequest of
|
||||
Browser.Internal url ->
|
||||
( model
|
||||
, Nav.pushUrl model.key (Url.toString url)
|
||||
)
|
||||
|
||||
Browser.External url ->
|
||||
( model
|
||||
, Nav.load url
|
||||
)
|
||||
|
||||
UrlChanged url ->
|
||||
( { model | page = Transitionable.Loading (Transitionable.unwrap model.page) }
|
||||
, delay config.routing.transition (PageLoaded url)
|
||||
)
|
||||
|
||||
PageLoaded url ->
|
||||
let
|
||||
route =
|
||||
config.routing.fromUrl url
|
||||
|
||||
( pageModel, pageCmd, contextCmd ) =
|
||||
config.pages.init
|
||||
route
|
||||
{ route = route
|
||||
, flags = model.flags
|
||||
, context = model.context
|
||||
}
|
||||
in
|
||||
( { model | url = url, page = Transitionable.Loaded pageModel }
|
||||
, Cmd.batch
|
||||
[ Cmd.map PageMsg pageCmd
|
||||
, Cmd.map ContextMsg contextCmd
|
||||
]
|
||||
)
|
||||
|
||||
ContextMsg msg_ ->
|
||||
let
|
||||
( contextModel, contextCmd, globalCmd ) =
|
||||
config.layout.update
|
||||
{ navigateTo = navigateTo config model.url
|
||||
, route = config.routing.fromUrl model.url
|
||||
, flags = model.flags
|
||||
}
|
||||
msg_
|
||||
model.context
|
||||
in
|
||||
( { model | context = contextModel }
|
||||
, Cmd.batch
|
||||
[ Cmd.map ContextMsg contextCmd
|
||||
, globalCmd
|
||||
]
|
||||
)
|
||||
|
||||
PageMsg msg_ ->
|
||||
let
|
||||
( pageModel, pageCmd, contextCmd ) =
|
||||
config.pages.update
|
||||
msg_
|
||||
(Transitionable.unwrap model.page)
|
||||
{ route = config.routing.fromUrl model.url
|
||||
, flags = model.flags
|
||||
, context = model.context
|
||||
}
|
||||
in
|
||||
( { model | page = Transitionable.map (always pageModel) model.page }
|
||||
, Cmd.batch
|
||||
[ Cmd.map ContextMsg contextCmd
|
||||
, Cmd.map PageMsg pageCmd
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
type alias Document msg =
|
||||
{ title : String
|
||||
, body : List (Html msg)
|
||||
}
|
||||
|
||||
|
||||
viewWithConfig :
|
||||
Adapters appElement appAttribute element contextMsg msg
|
||||
-> Config flags route contextModel contextMsg model msg appElement element
|
||||
-> Model flags contextModel model
|
||||
-> Document (Msg contextMsg msg)
|
||||
viewWithConfig adapters config model =
|
||||
let
|
||||
transitionProp : Float -> String
|
||||
transitionProp ms =
|
||||
"opacity " ++ String.fromFloat ms ++ "ms ease-in-out"
|
||||
|
||||
( context, pageModel ) =
|
||||
contextAndPage ( config, model )
|
||||
|
||||
bundle_ : TitleViewSubs msg element
|
||||
bundle_ =
|
||||
config.pages.bundle pageModel context
|
||||
in
|
||||
{ title = bundle_.title
|
||||
, body =
|
||||
[ Html.div
|
||||
[ Attr.class "app"
|
||||
, Attr.style "transition" (transitionProp config.routing.transition)
|
||||
, Attr.style "opacity" (Transitionable.layoutOpacity model.page)
|
||||
, Attr.style "height" "100%"
|
||||
, Attr.style "width" "100%"
|
||||
]
|
||||
[ adapters.toLayout <|
|
||||
config.layout.view
|
||||
{ flags = model.flags
|
||||
, route = config.routing.fromUrl model.url
|
||||
, toMsg = ContextMsg
|
||||
, viewPage =
|
||||
adapters.node
|
||||
[ adapters.fromAttribute (Attr.style "height" "100%")
|
||||
, adapters.fromAttribute (Attr.style "width" "100%")
|
||||
, adapters.fromAttribute (Attr.style "transition" (transitionProp config.routing.transition))
|
||||
, adapters.fromAttribute (Attr.style "opacity" (Transitionable.pageOpacity model.page))
|
||||
]
|
||||
[ adapters.map PageMsg bundle_.view
|
||||
]
|
||||
}
|
||||
model.context
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
subscriptionsWithConfig :
|
||||
Config flags route contextModel contextMsg model msg appElement element
|
||||
-> Model flags contextModel model
|
||||
-> Sub (Msg contextMsg msg)
|
||||
subscriptionsWithConfig config model =
|
||||
let
|
||||
( context, pageModel ) =
|
||||
contextAndPage ( config, model )
|
||||
|
||||
bundle_ =
|
||||
config.pages.bundle pageModel context
|
||||
in
|
||||
Sub.batch
|
||||
[ Sub.map ContextMsg
|
||||
(config.layout.subscriptions
|
||||
{ navigateTo = navigateTo config model.url
|
||||
, route = config.routing.fromUrl model.url
|
||||
, flags = model.flags
|
||||
}
|
||||
model.context
|
||||
)
|
||||
, Sub.map PageMsg bundle_.subscriptions
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- UTILS
|
||||
|
||||
|
||||
contextAndPage :
|
||||
( Config flags route contextModel contextMsg model msg appElement element, Model flags contextModel model )
|
||||
-> ( Context flags route contextModel, model )
|
||||
contextAndPage ( config, model ) =
|
||||
( { route = config.routing.fromUrl model.url
|
||||
, flags = model.flags
|
||||
, context = model.context
|
||||
}
|
||||
, Transitionable.unwrap model.page
|
||||
)
|
||||
|
||||
|
||||
navigateTo :
|
||||
Config flags route contextModel contextMsg model msg appElement element
|
||||
-> Url
|
||||
-> route
|
||||
-> Cmd (Msg contextMsg msg)
|
||||
navigateTo config url route =
|
||||
Task.succeed (config.routing.toPath route)
|
||||
|> Task.map (\path -> { url | path = path })
|
||||
|> Task.map Browser.Internal
|
||||
|> Task.perform UrlRequested
|
||||
|
||||
|
||||
|
||||
-- HELPERS
|
||||
|
||||
|
||||
type alias Update flags route contextModel contextMsg appModel appMsg =
|
||||
Context flags route contextModel -> ( appModel, Cmd appMsg, Cmd contextMsg )
|
||||
|
||||
|
||||
type alias Bundle flags route contextModel appMsg appElement =
|
||||
Context flags route contextModel -> TitleViewSubs appMsg appElement
|
||||
|
||||
|
||||
init :
|
||||
{ page : Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
}
|
||||
-> Update flags route contextModel contextMsg appModel appMsg
|
||||
init config context =
|
||||
Page.init config.page context
|
||||
|> mapTruple
|
||||
{ fromMsg = Page.toMsg config.page
|
||||
, fromModel = Page.toModel config.page
|
||||
}
|
||||
|
||||
|
||||
update :
|
||||
{ page : Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
, msg : msg
|
||||
, model : model
|
||||
}
|
||||
-> Update flags route contextModel contextMsg appModel appMsg
|
||||
update config context =
|
||||
Page.update config.page context config.msg config.model
|
||||
|> mapTruple
|
||||
{ fromMsg = Page.toMsg config.page
|
||||
, fromModel = Page.toModel config.page
|
||||
}
|
||||
|
||||
|
||||
keep :
|
||||
appModel
|
||||
-> Update flags route contextModel contextMsg appModel appMsg
|
||||
keep model _ =
|
||||
( model, Cmd.none, Cmd.none )
|
||||
|
||||
|
||||
type alias TitleViewSubs appMsg appElement =
|
||||
{ title : String
|
||||
, view : appElement
|
||||
, subscriptions : Sub appMsg
|
||||
}
|
||||
|
||||
|
||||
bundle :
|
||||
((msg -> appMsg) -> pageElement -> appElement)
|
||||
->
|
||||
{ page : Page route flags contextModel contextMsg model msg appModel appMsg pageElement
|
||||
, model : model
|
||||
}
|
||||
-> Bundle flags route contextModel appMsg appElement
|
||||
bundle toHtml config context =
|
||||
{ title =
|
||||
Page.title
|
||||
config.page
|
||||
context
|
||||
config.model
|
||||
, view =
|
||||
toHtml (Page.toMsg config.page) <|
|
||||
Page.view
|
||||
config.page
|
||||
context
|
||||
config.model
|
||||
, subscriptions =
|
||||
Sub.map (Page.toMsg config.page) <|
|
||||
Page.subscriptions
|
||||
config.page
|
||||
context
|
||||
config.model
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- UTILS
|
||||
|
||||
|
||||
mapTruple :
|
||||
{ fromMsg : msg -> appMsg
|
||||
, fromModel : model -> appModel
|
||||
}
|
||||
-> ( model, Cmd msg, Cmd contextMsg )
|
||||
-> ( appModel, Cmd appMsg, Cmd contextMsg )
|
||||
mapTruple { fromModel, fromMsg } ( a, b, c ) =
|
||||
( fromModel a
|
||||
, Cmd.map fromMsg b
|
||||
, c
|
||||
)
|
@ -1,65 +0,0 @@
|
||||
module Application.Page exposing
|
||||
( element
|
||||
, page
|
||||
, sandbox
|
||||
, static
|
||||
)
|
||||
|
||||
import Internals.Context exposing (Context)
|
||||
import Internals.Page as Internals
|
||||
|
||||
|
||||
type alias Page route flags contextModel contextMsg model msg appModel appMsg element =
|
||||
Internals.Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
|
||||
|
||||
static :
|
||||
{ title : String
|
||||
, view : neverElement
|
||||
, toModel : () -> appModel
|
||||
, fromNever : neverElement -> element
|
||||
}
|
||||
-> Page route flags contextModel contextMsg () Never appModel appMsg element
|
||||
static =
|
||||
Internals.static
|
||||
|
||||
|
||||
sandbox :
|
||||
{ title : model -> String
|
||||
, init : model
|
||||
, update : msg -> model -> model
|
||||
, view : model -> element
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
sandbox =
|
||||
Internals.sandbox
|
||||
|
||||
|
||||
element :
|
||||
{ title : model -> String
|
||||
, init : flags -> ( model, Cmd msg )
|
||||
, update : msg -> model -> ( model, Cmd msg )
|
||||
, subscriptions : model -> Sub msg
|
||||
, view : model -> element
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
element =
|
||||
Internals.element
|
||||
|
||||
|
||||
page :
|
||||
{ title : Context flags route contextModel -> model -> String
|
||||
, init : Context flags route contextModel -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, update : Context flags route contextModel -> msg -> model -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, subscriptions : Context flags route contextModel -> model -> Sub msg
|
||||
, view : Context flags route contextModel -> model -> element
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
page =
|
||||
Internals.page
|
@ -1,8 +0,0 @@
|
||||
module Internals.Context exposing (Context)
|
||||
|
||||
|
||||
type alias Context flags route contextModel =
|
||||
{ flags : flags
|
||||
, route : route
|
||||
, context : contextModel
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
module Internals.Page exposing
|
||||
( Page
|
||||
, element
|
||||
, init
|
||||
, page
|
||||
, sandbox
|
||||
, static
|
||||
, subscriptions
|
||||
, title
|
||||
, toModel
|
||||
, toMsg
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Internals.Context exposing (Context)
|
||||
|
||||
|
||||
type Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
= Page (Page_ route flags contextModel contextMsg model msg appModel appMsg element)
|
||||
|
||||
|
||||
type alias Page_ route flags contextModel contextMsg model msg appModel appMsg element =
|
||||
{ title : Context flags route contextModel -> model -> String
|
||||
, init : Context flags route contextModel -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, update : Context flags route contextModel -> msg -> model -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, subscriptions : Context flags route contextModel -> model -> Sub msg
|
||||
, view : Context flags route contextModel -> model -> element
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- CONSTRUCTORS
|
||||
|
||||
|
||||
static :
|
||||
{ title : String
|
||||
, view : neverElement
|
||||
, toModel : () -> appModel
|
||||
, fromNever : neverElement -> element
|
||||
}
|
||||
-> Page route flags contextModel contextMsg () Never appModel appMsg element
|
||||
static config =
|
||||
Page
|
||||
{ title = \c m -> config.title
|
||||
, init = \c -> ( (), Cmd.none, Cmd.none )
|
||||
, update = \c m model -> ( model, Cmd.none, Cmd.none )
|
||||
, subscriptions = \c m -> Sub.none
|
||||
, view = \c m -> config.fromNever config.view
|
||||
, toMsg = never
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
sandbox :
|
||||
{ title : model -> String
|
||||
, init : model
|
||||
, update : msg -> model -> model
|
||||
, view : model -> element
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
sandbox config =
|
||||
Page
|
||||
{ title = \c model -> config.title model
|
||||
, init = \c -> ( config.init, Cmd.none, Cmd.none )
|
||||
, update = \c msg model -> ( config.update msg model, Cmd.none, Cmd.none )
|
||||
, subscriptions = \c m -> Sub.none
|
||||
, view = \c model -> config.view model
|
||||
, toMsg = config.toMsg
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
element :
|
||||
{ title : model -> String
|
||||
, init : flags -> ( model, Cmd msg )
|
||||
, update : msg -> model -> ( model, Cmd msg )
|
||||
, subscriptions : model -> Sub msg
|
||||
, view : model -> element
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
element config =
|
||||
let
|
||||
appendCmd ( model, cmd ) =
|
||||
( model, cmd, Cmd.none )
|
||||
in
|
||||
Page
|
||||
{ title = \c model -> config.title model
|
||||
, init = \c -> config.init c.flags |> appendCmd
|
||||
, update = \c msg model -> config.update msg model |> appendCmd
|
||||
, subscriptions = \c model -> config.subscriptions model
|
||||
, view = \c model -> config.view model
|
||||
, toMsg = config.toMsg
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
page :
|
||||
{ title : Context flags route contextModel -> model -> String
|
||||
, init : Context flags route contextModel -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, update : Context flags route contextModel -> msg -> model -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, subscriptions : Context flags route contextModel -> model -> Sub msg
|
||||
, view : Context flags route contextModel -> model -> element
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
page config =
|
||||
let
|
||||
appendCmd ( model, cmd ) =
|
||||
( model, cmd, Cmd.none )
|
||||
in
|
||||
Page
|
||||
{ title = config.title
|
||||
, init = config.init
|
||||
, update = config.update
|
||||
, subscriptions = \c model -> config.subscriptions c model
|
||||
, view = \c model -> config.view c model
|
||||
, toMsg = config.toMsg
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ACCESSORS
|
||||
|
||||
|
||||
init :
|
||||
Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
-> Context flags route contextModel
|
||||
-> ( model, Cmd msg, Cmd contextMsg )
|
||||
init (Page page_) =
|
||||
page_.init
|
||||
|
||||
|
||||
update :
|
||||
Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
-> Context flags route contextModel
|
||||
-> msg
|
||||
-> model
|
||||
-> ( model, Cmd msg, Cmd contextMsg )
|
||||
update (Page page_) =
|
||||
page_.update
|
||||
|
||||
|
||||
title :
|
||||
Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
-> Context flags route contextModel
|
||||
-> model
|
||||
-> String
|
||||
title (Page page_) =
|
||||
page_.title
|
||||
|
||||
|
||||
view :
|
||||
Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
-> Context flags route contextModel
|
||||
-> model
|
||||
-> element
|
||||
view (Page page_) =
|
||||
page_.view
|
||||
|
||||
|
||||
subscriptions :
|
||||
Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
-> Context flags route contextModel
|
||||
-> model
|
||||
-> Sub msg
|
||||
subscriptions (Page page_) =
|
||||
page_.subscriptions
|
||||
|
||||
|
||||
toMsg :
|
||||
Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
-> msg
|
||||
-> appMsg
|
||||
toMsg (Page page_) =
|
||||
page_.toMsg
|
||||
|
||||
|
||||
toModel :
|
||||
Page route flags contextModel contextMsg model msg appModel appMsg element
|
||||
-> model
|
||||
-> appModel
|
||||
toModel (Page page_) =
|
||||
page_.toModel
|
@ -1,76 +0,0 @@
|
||||
module Internals.Transitionable exposing
|
||||
( Transitionable(..)
|
||||
, isFirstLoad
|
||||
, layoutOpacity
|
||||
, map
|
||||
, pageOpacity
|
||||
, unwrap
|
||||
)
|
||||
|
||||
|
||||
type Transitionable a
|
||||
= FirstLoad a
|
||||
| Loading a
|
||||
| Loaded a
|
||||
|
||||
|
||||
isFirstLoad : Transitionable a -> Bool
|
||||
isFirstLoad transitionable =
|
||||
case transitionable of
|
||||
FirstLoad _ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
unwrap : Transitionable a -> a
|
||||
unwrap transitionable =
|
||||
case transitionable of
|
||||
FirstLoad a ->
|
||||
a
|
||||
|
||||
Loading a ->
|
||||
a
|
||||
|
||||
Loaded a ->
|
||||
a
|
||||
|
||||
|
||||
map : (a -> b) -> Transitionable a -> Transitionable b
|
||||
map fn transitionable =
|
||||
case transitionable of
|
||||
FirstLoad a ->
|
||||
FirstLoad (fn a)
|
||||
|
||||
Loading a ->
|
||||
Loading (fn a)
|
||||
|
||||
Loaded a ->
|
||||
Loaded (fn a)
|
||||
|
||||
|
||||
layoutOpacity : Transitionable a -> String
|
||||
layoutOpacity transitionable =
|
||||
case transitionable of
|
||||
FirstLoad _ ->
|
||||
"0"
|
||||
|
||||
Loading _ ->
|
||||
"1"
|
||||
|
||||
Loaded _ ->
|
||||
"1"
|
||||
|
||||
|
||||
pageOpacity : Transitionable a -> String
|
||||
pageOpacity transitionable =
|
||||
case transitionable of
|
||||
FirstLoad _ ->
|
||||
"0"
|
||||
|
||||
Loading _ ->
|
||||
"0"
|
||||
|
||||
Loaded _ ->
|
||||
"1"
|
Loading…
Reference in New Issue
Block a user