more examples never hurt nobody

This commit is contained in:
Ryan Haskell-Glatz 2020-03-29 21:33:19 -05:00
parent 099e910865
commit 0e39d0375e
13 changed files with 2553 additions and 0 deletions

6
examples/transitions/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.DS_Store
dist
elm-stuff
node_modules
src/Generated

View File

@ -0,0 +1,148 @@
# examples/transition
```
npm start
```
## how i added transitions
```
npm install -g elm-spa
elm-spa init transitions
cd transitions
npm start
```
### 1. add more info to the model
__`src/Main.elm`__
```elm
type alias Model =
{ key : Key
, url : Url
, isTransitioning : Bool
, global : Global.Model
, page : Pages.Model
}
```
### 2. handle updates to that state
Added a new message called `PageLoaded` msg to delay the effects of `UrlChanged`:
__`src/Main.elm`__
```elm
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url
| PageLoaded Url
| Global Global.Msg
| Page Pages.Msg
```
```elm
import Process
import Task
delay : Float -> msg -> Cmd msg
delay ms msg =
Process.sleep ms
|> Task.andThen (\_ -> Task.succeed msg)
|> Task.perform identity
```
```elm
update msg model =
case msg of
-- ...
UrlChanged url ->
( { model | isTransitioning = True }
, delay 300 (PageLoaded url)
)
PageLoaded url ->
let
( page, pageCmd, globalCmd ) =
Pages.init (fromUrl url) model.global
in
( { model
| isTransitioning = False
, url = url
, page = page
}
, Cmd.batch
[ Cmd.map Page pageCmd
, Cmd.map Global globalCmd
]
)
```
### 3. pass that info to the layout component
__`src/Main.elm`__
```elm
Global.view
{ page = Pages.view model.page model.global |> documentMap Page
, global = model.global
, toMsg = Global
, isTransitioning = model.isTransitioning
}
```
__`src/Global.elm`__
```elm
view :
{ page : Document msg
, global : Model
, toMsg : Msg -> msg
, isTransitioning : Bool
}
-> Document msg
view { page, global, toMsg, isTransitioning } =
Components.layout
{ page = page
, isTransitioning = isTransitioning
}
```
__`src/Components.elm`__
```elm
layout :
{ page : Document msg
, isTransitioning : Bool
}
-> Document msg
layout { page, isTransitioning } =
{ title = page.title
, body =
[ div [ class "column spacing--large pad--medium container h--fill" ]
[ navbar
, div
[ class "column"
, style "flex" "1 0 auto"
, style "transition" "opacity 300ms ease-in-out"
, style "opacity"
(if isTransitioning then
"0"
else
"1"
)
]
page.body
, footer
]
]
}
```
### works with elm-ui too!
this example is using `elm/html`, but a similar strategy would work for `elm-ui` (just use `alpha` instead!)

View File

@ -0,0 +1,25 @@
{
"type": "application",
"source-directories": [
"src",
"../../src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "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": {}
}
}

1846
examples/transitions/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
{
"name": "elm-spa-app",
"version": "1.0.0",
"description": "my new elm-spa application",
"main": "public/index.html",
"scripts": {
"start": "npm install && npm run build && npm run dev",
"build": "npm run build:elm-spa && npm run build:elm",
"build:elm-spa": "elm-spa build .",
"build:elm": "elm make src/Main.elm --optimize --output public/dist/elm.js",
"dev": "concurrently --raw --kill-others \"npm run dev:elm-spa\" \"npm run dev:elm\"",
"dev:elm-spa": "chokidar src/Pages -c \"npm run build:elm-spa\"",
"dev:elm": "elm-live src/Main.elm -u -d public -- --debug --output public/dist/elm.js"
},
"keywords": [
"elm",
"spa"
],
"author": "",
"license": "ISC",
"devDependencies": {
"chokidar-cli": "2.1.0",
"concurrently": "5.0.0",
"elm": "0.19.1-3",
"elm-live": "4.0.2",
"elm-spa": "4.0.5"
}
}

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://not-much-css.netlify.com/not-much.css" />
<title>elm-spa</title>
</head>
<body>
<script src="/dist/elm.js"></script>
<script>
Elm.Main.init()
</script>
</body>
</html>

