im really happy with this thing 😭

This commit is contained in:
Ryan Haskell-Glatz 2019-11-24 16:52:43 -06:00
parent 2c9d529f84
commit fb0e01a083
26 changed files with 2246 additions and 0 deletions

4
examples/docs/.gitignore vendored Normal file
View File

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

24
examples/docs/README.md Normal file
View File

@ -0,0 +1,24 @@
# your elm-spa
> learn more at [https://elm-spa.dev](https://elm-spa.dev)
### local development
```
npm run dev
```
## folder structure
```elm
README.md -- this file you're reading 👀
elm.json -- has project dependencies
src/
Main.elm -- the entrypoint to the app
Global.elm -- share state across pages
Transitions.elm -- smoothly animate between pages
Ports.elm -- communicate with JS
Pages/ -- where all your pages go
Layouts/ -- reusable views around pages
Components/ -- views shared across the site
Utils/ -- a place for helper functions
```

31
examples/docs/elm.json Normal file
View File

@ -0,0 +1,31 @@
{
"type": "application",
"source-directories": [
"src",
"elm-stuff/.elm-spa"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/core": "1.0.2",
"elm/html": "1.0.0",
"elm/json": "1.1.3",
"elm/url": "1.0.0",
"elm-explorations/markdown": "1.0.0",
"mdgriffith/elm-ui": "1.1.5",
"ryannhg/elm-spa": "2.0.0"
},
"indirect": {
"elm/browser": "1.0.2",
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
},
"elm-spa": {
"ui": "Element"
}
}

1457
examples/docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
{
"name": "my-elm-spa-project",
"version": "1.0.0",
"description": "learn more at https://elm-spa.dev",
"scripts": {
"start": "npm install && npm run dev",
"dev": "npm run elm:spa:build && npm run elm:spa:watch & npm run elm:live",
"build": "npm run elm:spa:build && npm run elm:compile",
"elm:compile": "elm make src/Main.elm --output=public/dist/elm.compiled.js --optimize",
"elm:live": "elm-live src/Main.elm --dir=public --start-page=index.html --open --pushstate --port=1234 -- --output=public/dist/elm.compiled.js --debug",
"elm:spa:build": "elm-spa build .",
"elm:spa:watch": "SHELL=/bin/bash chokidar 'src/Pages' -c 'npm run elm:spa:build'"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"chokidar-cli": "2.1.0",
"elm": "0.19.1-3",
"elm-live": "4.0.1",
"elm-spa": "2.0.4"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,25 @@
<!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>our-elm-spa</title>
<link rel="shortcut icon" href="/favicon.png" type="image/png">
<link rel="stylesheet" href="/styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/atom-one-light.min.css">
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/languages/elm.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/languages/html.min.js"></script>
<script src="/dist/elm.compiled.js"></script>
<script src="/ports.js"></script>
<script>
window.hljs.initHighlightingOnLoad()
window.addEventListener('load', _ => {
window.ports.init(Elm.Main.init())
})
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
// On load, listen to Elm!
window.addEventListener('load', _ => {
window.ports = {
init: (app) =>
app.ports.outgoing.subscribe(({ action, data }) =>
actions[action]
? actions[action](data)
: console.warn(`I didn't recognize action "${action}".`)
)
}
})
// maps actions to functions!
const actions = {
'LOG': (message) =>
console.log(`From Elm:`, message)
}

View File

@ -0,0 +1,49 @@
/* you can include CSS here */
html, body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
height: 100%;
}
.markdown > * {
margin-bottom: 0;
}
.markdown > *:first-child {
margin-top: 0;
}
.markdown h3 {
margin-top: 1.5em;
line-height: 1.1;
font-size: 32px;
}
.markdown h4 {
line-height: 1.2;
font-size: 22px;
}
.markdown p, ul, ol {
line-height: 1.4;
font-size: 18px;
}
.markdown pre {
border: solid 1px #ddd;
border-radius: 4px;
padding: 1em;
margin: 0 calc(4px - 1em);
margin-top: 1em;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.markdown a {
color: rgb(200, 75, 85);
}
.markdown iframe {
width: 100%;
margin: 2em -1em;
background: #eee;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

View File

@ -0,0 +1,53 @@
module Components.Hero exposing (Options, view)
import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Region as Region
import Ui exposing (colors, styles)
type alias Options =
{ title : String
, subtitle : String
, links :
List
{ label : String
, url : String
}
}
view : Options -> Element msg
view options =
column
[ paddingEach
{ top = 128
, left = 0
, right = 0
, bottom = 32
}
, centerX
, spacing 24
]
[ column [ spacing 14, centerX ]
[ el
[ Font.size 56
, Font.bold
, centerX
, Region.heading 1
]
(text options.title)
, el [ centerX, alpha 0.5, Region.heading 2 ] (text options.subtitle)
]
, wrappedRow [ spacing 12, centerX ] <|
List.map
(\{ label, url } ->
link styles.button
{ label = text label
, url = url
}
)
options.links
]

View File

@ -0,0 +1,40 @@
module Components.Navbar exposing (view)
import Element exposing (..)
import Element.Font as Font
import Generated.Routes as Routes exposing (Route, routes)
import Ui exposing (colors, styles)
view : Route -> Element msg
view currentRoute =
row
[ spacing 24
, centerX
, width fill
]
[ row [ Font.color colors.coral, spacing 16 ]
[ el [ Font.semiBold, Font.size 20 ]
(viewLink currentRoute ( "elm-spa", routes.top ))
, viewLink currentRoute ( "docs", routes.docs )
, viewLink currentRoute ( "guide", routes.guide )
]
, el [ alignRight ] <|
link styles.button
{ label = text "get started"
, url = "/guide"
}
]
viewLink : Route -> ( String, Route ) -> Element msg
viewLink currentRoute ( label, route ) =
if currentRoute == route then
el styles.link.disabled
(text label)
else
link styles.link.enabled
{ label = text label
, url = Routes.toPath route
}

View File

@ -0,0 +1,2 @@
# src/Components
> views shared across the site

View File

@ -0,0 +1,49 @@
module Global exposing
( Flags
, Model
, Msg(..)
, init
, subscriptions
, update
)
import Generated.Routes as Routes exposing (Route)
import Ports
type alias Flags =
()
type alias Model =
{}
type Msg
= Msg
type alias Commands msg =
{ navigate : Route -> Cmd msg
}
init : Commands msg -> Flags -> ( Model, Cmd Msg, Cmd msg )
init _ _ =
( {}
, Cmd.none
, Ports.log "Hello!"
)
update : Commands msg -> Msg -> Model -> ( Model, Cmd Msg, Cmd msg )
update _ _ model =
( model
, Cmd.none
, Cmd.none
)
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none

View File

@ -0,0 +1,49 @@
module Layout exposing (view)
import Components.Navbar as Navbar
import Element exposing (..)
import Element.Font as Font
import Element.Region as Region
import Utils.Spa as Spa
view : Spa.LayoutContext msg -> Element msg
view { page, route } =
column
[ height fill
, width (fill |> maximum 512)
, centerX
, Font.size 16
, Font.family
[ Font.external
{ name = "IBM Plex Sans"
, url = "https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,400i,600,600i"
}
, Font.sansSerif
]
, paddingEach
{ top = 32
, left = 16
, right = 16
, bottom = 0
}
]
[ el [ Region.navigation, width fill ] (Navbar.view route)
, el [ Region.mainContent, width fill, height fill ] page
, el [ Region.footer, width fill ] viewFooter
]
viewFooter : Element msg
viewFooter =
row
[ width fill
, paddingEach { top = 96, left = 0, right = 0, bottom = 48 }
, alpha 0.5
]
[ text "this site was built with elm-spa!"
, newTabLink [ alignRight, Font.underline ]
{ label = text "github"
, url = "https://github.com/ryannhg/elm-spa"
}
]

View File

@ -0,0 +1,2 @@
# src/Layouts
> where all your pages go

View File

@ -0,0 +1,26 @@
module Main exposing (main)
import Generated.Pages as Pages
import Generated.Routes as Routes exposing (routes)
import Global
import Spa
import Transitions
main : Spa.Program Global.Flags Global.Model Global.Msg Pages.Model Pages.Msg
main =
Spa.create
{ ui = Spa.usingElmUi
, transitions = Transitions.transitions
, routing =
{ routes = Routes.parsers
, toPath = Routes.toPath
, notFound = routes.notFound
}
, global =
{ init = Global.init
, update = Global.update
, subscriptions = Global.subscriptions
}
, page = Pages.page
}

View File

@ -0,0 +1,49 @@
module Pages.Docs exposing (Model, Msg, page)
import Components.Hero as Hero
import Element exposing (..)
import Generated.Params as Params
import Spa.Page
import Ui
import Utils.Spa exposing (Page)
type alias Model =
()
type alias Msg =
Never
page : Page Params.Docs Model Msg model msg appMsg
page =
Spa.Page.static
{ title = always "docs | elm-spa"
, view = always view
}
-- VIEW
view : Element Msg
view =
Ui.sections
[ Hero.view
{ title = "docs"
, subtitle = "\"it's not done until the docs are great!\""
, links = []
}
, Ui.markdown """
### table of contents
1. [installation & setup](#installation)
1. project structure
1. adding pages
1. changing layouts
1. components and reusable ui
"""
]

View File

@ -0,0 +1,50 @@
module Pages.Guide exposing (Model, Msg, page)
import Components.Hero as Hero
import Element exposing (..)
import Generated.Params as Params
import Spa.Page
import Ui
import Utils.Spa exposing (Page)
type alias Model =
()
type alias Msg =
Never
page : Page Params.Guide Model Msg model msg appMsg
page =
Spa.Page.static
{ title = always "guide | elm-spa"
, view = always view
}
-- VIEW
view : Element Msg
view =
Ui.sections
[ Hero.view
{ title = "guide"
, subtitle = "let's build something together!"
, links = []
}
, Ui.markdown """
### what can i build with elm-spa?
__This entire site!__ And in this guide we'll build it together, from scratch.
(Step-by-step, with short videos)
<iframe title="elm-spa: welcome to the guide!" width="560" height="315" src="https://www.youtube.com/embed/OMDMawvANNs" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Are you officially hyped? Let's [get you setup](/guide/setup)!
"""
]

View File

@ -0,0 +1,42 @@
module Pages.NotFound exposing (Model, Msg, page)
import Components.Hero as Hero
import Element exposing (..)
import Element.Font as Font
import Generated.Params as Params
import Generated.Routes as Routes exposing (routes)
import Spa.Page
import Utils.Spa exposing (Page)
type alias Model =
()
type alias Msg =
Never
page : Page Params.NotFound Model Msg model msg appMsg
page =
Spa.Page.static
{ title = always "not found | elm-spa"
, view = always view
}
-- VIEW
view : Element Msg
view =
Hero.view
{ title = "page not found?"
, subtitle = "it's not you, it's me."
, links =
[ { label = "but this link works!"
, url = "/"
}
]
}

View File

@ -0,0 +1,3 @@
# src/Pages
> where all your pages go

View File

@ -0,0 +1,50 @@
module Pages.Top exposing (Model, Msg, page)
import Components.Hero as Hero
import Element exposing (..)
import Generated.Params as Params
import Spa.Page
import Ui
import Utils.Spa exposing (Page)
type alias Model =
()
type alias Msg =
Never
page : Page Params.Top Model Msg model msg appMsg
page =
Spa.Page.static
{ title = always "elm-spa"
, view = always view
}
-- VIEW
view : Element Msg
view =
Ui.sections
[ Hero.view
{ title = "elm-spa"
, subtitle = "a framework for building single page apps"
, links = [ { label = "get started", url = "/guide" } ]
}
, Ui.markdown """
### does elm _need_ a framework?
__nope, not really__ it's kinda got one built in! so building something like React, VueJS, or Angular wouldn't really make sense.
#### ...but even frameworks need frameworks!
that's why projects like VueJS also have awesome tools like NuxtJS that bring together the best tools in the ecosystem (and a set of shared best practices!)
welcome to __elm-spa__, a framework for Elm!
"""
]

View File

@ -0,0 +1,14 @@
port module Ports exposing (log)
import Json.Encode as Json
port outgoing : { action : String, data : Json.Value } -> Cmd msg
log : String -> Cmd msg
log message =
outgoing
{ action = "LOG"
, data = Json.string message
}

View File

@ -0,0 +1,12 @@
module Transitions exposing (transitions)
import Spa.Transition as Transition
import Utils.Spa as Spa
transitions : Spa.Transitions msg
transitions =
{ layout = Transition.none
, page = Transition.fadeElmUi 300
, pages = []
}

100
examples/docs/src/Ui.elm Normal file
View File

@ -0,0 +1,100 @@
module Ui exposing
( colors
, markdown
, sections
, styles
, transition
)
import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Html.Attributes as Attr
import Markdown
colors : { coral : Color, white : Color }
colors =
{ coral = rgb255 200 75 85
, white = rgb255 255 255 255
}
styles :
{ button : List (Attribute msg)
, link :
{ enabled : List (Attribute msg)
, disabled : List (Attribute msg)
}
}
styles =
{ link =
{ enabled =
[ Font.underline
, transition
{ duration = 200
, props = [ "opacity" ]
}
, mouseOver [ alpha 0.5 ]
]
, disabled =
[ alpha 0.5
]
}
, button =
[ centerX
, Font.size 14
, Font.semiBold
, Border.solid
, Border.width 2
, Border.rounded 4
, paddingXY 24 8
, Font.color colors.coral
, Border.color colors.coral
, Background.color colors.white
, pointer
, transition
{ duration = 200
, props = [ "background", "color" ]
}
, mouseOver
[ Background.color colors.coral
, Font.color colors.white
]
]
}
sections : List (Element msg) -> Element msg
sections =
column [ spacing 32, width fill ]
markdown : String -> Element msg
markdown =
let
options =
Markdown.defaultOptions
|> (\o -> { o | sanitize = False })
in
Markdown.toHtmlWith options [ Attr.class "markdown" ]
>> Element.html
>> List.singleton
>> paragraph []
transition : { props : List String, duration : Int } -> Attribute msg
transition options =
options.props
|> List.map
(\prop ->
String.join " "
[ prop
, String.fromInt options.duration ++ "ms"
, "ease-in-out"
]
)
|> String.join ", "
|> Attr.style "transition"
|> Element.htmlAttribute

View File

@ -0,0 +1,2 @@
# src/Utils
> a place for helper functions

View File

@ -0,0 +1,72 @@
module Utils.Spa exposing
( Bundle
, Init
, LayoutContext
, Page
, PageContext
, Recipe
, Transitions
, Update
, layout
, recipe
)
import Element exposing (Element)
import Generated.Routes as Routes exposing (Route)
import Global
import Spa.Page
import Spa.Types
type alias Page params model msg layoutModel layoutMsg appMsg =
Spa.Types.Page Route params model msg (Element msg) layoutModel layoutMsg (Element layoutMsg) Global.Model Global.Msg appMsg (Element appMsg)
type alias Recipe params model msg layoutModel layoutMsg appMsg =
Spa.Types.Recipe Route params model msg layoutModel layoutMsg (Element layoutMsg) Global.Model Global.Msg appMsg (Element appMsg)
type alias Init model msg =
Spa.Types.Init Route model msg Global.Model Global.Msg
type alias Update model msg =
Spa.Types.Update Route model msg Global.Model Global.Msg
type alias Bundle msg appMsg =
Spa.Types.Bundle Route msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg)
type alias LayoutContext msg =
Spa.Types.LayoutContext Route msg (Element msg) Global.Model Global.Msg
type alias PageContext =
Spa.Types.PageContext Route Global.Model
type alias Layout params model msg appMsg =
Spa.Types.Layout Route params model msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg)
layout :
Layout params model msg appMsg
-> Page params model msg layoutModel layoutMsg appMsg
layout =
Spa.Page.layout Element.map
type alias Upgrade params model msg layoutModel layoutMsg appMsg =
Spa.Types.Upgrade Route params model msg (Element msg) layoutModel layoutMsg (Element layoutMsg) Global.Model Global.Msg appMsg (Element appMsg)
recipe :
Upgrade params model msg layoutModel layoutMsg appMsg
-> Recipe params model msg layoutModel layoutMsg appMsg
recipe =
Spa.Page.recipe Element.map
type alias Transitions msg =
Spa.Types.Transitions (Element msg)