Merge branch 'spa' of cadey/mi into master

This commit is contained in:
Cadey Ratio 2020-01-18 18:26:56 +00:00 committed by Gitea
commit 11c751e6bc
33 changed files with 2584 additions and 524 deletions

View File

@ -88,6 +88,7 @@ func init() {
"/static/favicon.png": true,
"/debug/requests": true,
"/debug/events": true,
"/sign-in": true,
}
}

2
frontend/.dockerignore Normal file
View File

@ -0,0 +1,2 @@
elm-stuff
node_modules

1
frontend/.gitignore vendored
View File

@ -1 +1,2 @@
elm-stuff
node_modules

4
frontend/.npmignore Normal file
View File

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

24
frontend/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
```

3
frontend/elm-spa.json Normal file
View File

@ -0,0 +1,3 @@
{
"ui": "Element"
}

View File

@ -1,21 +1,24 @@
{
"type": "application",
"source-directories": [
"src"
"src",
"elm-stuff/.elm-spa"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.4",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"rtfeldman/elm-iso8601-date-strings": "1.1.3"
"mdgriffith/elm-ui": "1.1.5",
"rtfeldman/elm-iso8601-date-strings": "1.1.3",
"ryannhg/elm-spa": "3.0.0"
},
"indirect": {
"elm/browser": "1.0.2",
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/parser": "1.1.0",

6
frontend/netlify.toml Normal file
View File

@ -0,0 +1,6 @@
# sends all routes to /index.html
# (so you can handle 404s there!)
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

1893
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
frontend/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"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 build:watch",
"build": "npm run elm:spa:build && npm run elm:compile",
"build:watch": "concurrently --raw --kill-others \"npm run elm:spa:watch\" \"npm run elm:live\"",
"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": "chokidar src/Pages -c \"npm run elm:spa:build\""
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"chokidar-cli": "2.1.0",
"concurrently": "5.0.0",
"elm": "0.19.1-3",
"elm-live": "4.0.1",
"elm-spa": "3.0.3"
}
}

1
frontend/public/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

View File

@ -0,0 +1,19 @@
<!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="stylesheet" href="/styles.css">
</head>
<body>
<script src="/dist/elm.compiled.js"></script>
<script src="/ports.js"></script>
<script>
window.addEventListener('load', _ =>
window.ports.init(Elm.Main.init())
)
</script>
</body>
</html>

17
frontend/public/ports.js Normal file
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,5 @@
/* 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%;
}

View File

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

60
frontend/src/Global.elm Normal file
View File

@ -0,0 +1,60 @@
module Global exposing
( Flags
, Model
, Msg(..)
, init
, subscriptions
, update
)
import Generated.Routes as Routes exposing (Route)
import Ports
type alias Flags =
()
type alias Model =
{ token : Maybe String
}
type Msg
= SignIn String
| SignOut
type alias Commands msg =
{ navigate : Route -> Cmd msg
}
init : Commands msg -> Flags -> ( Model, Cmd Msg, Cmd msg )
init _ _ =
( { token = Nothing
}
, Cmd.none
, Ports.log "Hello!"
)
update : Commands msg -> Msg -> Model -> ( Model, Cmd Msg, Cmd msg )
update _ msg model =
case msg of
SignIn token ->
( { model | token = Just token }
, Cmd.none
, Cmd.none
)
SignOut ->
( { model | token = Nothing }
, Cmd.none
, Cmd.none
)
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none

70
frontend/src/Layout.elm Normal file
View File

@ -0,0 +1,70 @@
module Layout exposing (view)
import Element exposing (..)
import Element.Font as Font
import Generated.Routes as Routes exposing (Route, routes)
import Utils.Spa as Spa
view : Spa.LayoutContext msg -> Element msg
view { page, route, global } =
case global.token of
Just _ ->
column [ height fill, width (fill |> maximum 750), centerX ]
[ el [ padding 15 ] (viewHeader route)
, page
]
Nothing ->
if route /= routes.signIn then
el [ centerX, centerY ]
(link
[ Font.underline
, Font.color (rgb255 204 75 75)
, Font.size 48
]
{ label = text "Login"
, url = Routes.toPath routes.signIn
}
)
else
el
[ centerX, centerY ]
page
viewHeader : Route -> Element msg
viewHeader currentRoute =
row
[ spacing 24
, paddingEach { top = 32, left = 16, right = 16, bottom = 0 }
, centerX
, width (fill |> maximum 750)
]
[ viewLink currentRoute ( "Mi", routes.top )
, viewLink currentRoute ( "Switch Data", routes.switches )
]
viewLink : Route -> ( String, Route ) -> Element msg
viewLink currentRoute ( label, route ) =
if currentRoute == route then
el
[ Font.underline
, Font.color (rgb255 204 75 75)
, alpha 0.5
, Font.size 16
]
(text label)
else
link
[ Font.underline
, Font.color (rgb255 204 75 75)
, Font.size 16
, mouseOver [ alpha 0.5 ]
]
{ label = text label
, url = Routes.toPath route
}

View File

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

View File

@ -1,185 +1,27 @@
module Main exposing (main)
import Browser
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import Http
import Json.Decode as D
import Mi exposing (..)
import Page exposing (..)
import Page.SwitchData as PSD
import SwitchData
import Url
import Url.Builder
import Generated.Pages as Pages
import Generated.Routes as Routes exposing (routes)
import Global
import Spa
import Transitions
main : Program () Model Msg
main : Spa.Program Global.Flags Global.Model Global.Msg Pages.Model Pages.Msg
main =
Browser.application
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
, onUrlChange = UrlChanged
, onUrlRequest = LinkClicked
}
type alias Model =
{ key : Nav.Key
, url : Url.Url
, token : Maybe String
, token_data : Maybe TokenData
, switch_data_model : PSD.Model
}
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
( Model
key
url
Nothing
Nothing
PSD.init
, Cmd.none
)
type Page
= NotFound
| Index
| SwitchData PSD.Model
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url.Url
| TokenInput String
| TokenValidate (Result Http.Error TokenData)
| Logout
| GotSwitchDataMSG PSD.Msg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkClicked urlRequest ->
case urlRequest of
Browser.Internal url ->
( model
, Nav.pushUrl
model.key
(Url.toString url)
)
Browser.External href ->
( model, Nav.load href )
UrlChanged url ->
case url.path of
"/logout" ->
( { model | token = Nothing, token_data = Nothing }
, Nav.pushUrl
model.key
"/"
)
default ->
( { model | url = url }
, Cmd.none
)
TokenInput token ->
( { model | token = Just token }
, request
"GET"
token
"/.within/tokeninfo"
Http.emptyBody
(expectJson TokenValidate tokenDecoder)
)
TokenValidate result ->
case result of
Ok data ->
( { model | token_data = Just data }
, Cmd.none
)
Err _ ->
( { model | token = Nothing }
, Cmd.none
)
Logout ->
( { model | token = Nothing, token_data = Nothing }
, Nav.load "/"
)
GotSwitchDataMSG psd_msg ->
let
( psd_model, cmd ) =
PSD.update
(Maybe.withDefault "" model.token)
psd_msg
model.switch_data_model
in
( { model | switch_data_model = psd_model }
, Cmd.map GotSwitchDataMSG cmd
)
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
view : Model -> Browser.Document Msg
view model =
case model.token_data of
Nothing ->
{ title = "Login"
, body =
[ node "main"
[ style "align" "center" ]
[ h1 [] [ text "Login" ]
, viewInput "password" "API Token" "" TokenInput
]
]
Spa.create
{ ui = Spa.usingElmUi
, transitions = Transitions.transitions
, routing =
{ routes = Routes.parsers
, toPath = Routes.toPath
, notFound = routes.notFound
, afterNavigate = Nothing
}
Just token_data ->
case model.url.path of
"/" ->
template "Mi"
[ h1 [] [ text "Mi" ]
, h2 [] [ text "TODO" ]
, ul []
[ li [] [ text "Switch CRUD" ]
, li [] [ text "POSSE manual announcement" ]
]
, h2 [] [ text "Token data" ]
, p []
[ text "Token sub: "
, text token_data.sub
, Html.br [] []
, text "ID: "
, text token_data.jti
]
]
"/switch" ->
PSD.view model.switch_data_model
other ->
template "Not found"
[ h1 [] [ text "Not found" ]
, p []
[ text "The requested URL "
, b [] [ text other ]
, text " was not found."
]
]
, global =
{ init = Global.init
, update = Global.update
, subscriptions = Global.subscriptions
}
, page = Pages.page
}

View File

@ -1,4 +1,4 @@
module SwitchData exposing
module Mi.SwitchData exposing
( Switch
, decoder
, frontURL

View File

@ -1,57 +0,0 @@
module Page exposing (..)
import Browser exposing (Document)
import Html exposing (Html, a, div, input, node, p, span, text)
import Html.Attributes exposing (class, href, placeholder, style, type_, value)
import Html.Events exposing (onInput)
template : String -> List (Html msg) -> Browser.Document msg
template title body =
{ title = title
, body =
[ node "main"
[]
[ navBar
, div [] body
, footer
]
]
}
navBar : Html msg
navBar =
node "nav"
[]
[ p []
[ viewLink "/" "Mi"
, text " - "
, viewLink "/switch" "Switch tracker"
, span
[ class "right" ]
[ viewLink "/logout" "Logout" ]
]
]
footer : Html msg
footer =
node "footer"
[]
[ p []
[ a [ href "https://within.website" ] [ text "From Within" ]
, text " - "
, a [ href "https://tulpa.dev/cadey/mi" ] [ text "Source code" ]
]
]
viewInput : String -> String -> String -> (String -> msg) -> Html msg
viewInput t p v toMsg =
input [ type_ t, placeholder p, value v, onInput toMsg ] []
viewLink : String -> String -> Html msg
viewLink path title =
a [ href path ] [ text title ]

View File

@ -1,102 +0,0 @@
module Page.SwitchData exposing
( Model
, Msg
, init
, update
, view
)
import Browser exposing (Document)
import Html exposing (..)
import Html.Attributes exposing (..)
import Http
import Json.Decode exposing (list)
import Mi
import Page
import SwitchData
type alias Model =
{ page : Int
, limit : Int
, data : Data
}
type Data
= Init
| HaveData (List SwitchData.Switch)
| Error String
init : Model
init =
Model
0
40
Init
type Msg
= NeedData
| Settings Int Int
| GotData (Result Http.Error (List SwitchData.Switch))
update : String -> Msg -> Model -> ( Model, Cmd Msg )
update token msg model =
if token == "" then
( model, Cmd.none )
else
case msg of
NeedData ->
( model
, Mi.request
"GET"
token
(SwitchData.listURL model.limit model.page)
Http.emptyBody
(Mi.expectJson GotData (Json.Decode.list SwitchData.decoder))
)
Settings page limit ->
( { model | page = page, limit = limit }
, Mi.request
"GET"
token
(SwitchData.listURL model.limit model.page)
Http.emptyBody
(Mi.expectJson GotData (Json.Decode.list SwitchData.decoder))
)
GotData result ->
case result of
Ok data ->
( { model | data = HaveData data }
, Cmd.none
)
Err _ ->
( { model | data = Error "got an error" }
, Cmd.none
)
view : Model -> Browser.Document msg
view model =
case model.data of
Init ->
Page.template "Switch data"
[ h1 [] [ text "loading data..." ] ]
HaveData _ ->
Page.template "Switch data"
[ h1 [] [ text "Switch data here" ]
]
Error msg ->
Page.template "Switch data error"
[ h1 [] [ text "oh no got an error" ]
, p [] [ text msg ]
]

View File

@ -1,104 +0,0 @@
module Page.Token exposing
( Model
, Msg
, init
, update
, view
)
import Browser
import Html exposing (Html, h1, h2, li, p, text, ul)
import Http exposing (..)
import Mi exposing (TokenData, expectJson, request, tokenDecoder)
import Page
type alias Model =
{ token : Maybe String
, token_data : Maybe TokenData
, error : Maybe String
}
init : Model
init =
Model Nothing Nothing Nothing
type Msg
= Init
| GotTokenData (Result Http.Error TokenData)
update : Msg -> String -> Model -> ( Model, Cmd Msg )
update msg token model =
case msg of
Init ->
( { model | token = Just token }
, Mi.request
"GET"
token
"/.within/tokeninfo"
Http.emptyBody
(Mi.expectJson GotTokenData tokenDecoder)
)
GotTokenData result ->
case result of
Ok data ->
( { model | token_data = Just data }
, Cmd.none
)
Err (BadUrl val) ->
( { model | error = Just ("bad URL " ++ val) }
, Cmd.none
)
Err Timeout ->
( { model | error = Just "Timeout" }
, Cmd.none
)
Err NetworkError ->
( { model | error = Just "network error" }
, Cmd.none
)
Err (BadStatus code) ->
( { model | error = Just ("bad status code " ++ String.fromInt code) }
, Cmd.none
)
Err (BadBody err_msg) ->
( { model | error = Just err_msg }
, Cmd.none
)
view : Model -> Browser.Document msg
view model =
case model.token_data of
Nothing ->
Page.template "No token data?"
[ h1 [] [ text "No token data?" ]
, p [] [ text "this should be impossible" ]
]
Just token_data ->
Page.template "Mi"
[ h1 [] [ text "Mi" ]
, h2 [] [ text "TODO" ]
, ul []
[ li [] [ text "Switch CRUD" ]
, li [] [ text "POSSE manual announcement" ]
]
, h2 [] [ text "Token data" ]
, p []
[ text "Token sub: "
, text token_data.sub
, Html.br [] []
, text "ID: "
, text token_data.jti
]
]

View File

@ -0,0 +1,39 @@
module Pages.NotFound exposing (Model, Msg, page)
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 =
column [ centerX, centerY, spacing 16 ]
[ el [ Font.size 32, Font.semiBold ] (text "404 is life.")
, link [ Font.size 16, Font.underline, centerX, Font.color (rgb255 204 75 75), mouseOver [ alpha 0.5 ] ]
{ label = text "back home?"
, url = Routes.toPath routes.top
}
]

View File

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

View File

@ -0,0 +1,144 @@
module Pages.SignIn exposing (Model, Msg, page)
import Element exposing (..)
import Element.Input as Input
import Generated.Params as Params
import Global
import Http
import Mi
import Spa.Page
import Utils.Spa exposing (Page)
page : Page Params.SignIn Model Msg model msg appMsg
page =
Spa.Page.component
{ title = always "Login"
, init = always init
, update = always update
, subscriptions = always subscriptions
, view = always view
}
-- INIT
type alias Model =
{ token : String
, tokenData : Maybe Mi.TokenData
, error : Maybe String
}
init : Params.SignIn -> ( Model, Cmd Msg, Cmd Global.Msg )
init _ =
( { token = ""
, tokenData = Nothing
, error = Nothing
}
, Cmd.none
, Cmd.none
)
-- UPDATE
type Msg
= SignIn
| SignOut
| ValidateToken (Result Http.Error Mi.TokenData)
| UpdateToken String
update : Msg -> Model -> ( Model, Cmd Msg, Cmd Global.Msg )
update msg model =
case msg of
UpdateToken token ->
( { model | token = token }
, Cmd.none
, Cmd.none
)
ValidateToken result ->
case result of
Ok data ->
( { model | tokenData = Just data }
, Cmd.none
, Spa.Page.send (Global.SignIn model.token)
)
Err _ ->
( { model | token = "", error = Just "got an error :(" }
, Cmd.none
, Cmd.none
)
SignIn ->
( model
, Mi.request
"GET"
model.token
"/.within/tokeninfo"
Http.emptyBody
(Mi.expectJson ValidateToken Mi.tokenDecoder)
, Cmd.none
)
SignOut ->
( model
, Cmd.none
, Spa.Page.send Global.SignOut
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Element Msg
view model =
let
errMsg =
case model.error of
Just msg ->
msg
Nothing ->
""
tData =
case model.tokenData of
Just data ->
Element.paragraph []
[ text ("ID: " ++ data.jti)
, paragraph [] [ text ("Sub: " ++ data.sub) ]
]
Nothing ->
Element.column []
[ Input.text []
{ label = Input.labelAbove [] (text "API Token")
, onChange = UpdateToken
, text = model.token
, placeholder = Just (Input.placeholder [] (text ""))
}
, Input.button [] { onPress = Just SignIn, label = text "Login" }
]
in
column [ spacing 16 ]
[ tData
, text errMsg
]

View File

@ -0,0 +1,70 @@
module Pages.Switches exposing (Model, Msg, page)
import Element exposing (..)
import Generated.Params as Params
import Global
import Mi
import Mi.SwitchData
import Spa.Page
import Utils.Spa exposing (Page)
page : Page Params.Switches Model Msg model msg appMsg
page =
Spa.Page.component
{ title = always "Switches"
, init = always init
, update = always update
, subscriptions = always subscriptions
, view = always view
}
-- INIT
type alias Model =
{}
init : Params.Switches -> ( Model, Cmd Msg, Cmd Global.Msg )
init _ =
( {}
, Cmd.none
, Cmd.none
)
-- UPDATE
type Msg
= Msg
update : Msg -> Model -> ( Model, Cmd Msg, Cmd Global.Msg )
update msg model =
( model
, Cmd.none
, Cmd.none
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Element Msg
view model =
text "Switches"

View File

@ -0,0 +1,59 @@
module Pages.Top exposing (Model, Msg, page)
import Element exposing (..)
import Element.Font as Font
import Generated.Params as Params
import Generated.Routes as Routes exposing (Route, routes)
import Spa.Page
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 "/"
, view = view
}
-- VIEW
view : Utils.Spa.PageContext -> Element Msg
view { global } =
case global.token of
Just _ ->
column
[ spacing 12
]
[ row [ spacing 14 ]
[ el [ Font.size 48, Font.semiBold ] (text "Mi")
, el [ alpha 0.5 ] (text "POSSE and stuff")
]
, text "TODO:"
, text "* POSSE"
, text "* Switch Data"
]
Nothing ->
el
[ Font.size 48, centerX, centerY ]
(link
[ Font.underline
, Font.color (rgb255 204 75 75)
, Font.size 48
, mouseOver [ alpha 0.5 ]
]
{ label = text "Login"
, url = Routes.toPath routes.signIn
}
)

14
frontend/src/Ports.elm Normal file
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.fadeElmUi 500
, page = Transition.fadeElmUi 300
, pages = []
}

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)

View File

@ -1,78 +1,5 @@
main {
font-family: monospace, monospace;
max-width: 38rem;
padding: 2rem;
margin: auto;
}
@media only screen and (max-device-width: 736px) {
main {
padding: 0rem;
}
}
::selection {
background: #d3869b;
}
body {
background: #282828;
color: #ebdbb2;
}
pre {
background-color: #3c3836;
padding: 1em;
border: 0;
}
.right {
float: right;
}
a, a:active, a:visited {
color: #b16286;
background-color: #1d2021;
}
h1, h2, h3, h4, h5 {
margin-bottom: .1rem;
}
blockquote {
border-left: 1px solid #bdae93;
margin: 0.5em 10px;
padding: 0.5em 10px;
}
footer {
align: center;
}
@media (prefers-color-scheme: light) {
body {
background: #fbf1c7;
color: #3c3836;
}
pre {
background-color: #ebdbb2;
padding: 1em;
border: 0;
}
a, a:active, a:visited {
color: #b16286;
background-color: #f9f5d7;
}
h1, h2, h3, h4, h5 {
margin-bottom: .1rem;
}
blockquote {
border-left: 1px solid #655c54;
margin: 0.5em 10px;
padding: 0.5em 10px;
}
}
/* 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%;
}