add first two examples

This commit is contained in:
Ryan Haskell-Glatz 2021-03-15 20:29:52 -05:00
parent c8689cf7f3
commit 896145a2d9
18 changed files with 456 additions and 30 deletions

5
examples/01-hello-world/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.DS_Store
.elm-spa
elm-stuff
node_modules
dist

View File

@ -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)

View File

@ -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",

View File

@ -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!" ]
}

View File

@ -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

View File

@ -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"
}

View File

@ -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>

View 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;
}

View 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)!" ]
]
}

View 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 ]
]
}

View File

@ -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 ++ " )"

View File

@ -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!" ]
]
}

View 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 "+" ]
]
}

View File

@ -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." ]
]
}

View 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

View 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 ]

View File

@ -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`
}
}

View File

@ -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`
}
}