mirror of
https://github.com/ryannhg/elm-spa.git
synced 2024-11-25 23:43:02 +03:00
set up a project for api exploration
This commit is contained in:
commit
3fccf3dbe1
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
elm-stuff/0.19.1
|
||||||
|
node_modules
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# elm-spa
|
||||||
|
> for building single page apps
|
||||||
|
|
||||||
|
|
||||||
|
## local development
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install && npm run dev
|
||||||
|
```
|
5
cli/README.md
Normal file
5
cli/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# cli
|
||||||
|
> the thing that types the stuff
|
||||||
|
|
||||||
|
__Note:__ I will not implement this until I understand
|
||||||
|
the manual workflow first.
|
12
elm-stuff/.elm-spa/Generated/Docs/Flags.elm
Normal file
12
elm-stuff/.elm-spa/Generated/Docs/Flags.elm
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module Generated.Docs.Flags exposing
|
||||||
|
( Dynamic
|
||||||
|
, Static
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Static =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Dynamic =
|
||||||
|
String
|
111
elm-stuff/.elm-spa/Generated/Docs/Pages.elm
Normal file
111
elm-stuff/.elm-spa/Generated/Docs/Pages.elm
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
module Generated.Docs.Pages exposing
|
||||||
|
( Model
|
||||||
|
, Msg
|
||||||
|
, page
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Generated.Docs.Flags as Flags
|
||||||
|
import Generated.Docs.Routes as Routes exposing (Route(..))
|
||||||
|
import Layouts.Docs as Layout
|
||||||
|
import Pages.Docs.Dynamic
|
||||||
|
import Pages.Docs.Static
|
||||||
|
import Utils.Page as Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type Model
|
||||||
|
= DynamicModel Pages.Docs.Dynamic.Model
|
||||||
|
| StaticModel Pages.Docs.Static.Model
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= DynamicMsg Pages.Docs.Dynamic.Msg
|
||||||
|
| StaticMsg Pages.Docs.Static.Msg
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Route Model Msg layoutModel layoutMsg appMsg
|
||||||
|
page =
|
||||||
|
Page.layout
|
||||||
|
{ view = Layout.view
|
||||||
|
, recipe =
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- RECIPES
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipe flags model msg appMsg =
|
||||||
|
Page.Recipe flags model msg Model Msg appMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipes msg =
|
||||||
|
{ dynamic : Recipe Flags.Dynamic Pages.Docs.Dynamic.Model Pages.Docs.Dynamic.Msg msg
|
||||||
|
, static : Recipe Flags.Static Pages.Docs.Static.Model Pages.Docs.Static.Msg msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
recipes : Recipes msg
|
||||||
|
recipes =
|
||||||
|
{ dynamic =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Docs.Dynamic.page
|
||||||
|
, toModel = DynamicModel
|
||||||
|
, toMsg = DynamicMsg
|
||||||
|
}
|
||||||
|
, static =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Docs.Static.page
|
||||||
|
, toModel = StaticModel
|
||||||
|
, toMsg = StaticMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Route -> Page.Init Model Msg
|
||||||
|
init route =
|
||||||
|
case route of
|
||||||
|
Routes.Dynamic flags ->
|
||||||
|
recipes.dynamic.init flags
|
||||||
|
|
||||||
|
Routes.Static flags ->
|
||||||
|
recipes.static.init flags
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> Page.Update Model Msg
|
||||||
|
update bigMsg bigModel =
|
||||||
|
case ( bigMsg, bigModel ) of
|
||||||
|
( DynamicMsg msg, DynamicModel model ) ->
|
||||||
|
recipes.dynamic.update msg model
|
||||||
|
|
||||||
|
( StaticMsg msg, StaticModel model ) ->
|
||||||
|
recipes.static.update msg model
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
App.Page.keep bigModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- BUNDLE
|
||||||
|
|
||||||
|
|
||||||
|
bundle : Model -> Page.Bundle Msg msg
|
||||||
|
bundle bigModel =
|
||||||
|
case bigModel of
|
||||||
|
DynamicModel model ->
|
||||||
|
recipes.dynamic.bundle model
|
||||||
|
|
||||||
|
StaticModel model ->
|
||||||
|
recipes.static.bundle model
|
30
elm-stuff/.elm-spa/Generated/Docs/Routes.elm
Normal file
30
elm-stuff/.elm-spa/Generated/Docs/Routes.elm
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module Generated.Docs.Routes exposing
|
||||||
|
( Route(..)
|
||||||
|
, routes
|
||||||
|
, toPath
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Route as Route
|
||||||
|
import Generated.Docs.Flags as Flags
|
||||||
|
|
||||||
|
|
||||||
|
type Route
|
||||||
|
= Static Flags.Static
|
||||||
|
| Dynamic Flags.Dynamic
|
||||||
|
|
||||||
|
|
||||||
|
routes : List (Route.Route Route a)
|
||||||
|
routes =
|
||||||
|
[ Route.path "static" Static
|
||||||
|
, Route.dynamic Dynamic
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
toPath : Route -> String
|
||||||
|
toPath route =
|
||||||
|
case route of
|
||||||
|
Static _ ->
|
||||||
|
"/static"
|
||||||
|
|
||||||
|
Dynamic string ->
|
||||||
|
"/" ++ string
|
27
elm-stuff/.elm-spa/Generated/Flags.elm
Normal file
27
elm-stuff/.elm-spa/Generated/Flags.elm
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
module Generated.Flags exposing
|
||||||
|
( Docs
|
||||||
|
, Guide
|
||||||
|
, NotFound
|
||||||
|
, SignIn
|
||||||
|
, Top
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Top =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Docs =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias NotFound =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias SignIn =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Guide =
|
||||||
|
()
|
5
elm-stuff/.elm-spa/Generated/Guide/Dynamic/Flags.elm
Normal file
5
elm-stuff/.elm-spa/Generated/Guide/Dynamic/Flags.elm
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module Generated.Guide.Dynamic.Flags exposing (Intro)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Intro =
|
||||||
|
()
|
91
elm-stuff/.elm-spa/Generated/Guide/Dynamic/Pages.elm
Normal file
91
elm-stuff/.elm-spa/Generated/Guide/Dynamic/Pages.elm
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
module Generated.Guide.Dynamic.Pages exposing
|
||||||
|
( Model
|
||||||
|
, Msg
|
||||||
|
, page
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Generated.Guide.Dynamic.Flags as Flags
|
||||||
|
import Generated.Guide.Dynamic.Routes as Routes exposing (Route(..))
|
||||||
|
import Layouts.Guide.Dynamic as Layout
|
||||||
|
import Pages.Guide.Dynamic.Intro
|
||||||
|
import Utils.Page as Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type Model
|
||||||
|
= IntroModel Pages.Guide.Dynamic.Intro.Model
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= IntroMsg Pages.Guide.Dynamic.Intro.Msg
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Route Model Msg layoutModel layoutMsg appMsg
|
||||||
|
page =
|
||||||
|
Page.layout
|
||||||
|
{ view = Layout.view
|
||||||
|
, recipe =
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- RECIPES
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipe flags model msg appMsg =
|
||||||
|
Page.Recipe flags model msg Model Msg appMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipes msg =
|
||||||
|
{ intro : Recipe Flags.Intro Pages.Guide.Dynamic.Intro.Model Pages.Guide.Dynamic.Intro.Msg msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
recipes : Recipes msg
|
||||||
|
recipes =
|
||||||
|
{ intro =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Guide.Dynamic.Intro.page
|
||||||
|
, toModel = IntroModel
|
||||||
|
, toMsg = IntroMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Route -> Page.Init Model Msg
|
||||||
|
init route =
|
||||||
|
case route of
|
||||||
|
Routes.Intro flags ->
|
||||||
|
recipes.intro.init flags
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> Page.Update Model Msg
|
||||||
|
update bigMsg bigModel =
|
||||||
|
case ( bigMsg, bigModel ) of
|
||||||
|
( IntroMsg msg, IntroModel model ) ->
|
||||||
|
recipes.intro.update msg model
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- _ ->
|
||||||
|
-- App.Page.keep bigModel
|
||||||
|
-- BUNDLE
|
||||||
|
|
||||||
|
|
||||||
|
bundle : Model -> Page.Bundle Msg msg
|
||||||
|
bundle bigModel =
|
||||||
|
case bigModel of
|
||||||
|
IntroModel model ->
|
||||||
|
recipes.intro.bundle model
|
25
elm-stuff/.elm-spa/Generated/Guide/Dynamic/Routes.elm
Normal file
25
elm-stuff/.elm-spa/Generated/Guide/Dynamic/Routes.elm
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
module Generated.Guide.Dynamic.Routes exposing
|
||||||
|
( Route(..)
|
||||||
|
, routes
|
||||||
|
, toPath
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Route as Route
|
||||||
|
import Generated.Guide.Dynamic.Flags as Flags
|
||||||
|
|
||||||
|
|
||||||
|
type Route
|
||||||
|
= Intro Flags.Intro
|
||||||
|
|
||||||
|
|
||||||
|
routes : List (Route.Route Route a)
|
||||||
|
routes =
|
||||||
|
[ Route.path "intro" Intro
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
toPath : Route -> String
|
||||||
|
toPath route =
|
||||||
|
case route of
|
||||||
|
Intro _ ->
|
||||||
|
"/intro"
|
17
elm-stuff/.elm-spa/Generated/Guide/Flags.elm
Normal file
17
elm-stuff/.elm-spa/Generated/Guide/Flags.elm
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module Generated.Guide.Flags exposing
|
||||||
|
( Elm
|
||||||
|
, ElmSpa
|
||||||
|
, Programming
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Elm =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias ElmSpa =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Programming =
|
||||||
|
()
|
150
elm-stuff/.elm-spa/Generated/Guide/Pages.elm
Normal file
150
elm-stuff/.elm-spa/Generated/Guide/Pages.elm
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
module Generated.Guide.Pages exposing
|
||||||
|
( Model
|
||||||
|
, Msg
|
||||||
|
, page
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Generated.Guide.Dynamic.Pages
|
||||||
|
import Generated.Guide.Dynamic.Routes
|
||||||
|
import Generated.Guide.Flags as Flags
|
||||||
|
import Generated.Guide.Routes as Routes exposing (Route(..))
|
||||||
|
import Layouts.Guide as Layout
|
||||||
|
import Pages.Guide.Elm
|
||||||
|
import Pages.Guide.ElmSpa
|
||||||
|
import Pages.Guide.Programming
|
||||||
|
import Utils.Page as Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type Model
|
||||||
|
= ElmModel Pages.Guide.Elm.Model
|
||||||
|
| ElmSpaModel Pages.Guide.ElmSpa.Model
|
||||||
|
| ProgrammingModel Pages.Guide.Programming.Model
|
||||||
|
| Dynamic_Folder_Model Generated.Guide.Dynamic.Pages.Model
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= ElmMsg Pages.Guide.Elm.Msg
|
||||||
|
| ElmSpaMsg Pages.Guide.ElmSpa.Msg
|
||||||
|
| ProgrammingMsg Pages.Guide.Programming.Msg
|
||||||
|
| Dynamic_Folder_Msg Generated.Guide.Dynamic.Pages.Msg
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Route Model Msg layoutModel layoutMsg appMsg
|
||||||
|
page =
|
||||||
|
Page.layout
|
||||||
|
{ view = Layout.view
|
||||||
|
, recipe =
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- RECIPES
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipe flags model msg appMsg =
|
||||||
|
Page.Recipe flags model msg Model Msg appMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipes msg =
|
||||||
|
{ elm : Recipe Flags.Elm Pages.Guide.Elm.Model Pages.Guide.Elm.Msg msg
|
||||||
|
, elmApp : Recipe Flags.ElmSpa Pages.Guide.ElmSpa.Model Pages.Guide.ElmSpa.Msg msg
|
||||||
|
, programming : Recipe Flags.Programming Pages.Guide.Programming.Model Pages.Guide.Programming.Msg msg
|
||||||
|
, dynamic_folder : Recipe Generated.Guide.Dynamic.Routes.Route Generated.Guide.Dynamic.Pages.Model Generated.Guide.Dynamic.Pages.Msg msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
recipes : Recipes msg
|
||||||
|
recipes =
|
||||||
|
{ elm =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Guide.Elm.page
|
||||||
|
, toModel = ElmModel
|
||||||
|
, toMsg = ElmMsg
|
||||||
|
}
|
||||||
|
, elmApp =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Guide.ElmSpa.page
|
||||||
|
, toModel = ElmSpaModel
|
||||||
|
, toMsg = ElmSpaMsg
|
||||||
|
}
|
||||||
|
, programming =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Guide.Programming.page
|
||||||
|
, toModel = ProgrammingModel
|
||||||
|
, toMsg = ProgrammingMsg
|
||||||
|
}
|
||||||
|
, dynamic_folder =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Generated.Guide.Dynamic.Pages.page
|
||||||
|
, toModel = Dynamic_Folder_Model
|
||||||
|
, toMsg = Dynamic_Folder_Msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Route -> Page.Init Model Msg
|
||||||
|
init route =
|
||||||
|
case route of
|
||||||
|
Routes.Elm flags ->
|
||||||
|
recipes.elm.init flags
|
||||||
|
|
||||||
|
Routes.ElmSpa flags ->
|
||||||
|
recipes.elmApp.init flags
|
||||||
|
|
||||||
|
Routes.Programming flags ->
|
||||||
|
recipes.programming.init flags
|
||||||
|
|
||||||
|
Routes.Dynamic_Folder flags route_ ->
|
||||||
|
recipes.dynamic_folder.init route_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> Page.Update Model Msg
|
||||||
|
update bigMsg bigModel =
|
||||||
|
case ( bigMsg, bigModel ) of
|
||||||
|
( ElmMsg msg, ElmModel model ) ->
|
||||||
|
recipes.elm.update msg model
|
||||||
|
|
||||||
|
( ElmSpaMsg msg, ElmSpaModel model ) ->
|
||||||
|
recipes.elmApp.update msg model
|
||||||
|
|
||||||
|
( ProgrammingMsg msg, ProgrammingModel model ) ->
|
||||||
|
recipes.programming.update msg model
|
||||||
|
|
||||||
|
( Dynamic_Folder_Msg msg, Dynamic_Folder_Model model ) ->
|
||||||
|
recipes.dynamic_folder.update msg model
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
App.Page.keep bigModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- BUNDLE
|
||||||
|
|
||||||
|
|
||||||
|
bundle : Model -> Page.Bundle Msg msg
|
||||||
|
bundle bigModel =
|
||||||
|
case bigModel of
|
||||||
|
ElmModel model ->
|
||||||
|
recipes.elm.bundle model
|
||||||
|
|
||||||
|
ElmSpaModel model ->
|
||||||
|
recipes.elmApp.bundle model
|
||||||
|
|
||||||
|
ProgrammingModel model ->
|
||||||
|
recipes.programming.bundle model
|
||||||
|
|
||||||
|
Dynamic_Folder_Model model ->
|
||||||
|
recipes.dynamic_folder.bundle model
|
41
elm-stuff/.elm-spa/Generated/Guide/Routes.elm
Normal file
41
elm-stuff/.elm-spa/Generated/Guide/Routes.elm
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
module Generated.Guide.Routes exposing
|
||||||
|
( Route(..)
|
||||||
|
, routes
|
||||||
|
, toPath
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Route as Route
|
||||||
|
import Generated.Guide.Dynamic.Routes
|
||||||
|
import Generated.Guide.Flags as Flags
|
||||||
|
|
||||||
|
|
||||||
|
type Route
|
||||||
|
= Elm Flags.Elm
|
||||||
|
| ElmSpa Flags.ElmSpa
|
||||||
|
| Programming Flags.Programming
|
||||||
|
| Dynamic_Folder String Generated.Guide.Dynamic.Routes.Route
|
||||||
|
|
||||||
|
|
||||||
|
routes : List (Route.Route Route a)
|
||||||
|
routes =
|
||||||
|
[ Route.path "elm" Elm
|
||||||
|
, Route.path "elm-spa" ElmSpa
|
||||||
|
, Route.path "programming" Programming
|
||||||
|
, Route.dynamicFolder Dynamic_Folder Generated.Guide.Dynamic.Routes.routes
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
toPath : Route -> String
|
||||||
|
toPath route =
|
||||||
|
case route of
|
||||||
|
Elm _ ->
|
||||||
|
"/"
|
||||||
|
|
||||||
|
ElmSpa _ ->
|
||||||
|
"/elm-spa"
|
||||||
|
|
||||||
|
Programming _ ->
|
||||||
|
"/programming"
|
||||||
|
|
||||||
|
Dynamic_Folder string route_ ->
|
||||||
|
"/" ++ string ++ Generated.Guide.Dynamic.Routes.toPath route_
|
208
elm-stuff/.elm-spa/Generated/Pages.elm
Normal file
208
elm-stuff/.elm-spa/Generated/Pages.elm
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
module Generated.Pages exposing
|
||||||
|
( Model
|
||||||
|
, Msg
|
||||||
|
, page
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Generated.Docs.Pages
|
||||||
|
import Generated.Docs.Routes
|
||||||
|
import Generated.Flags as Flags
|
||||||
|
import Generated.Guide.Pages
|
||||||
|
import Generated.Guide.Routes
|
||||||
|
import Generated.Routes as Routes exposing (Route(..))
|
||||||
|
import Layout as Layout
|
||||||
|
import Pages.Docs
|
||||||
|
import Pages.Guide
|
||||||
|
import Pages.NotFound
|
||||||
|
import Pages.SignIn
|
||||||
|
import Pages.Top
|
||||||
|
import Utils.Page as Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type Model
|
||||||
|
= TopModel Pages.Top.Model
|
||||||
|
| DocsModel Pages.Docs.Model
|
||||||
|
| NotFoundModel Pages.NotFound.Model
|
||||||
|
| SignInModel Pages.SignIn.Model
|
||||||
|
| GuideModel Pages.Guide.Model
|
||||||
|
| Guide_Folder_Model Generated.Guide.Pages.Model
|
||||||
|
| Docs_Folder_Model Generated.Docs.Pages.Model
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= TopMsg Pages.Top.Msg
|
||||||
|
| DocsMsg Pages.Docs.Msg
|
||||||
|
| NotFoundMsg Pages.NotFound.Msg
|
||||||
|
| SignInMsg Pages.SignIn.Msg
|
||||||
|
| GuideMsg Pages.Guide.Msg
|
||||||
|
| Guide_Folder_Msg Generated.Guide.Pages.Msg
|
||||||
|
| Docs_Folder_Msg Generated.Docs.Pages.Msg
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Route Model Msg layoutModel layoutMsg appMsg
|
||||||
|
page =
|
||||||
|
Page.layout
|
||||||
|
{ view = Layout.view
|
||||||
|
, recipe =
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- RECIPES
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipe flags model msg appMsg =
|
||||||
|
Page.Recipe flags model msg Model Msg appMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipes msg =
|
||||||
|
{ top : Recipe Flags.Top Pages.Top.Model Pages.Top.Msg msg
|
||||||
|
, docs : Recipe Flags.Docs Pages.Docs.Model Pages.Docs.Msg msg
|
||||||
|
, notFound : Recipe Flags.NotFound Pages.NotFound.Model Pages.NotFound.Msg msg
|
||||||
|
, signIn : Recipe Flags.SignIn Pages.SignIn.Model Pages.SignIn.Msg msg
|
||||||
|
, guide : Recipe Flags.Guide Pages.Guide.Model Pages.Guide.Msg msg
|
||||||
|
, guide_folder : Recipe Generated.Guide.Routes.Route Generated.Guide.Pages.Model Generated.Guide.Pages.Msg msg
|
||||||
|
, docs_folder : Recipe Generated.Docs.Routes.Route Generated.Docs.Pages.Model Generated.Docs.Pages.Msg msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
recipes : Recipes msg
|
||||||
|
recipes =
|
||||||
|
{ top =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Top.page
|
||||||
|
, toModel = TopModel
|
||||||
|
, toMsg = TopMsg
|
||||||
|
}
|
||||||
|
, docs =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Docs.page
|
||||||
|
, toModel = DocsModel
|
||||||
|
, toMsg = DocsMsg
|
||||||
|
}
|
||||||
|
, notFound =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.NotFound.page
|
||||||
|
, toModel = NotFoundModel
|
||||||
|
, toMsg = NotFoundMsg
|
||||||
|
}
|
||||||
|
, signIn =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.SignIn.page
|
||||||
|
, toModel = SignInModel
|
||||||
|
, toMsg = SignInMsg
|
||||||
|
}
|
||||||
|
, guide =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Pages.Guide.page
|
||||||
|
, toModel = GuideModel
|
||||||
|
, toMsg = GuideMsg
|
||||||
|
}
|
||||||
|
, guide_folder =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Generated.Guide.Pages.page
|
||||||
|
, toModel = Guide_Folder_Model
|
||||||
|
, toMsg = Guide_Folder_Msg
|
||||||
|
}
|
||||||
|
, docs_folder =
|
||||||
|
Page.recipe
|
||||||
|
{ page = Generated.Docs.Pages.page
|
||||||
|
, toModel = Docs_Folder_Model
|
||||||
|
, toMsg = Docs_Folder_Msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Route -> Page.Init Model Msg
|
||||||
|
init route_ =
|
||||||
|
case route_ of
|
||||||
|
Routes.Top flags ->
|
||||||
|
recipes.top.init flags
|
||||||
|
|
||||||
|
Routes.Docs flags ->
|
||||||
|
recipes.docs.init flags
|
||||||
|
|
||||||
|
Routes.NotFound flags ->
|
||||||
|
recipes.notFound.init flags
|
||||||
|
|
||||||
|
Routes.SignIn flags ->
|
||||||
|
recipes.signIn.init flags
|
||||||
|
|
||||||
|
Routes.Guide flags ->
|
||||||
|
recipes.guide.init flags
|
||||||
|
|
||||||
|
Routes.Guide_Folder route ->
|
||||||
|
recipes.guide_folder.init route
|
||||||
|
|
||||||
|
Routes.Docs_Folder route ->
|
||||||
|
recipes.docs_folder.init route
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> Page.Update Model Msg
|
||||||
|
update bigMsg bigModel =
|
||||||
|
case ( bigMsg, bigModel ) of
|
||||||
|
( TopMsg msg, TopModel model ) ->
|
||||||
|
recipes.top.update msg model
|
||||||
|
|
||||||
|
( DocsMsg msg, DocsModel model ) ->
|
||||||
|
recipes.docs.update msg model
|
||||||
|
|
||||||
|
( NotFoundMsg msg, NotFoundModel model ) ->
|
||||||
|
recipes.notFound.update msg model
|
||||||
|
|
||||||
|
( SignInMsg msg, SignInModel model ) ->
|
||||||
|
recipes.signIn.update msg model
|
||||||
|
|
||||||
|
( GuideMsg msg, GuideModel model ) ->
|
||||||
|
recipes.guide.update msg model
|
||||||
|
|
||||||
|
( Guide_Folder_Msg msg, Guide_Folder_Model model ) ->
|
||||||
|
recipes.guide_folder.update msg model
|
||||||
|
|
||||||
|
( Docs_Folder_Msg msg, Docs_Folder_Model model ) ->
|
||||||
|
recipes.docs_folder.update msg model
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
App.Page.keep bigModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- BUNDLE
|
||||||
|
|
||||||
|
|
||||||
|
bundle : Model -> Page.Bundle Msg msg
|
||||||
|
bundle bigModel =
|
||||||
|
case bigModel of
|
||||||
|
TopModel model ->
|
||||||
|
recipes.top.bundle model
|
||||||
|
|
||||||
|
DocsModel model ->
|
||||||
|
recipes.docs.bundle model
|
||||||
|
|
||||||
|
NotFoundModel model ->
|
||||||
|
recipes.notFound.bundle model
|
||||||
|
|
||||||
|
SignInModel model ->
|
||||||
|
recipes.signIn.bundle model
|
||||||
|
|
||||||
|
GuideModel model ->
|
||||||
|
recipes.guide.bundle model
|
||||||
|
|
||||||
|
Guide_Folder_Model model ->
|
||||||
|
recipes.guide_folder.bundle model
|
||||||
|
|
||||||
|
Docs_Folder_Model model ->
|
||||||
|
recipes.docs_folder.bundle model
|
57
elm-stuff/.elm-spa/Generated/Routes.elm
Normal file
57
elm-stuff/.elm-spa/Generated/Routes.elm
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
module Generated.Routes exposing
|
||||||
|
( Route(..)
|
||||||
|
, routes
|
||||||
|
, toPath
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Route as Route
|
||||||
|
import Generated.Docs.Routes
|
||||||
|
import Generated.Flags as Flags
|
||||||
|
import Generated.Guide.Routes
|
||||||
|
|
||||||
|
|
||||||
|
type Route
|
||||||
|
= Top Flags.Top
|
||||||
|
| Docs Flags.Docs
|
||||||
|
| NotFound Flags.NotFound
|
||||||
|
| SignIn Flags.SignIn
|
||||||
|
| Guide Flags.Guide
|
||||||
|
| Guide_Folder Generated.Guide.Routes.Route
|
||||||
|
| Docs_Folder Generated.Docs.Routes.Route
|
||||||
|
|
||||||
|
|
||||||
|
routes : List (Route.Route Route a)
|
||||||
|
routes =
|
||||||
|
[ Route.top Top
|
||||||
|
, Route.path "docs" Docs
|
||||||
|
, Route.path "not-found" NotFound
|
||||||
|
, Route.path "sign-in" SignIn
|
||||||
|
, Route.path "guide" Guide
|
||||||
|
, Route.folder "guide" Guide_Folder Generated.Guide.Routes.routes
|
||||||
|
, Route.folder "docs" Docs_Folder Generated.Docs.Routes.routes
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
toPath : Route -> String
|
||||||
|
toPath route =
|
||||||
|
case route of
|
||||||
|
Top _ ->
|
||||||
|
"/"
|
||||||
|
|
||||||
|
Docs _ ->
|
||||||
|
"/docs"
|
||||||
|
|
||||||
|
NotFound _ ->
|
||||||
|
"/not-found"
|
||||||
|
|
||||||
|
SignIn _ ->
|
||||||
|
"/sign-in"
|
||||||
|
|
||||||
|
Guide _ ->
|
||||||
|
"/guide"
|
||||||
|
|
||||||
|
Guide_Folder route_ ->
|
||||||
|
"/guide" ++ Generated.Guide.Routes.toPath route_
|
||||||
|
|
||||||
|
Docs_Folder route_ ->
|
||||||
|
"/docs" ++ Generated.Docs.Routes.toPath route_
|
6
elm-stuff/.elm-spa/README.md
Normal file
6
elm-stuff/.elm-spa/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# elm-stuff/.elm-spa
|
||||||
|
> this is where all the generated code goes!
|
||||||
|
|
||||||
|
Normally, this whole directory should be `.gitignore`d,
|
||||||
|
but I'm keeping it around so I can manually practice
|
||||||
|
what `elm-spa build` should be building.
|
29
elm.json
Normal file
29
elm.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "application",
|
||||||
|
"source-directories": [
|
||||||
|
"src",
|
||||||
|
"example",
|
||||||
|
"elm-stuff/.elm-spa"
|
||||||
|
],
|
||||||
|
"elm-version": "0.19.1",
|
||||||
|
"dependencies": {
|
||||||
|
"direct": {
|
||||||
|
"elm/browser": "1.0.2",
|
||||||
|
"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": "1.0.0"
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"elm/time": "1.0.0",
|
||||||
|
"elm/virtual-dom": "1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-dependencies": {
|
||||||
|
"direct": {},
|
||||||
|
"indirect": {}
|
||||||
|
}
|
||||||
|
}
|
27
example/Components/Button.elm
Normal file
27
example/Components/Button.elm
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
module Components.Button exposing (view)
|
||||||
|
|
||||||
|
import Components.Styles as Styles
|
||||||
|
import Element exposing (..)
|
||||||
|
import Element.Background as Background
|
||||||
|
import Element.Border as Border
|
||||||
|
import Element.Font as Font
|
||||||
|
import Element.Input as Input
|
||||||
|
import Html.Attributes as Attr
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
{ onPress : Maybe msg
|
||||||
|
, label : Element msg
|
||||||
|
}
|
||||||
|
-> Element msg
|
||||||
|
view config =
|
||||||
|
Input.button
|
||||||
|
((if config.onPress == Nothing then
|
||||||
|
alpha 0.6
|
||||||
|
|
||||||
|
else
|
||||||
|
alpha 1
|
||||||
|
)
|
||||||
|
:: Styles.button
|
||||||
|
)
|
||||||
|
config
|
58
example/Components/Hero.elm
Normal file
58
example/Components/Hero.elm
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
module Components.Hero exposing (Action(..), view)
|
||||||
|
|
||||||
|
import Components.Styles as Styles
|
||||||
|
import Element exposing (..)
|
||||||
|
import Element.Input as Input
|
||||||
|
|
||||||
|
|
||||||
|
type Action msg
|
||||||
|
= Link String
|
||||||
|
| Button msg
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
{ title : String
|
||||||
|
, subtitle : Element msg
|
||||||
|
, buttons : List { action : Action msg, label : Element msg }
|
||||||
|
}
|
||||||
|
-> Element msg
|
||||||
|
view config =
|
||||||
|
column
|
||||||
|
[ paddingEach
|
||||||
|
{ top = 128
|
||||||
|
, bottom = 148
|
||||||
|
, left = 0
|
||||||
|
, right = 0
|
||||||
|
}
|
||||||
|
, spacing 20
|
||||||
|
, centerX
|
||||||
|
]
|
||||||
|
<|
|
||||||
|
List.concat
|
||||||
|
[ [ Styles.h1 [ centerX ] (text config.title)
|
||||||
|
, el [ centerX, alpha 0.8 ] config.subtitle
|
||||||
|
]
|
||||||
|
, config.buttons
|
||||||
|
|> List.map (viewAction (centerX :: Styles.button))
|
||||||
|
|> viewActions
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewAction : List (Attribute msg) -> { action : Action msg, label : Element msg } -> Element msg
|
||||||
|
viewAction attrs { action, label } =
|
||||||
|
case action of
|
||||||
|
Link url ->
|
||||||
|
link attrs { url = url, label = label }
|
||||||
|
|
||||||
|
Button msg ->
|
||||||
|
Input.button attrs { onPress = Just msg, label = label }
|
||||||
|
|
||||||
|
|
||||||
|
viewActions : List (Element msg) -> List (Element msg)
|
||||||
|
viewActions links =
|
||||||
|
if List.isEmpty links then
|
||||||
|
[]
|
||||||
|
|
||||||
|
else
|
||||||
|
[ wrappedRow [ spacing 24, centerX ] links
|
||||||
|
]
|
18
example/Components/Section.elm
Normal file
18
example/Components/Section.elm
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Components.Section exposing (view)
|
||||||
|
|
||||||
|
import Components.Styles as Styles
|
||||||
|
import Element exposing (..)
|
||||||
|
import Html.Attributes as Attr
|
||||||
|
import Markdown
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
{ title : String
|
||||||
|
, content : String
|
||||||
|
}
|
||||||
|
-> Element msg
|
||||||
|
view config =
|
||||||
|
paragraph []
|
||||||
|
[ Styles.h3 [] (text config.title)
|
||||||
|
, Element.html (Markdown.toHtml [ Attr.class "markdown" ] config.content)
|
||||||
|
]
|
104
example/Components/Styles.elm
Normal file
104
example/Components/Styles.elm
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
module Components.Styles exposing
|
||||||
|
( button
|
||||||
|
, colors
|
||||||
|
, fonts
|
||||||
|
, h1
|
||||||
|
, h3
|
||||||
|
, link
|
||||||
|
, transition
|
||||||
|
)
|
||||||
|
|
||||||
|
import Element exposing (..)
|
||||||
|
import Element.Background as Background
|
||||||
|
import Element.Border as Border
|
||||||
|
import Element.Font as Font
|
||||||
|
import Html.Attributes as Attr
|
||||||
|
|
||||||
|
|
||||||
|
colors =
|
||||||
|
{ white = rgb 1 1 1
|
||||||
|
, jet = rgb255 40 40 40
|
||||||
|
, coral = rgb255 204 75 75
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fonts =
|
||||||
|
{ sans =
|
||||||
|
[ Font.external
|
||||||
|
{ name = "IBM Plex Sans"
|
||||||
|
, url = "https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,400i,600,600i&display=swap"
|
||||||
|
}
|
||||||
|
, Font.serif
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
link : List (Attribute msg)
|
||||||
|
link =
|
||||||
|
[ Font.underline
|
||||||
|
, Font.color colors.coral
|
||||||
|
, transition
|
||||||
|
{ property = "opacity"
|
||||||
|
, speed = 150
|
||||||
|
}
|
||||||
|
, mouseOver
|
||||||
|
[ alpha 0.6
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
button : List (Attribute msg)
|
||||||
|
button =
|
||||||
|
[ paddingXY 16 8
|
||||||
|
, Font.size 14
|
||||||
|
, Border.color colors.coral
|
||||||
|
, Font.color colors.coral
|
||||||
|
, Background.color colors.white
|
||||||
|
, Border.width 2
|
||||||
|
, Border.rounded 4
|
||||||
|
, pointer
|
||||||
|
, transition
|
||||||
|
{ property = "all"
|
||||||
|
, speed = 150
|
||||||
|
}
|
||||||
|
, mouseOver
|
||||||
|
[ Font.color colors.white
|
||||||
|
, Background.color colors.coral
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
h1 : List (Attribute msg) -> Element msg -> Element msg
|
||||||
|
h1 =
|
||||||
|
elWith
|
||||||
|
[ Font.family fonts.sans
|
||||||
|
, Font.semiBold
|
||||||
|
, Font.size 64
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
h3 : List (Attribute msg) -> Element msg -> Element msg
|
||||||
|
h3 =
|
||||||
|
elWith
|
||||||
|
[ Font.family fonts.sans
|
||||||
|
, Font.semiBold
|
||||||
|
, Font.size 36
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
transition :
|
||||||
|
{ property : String
|
||||||
|
, speed : Int
|
||||||
|
}
|
||||||
|
-> Attribute msg
|
||||||
|
transition { property, speed } =
|
||||||
|
Element.htmlAttribute
|
||||||
|
(Attr.style
|
||||||
|
"transition"
|
||||||
|
(property ++ " " ++ String.fromInt speed ++ "ms ease-in-out")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
elWith : List (Attribute msg) -> List (Attribute msg) -> Element msg -> Element msg
|
||||||
|
elWith styles otherStyles =
|
||||||
|
el ([ Element.htmlAttribute (Attr.class "markdown") ] ++ styles ++ otherStyles)
|
58
example/Global.elm
Normal file
58
example/Global.elm
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
module Global exposing
|
||||||
|
( Flags
|
||||||
|
, Model
|
||||||
|
, Msg(..)
|
||||||
|
, init
|
||||||
|
, subscriptions
|
||||||
|
, update
|
||||||
|
)
|
||||||
|
|
||||||
|
import Generated.Routes as Routes exposing (Route)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Flags =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ user : 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 _ _ =
|
||||||
|
( { user = Nothing }
|
||||||
|
, Cmd.none
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
update : Commands msg -> Msg -> Model -> ( Model, Cmd Msg, Cmd msg )
|
||||||
|
update commands msg model =
|
||||||
|
case msg of
|
||||||
|
SignIn user ->
|
||||||
|
( { model | user = Just user }
|
||||||
|
, Cmd.none
|
||||||
|
, commands.navigate (Routes.Top ())
|
||||||
|
)
|
||||||
|
|
||||||
|
SignOut ->
|
||||||
|
( { model | user = Nothing }
|
||||||
|
, Cmd.none
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions : Model -> Sub Msg
|
||||||
|
subscriptions _ =
|
||||||
|
Sub.none
|
106
example/Layout.elm
Normal file
106
example/Layout.elm
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
module Layout exposing (view)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Button
|
||||||
|
import Components.Styles as Styles
|
||||||
|
import Element exposing (..)
|
||||||
|
import Element.Background as Background
|
||||||
|
import Element.Border as Border
|
||||||
|
import Element.Font as Font
|
||||||
|
import Element.Input as Input
|
||||||
|
import Global
|
||||||
|
import Html.Attributes as Attr
|
||||||
|
|
||||||
|
|
||||||
|
type alias Context msg =
|
||||||
|
{ page : Element msg
|
||||||
|
, global : Global.Model
|
||||||
|
, toMsg : Global.Msg -> msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
view : Context msg -> Element msg
|
||||||
|
view { page, global, toMsg } =
|
||||||
|
column
|
||||||
|
[ Font.size 16
|
||||||
|
, Font.color Styles.colors.jet
|
||||||
|
, Font.family Styles.fonts.sans
|
||||||
|
, paddingEach
|
||||||
|
{ top = 32
|
||||||
|
, left = 16
|
||||||
|
, right = 16
|
||||||
|
, bottom = 128
|
||||||
|
}
|
||||||
|
, spacing 32
|
||||||
|
, width (fill |> maximum 640)
|
||||||
|
, height fill
|
||||||
|
, centerX
|
||||||
|
]
|
||||||
|
[ Element.map toMsg (viewNavbar global.user)
|
||||||
|
, page
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewNavbar : Maybe String -> Element Global.Msg
|
||||||
|
viewNavbar user_ =
|
||||||
|
row
|
||||||
|
[ width fill
|
||||||
|
, spacing 24
|
||||||
|
]
|
||||||
|
[ row [ Font.size 18, spacing 24 ] <|
|
||||||
|
(link
|
||||||
|
[ Font.size 20
|
||||||
|
, Font.semiBold
|
||||||
|
, Font.color Styles.colors.coral
|
||||||
|
, Styles.transition
|
||||||
|
{ property = "opacity"
|
||||||
|
, speed = 150
|
||||||
|
}
|
||||||
|
, mouseOver [ alpha 0.6 ]
|
||||||
|
]
|
||||||
|
{ label = text "elm-spa"
|
||||||
|
, url = "/"
|
||||||
|
}
|
||||||
|
:: List.map viewLink
|
||||||
|
[ ( "docs", "/docs" )
|
||||||
|
, ( "guide", "/guide" )
|
||||||
|
]
|
||||||
|
)
|
||||||
|
, el [ alignRight ] <|
|
||||||
|
case user_ of
|
||||||
|
Just name ->
|
||||||
|
Components.Button.view
|
||||||
|
{ onPress = Just Global.SignOut
|
||||||
|
, label = text ("sign out " ++ name)
|
||||||
|
}
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
viewButtonLink ( "sign in", "/sign-in" )
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewLink : ( String, String ) -> Element msg
|
||||||
|
viewLink ( label, url ) =
|
||||||
|
link Styles.link
|
||||||
|
{ url = url
|
||||||
|
, label = text label
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
viewButtonLink : ( String, String ) -> Element msg
|
||||||
|
viewButtonLink ( label, url ) =
|
||||||
|
link Styles.button
|
||||||
|
{ url = url
|
||||||
|
, label = text label
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
transition :
|
||||||
|
{ property : String, speed : Int }
|
||||||
|
-> Element.Attribute msg
|
||||||
|
transition { property, speed } =
|
||||||
|
Element.htmlAttribute
|
||||||
|
(Attr.style
|
||||||
|
"transition"
|
||||||
|
(property ++ " " ++ String.fromInt speed ++ "ms ease-in-out")
|
||||||
|
)
|
16
example/Layouts/Docs.elm
Normal file
16
example/Layouts/Docs.elm
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module Layouts.Docs exposing (view)
|
||||||
|
|
||||||
|
import Element exposing (..)
|
||||||
|
import Global
|
||||||
|
|
||||||
|
|
||||||
|
type alias Context msg =
|
||||||
|
{ page : Element msg
|
||||||
|
, global : Global.Model
|
||||||
|
, toMsg : Global.Msg -> msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
view : Context msg -> Element msg
|
||||||
|
view { page } =
|
||||||
|
page
|
16
example/Layouts/Guide.elm
Normal file
16
example/Layouts/Guide.elm
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module Layouts.Guide exposing (view)
|
||||||
|
|
||||||
|
import Element exposing (..)
|
||||||
|
import Global
|
||||||
|
|
||||||
|
|
||||||
|
type alias Context msg =
|
||||||
|
{ page : Element msg
|
||||||
|
, global : Global.Model
|
||||||
|
, toMsg : Global.Msg -> msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
view : Context msg -> Element msg
|
||||||
|
view { page } =
|
||||||
|
page
|
16
example/Layouts/Guide/Dynamic.elm
Normal file
16
example/Layouts/Guide/Dynamic.elm
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module Layouts.Guide.Dynamic exposing (view)
|
||||||
|
|
||||||
|
import Element exposing (..)
|
||||||
|
import Global
|
||||||
|
|
||||||
|
|
||||||
|
type alias Context msg =
|
||||||
|
{ page : Element msg
|
||||||
|
, global : Global.Model
|
||||||
|
, toMsg : Global.Msg -> msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
view : Context msg -> Element msg
|
||||||
|
view { page } =
|
||||||
|
page
|
29
example/Main.elm
Normal file
29
example/Main.elm
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module Main exposing (main)
|
||||||
|
|
||||||
|
import App
|
||||||
|
import Element
|
||||||
|
import Generated.Pages as Pages
|
||||||
|
import Generated.Routes as Routes
|
||||||
|
import Global
|
||||||
|
import Pages.NotFound
|
||||||
|
|
||||||
|
|
||||||
|
main : App.Program Global.Flags Global.Model Global.Msg Pages.Model Pages.Msg
|
||||||
|
main =
|
||||||
|
App.create
|
||||||
|
{ ui =
|
||||||
|
{ toHtml = Element.layout []
|
||||||
|
, map = Element.map
|
||||||
|
}
|
||||||
|
, routing =
|
||||||
|
{ routes = Routes.routes
|
||||||
|
, toPath = Routes.toPath
|
||||||
|
, notFound = Routes.NotFound ()
|
||||||
|
}
|
||||||
|
, global =
|
||||||
|
{ init = Global.init
|
||||||
|
, update = Global.update
|
||||||
|
, subscriptions = Global.subscriptions
|
||||||
|
}
|
||||||
|
, page = Pages.page
|
||||||
|
}
|
41
example/Pages/Docs.elm
Normal file
41
example/Pages/Docs.elm
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
module Pages.Docs exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Docs Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "Docs"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column [ width fill ]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "docs"
|
||||||
|
, subtitle = text "\"it's not done until the docs are great.\""
|
||||||
|
, buttons =
|
||||||
|
[ { label = text "elm-app", action = Components.Hero.Link "/docs/elm-app" }
|
||||||
|
, { label = text "elm-spa", action = Components.Hero.Link "/docs/elm-spa" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
70
example/Pages/Docs/Dynamic.elm
Normal file
70
example/Pages/Docs/Dynamic.elm
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
module Pages.Docs.Dynamic exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Docs.Flags as Flags
|
||||||
|
import Global
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ slug : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Dynamic Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.sandbox
|
||||||
|
{ title = always "Dynamic"
|
||||||
|
, init = always init
|
||||||
|
, update = always update
|
||||||
|
, view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Flags.Dynamic -> Model
|
||||||
|
init slug =
|
||||||
|
{ slug = slug
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> Model
|
||||||
|
update msg model =
|
||||||
|
model
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Global.Model -> Model -> Element Msg
|
||||||
|
view global model =
|
||||||
|
column
|
||||||
|
[ width fill
|
||||||
|
]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "docs: " ++ model.slug
|
||||||
|
, subtitle = text "\"it's not done until the docs are great.\""
|
||||||
|
, buttons =
|
||||||
|
[ { label = text "back to docs", action = Components.Hero.Link "/docs" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, global.user
|
||||||
|
|> Maybe.map (\name -> "Oh hey there, " ++ name ++ "!")
|
||||||
|
|> Maybe.withDefault "Sign in if you want me to say hello!"
|
||||||
|
|> text
|
||||||
|
|> el [ centerX ]
|
||||||
|
]
|
40
example/Pages/Docs/Static.elm
Normal file
40
example/Pages/Docs/Static.elm
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
module Pages.Docs.Static exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Docs.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Static Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "Static"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column
|
||||||
|
[ width fill
|
||||||
|
]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "static tho"
|
||||||
|
, subtitle = text "\"it's not done until the docs are great.\""
|
||||||
|
, buttons = []
|
||||||
|
}
|
||||||
|
]
|
43
example/Pages/Guide.elm
Normal file
43
example/Pages/Guide.elm
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
module Pages.Guide exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Guide Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "Guide"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column
|
||||||
|
[ width fill ]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "guide"
|
||||||
|
, subtitle = text "alright, where should we begin?"
|
||||||
|
, buttons =
|
||||||
|
[ { label = text "new to web dev", action = Components.Hero.Link "/guide/programming" }
|
||||||
|
, { label = text "new to elm", action = Components.Hero.Link "/guide/elm" }
|
||||||
|
, { label = text "new to elm-spa", action = Components.Hero.Link "/guide/elm-spa" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
40
example/Pages/Guide/Dynamic/Intro.elm
Normal file
40
example/Pages/Guide/Dynamic/Intro.elm
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
module Pages.Guide.Dynamic.Intro exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Guide.Dynamic.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Intro Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "Guide.Dynamic.Intro"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column
|
||||||
|
[ width fill
|
||||||
|
]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "intro"
|
||||||
|
, subtitle = text "\"you're gonna be great.\""
|
||||||
|
, buttons = []
|
||||||
|
}
|
||||||
|
]
|
38
example/Pages/Guide/Elm.elm
Normal file
38
example/Pages/Guide/Elm.elm
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
module Pages.Guide.Elm exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Guide.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Elm Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "Guide.Elm"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column [ width fill ]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "intro to elm"
|
||||||
|
, subtitle = text "\"you're gonna be great.\""
|
||||||
|
, buttons = []
|
||||||
|
}
|
||||||
|
]
|
38
example/Pages/Guide/ElmSpa.elm
Normal file
38
example/Pages/Guide/ElmSpa.elm
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
module Pages.Guide.ElmSpa exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Guide.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.ElmSpa Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "Guide.ElmSpa"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column [ width fill ]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "intro to elm-spa"
|
||||||
|
, subtitle = text "\"you're gonna be great.\""
|
||||||
|
, buttons = []
|
||||||
|
}
|
||||||
|
]
|
38
example/Pages/Guide/Programming.elm
Normal file
38
example/Pages/Guide/Programming.elm
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
module Pages.Guide.Programming exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Guide.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Programming Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "Guide.Programming"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column [ width fill ]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "programming"
|
||||||
|
, subtitle = text "become nerdy, in a lovable way"
|
||||||
|
, buttons = []
|
||||||
|
}
|
||||||
|
]
|
40
example/Pages/NotFound.elm
Normal file
40
example/Pages/NotFound.elm
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
module Pages.NotFound exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Flags as Flags
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.NotFound Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.static
|
||||||
|
{ title = always "NotFound"
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Element Msg
|
||||||
|
view =
|
||||||
|
column [ width fill ]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "that's not a place."
|
||||||
|
, subtitle = text "but i'm not even mad about it."
|
||||||
|
, buttons =
|
||||||
|
[ { label = text "back home", action = Components.Hero.Link "/" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
204
example/Pages/SignIn.elm
Normal file
204
example/Pages/SignIn.elm
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
module Pages.SignIn exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Button
|
||||||
|
import Components.Styles as Styles
|
||||||
|
import Element exposing (..)
|
||||||
|
import Element.Border as Border
|
||||||
|
import Element.Font as Font
|
||||||
|
import Element.Input as Input
|
||||||
|
import Generated.Flags as Flags
|
||||||
|
import Global
|
||||||
|
import Html exposing (Html)
|
||||||
|
import Html.Attributes as Attr
|
||||||
|
import Html.Events as Events
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ username : String
|
||||||
|
, password : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= UpdatedField Field String
|
||||||
|
| ClickedSignIn
|
||||||
|
|
||||||
|
|
||||||
|
type Field
|
||||||
|
= Username
|
||||||
|
| Password
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.SignIn Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.component
|
||||||
|
{ title = always "sign in | elm-spa"
|
||||||
|
, init = always init
|
||||||
|
, update = always update
|
||||||
|
, subscriptions = always subscriptions
|
||||||
|
, view = always view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Flags.SignIn -> ( Model, Cmd Msg, Cmd Global.Msg )
|
||||||
|
init flags =
|
||||||
|
( { username = ""
|
||||||
|
, password = ""
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg, Cmd Global.Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
UpdatedField Username value ->
|
||||||
|
( { model | username = value }
|
||||||
|
, Cmd.none
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
UpdatedField Password value ->
|
||||||
|
( { model | password = value }
|
||||||
|
, Cmd.none
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
ClickedSignIn ->
|
||||||
|
( model
|
||||||
|
, Cmd.none
|
||||||
|
, App.Page.send <|
|
||||||
|
Global.SignIn model.username
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- SUBSCRIPTIONS
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions : Model -> Sub Msg
|
||||||
|
subscriptions model =
|
||||||
|
Sub.none
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Element Msg
|
||||||
|
view model =
|
||||||
|
el [ centerX, centerY ] <|
|
||||||
|
form
|
||||||
|
{ onSubmit = ClickedSignIn
|
||||||
|
}
|
||||||
|
[ spacing 32 ]
|
||||||
|
[ el [ Font.size 24, Font.semiBold ]
|
||||||
|
(text "Sign in")
|
||||||
|
, column [ spacing 16 ]
|
||||||
|
[ viewField
|
||||||
|
{ label = "Username"
|
||||||
|
, onChange = UpdatedField Username
|
||||||
|
, inputType = TextInput
|
||||||
|
, value = model.username
|
||||||
|
}
|
||||||
|
, viewField
|
||||||
|
{ label = "Password"
|
||||||
|
, onChange = UpdatedField Password
|
||||||
|
, inputType = PasswordInput
|
||||||
|
, value = model.password
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, el [ alignRight ] <|
|
||||||
|
if String.isEmpty model.username then
|
||||||
|
Input.button (Styles.button ++ [ alpha 0.6 ])
|
||||||
|
{ onPress = Nothing
|
||||||
|
, label = text "Sign In"
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
Input.button (Styles.button ++ [ htmlAttribute (Attr.type_ "submit") ])
|
||||||
|
{ onPress = Just ClickedSignIn
|
||||||
|
, label = text "Sign In"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
form : { onSubmit : msg } -> List (Attribute msg) -> List (Element msg) -> Element msg
|
||||||
|
form config attrs children =
|
||||||
|
Element.html
|
||||||
|
(Html.form
|
||||||
|
[ Events.onSubmit config.onSubmit ]
|
||||||
|
[ toHtml (column attrs children)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
toHtml : Element msg -> Html msg
|
||||||
|
toHtml =
|
||||||
|
Element.layoutWith { options = [ Element.noStaticStyleSheet ] } []
|
||||||
|
|
||||||
|
|
||||||
|
type InputType
|
||||||
|
= TextInput
|
||||||
|
| PasswordInput
|
||||||
|
|
||||||
|
|
||||||
|
viewField :
|
||||||
|
{ inputType : InputType
|
||||||
|
, label : String
|
||||||
|
, onChange : String -> msg
|
||||||
|
, value : String
|
||||||
|
}
|
||||||
|
-> Element msg
|
||||||
|
viewField config =
|
||||||
|
let
|
||||||
|
styles =
|
||||||
|
{ field =
|
||||||
|
[ paddingXY 4 4
|
||||||
|
, Border.rounded 0
|
||||||
|
, Border.widthEach
|
||||||
|
{ top = 0
|
||||||
|
, left = 0
|
||||||
|
, right = 0
|
||||||
|
, bottom = 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, label =
|
||||||
|
[ Font.size 16
|
||||||
|
, Font.semiBold
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
label =
|
||||||
|
Input.labelAbove
|
||||||
|
styles.label
|
||||||
|
(text config.label)
|
||||||
|
in
|
||||||
|
case config.inputType of
|
||||||
|
TextInput ->
|
||||||
|
Input.text styles.field
|
||||||
|
{ onChange = config.onChange
|
||||||
|
, text = config.value
|
||||||
|
, placeholder = Nothing
|
||||||
|
, label = label
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordInput ->
|
||||||
|
Input.currentPassword styles.field
|
||||||
|
{ onChange = config.onChange
|
||||||
|
, text = config.value
|
||||||
|
, placeholder = Nothing
|
||||||
|
, label = label
|
||||||
|
, show = False
|
||||||
|
}
|
131
example/Pages/Top.elm
Normal file
131
example/Pages/Top.elm
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
module Pages.Top exposing (Model, Msg, page)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import Components.Hero
|
||||||
|
import Components.Section
|
||||||
|
import Components.Styles as Styles
|
||||||
|
import Element exposing (..)
|
||||||
|
import Generated.Flags as Flags
|
||||||
|
import Html.Attributes as Attr
|
||||||
|
import Ports
|
||||||
|
import Utils.Page exposing (Page)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
page : Page Flags.Top Model Msg model msg appMsg
|
||||||
|
page =
|
||||||
|
App.Page.element
|
||||||
|
{ title = always "elm-spa"
|
||||||
|
, init = always init
|
||||||
|
, update = always update
|
||||||
|
, view = always view
|
||||||
|
, subscriptions = always subscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Flags.Top -> ( Model, Cmd Msg )
|
||||||
|
init flags =
|
||||||
|
( ()
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= ScrollTo String
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
ScrollTo id ->
|
||||||
|
( model
|
||||||
|
, Ports.scrollTo id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- SUBSCRIPTIONS
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions : Model -> Sub Msg
|
||||||
|
subscriptions model =
|
||||||
|
Sub.none
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Element Msg
|
||||||
|
view model =
|
||||||
|
column [ width fill ]
|
||||||
|
[ Components.Hero.view
|
||||||
|
{ title = "elm-spa"
|
||||||
|
, subtitle = text "a framework for Elm."
|
||||||
|
, buttons =
|
||||||
|
[ { label = text "learn more"
|
||||||
|
, action = Components.Hero.Button (ScrollTo "page-content")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, column
|
||||||
|
[ width fill
|
||||||
|
, spacing 48
|
||||||
|
, htmlAttribute (Attr.id "page-content")
|
||||||
|
]
|
||||||
|
[ Components.Section.view
|
||||||
|
{ title = "does elm need a framework?"
|
||||||
|
, content = """
|
||||||
|
__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](#nuxt) that bring together the best tools in the ecosystem (and a set of shared best practices!)
|
||||||
|
|
||||||
|
welcome to __elm-spa__, a framework for Elm!
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
, Components.Section.view
|
||||||
|
{ title = "what does it do?"
|
||||||
|
, content = """
|
||||||
|
__elm-spa__ brings together the best of the Elm ecosystem in one place.
|
||||||
|
|
||||||
|
- [elm-ui](#elm-ui) – a package for creating layout and styles (without CSS!)
|
||||||
|
|
||||||
|
- [elm-live](#elm-live) – a dev server (without a webpack config!)
|
||||||
|
|
||||||
|
- [elm-spa](#elm-spa) – a package for composing pages (without all the typing!)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
, Components.Section.view
|
||||||
|
{ title = "new to programming?"
|
||||||
|
, content = """
|
||||||
|
perfect! if you're able to read through this paragraph, you're already _overqualified_.
|
||||||
|
|
||||||
|
#### new to elm?
|
||||||
|
|
||||||
|
welcome aboard! we've got a series of short tutorials to help you get started.
|
||||||
|
|
||||||
|
#### new to elm-spa?
|
||||||
|
|
||||||
|
let's dive in and check out all the neat stuff that's ready for your next Elm app!
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
, wrappedRow [ spacing 24 ]
|
||||||
|
[ link Styles.button { label = text "new to programming", url = "/guide/programming" }
|
||||||
|
, link Styles.button { label = text "new to elm", url = "/guide/elm" }
|
||||||
|
, link Styles.button { label = text "new to elm-spa", url = "/guide/elm-spa" }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
14
example/Ports.elm
Normal file
14
example/Ports.elm
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
port module Ports exposing (scrollTo)
|
||||||
|
|
||||||
|
import Json.Encode as Json
|
||||||
|
|
||||||
|
|
||||||
|
port outgoing : { action : String, data : Json.Value } -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
|
scrollTo : String -> Cmd msg
|
||||||
|
scrollTo id =
|
||||||
|
outgoing
|
||||||
|
{ action = "SCROLL_TO"
|
||||||
|
, data = Json.string id
|
||||||
|
}
|
5
example/README.md
Normal file
5
example/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# src
|
||||||
|
|
||||||
|
```elm
|
||||||
|
|
||||||
|
```
|
51
example/Utils/Page.elm
Normal file
51
example/Utils/Page.elm
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
module Utils.Page exposing
|
||||||
|
( Bundle
|
||||||
|
, Init
|
||||||
|
, Page
|
||||||
|
, Recipe
|
||||||
|
, Update
|
||||||
|
, layout
|
||||||
|
, recipe
|
||||||
|
)
|
||||||
|
|
||||||
|
import App.Page
|
||||||
|
import App.Types
|
||||||
|
import Element exposing (Element)
|
||||||
|
import Global
|
||||||
|
|
||||||
|
|
||||||
|
type alias Page flags model msg layoutModel layoutMsg appMsg =
|
||||||
|
App.Types.Page flags model msg (Element msg) layoutModel layoutMsg (Element layoutMsg) Global.Model Global.Msg appMsg (Element appMsg)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipe flags model msg layoutModel layoutMsg appMsg =
|
||||||
|
App.Types.Recipe flags model msg layoutModel layoutMsg (Element layoutMsg) Global.Model Global.Msg appMsg (Element appMsg)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Init model msg =
|
||||||
|
App.Types.Init model msg Global.Model Global.Msg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Update model msg =
|
||||||
|
App.Types.Update model msg Global.Model Global.Msg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Bundle msg appMsg =
|
||||||
|
App.Types.Bundle msg (Element msg) Global.Model Global.Msg appMsg (Element appMsg)
|
||||||
|
|
||||||
|
|
||||||
|
layout config =
|
||||||
|
App.Page.layout
|
||||||
|
{ map = Element.map
|
||||||
|
, view = config.view
|
||||||
|
, recipe = config.recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
recipe config =
|
||||||
|
App.Page.recipe
|
||||||
|
{ map = Element.map
|
||||||
|
, page = config.page
|
||||||
|
, toModel = config.toModel
|
||||||
|
, toMsg = config.toMsg
|
||||||
|
}
|
23
index.html
Normal file
23
index.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!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>elm-spa</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/atom-one-light.min.css" />
|
||||||
|
<link rel="stylesheet" href="/public/styles.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="/dist/elm.compiled.js"></script>
|
||||||
|
<script src="/public/ports.js"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', _ => {
|
||||||
|
window.ports.init(Elm.Main.init())
|
||||||
|
hljs.initHighlightingOnLoad()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
6
netlify.toml
Normal file
6
netlify.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# sends all routes to /index.html
|
||||||
|
# (so you can handle 404s there!)
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
1256
package-lock.json
generated
Normal file
1256
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "elm-spa-elm-ui",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "elm-live running in the browser!",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "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 example/Main.elm --output=dist/elm.compiled.js --optimize",
|
||||||
|
"elm:live": "elm-live example/Main.elm --start-page=index.html --pushstate --port=8080 -- --output=dist/elm.compiled.js --debug",
|
||||||
|
"elm:spa:build": "echo '🌳 elm-spa build .'",
|
||||||
|
"elm:spa:watch": "SHELL=/bin/bash chokidar './example' -c 'npm run elm:spa:build'"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar-cli": "2.1.0",
|
||||||
|
"elm": "0.19.1-3",
|
||||||
|
"elm-live": "4.0.1",
|
||||||
|
"elm-spa": "1.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"keywords": []
|
||||||
|
}
|
27
public/ports.js
Normal file
27
public/ports.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
const SCROLL_TO = (id) =>
|
||||||
|
document.getElementById(id) &&
|
||||||
|
window.scrollTo({
|
||||||
|
top: document.getElementById(id).offsetTop,
|
||||||
|
left: 0,
|
||||||
|
behavior: "smooth"
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
window.ports = {
|
||||||
|
init: (app) => {
|
||||||
|
app.ports.outgoing.subscribe(({ action, data }) => {
|
||||||
|
const actions = {
|
||||||
|
SCROLL_TO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions[action]) {
|
||||||
|
actions[action](data)
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`I didn't recognize action "${action}". Check out ./public/ports.js`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
37
public/styles.css
Normal file
37
public/styles.css
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
body {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.markdown > * {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
.markdown > *:not(:first-child) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
.markdown pre {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.3;
|
||||||
|
border: solid 1px #d0d0d0;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.markdown p {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.markdown h3,
|
||||||
|
.markdown h3 * {
|
||||||
|
font-size: 36px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.markdown h4,
|
||||||
|
.markdown h4 * {
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.markdown a {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgb(204, 75, 75);
|
||||||
|
}
|
8
sandbox.config.json
Normal file
8
sandbox.config.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"infiniteLoopProtection": true,
|
||||||
|
"hardReloadOnChange": false,
|
||||||
|
"view": "browser",
|
||||||
|
"container": {
|
||||||
|
"port": 8080
|
||||||
|
}
|
||||||
|
}
|
407
src/App.elm
Normal file
407
src/App.elm
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
module App exposing
|
||||||
|
( Program, create
|
||||||
|
, usingHtml
|
||||||
|
)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
|
||||||
|
## Let's build some single page applications!
|
||||||
|
|
||||||
|
`App.create` replaces [Browser.application](https://package.elm-lang.org/packages/elm/browser/latest/Browser#application)
|
||||||
|
as the entrypoint to your app.
|
||||||
|
|
||||||
|
import App
|
||||||
|
import Generated.Pages as Pages
|
||||||
|
import Generated.Route as Route
|
||||||
|
import Global
|
||||||
|
|
||||||
|
main =
|
||||||
|
App.create
|
||||||
|
{ ui = App.usingHtml
|
||||||
|
, routing =
|
||||||
|
{ routes = Route.routes
|
||||||
|
, toPath = Route.toPath
|
||||||
|
, notFound = Route.NotFound ()
|
||||||
|
}
|
||||||
|
, global =
|
||||||
|
{ init = Global.init
|
||||||
|
, update = Global.update
|
||||||
|
, subscriptions = Global.subscriptions
|
||||||
|
}
|
||||||
|
, page = Pages.page
|
||||||
|
}
|
||||||
|
|
||||||
|
@docs Program, create
|
||||||
|
|
||||||
|
|
||||||
|
# Supports more than elm/html
|
||||||
|
|
||||||
|
If you're a fan of [mdgriffith/elm-ui](https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/),
|
||||||
|
it's important to support using `Element msg` instead of `Html msg` for your pages and components.
|
||||||
|
|
||||||
|
Let `App.create` know about this by passing in your own `Options` like these:
|
||||||
|
|
||||||
|
import Element
|
||||||
|
-- other imports
|
||||||
|
|
||||||
|
App.create
|
||||||
|
{ ui =
|
||||||
|
{ toHtml = Element.layout []
|
||||||
|
, map = Element.map
|
||||||
|
}
|
||||||
|
, -- ... the rest of your app
|
||||||
|
}
|
||||||
|
|
||||||
|
@docs usingHtml
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import App.Route as Route exposing (Route)
|
||||||
|
import Browser
|
||||||
|
import Browser.Navigation as Nav
|
||||||
|
import Html exposing (Html)
|
||||||
|
import Internals.Page as Page
|
||||||
|
import Internals.Utils as Utils
|
||||||
|
import Url exposing (Url)
|
||||||
|
import Url.Parser as Parser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- APPLICATION
|
||||||
|
|
||||||
|
|
||||||
|
{-| An alias for `Platform.Program` to make annotations a little more clear.
|
||||||
|
-}
|
||||||
|
type alias Program flags globalModel globalMsg layoutModel layoutMsg =
|
||||||
|
Platform.Program flags (Model flags globalModel layoutModel) (Msg globalMsg layoutMsg)
|
||||||
|
|
||||||
|
|
||||||
|
{-| Pass this in when calling `App.create`
|
||||||
|
|
||||||
|
( It will work if your view returns the standard `Html msg` )
|
||||||
|
|
||||||
|
-}
|
||||||
|
usingHtml :
|
||||||
|
{ map :
|
||||||
|
(layoutMsg -> Msg globalMsg layoutMsg)
|
||||||
|
-> Html layoutMsg
|
||||||
|
-> Html (Msg globalMsg layoutMsg)
|
||||||
|
, toHtml : uiMsg -> uiMsg
|
||||||
|
}
|
||||||
|
usingHtml =
|
||||||
|
{ toHtml = identity
|
||||||
|
, map = Html.map
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Creates a new `Program` given some one-time configuration:
|
||||||
|
|
||||||
|
- `ui` - How do we convert the view to `Html msg`?
|
||||||
|
- `routing` - What are the app's routes?
|
||||||
|
- `global` - How do we manage shared state between pages?
|
||||||
|
- `page` - What pages do we have available?
|
||||||
|
|
||||||
|
-}
|
||||||
|
create :
|
||||||
|
{ ui :
|
||||||
|
{ toHtml : uiMsg -> Html (Msg globalMsg layoutMsg)
|
||||||
|
, map : (layoutMsg -> Msg globalMsg layoutMsg) -> uiLayoutMsg -> uiMsg
|
||||||
|
}
|
||||||
|
, routing :
|
||||||
|
{ routes : List (Route route route)
|
||||||
|
, toPath : route -> String
|
||||||
|
, notFound : route
|
||||||
|
}
|
||||||
|
, global :
|
||||||
|
{ init :
|
||||||
|
{ navigate : route -> Cmd (Msg globalMsg layoutMsg)
|
||||||
|
}
|
||||||
|
-> flags
|
||||||
|
-> ( globalModel, Cmd globalMsg, Cmd (Msg globalMsg layoutMsg) )
|
||||||
|
, update :
|
||||||
|
{ navigate : route -> Cmd (Msg globalMsg layoutMsg)
|
||||||
|
}
|
||||||
|
-> globalMsg
|
||||||
|
-> globalModel
|
||||||
|
-> ( globalModel, Cmd globalMsg, Cmd (Msg globalMsg layoutMsg) )
|
||||||
|
, subscriptions : globalModel -> Sub globalMsg
|
||||||
|
}
|
||||||
|
, page : Page.Page route layoutModel layoutMsg uiLayoutMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg (Msg globalMsg layoutMsg) uiMsg
|
||||||
|
}
|
||||||
|
-> Program flags globalModel globalMsg layoutModel layoutMsg
|
||||||
|
create config =
|
||||||
|
let
|
||||||
|
page =
|
||||||
|
Page.upgrade
|
||||||
|
{ toModel = identity
|
||||||
|
, toMsg = identity
|
||||||
|
, page = config.page
|
||||||
|
, map = always identity
|
||||||
|
}
|
||||||
|
in
|
||||||
|
Browser.application
|
||||||
|
{ init =
|
||||||
|
init
|
||||||
|
{ init =
|
||||||
|
{ global = config.global.init
|
||||||
|
, pages = page.init
|
||||||
|
}
|
||||||
|
, routing =
|
||||||
|
{ fromUrl = fromUrl config.routing
|
||||||
|
, toPath = config.routing.toPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, update =
|
||||||
|
update
|
||||||
|
{ routing =
|
||||||
|
{ fromUrl = fromUrl config.routing
|
||||||
|
, toPath = config.routing.toPath
|
||||||
|
, routes = config.routing.routes
|
||||||
|
}
|
||||||
|
, init = page.init
|
||||||
|
, update =
|
||||||
|
{ global = config.global.update
|
||||||
|
, pages = page.update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, subscriptions =
|
||||||
|
subscriptions
|
||||||
|
{ bundle = page.bundle
|
||||||
|
, map = config.ui.map
|
||||||
|
}
|
||||||
|
, view =
|
||||||
|
view
|
||||||
|
{ toHtml = config.ui.toHtml
|
||||||
|
, bundle = page.bundle
|
||||||
|
, map = config.ui.map
|
||||||
|
}
|
||||||
|
, onUrlChange = ChangedUrl
|
||||||
|
, onUrlRequest = ClickedLink
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- ROUTING
|
||||||
|
|
||||||
|
|
||||||
|
type alias Routes route a =
|
||||||
|
List (Route.Route route a)
|
||||||
|
|
||||||
|
|
||||||
|
fromUrl : { a | routes : Routes route route, notFound : route } -> Url -> route
|
||||||
|
fromUrl config =
|
||||||
|
Parser.parse (Parser.oneOf config.routes)
|
||||||
|
>> Maybe.withDefault config.notFound
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model flags globalModel model =
|
||||||
|
{ url : Url
|
||||||
|
, flags : flags
|
||||||
|
, key : Nav.Key
|
||||||
|
, global : globalModel
|
||||||
|
, page : model
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init :
|
||||||
|
{ routing :
|
||||||
|
{ fromUrl : Url -> route
|
||||||
|
, toPath : route -> String
|
||||||
|
}
|
||||||
|
, init :
|
||||||
|
{ global :
|
||||||
|
{ navigate : route -> Cmd (Msg globalMsg layoutMsg) }
|
||||||
|
-> flags
|
||||||
|
-> ( globalModel, Cmd globalMsg, Cmd (Msg globalMsg layoutMsg) )
|
||||||
|
, pages : route -> Page.Init layoutModel layoutMsg globalModel globalMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-> flags
|
||||||
|
-> Url
|
||||||
|
-> Nav.Key
|
||||||
|
-> ( Model flags globalModel layoutModel, Cmd (Msg globalMsg layoutMsg) )
|
||||||
|
init config flags url key =
|
||||||
|
url
|
||||||
|
|> config.routing.fromUrl
|
||||||
|
|> (\route ->
|
||||||
|
let
|
||||||
|
( globalModel, globalCmd, cmd ) =
|
||||||
|
config.init.global
|
||||||
|
{ navigate = navigate config.routing.toPath url
|
||||||
|
}
|
||||||
|
flags
|
||||||
|
|
||||||
|
( pageModel, pageCmd, pageGlobalCmd ) =
|
||||||
|
config.init.pages route { global = globalModel }
|
||||||
|
in
|
||||||
|
( { flags = flags
|
||||||
|
, url = url
|
||||||
|
, key = key
|
||||||
|
, global = globalModel
|
||||||
|
, page = pageModel
|
||||||
|
}
|
||||||
|
, Cmd.batch
|
||||||
|
[ Cmd.map Page pageCmd
|
||||||
|
, Cmd.map Global pageGlobalCmd
|
||||||
|
, Cmd.map Global globalCmd
|
||||||
|
, cmd
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
type Msg globalMsg msg
|
||||||
|
= ChangedUrl Url
|
||||||
|
| ClickedLink Browser.UrlRequest
|
||||||
|
| Global globalMsg
|
||||||
|
| Page msg
|
||||||
|
|
||||||
|
|
||||||
|
update :
|
||||||
|
{ routing :
|
||||||
|
{ fromUrl : Url -> route
|
||||||
|
, toPath : route -> String
|
||||||
|
, routes : Routes route a
|
||||||
|
}
|
||||||
|
, init : route -> Page.Init layoutModel layoutMsg globalModel globalMsg
|
||||||
|
, update :
|
||||||
|
{ global :
|
||||||
|
{ navigate : route -> Cmd (Msg globalMsg layoutMsg) }
|
||||||
|
-> globalMsg
|
||||||
|
-> globalModel
|
||||||
|
-> ( globalModel, Cmd globalMsg, Cmd (Msg globalMsg layoutMsg) )
|
||||||
|
, pages :
|
||||||
|
layoutMsg
|
||||||
|
-> layoutModel
|
||||||
|
-> Page.Update layoutModel layoutMsg globalModel globalMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-> Msg globalMsg layoutMsg
|
||||||
|
-> Model flags globalModel layoutModel
|
||||||
|
-> ( Model flags globalModel layoutModel, Cmd (Msg globalMsg layoutMsg) )
|
||||||
|
update config msg model =
|
||||||
|
case msg of
|
||||||
|
ClickedLink (Browser.Internal url) ->
|
||||||
|
if url == model.url then
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
else
|
||||||
|
( model
|
||||||
|
, Nav.pushUrl model.key (Url.toString url)
|
||||||
|
)
|
||||||
|
|
||||||
|
ClickedLink (Browser.External url) ->
|
||||||
|
( model
|
||||||
|
, Nav.load url
|
||||||
|
)
|
||||||
|
|
||||||
|
ChangedUrl url ->
|
||||||
|
url
|
||||||
|
|> config.routing.fromUrl
|
||||||
|
|> (\route -> config.init route { global = model.global })
|
||||||
|
|> (\( pageModel, pageCmd, globalCmd ) ->
|
||||||
|
( { model
|
||||||
|
| url = url
|
||||||
|
, page = pageModel
|
||||||
|
}
|
||||||
|
, Cmd.batch
|
||||||
|
[ Cmd.map Page pageCmd
|
||||||
|
, Cmd.map Global globalCmd
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Global globalMsg ->
|
||||||
|
config.update.global
|
||||||
|
{ navigate = navigate config.routing.toPath model.url
|
||||||
|
}
|
||||||
|
globalMsg
|
||||||
|
model.global
|
||||||
|
|> (\( global, globalCmd, cmd ) ->
|
||||||
|
( { model | global = global }
|
||||||
|
, Cmd.batch
|
||||||
|
[ Cmd.map Global globalCmd
|
||||||
|
, cmd
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Page pageMsg ->
|
||||||
|
config.update.pages pageMsg model.page { global = model.global }
|
||||||
|
|> (\( page, pageCmd, globalCmd ) ->
|
||||||
|
( { model | page = page }
|
||||||
|
, Cmd.batch
|
||||||
|
[ Cmd.map Page pageCmd
|
||||||
|
, Cmd.map Global globalCmd
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
navigate : (route -> String) -> Url -> route -> Cmd (Msg globalMsg layoutMsg)
|
||||||
|
navigate toPath url route =
|
||||||
|
Utils.send <|
|
||||||
|
ClickedLink (Browser.Internal { url | path = toPath route })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- SUBSCRIPTIONS
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions :
|
||||||
|
{ map : (layoutMsg -> Msg globalMsg layoutMsg) -> uiLayoutMsg -> uiMsg
|
||||||
|
, bundle :
|
||||||
|
layoutModel
|
||||||
|
-> Page.Bundle layoutMsg uiLayoutMsg globalModel globalMsg (Msg globalMsg layoutMsg) uiMsg
|
||||||
|
}
|
||||||
|
-> Model flags globalModel layoutModel
|
||||||
|
-> Sub (Msg globalMsg layoutMsg)
|
||||||
|
subscriptions config model =
|
||||||
|
(config.bundle
|
||||||
|
model.page
|
||||||
|
{ fromGlobalMsg = Global
|
||||||
|
, fromPageMsg = Page
|
||||||
|
, global = model.global
|
||||||
|
, map = config.map
|
||||||
|
}
|
||||||
|
).subscriptions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
{ map : (layoutMsg -> Msg globalMsg layoutMsg) -> uiLayoutMsg -> uiMsg
|
||||||
|
, toHtml : uiMsg -> Html (Msg globalMsg layoutMsg)
|
||||||
|
, bundle :
|
||||||
|
layoutModel
|
||||||
|
-> Page.Bundle layoutMsg uiLayoutMsg globalModel globalMsg (Msg globalMsg layoutMsg) uiMsg
|
||||||
|
}
|
||||||
|
-> Model flags globalModel layoutModel
|
||||||
|
-> Browser.Document (Msg globalMsg layoutMsg)
|
||||||
|
view config model =
|
||||||
|
let
|
||||||
|
bundle =
|
||||||
|
config.bundle
|
||||||
|
model.page
|
||||||
|
{ fromGlobalMsg = Global
|
||||||
|
, fromPageMsg = Page
|
||||||
|
, global = model.global
|
||||||
|
, map = config.map
|
||||||
|
}
|
||||||
|
in
|
||||||
|
{ title = bundle.title
|
||||||
|
, body =
|
||||||
|
[ config.toHtml bundle.view
|
||||||
|
]
|
||||||
|
}
|
471
src/App/Page.elm
Normal file
471
src/App/Page.elm
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
module App.Page exposing
|
||||||
|
( static
|
||||||
|
, sandbox
|
||||||
|
, element
|
||||||
|
, component, send
|
||||||
|
, layout
|
||||||
|
, recipe
|
||||||
|
, keep
|
||||||
|
)
|
||||||
|
|
||||||
|
{-| Each page can be as simple or complex as you need:
|
||||||
|
|
||||||
|
1. [Static](#static) - for rendering a simple view
|
||||||
|
|
||||||
|
2. [Sandbox](#sandbox) - for maintaining state, without any side-effects
|
||||||
|
|
||||||
|
3. [Element](#element) - for maintaining state, **with** side-effects
|
||||||
|
|
||||||
|
4. [Component](#component) - for a full-blown page, that can view and update global state
|
||||||
|
|
||||||
|
|
||||||
|
## Static
|
||||||
|
|
||||||
|
For rendering a simple view.
|
||||||
|
|
||||||
|
page =
|
||||||
|
Page.static
|
||||||
|
{ title = title
|
||||||
|
, view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
@docs static
|
||||||
|
|
||||||
|
|
||||||
|
## Sandbox
|
||||||
|
|
||||||
|
For maintaining state, without any side-effects.
|
||||||
|
|
||||||
|
page =
|
||||||
|
Page.sandbox
|
||||||
|
{ title = title
|
||||||
|
, init = init
|
||||||
|
, update = update
|
||||||
|
, view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
@docs sandbox
|
||||||
|
|
||||||
|
|
||||||
|
## Element
|
||||||
|
|
||||||
|
For maintaining state, **with** side-effects.
|
||||||
|
|
||||||
|
page =
|
||||||
|
Page.element
|
||||||
|
{ title = title
|
||||||
|
, init = init
|
||||||
|
, update = update
|
||||||
|
, view = view
|
||||||
|
, subscriptions = subscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
@docs element
|
||||||
|
|
||||||
|
|
||||||
|
## Component
|
||||||
|
|
||||||
|
For a full-blown page, that can view and update global state.
|
||||||
|
|
||||||
|
page =
|
||||||
|
Page.component
|
||||||
|
{ title = title
|
||||||
|
, init = init
|
||||||
|
, update = update
|
||||||
|
, view = view
|
||||||
|
, subscriptions = subscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
@docs component, send
|
||||||
|
|
||||||
|
|
||||||
|
# Composing pages together
|
||||||
|
|
||||||
|
The rest of this module contains types and functions that
|
||||||
|
can be generated with the [cli companion tool](https://github.com/ryannhg/elm-spa/tree/master/cli)
|
||||||
|
|
||||||
|
If you're typing this stuff manually, you might need to know what
|
||||||
|
these are for!
|
||||||
|
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
A page that is comprimised of smaller pages, that is
|
||||||
|
able to share a common layout (maybe a something like a sidebar!)
|
||||||
|
|
||||||
|
page =
|
||||||
|
Page.layout
|
||||||
|
{ map = Html.map
|
||||||
|
, layout = Layout.view
|
||||||
|
, pages =
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@docs layout
|
||||||
|
|
||||||
|
|
||||||
|
## Recipe
|
||||||
|
|
||||||
|
Implementing the `init`, `update` and `bundle` functions is much easier
|
||||||
|
when you turn a `Page` type into `Recipe`.
|
||||||
|
|
||||||
|
A `Recipe` contains a record waiting for page specific data.
|
||||||
|
|
||||||
|
- `init`: just needs a `route`
|
||||||
|
|
||||||
|
- `upgrade` : just needs a `msg` and `model`
|
||||||
|
|
||||||
|
- `bundle` (`view`/`subscriptions`) : just needs a `model`
|
||||||
|
|
||||||
|
|
||||||
|
### What's a "bundle"?
|
||||||
|
|
||||||
|
We can "bundle" the `view` and `subscriptions` functions together,
|
||||||
|
because they both only depend on the current `model`. That's why this
|
||||||
|
API exposes `bundle` instead of making you type this:
|
||||||
|
|
||||||
|
-- BEFORE
|
||||||
|
view model =
|
||||||
|
case model_ of
|
||||||
|
FooModel model ->
|
||||||
|
foo.view model
|
||||||
|
|
||||||
|
BarModel model ->
|
||||||
|
bar.view model
|
||||||
|
|
||||||
|
BazModel model ->
|
||||||
|
baz.view model
|
||||||
|
|
||||||
|
subscriptions model =
|
||||||
|
case model_ of
|
||||||
|
FooModel model ->
|
||||||
|
foo.subscriptions model
|
||||||
|
|
||||||
|
BarModel model ->
|
||||||
|
bar.subscriptions model
|
||||||
|
|
||||||
|
BazModel model ->
|
||||||
|
baz.subscriptions model
|
||||||
|
|
||||||
|
-- AFTER
|
||||||
|
bundle model =
|
||||||
|
case model_ of
|
||||||
|
FooModel model ->
|
||||||
|
foo.bundle model
|
||||||
|
|
||||||
|
BarModel model ->
|
||||||
|
bar.bundle model
|
||||||
|
|
||||||
|
BazModel model ->
|
||||||
|
baz.bundle model
|
||||||
|
|
||||||
|
(Woohoo, less case expressions to type out!)
|
||||||
|
|
||||||
|
@docs recipe
|
||||||
|
|
||||||
|
|
||||||
|
## Update helper
|
||||||
|
|
||||||
|
@docs keep
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Internals.Page exposing (..)
|
||||||
|
import Internals.Utils as Utils
|
||||||
|
|
||||||
|
|
||||||
|
type alias Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg =
|
||||||
|
Internals.Page.Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
|
||||||
|
|
||||||
|
{-| Turns a page and some upgrade information into a recipe,
|
||||||
|
for use in a layout's `init`, `update`, and `bundle` functions!
|
||||||
|
|
||||||
|
Page.recipe Homepage.page
|
||||||
|
{ toModel = HomepageModel
|
||||||
|
, toMsg = HomepageMsg
|
||||||
|
, map = Element.map -- ( if using elm-ui )
|
||||||
|
}
|
||||||
|
|
||||||
|
-}
|
||||||
|
recipe :
|
||||||
|
{ page : Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
, toModel : pageModel -> layoutModel
|
||||||
|
, toMsg : pageMsg -> layoutMsg
|
||||||
|
, map : (pageMsg -> layoutMsg) -> uiPageMsg -> uiLayoutMsg
|
||||||
|
}
|
||||||
|
-> Recipe pageRoute pageModel pageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
recipe =
|
||||||
|
Internals.Page.upgrade
|
||||||
|
|
||||||
|
|
||||||
|
{-| In the event that our `case` expression in `update` receives a `msg` that doesn't
|
||||||
|
match it's `model`, we use this function to keep the model as-is.
|
||||||
|
|
||||||
|
update msg_ model_ =
|
||||||
|
case ( msg_, model_ ) of
|
||||||
|
( FooMsg msg, FooModel model ) ->
|
||||||
|
foo.update msg model
|
||||||
|
|
||||||
|
( BarMsg msg, BarModel model ) ->
|
||||||
|
bar.update msg model
|
||||||
|
|
||||||
|
( BazMsg msg, BazModel model ) ->
|
||||||
|
baz.update msg model
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Page.keep model_
|
||||||
|
|
||||||
|
-}
|
||||||
|
keep :
|
||||||
|
layoutModel
|
||||||
|
-> Update layoutModel layoutMsg globalModel globalMsg
|
||||||
|
keep model =
|
||||||
|
always ( model, Cmd.none, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- STATIC
|
||||||
|
|
||||||
|
|
||||||
|
{-| Create an `static` page from a record. [Here's an example](https://github.com/ryannhg/elm-spa/examples/html/src/Pages/Index.elm)
|
||||||
|
-}
|
||||||
|
static :
|
||||||
|
{ title : { global : globalModel } -> String
|
||||||
|
, view : globalModel -> uiPageMsg
|
||||||
|
}
|
||||||
|
-> Page pageRoute () Never uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
static page =
|
||||||
|
Page
|
||||||
|
(\{ toModel, toMsg, map } ->
|
||||||
|
{ init = \_ _ -> ( toModel (), Cmd.none, Cmd.none )
|
||||||
|
, update = \_ model _ -> ( toModel model, Cmd.none, Cmd.none )
|
||||||
|
, bundle =
|
||||||
|
\_ context ->
|
||||||
|
{ title = page.title { global = context.global }
|
||||||
|
, view = page.view context.global |> map toMsg |> context.map context.fromPageMsg
|
||||||
|
, subscriptions = Sub.none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- SANDBOX
|
||||||
|
|
||||||
|
|
||||||
|
{-| Create an `sandbox` page from a record. [Here's an example](https://github.com/ryannhg/elm-spa/examples/html/src/Pages/Counter.elm)
|
||||||
|
-}
|
||||||
|
sandbox :
|
||||||
|
{ title : { global : globalModel, model : pageModel } -> String
|
||||||
|
, init : globalModel -> pageRoute -> pageModel
|
||||||
|
, update : globalModel -> pageMsg -> pageModel -> pageModel
|
||||||
|
, view : globalModel -> pageModel -> uiPageMsg
|
||||||
|
}
|
||||||
|
-> Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
sandbox page =
|
||||||
|
Page
|
||||||
|
(\{ toModel, toMsg, map } ->
|
||||||
|
{ init =
|
||||||
|
\pageRoute context ->
|
||||||
|
( toModel (page.init context.global pageRoute)
|
||||||
|
, Cmd.none
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
, update =
|
||||||
|
\msg model context ->
|
||||||
|
( page.update context.global msg model |> toModel
|
||||||
|
, Cmd.none
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
, bundle =
|
||||||
|
\model context ->
|
||||||
|
{ title = page.title { global = context.global, model = model }
|
||||||
|
, view = page.view context.global model |> map toMsg |> context.map context.fromPageMsg
|
||||||
|
, subscriptions = Sub.none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- ELEMENT
|
||||||
|
|
||||||
|
|
||||||
|
{-| Create an `element` page from a record. [Here's an example](https://github.com/ryannhg/elm-spa/examples/html/src/Pages/Random.elm)
|
||||||
|
-}
|
||||||
|
element :
|
||||||
|
{ title : { global : globalModel, model : pageModel } -> String
|
||||||
|
, init : globalModel -> pageRoute -> ( pageModel, Cmd pageMsg )
|
||||||
|
, update : globalModel -> pageMsg -> pageModel -> ( pageModel, Cmd pageMsg )
|
||||||
|
, view : globalModel -> pageModel -> uiPageMsg
|
||||||
|
, subscriptions : globalModel -> pageModel -> Sub pageMsg
|
||||||
|
}
|
||||||
|
-> Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
element page =
|
||||||
|
Page
|
||||||
|
(\{ toModel, toMsg, map } ->
|
||||||
|
{ init =
|
||||||
|
\pageRoute context ->
|
||||||
|
page.init context.global pageRoute
|
||||||
|
|> tuple toModel toMsg
|
||||||
|
, update =
|
||||||
|
\msg model context ->
|
||||||
|
page.update context.global msg model
|
||||||
|
|> tuple toModel toMsg
|
||||||
|
, bundle =
|
||||||
|
\model context ->
|
||||||
|
{ title = page.title { global = context.global, model = model }
|
||||||
|
, view = page.view context.global model |> map toMsg |> context.map context.fromPageMsg
|
||||||
|
, subscriptions = page.subscriptions context.global model |> Sub.map (toMsg >> context.fromPageMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- COMPONENT
|
||||||
|
|
||||||
|
|
||||||
|
{-| Create an `component` page from a record. [Here's an example](https://github.com/ryannhg/elm-spa/examples/html/src/Pages/SignIn.elm)
|
||||||
|
-}
|
||||||
|
component :
|
||||||
|
{ title : { global : globalModel, model : pageModel } -> String
|
||||||
|
, init : globalModel -> pageRoute -> ( pageModel, Cmd pageMsg, Cmd globalMsg )
|
||||||
|
, update : globalModel -> pageMsg -> pageModel -> ( pageModel, Cmd pageMsg, Cmd globalMsg )
|
||||||
|
, view : globalModel -> pageModel -> uiPageMsg
|
||||||
|
, subscriptions : globalModel -> pageModel -> Sub pageMsg
|
||||||
|
}
|
||||||
|
-> Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
component page =
|
||||||
|
Page
|
||||||
|
(\{ toModel, toMsg, map } ->
|
||||||
|
{ init =
|
||||||
|
\pageRoute context ->
|
||||||
|
page.init context.global pageRoute
|
||||||
|
|> truple toModel toMsg
|
||||||
|
, update =
|
||||||
|
\msg model context ->
|
||||||
|
page.update context.global msg model
|
||||||
|
|> truple toModel toMsg
|
||||||
|
, bundle =
|
||||||
|
\model context ->
|
||||||
|
{ title = page.title { global = context.global, model = model }
|
||||||
|
, view = page.view context.global model |> map toMsg |> context.map context.fromPageMsg
|
||||||
|
, subscriptions = page.subscriptions context.global model |> Sub.map (toMsg >> context.fromPageMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
{-| Useful for sending `Global.Msg` from a component.
|
||||||
|
|
||||||
|
update : Global.Model -> Msg -> Model -> ( Model, Cmd Msg, Cmd Global.Msg )
|
||||||
|
update global msg model =
|
||||||
|
case msg of
|
||||||
|
SignIn ->
|
||||||
|
( model
|
||||||
|
, Cmd.none
|
||||||
|
, Page.send Global.SignIn
|
||||||
|
)
|
||||||
|
|
||||||
|
-}
|
||||||
|
send : msg -> Cmd msg
|
||||||
|
send =
|
||||||
|
Utils.send
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- LAYOUT
|
||||||
|
|
||||||
|
|
||||||
|
{-| These are used by top-level files like `src/Generated/Pages.elm`
|
||||||
|
to compose together pages and layouts.
|
||||||
|
|
||||||
|
We'll get a better understanding of `init`, `update`, and `bundle` below!
|
||||||
|
|
||||||
|
Page.layout
|
||||||
|
{ map = Html.map
|
||||||
|
, layout = Layout.view
|
||||||
|
, pages =
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-}
|
||||||
|
layout :
|
||||||
|
{ map : (pageMsg -> msg) -> uiPageMsg -> uiMsg
|
||||||
|
, view :
|
||||||
|
{ page : uiMsg
|
||||||
|
, global : globalModel
|
||||||
|
, toMsg : globalMsg -> msg
|
||||||
|
}
|
||||||
|
-> uiMsg
|
||||||
|
, recipe : Recipe pageRoute pageModel pageMsg pageModel pageMsg uiPageMsg globalModel globalMsg msg uiMsg
|
||||||
|
}
|
||||||
|
-> Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
layout options =
|
||||||
|
Page
|
||||||
|
(\{ toModel, toMsg } ->
|
||||||
|
{ init =
|
||||||
|
\pageRoute global ->
|
||||||
|
options.recipe.init pageRoute global
|
||||||
|
|> truple toModel toMsg
|
||||||
|
, update =
|
||||||
|
\msg model global ->
|
||||||
|
options.recipe.update msg model global
|
||||||
|
|> truple toModel toMsg
|
||||||
|
, bundle =
|
||||||
|
\model context ->
|
||||||
|
let
|
||||||
|
bundle : { title : String, view : uiMsg, subscriptions : Sub msg }
|
||||||
|
bundle =
|
||||||
|
options.recipe.bundle
|
||||||
|
model
|
||||||
|
{ fromGlobalMsg = context.fromGlobalMsg
|
||||||
|
, fromPageMsg = toMsg >> context.fromPageMsg
|
||||||
|
, global = context.global
|
||||||
|
, map = options.map
|
||||||
|
}
|
||||||
|
in
|
||||||
|
{ title = bundle.title
|
||||||
|
, view =
|
||||||
|
options.view
|
||||||
|
{ page = bundle.view
|
||||||
|
, global = context.global
|
||||||
|
, toMsg = context.fromGlobalMsg
|
||||||
|
}
|
||||||
|
, subscriptions = bundle.subscriptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UTILS
|
||||||
|
|
||||||
|
|
||||||
|
tuple :
|
||||||
|
(model -> bigModel)
|
||||||
|
-> (msg -> bigMsg)
|
||||||
|
-> ( model, Cmd msg )
|
||||||
|
-> ( bigModel, Cmd bigMsg, Cmd a )
|
||||||
|
tuple toModel toMsg ( model, cmd ) =
|
||||||
|
( toModel model
|
||||||
|
, Cmd.map toMsg cmd
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
truple :
|
||||||
|
(model -> bigModel)
|
||||||
|
-> (msg -> bigMsg)
|
||||||
|
-> ( model, Cmd msg, Cmd a )
|
||||||
|
-> ( bigModel, Cmd bigMsg, Cmd a )
|
||||||
|
truple toModel toMsg ( a, b, c ) =
|
||||||
|
( toModel a, Cmd.map toMsg b, c )
|
100
src/App/Route.elm
Normal file
100
src/App/Route.elm
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
module App.Route exposing
|
||||||
|
( Route
|
||||||
|
, top
|
||||||
|
, path
|
||||||
|
, folder
|
||||||
|
, dynamic, dynamicFolder
|
||||||
|
)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
|
||||||
|
# Routing
|
||||||
|
|
||||||
|
The [cli tool](https://github.com/ryannhg/elm-spa/tree/master/cli) is able to generate routes based on the folder
|
||||||
|
structure and files in the `src/Pages` folder.
|
||||||
|
|
||||||
|
If you're choosing to type out the routes manually, these are just
|
||||||
|
convenience functions for creating `Url.Parser` types for your application.
|
||||||
|
|
||||||
|
@docs Route
|
||||||
|
|
||||||
|
@docs top
|
||||||
|
|
||||||
|
@docs path
|
||||||
|
|
||||||
|
@docs slug
|
||||||
|
|
||||||
|
@docs folder
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Url exposing (Url)
|
||||||
|
import Url.Parser as Parser exposing ((</>), Parser)
|
||||||
|
|
||||||
|
|
||||||
|
{-| Literally just a Url.Parser under the hood.
|
||||||
|
-}
|
||||||
|
type alias Route route a =
|
||||||
|
Parser (route -> a) a
|
||||||
|
|
||||||
|
|
||||||
|
{-| A route for a path like. These are generated by other file names.
|
||||||
|
|
||||||
|
Route.path "about-us" AboutUs
|
||||||
|
|
||||||
|
-}
|
||||||
|
path : String -> (() -> route) -> Route route a
|
||||||
|
path path_ toRoute =
|
||||||
|
Parser.map toRoute (Parser.s path_ |> Parser.map ())
|
||||||
|
|
||||||
|
|
||||||
|
{-| A top level route.
|
||||||
|
Usually generated by an `Top.elm` file.
|
||||||
|
|
||||||
|
Route.top Top
|
||||||
|
|
||||||
|
-}
|
||||||
|
top : (() -> route) -> Route route a
|
||||||
|
top toRoute =
|
||||||
|
Parser.map toRoute (Parser.top |> Parser.map ())
|
||||||
|
|
||||||
|
|
||||||
|
{-| A dynamic route, that passes the `String` value.
|
||||||
|
Usually generated by an `Dynamic.elm` file.
|
||||||
|
|
||||||
|
Route.dynamic Dynamic
|
||||||
|
|
||||||
|
-}
|
||||||
|
dynamic : (String -> route) -> Route route a
|
||||||
|
dynamic toRoute =
|
||||||
|
Parser.map toRoute Parser.string
|
||||||
|
|
||||||
|
|
||||||
|
{-| A route for nested routes, generated by a folder.
|
||||||
|
|
||||||
|
Route.folder "settings" Settings_ Generated.Settings.Route.routes
|
||||||
|
|
||||||
|
-}
|
||||||
|
folder :
|
||||||
|
String
|
||||||
|
-> (subRoute -> route)
|
||||||
|
-> List (Route subRoute route)
|
||||||
|
-> Route route a
|
||||||
|
folder path_ toRoute children =
|
||||||
|
Parser.map toRoute
|
||||||
|
(Parser.s path_ </> Parser.oneOf children)
|
||||||
|
|
||||||
|
|
||||||
|
{-| A route for nested dynamic routes, generated by a folder.
|
||||||
|
|
||||||
|
Route.dynamicFolder Dynamic_ Dynamic_.routes
|
||||||
|
|
||||||
|
-}
|
||||||
|
dynamicFolder :
|
||||||
|
(String -> subRoute -> route)
|
||||||
|
-> List (Route subRoute route)
|
||||||
|
-> Route route a
|
||||||
|
dynamicFolder toRoute children =
|
||||||
|
Parser.map toRoute
|
||||||
|
(Parser.string </> Parser.oneOf children)
|
29
src/App/Types.elm
Normal file
29
src/App/Types.elm
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module App.Types exposing
|
||||||
|
( Bundle
|
||||||
|
, Init
|
||||||
|
, Page
|
||||||
|
, Recipe
|
||||||
|
, Update
|
||||||
|
)
|
||||||
|
|
||||||
|
import Internals.Page as Page
|
||||||
|
|
||||||
|
|
||||||
|
type alias Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg =
|
||||||
|
Page.Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Recipe pageRoute pageModel pageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg =
|
||||||
|
Page.Recipe pageRoute pageModel pageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Init layoutModel layoutMsg globalModel globalMsg =
|
||||||
|
Page.Init layoutModel layoutMsg globalModel globalMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Update layoutModel layoutMsg globalModel globalMsg =
|
||||||
|
Page.Update layoutModel layoutMsg globalModel globalMsg
|
||||||
|
|
||||||
|
|
||||||
|
type alias Bundle layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg =
|
||||||
|
Page.Bundle layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
80
src/Internals/Page.elm
Normal file
80
src/Internals/Page.elm
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
module Internals.Page exposing
|
||||||
|
( Bundle
|
||||||
|
, Init
|
||||||
|
, Page(..)
|
||||||
|
, Recipe
|
||||||
|
, Update
|
||||||
|
, upgrade
|
||||||
|
)
|
||||||
|
|
||||||
|
{-| Page docs
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
type Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
= Page (Page_ pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Page_ pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg =
|
||||||
|
{ toModel : pageModel -> layoutModel
|
||||||
|
, toMsg : pageMsg -> layoutMsg
|
||||||
|
, map : (pageMsg -> layoutMsg) -> uiPageMsg -> uiLayoutMsg
|
||||||
|
}
|
||||||
|
-> Recipe pageRoute pageModel pageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
|
||||||
|
|
||||||
|
{-| Recipe docs
|
||||||
|
-}
|
||||||
|
type alias Recipe pageRoute pageModel pageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg =
|
||||||
|
{ init : pageRoute -> Init layoutModel layoutMsg globalModel globalMsg
|
||||||
|
, update : pageMsg -> pageModel -> Update layoutModel layoutMsg globalModel globalMsg
|
||||||
|
, bundle : pageModel -> Bundle layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
upgrade :
|
||||||
|
{ page : Page pageRoute pageModel pageMsg uiPageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
, toModel : pageModel -> layoutModel
|
||||||
|
, toMsg : pageMsg -> layoutMsg
|
||||||
|
, map : (pageMsg -> layoutMsg) -> uiPageMsg -> uiLayoutMsg
|
||||||
|
}
|
||||||
|
-> Recipe pageRoute pageModel pageMsg layoutModel layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg
|
||||||
|
upgrade config =
|
||||||
|
let
|
||||||
|
(Page page) =
|
||||||
|
config.page
|
||||||
|
in
|
||||||
|
page
|
||||||
|
{ toModel = config.toModel
|
||||||
|
, toMsg = config.toMsg
|
||||||
|
, map = config.map
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Init docs
|
||||||
|
-}
|
||||||
|
type alias Init layoutModel layoutMsg globalModel globalMsg =
|
||||||
|
{ global : globalModel }
|
||||||
|
-> ( layoutModel, Cmd layoutMsg, Cmd globalMsg )
|
||||||
|
|
||||||
|
|
||||||
|
{-| Update docs
|
||||||
|
-}
|
||||||
|
type alias Update layoutModel layoutMsg globalModel globalMsg =
|
||||||
|
{ global : globalModel }
|
||||||
|
-> ( layoutModel, Cmd layoutMsg, Cmd globalMsg )
|
||||||
|
|
||||||
|
|
||||||
|
{-| Bundle docs
|
||||||
|
-}
|
||||||
|
type alias Bundle layoutMsg uiLayoutMsg globalModel globalMsg msg uiMsg =
|
||||||
|
{ global : globalModel
|
||||||
|
, fromGlobalMsg : globalMsg -> msg
|
||||||
|
, fromPageMsg : layoutMsg -> msg
|
||||||
|
, map : (layoutMsg -> msg) -> uiLayoutMsg -> uiMsg
|
||||||
|
}
|
||||||
|
->
|
||||||
|
{ title : String
|
||||||
|
, view : uiMsg
|
||||||
|
, subscriptions : Sub msg
|
||||||
|
}
|
18
src/Internals/Utils.elm
Normal file
18
src/Internals/Utils.elm
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Internals.Utils exposing
|
||||||
|
( delay
|
||||||
|
, send
|
||||||
|
)
|
||||||
|
|
||||||
|
import Process
|
||||||
|
import Task
|
||||||
|
|
||||||
|
|
||||||
|
delay : Int -> msg -> Cmd msg
|
||||||
|
delay ms msg =
|
||||||
|
Process.sleep (toFloat ms)
|
||||||
|
|> Task.perform (\_ -> msg)
|
||||||
|
|
||||||
|
|
||||||
|
send : msg -> Cmd msg
|
||||||
|
send =
|
||||||
|
Task.succeed >> Task.perform identity
|
2
src/README.md
Normal file
2
src/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# ryannhg/elm-app
|
||||||
|
> the elm package that makes building `elm-spa` super easy!
|
Loading…
Reference in New Issue
Block a user