View File

@ -0,0 +1,52 @@
module Components exposing (layout)
import Browser exposing (Document)
import Generated.Route as Route exposing (Route)
import Html exposing (..)
import Html.Attributes as Attr exposing (class, href, style)
layout :
{ page : Document msg
, isTransitioning : Bool
}
-> Document msg
layout { page, isTransitioning } =
{ title = page.title
, body =
[ div [ class "column spacing--large pad--medium container h--fill" ]
[ navbar
, div
[ class "column"
, style "flex" "1 0 auto"
, style "transition" "opacity 300ms ease-in-out"
, style "opacity"
(if isTransitioning then
"0"
else
"1"
)
]
page.body
, footer
]
]
}
navbar : Html msg
navbar =
header [ class "row center-y spacing--between" ]
[ a [ class "link font--h5", href (Route.toHref Route.Top) ] [ text "home" ]
, div [ class "row center-y spacing--medium" ]
[ a [ class "link", href (Route.toHref Route.Docs) ] [ text "docs" ]
, a [ class "link", href (Route.toHref Route.NotFound) ] [ text "a broken link" ]
, a [ class "button", href "https://twitter.com/intent/tweet?text=elm-spa is ez pz" ] [ text "tweet about it" ]
]
]
footer : Html msg
footer =
Html.footer [] [ text "built with elm " ]

View File

@ -0,0 +1,100 @@
module Global exposing
( Flags
, Model
, Msg
, init
, navigate
, subscriptions
, update
, view
)
import Browser exposing (Document)
import Browser.Navigation as Nav
import Components
import Generated.Route as Route exposing (Route)
import Task
import Url exposing (Url)
-- INIT
type alias Flags =
()
type alias Model =
{ flags : Flags
, url : Url
, key : Nav.Key
}
init : Flags -> Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
( Model
flags
url
key
, Cmd.none
)
-- UPDATE
type Msg
= Navigate Route
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Navigate route ->
( model
, Nav.pushUrl model.key (Route.toHref route)
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view :
{ page : Document msg
, global : Model
, toMsg : Msg -> msg
, isTransitioning : Bool
}
-> Document msg
view { page, global, toMsg, isTransitioning } =
Components.layout
{ page = page
, isTransitioning = isTransitioning
}
-- COMMANDS
send : msg -> Cmd msg
send =
Task.succeed >> Task.perform identity
navigate : Route -> Cmd Msg
navigate route =
send (Navigate route)

View File

@ -0,0 +1,162 @@
module Main exposing (main)
import Browser exposing (Document)
import Browser.Navigation as Nav exposing (Key)
import Generated.Pages as Pages
import Generated.Route as Route exposing (Route)
import Global
import Html
import Process
import Task
import Url exposing (Url)
main : Program Flags Model Msg
main =
Browser.application
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
, onUrlRequest = LinkClicked
, onUrlChange = UrlChanged
}
-- INIT
type alias Flags =
()
type alias Model =
{ key : Key
, url : Url
, isTransitioning : Bool
, global : Global.Model
, page : Pages.Model
}
init : Flags -> Url -> Key -> ( Model, Cmd Msg )
init flags url key =
let
( global, globalCmd ) =
Global.init flags url key
( page, pageCmd, pageGlobalCmd ) =
Pages.init (fromUrl url) global
in
( Model key url False global page
, Cmd.batch
[ Cmd.map Global globalCmd
, Cmd.map Global pageGlobalCmd
, Cmd.map Page pageCmd
]
)
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url
| PageLoaded Url
| Global Global.Msg
| Page Pages.Msg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkClicked (Browser.Internal url) ->
( model, Nav.pushUrl model.key (Url.toString url) )
LinkClicked (Browser.External href) ->
( model, Nav.load href )
UrlChanged url ->
( { model | isTransitioning = True }
, delay 300 (PageLoaded url)
)
PageLoaded url ->
let
( page, pageCmd, globalCmd ) =
Pages.init (fromUrl url) model.global
in
( { model
| isTransitioning = False
, url = url
, page = page
}
, Cmd.batch
[ Cmd.map Page pageCmd
, Cmd.map Global globalCmd
]
)
Global globalMsg ->
let
( global, globalCmd ) =
Global.update globalMsg model.global
in
( { model | global = global }
, Cmd.map Global globalCmd
)
Page pageMsg ->
let
( page, pageCmd, globalCmd ) =
Pages.update pageMsg model.page model.global
in
( { model | page = page }
, Cmd.batch
[ Cmd.map Page pageCmd
, Cmd.map Global globalCmd
]
)
delay : Float -> msg -> Cmd msg
delay ms msg =
Process.sleep ms
|> Task.andThen (\_ -> Task.succeed msg)
|> Task.perform identity
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ model.global
|> Global.subscriptions
|> Sub.map Global
, model.page
|> (\page -> Pages.subscriptions page model.global)
|> Sub.map Page
]
view : Model -> Browser.Document Msg
view model =
let
documentMap :
(msg1 -> msg2)
-> Document msg1
-> Document msg2
documentMap fn doc =
{ title = doc.title
, body = List.map (Html.map fn) doc.body
}
in
Global.view
{ page = Pages.view model.page model.global |> documentMap Page
, isTransitioning = model.isTransitioning
, global = model.global
, toMsg = Global
}
fromUrl : Url -> Route
fromUrl =
Route.fromUrl >> Maybe.withDefault Route.NotFound

