mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-11-22 03:12:01 +03:00
add first two examples
This commit is contained in:
parent
c8689cf7f3
commit
896145a2d9
5
examples/01-hello-world/.gitignore
vendored
Normal file
5
examples/01-hello-world/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
.elm-spa
|
||||
elm-stuff
|
||||
node_modules
|
||||
dist
|
@ -1,16 +1,28 @@
|
||||
# examples/01-hello-world
|
||||
> A web application made with [elm-spa](https://elm-spa.dev)
|
||||
# my new project
|
||||
> 🌳 built with [elm-spa](https://elm-spa.dev)
|
||||
|
||||
## dependencies
|
||||
|
||||
This project requires the latest LTS version of [Node.js](https://nodejs.org/)
|
||||
|
||||
```bash
|
||||
npm install -g elm elm-spa
|
||||
```
|
||||
|
||||
## running locally
|
||||
|
||||
```bash
|
||||
elm-spa server
|
||||
elm-spa server # starts this app at http:/localhost:1234
|
||||
```
|
||||
|
||||
### other commands
|
||||
|
||||
```bash
|
||||
elm-spa add <url> # add a new page
|
||||
elm-spa build # production build
|
||||
elm-spa watch # compile as you code, without the server!
|
||||
elm-spa add # add a new page to the application
|
||||
elm-spa build # production build
|
||||
elm-spa watch # runs build as you code (without the server)
|
||||
```
|
||||
|
||||
## learn more
|
||||
|
||||
You can learn more at [elm-spa.dev](https://elm-spa.dev)
|
@ -3,8 +3,7 @@
|
||||
"source-directories": [
|
||||
"src",
|
||||
".elm-spa/defaults",
|
||||
".elm-spa/generated",
|
||||
"../../src"
|
||||
".elm-spa/generated"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
@ -13,7 +12,8 @@
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/url": "1.0.0"
|
||||
"elm/url": "1.0.0",
|
||||
"ryannhg/elm-spa": "5.3.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/time": "1.0.0",
|
||||
|
@ -1,11 +1,11 @@
|
||||
module Pages.Home_ exposing (view)
|
||||
|
||||
import Html
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
view : View msg
|
||||
view =
|
||||
{ title = "Homepage"
|
||||
, body =
|
||||
[ Html.text "Hello, world!"
|
||||
]
|
||||
, body = [ Html.text "Hello, world!" ]
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
This project requires the latest LTS version of [Node.js](https://nodejs.org/)
|
||||
|
||||
```bash
|
||||
npm install -g elm-spa@latest
|
||||
npm install -g elm elm-spa
|
||||
```
|
||||
|
||||
## running locally
|
||||
|
@ -11,11 +11,14 @@
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/http": "2.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/url": "1.0.0",
|
||||
"ryannhg/elm-spa": "5.3.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="/dist/elm.js"></script>
|
||||
|
27
examples/02-pages/public/style.css
Normal file
27
examples/02-pages/public/style.css
Normal file
@ -0,0 +1,27 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar .brand {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navbar .splitter { flex: 1 1 auto; }
|
||||
|
||||
.navbar a {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-top: 3rem;
|
||||
}
|
86
examples/02-pages/src/Pages/Advanced.elm
Normal file
86
examples/02-pages/src/Pages/Advanced.elm
Normal file
@ -0,0 +1,86 @@
|
||||
module Pages.Advanced exposing (Model, Msg, page)
|
||||
|
||||
import Effect exposing (Effect)
|
||||
import Gen.Params.Advanced exposing (Params)
|
||||
import Html
|
||||
import Html.Events as Events
|
||||
import Page
|
||||
import Request
|
||||
import Shared
|
||||
import UI
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
page : Shared.Model -> Request.With Params -> Page.With Model Msg
|
||||
page shared req =
|
||||
Page.advanced
|
||||
{ init = init
|
||||
, update = update
|
||||
, view = view shared
|
||||
, subscriptions = subscriptions
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- INIT
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
init : ( Model, Effect Msg )
|
||||
init =
|
||||
( {}, Effect.none )
|
||||
|
||||
|
||||
|
||||
-- UPDATE
|
||||
|
||||
|
||||
type Msg
|
||||
= IncrementShared
|
||||
| DecrementShared
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Effect Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
IncrementShared ->
|
||||
( model
|
||||
, Effect.fromShared Shared.Increment
|
||||
)
|
||||
|
||||
DecrementShared ->
|
||||
( model
|
||||
, Effect.fromShared Shared.Decrement
|
||||
)
|
||||
|
||||
|
||||
|
||||
-- SUBSCRIPTIONS
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.none
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
view : Shared.Model -> Model -> View Msg
|
||||
view shared model =
|
||||
{ title = "Advanced"
|
||||
, body =
|
||||
UI.layout
|
||||
[ UI.h1 "Advanced"
|
||||
, Html.p [] [ Html.text "An advanced page uses Effects instead of Cmds, which allow you to send Shared messages directly from a page." ]
|
||||
, Html.h2 [] [ Html.text "Shared Counter" ]
|
||||
, Html.h3 [] [ Html.text (String.fromInt shared.counter) ]
|
||||
, Html.button [ Events.onClick DecrementShared ] [ Html.text "-" ]
|
||||
, Html.button [ Events.onClick IncrementShared ] [ Html.text "+" ]
|
||||
, Html.p [] [ Html.text "This value doesn't reset as you navigate from one page to another (but will on page refresh)!" ]
|
||||
]
|
||||
}
|
30
examples/02-pages/src/Pages/Dynamic/Name_.elm
Normal file
30
examples/02-pages/src/Pages/Dynamic/Name_.elm
Normal file
@ -0,0 +1,30 @@
|
||||
module Pages.Dynamic.Name_ exposing (page)
|
||||
|
||||
import Gen.Params.Dynamic.Name_ exposing (Params)
|
||||
import Html exposing (Html)
|
||||
import Page exposing (Page)
|
||||
import Request
|
||||
import Shared
|
||||
import UI
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
page : Shared.Model -> Request.With Params -> Page
|
||||
page shared req =
|
||||
Page.static
|
||||
{ view = view req.params
|
||||
}
|
||||
|
||||
|
||||
view : Params -> View msg
|
||||
view params =
|
||||
{ title = "Dynamic: " ++ params.name
|
||||
, body =
|
||||
UI.layout
|
||||
[ UI.h1 "Dynamic Page"
|
||||
, Html.p [] [ Html.text "Dynamic pages with underscores can safely access URL parameters." ]
|
||||
, Html.p [] [ Html.text "Because this file is named \"Name_.elm\", it has a \"name\" parameter." ]
|
||||
, Html.p [] [ Html.text "Try changing the URL above to something besides \"apple\" or \"banana\"! " ]
|
||||
, Html.h2 [] [ Html.text params.name ]
|
||||
]
|
||||
}
|
@ -1,13 +1,22 @@
|
||||
module Pages.Element exposing (Model, Msg, page)
|
||||
|
||||
import Browser.Dom exposing (Viewport)
|
||||
import Browser.Events
|
||||
import Gen.Params.Element exposing (Params)
|
||||
import Page exposing (Page)
|
||||
import Request exposing (Request)
|
||||
import Html
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Http
|
||||
import Json.Decode as Json
|
||||
import Page
|
||||
import Request
|
||||
import Shared
|
||||
import Task
|
||||
import UI
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
page : Shared.Model -> Request Params -> Page Model Msg
|
||||
page : Shared.Model -> Request.With Params -> Page.With Model Msg
|
||||
page shared req =
|
||||
Page.element
|
||||
{ init = init
|
||||
@ -22,12 +31,25 @@ page shared req =
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
{ window : { width : Int, height : Int }
|
||||
, image : WebRequest
|
||||
}
|
||||
|
||||
|
||||
type WebRequest
|
||||
= NotAsked
|
||||
| Success String
|
||||
| Failure
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( {}, Cmd.none )
|
||||
( { window = { width = 0, height = 0 }
|
||||
, image = NotAsked
|
||||
}
|
||||
, Browser.Dom.getViewport
|
||||
|> Task.perform GotInitialViewport
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -35,14 +57,52 @@ init =
|
||||
|
||||
|
||||
type Msg
|
||||
= ReplaceMe
|
||||
= ResizedWindow Int Int
|
||||
| GotInitialViewport Viewport
|
||||
| ClickedFetchCat
|
||||
| GotCatGif (Result Http.Error String)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
ReplaceMe ->
|
||||
( model, Cmd.none )
|
||||
GotInitialViewport { viewport } ->
|
||||
( { model
|
||||
| window =
|
||||
{ width = floor viewport.width
|
||||
, height = floor viewport.height
|
||||
}
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ResizedWindow w h ->
|
||||
( { model | window = { width = w, height = h } }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ClickedFetchCat ->
|
||||
let
|
||||
gifDecoder =
|
||||
Json.field "url" Json.string
|
||||
|> Json.map (\url -> "https://cataas.com" ++ url)
|
||||
in
|
||||
( model
|
||||
, Http.get
|
||||
{ url = "https://cataas.com/cat?json=true&type=sm"
|
||||
, expect = Http.expectJson GotCatGif gifDecoder
|
||||
}
|
||||
)
|
||||
|
||||
GotCatGif (Ok url) ->
|
||||
( { model | image = Success url }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GotCatGif (Err _) ->
|
||||
( { model | image = Failure }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -51,7 +111,7 @@ update msg model =
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.none
|
||||
Browser.Events.onResize ResizedWindow
|
||||
|
||||
|
||||
|
||||
@ -60,4 +120,35 @@ subscriptions model =
|
||||
|
||||
view : Model -> View Msg
|
||||
view model =
|
||||
View.placeholder "Element"
|
||||
{ title = "Element"
|
||||
, body =
|
||||
UI.layout
|
||||
[ UI.h1 "Element"
|
||||
, Html.p [] [ Html.text "An element page can perform side-effects like HTTP requests and subscribe to events from the browser!" ]
|
||||
, Html.br [] []
|
||||
, Html.h2 [] [ Html.text "Commands" ]
|
||||
, Html.p []
|
||||
[ Html.button [ Events.onClick ClickedFetchCat ] [ Html.text "Get a cat" ]
|
||||
]
|
||||
, case model.image of
|
||||
NotAsked ->
|
||||
Html.text ""
|
||||
|
||||
Failure ->
|
||||
Html.text "Something went wrong, please try again."
|
||||
|
||||
Success image ->
|
||||
Html.img [ Attr.src image, Attr.alt "Cat" ] []
|
||||
, Html.br [] []
|
||||
, Html.h2 [] [ Html.text "Subscriptions" ]
|
||||
, Html.p []
|
||||
[ Html.strong [] [ Html.text "Window size:" ]
|
||||
, Html.text (windowSizeToString model.window)
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
windowSizeToString : { width : Int, height : Int } -> String
|
||||
windowSizeToString { width, height } =
|
||||
"( " ++ String.fromInt width ++ ", " ++ String.fromInt height ++ " )"
|
||||
|
@ -1,11 +1,16 @@
|
||||
module Pages.Home_ exposing (view)
|
||||
|
||||
import Html
|
||||
import UI
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
view : View Never
|
||||
view : View msg
|
||||
view =
|
||||
{ title = "Homepage"
|
||||
, body = [ Html.text "Hello, world!" ]
|
||||
, body =
|
||||
UI.layout
|
||||
[ Html.h1 [] [ Html.text "Homepage" ]
|
||||
, Html.p [] [ Html.text "This homepage is just a view function, click the links in the navbar to see more pages!" ]
|
||||
]
|
||||
}
|
||||
|
71
examples/02-pages/src/Pages/Sandbox.elm
Normal file
71
examples/02-pages/src/Pages/Sandbox.elm
Normal file
@ -0,0 +1,71 @@
|
||||
module Pages.Sandbox exposing (Model, Msg, page)
|
||||
|
||||
import Gen.Params.Sandbox exposing (Params)
|
||||
import Html
|
||||
import Html.Events
|
||||
import Page
|
||||
import Request
|
||||
import Shared
|
||||
import UI
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
page : Shared.Model -> Request.With Params -> Page.With Model Msg
|
||||
page shared req =
|
||||
Page.sandbox
|
||||
{ init = init
|
||||
, update = update
|
||||
, view = view
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- INIT
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ counter = 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- UPDATE
|
||||
|
||||
|
||||
type Msg
|
||||
= Increment
|
||||
| Decrement
|
||||
|
||||
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
Increment ->
|
||||
{ model | counter = model.counter + 1 }
|
||||
|
||||
Decrement ->
|
||||
{ model | counter = model.counter - 1 }
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
view : Model -> View Msg
|
||||
view model =
|
||||
{ title = "Sandbox"
|
||||
, body =
|
||||
UI.layout
|
||||
[ UI.h1 "Sandbox"
|
||||
, Html.p [] [ Html.text "A sandbox page can keep track of state!" ]
|
||||
, Html.h3 [] [ Html.text (String.fromInt model.counter) ]
|
||||
, Html.button [ Html.Events.onClick Decrement ] [ Html.text "-" ]
|
||||
, Html.button [ Html.Events.onClick Increment ] [ Html.text "+" ]
|
||||
]
|
||||
}
|
@ -1,14 +1,27 @@
|
||||
module Pages.Static exposing (page)
|
||||
|
||||
import Page
|
||||
import View
|
||||
import Gen.Params.Static exposing (Params)
|
||||
import Html
|
||||
import Page exposing (Page)
|
||||
import Request
|
||||
import Shared
|
||||
import UI
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
page : Shared.Model -> Request.With Params -> Page
|
||||
page shared req =
|
||||
Page.static
|
||||
{ view = view
|
||||
}
|
||||
|
||||
|
||||
view : View msg
|
||||
view =
|
||||
View.placeholder "Static"
|
||||
{ title = "Static"
|
||||
, body =
|
||||
UI.layout
|
||||
[ UI.h1 "Static"
|
||||
, Html.p [] [ Html.text "A static page only renders a view, but has access to shared state and URL information." ]
|
||||
]
|
||||
}
|
||||
|
49
examples/02-pages/src/Shared.elm
Normal file
49
examples/02-pages/src/Shared.elm
Normal file
@ -0,0 +1,49 @@
|
||||
module Shared exposing
|
||||
( Flags
|
||||
, Model
|
||||
, Msg(..)
|
||||
, init
|
||||
, subscriptions
|
||||
, update
|
||||
)
|
||||
|
||||
import Json.Decode as Json
|
||||
import Request exposing (Request)
|
||||
|
||||
|
||||
type alias Flags =
|
||||
Json.Value
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Increment
|
||||
| Decrement
|
||||
|
||||
|
||||
init : Request -> Flags -> ( Model, Cmd Msg )
|
||||
init _ _ =
|
||||
( { counter = 0 }, Cmd.none )
|
||||
|
||||
|
||||
update : Request -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update _ msg model =
|
||||
case msg of
|
||||
Increment ->
|
||||
( { model | counter = model.counter + 1 }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Decrement ->
|
||||
( { model | counter = model.counter - 1 }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Request -> Model -> Sub Msg
|
||||
subscriptions _ _ =
|
||||
Sub.none
|
33
examples/02-pages/src/UI.elm
Normal file
33
examples/02-pages/src/UI.elm
Normal file
@ -0,0 +1,33 @@
|
||||
module UI exposing (h1, layout)
|
||||
|
||||
import Gen.Route as Route exposing (Route)
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attr
|
||||
|
||||
|
||||
layout : List (Html msg) -> List (Html msg)
|
||||
layout children =
|
||||
let
|
||||
viewLink : String -> Route -> Html msg
|
||||
viewLink label route =
|
||||
Html.a [ Attr.href (Route.toHref route) ] [ Html.text label ]
|
||||
in
|
||||
[ Html.div [ Attr.class "container" ]
|
||||
[ Html.header [ Attr.class "navbar" ]
|
||||
[ Html.strong [ Attr.class "brand" ] [ viewLink "Home" Route.Home_ ]
|
||||
, viewLink "Static" Route.Static
|
||||
, viewLink "Sandbox" Route.Sandbox
|
||||
, viewLink "Element" Route.Element
|
||||
, viewLink "Advanced" Route.Advanced
|
||||
, Html.div [ Attr.class "splitter" ] []
|
||||
, viewLink "Dynamic: Apple" (Route.Dynamic__Name_ { name = "apple" })
|
||||
, viewLink "Dynamic: Banana" (Route.Dynamic__Name_ { name = "banana" })
|
||||
]
|
||||
, Html.main_ [] children
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
h1 : String -> Html msg
|
||||
h1 label =
|
||||
Html.h1 [] [ Html.text label ]
|
@ -34,7 +34,7 @@ export default {
|
||||
|
||||
await File.create(outputFilepath, contents)
|
||||
|
||||
return ` ${bold('New page created at:')}\n ${outputFilepath}`
|
||||
return ` ${bold('New page created at:')}\n ${outputFilepath}\n`
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,6 @@ export default {
|
||||
const dest = process.cwd()
|
||||
File.copy(config.folders.init, dest)
|
||||
try { fs.renameSync(path.join(dest, '_gitignore'), path.join(dest, '.gitignore')) } catch (_) {}
|
||||
return ` ${bold}New project created in:${reset}\n ${process.cwd()}`
|
||||
return ` ${bold}New project created in:${reset}\n ${process.cwd()}\n`
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user