mirror of
https://github.com/ryannhg/elm-spa.git
synced 2024-11-22 17:52:33 +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