View File

@ -0,0 +1,81 @@
module Page exposing
( Page, Document, Bundle
, upgrade
, static, sandbox, element, component
)
{-|
@docs Page, Document, Bundle
@docs upgrade
@docs static, sandbox, element, component
-}
import Browser
import Global
import Spa
type alias Document msg =
Browser.Document msg
type alias Page flags model msg =
Spa.Page flags model msg Global.Model Global.Msg
type alias Bundle msg =
Spa.Bundle msg
upgrade :
(pageModel -> model)
-> (pageMsg -> msg)
-> Page pageFlags pageModel pageMsg
->
{ init : pageFlags -> Global.Model -> ( model, Cmd msg, Cmd Global.Msg )
, update : pageMsg -> pageModel -> Global.Model -> ( model, Cmd msg, Cmd Global.Msg )
, bundle : pageModel -> Global.Model -> Bundle msg
}
upgrade =
Spa.upgrade
static : { view : Document msg } -> Page flags () msg
static =
Spa.static
sandbox :
{ init : model
, update : msg -> model -> model
, view : model -> Document msg
}
-> Page flags model msg
sandbox =
Spa.sandbox
element :
{ init : flags -> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, view : model -> Document msg
}
-> Page flags model msg
element =
Spa.element
component :
{ init : Global.Model -> flags -> ( model, Cmd msg, Cmd Global.Msg )
, update : Global.Model -> msg -> model -> ( model, Cmd msg, Cmd Global.Msg )
, subscriptions : Global.Model -> model -> Sub msg
, view : Global.Model -> model -> Document msg
}
-> Page flags model msg
component =
Spa.component

View File

@ -0,0 +1,30 @@
module Pages.Docs exposing (Flags, Model, Msg, page)
import Html
import Page exposing (Document, Page)
type alias Flags =
()
type alias Model =
()
type alias Msg =
Never
page : Page Flags Model Msg
page =
Page.static
{ view = view
}
view : Document Msg
view =
{ title = "Docs"
, body = [ Html.text "Docs" ]
}

View File

@ -0,0 +1,30 @@
module Pages.NotFound exposing (Flags, Model, Msg, page)
import Html
import Page exposing (Document, Page)
type alias Flags =
()
type alias Model =
()
type alias Msg =
Never
page : Page Flags Model Msg
page =
Page.static
{ view = view
}
view : Document Msg
view =
{ title = "NotFound"
, body = [ Html.text "NotFound" ]
}

View File

@ -0,0 +1,30 @@
module Pages.Top exposing (Flags, Model, Msg, page)
import Html
import Page exposing (Document, Page)
type alias Flags =
()
type alias Model =
()
type alias Msg =
Never
page : Page Flags Model Msg
page =
Page.static
{ view = view
}
view : Document Msg
view =
{ title = "Top"
, body = [ Html.text "Top" ]
}