commit 3fccf3dbe12aba2e5bb2e372107780fc9b51f40b Author: Ryan Haskell-Glatz Date: Tue Nov 5 17:37:10 2019 -0600 set up a project for api exploration diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..088b52a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +dist +elm-stuff/0.19.1 +node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bda966 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# elm-spa +> for building single page apps + + +## local development + +``` +npm install && npm run dev +``` diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..701fa31 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,5 @@ +# cli +> the thing that types the stuff + +__Note:__ I will not implement this until I understand +the manual workflow first. \ No newline at end of file diff --git a/elm-stuff/.elm-spa/Generated/Docs/Flags.elm b/elm-stuff/.elm-spa/Generated/Docs/Flags.elm new file mode 100644 index 0000000..e1ff002 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Docs/Flags.elm @@ -0,0 +1,12 @@ +module Generated.Docs.Flags exposing + ( Dynamic + , Static + ) + + +type alias Static = + () + + +type alias Dynamic = + String diff --git a/elm-stuff/.elm-spa/Generated/Docs/Pages.elm b/elm-stuff/.elm-spa/Generated/Docs/Pages.elm new file mode 100644 index 0000000..d81cc52 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Docs/Pages.elm @@ -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 diff --git a/elm-stuff/.elm-spa/Generated/Docs/Routes.elm b/elm-stuff/.elm-spa/Generated/Docs/Routes.elm new file mode 100644 index 0000000..c1b9dd5 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Docs/Routes.elm @@ -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 diff --git a/elm-stuff/.elm-spa/Generated/Flags.elm b/elm-stuff/.elm-spa/Generated/Flags.elm new file mode 100644 index 0000000..80f794c --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Flags.elm @@ -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 = + () diff --git a/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Flags.elm b/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Flags.elm new file mode 100644 index 0000000..fd106a7 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Flags.elm @@ -0,0 +1,5 @@ +module Generated.Guide.Dynamic.Flags exposing (Intro) + + +type alias Intro = + () diff --git a/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Pages.elm b/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Pages.elm new file mode 100644 index 0000000..37b5798 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Pages.elm @@ -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 diff --git a/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Routes.elm b/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Routes.elm new file mode 100644 index 0000000..3ee82e4 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Guide/Dynamic/Routes.elm @@ -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" diff --git a/elm-stuff/.elm-spa/Generated/Guide/Flags.elm b/elm-stuff/.elm-spa/Generated/Guide/Flags.elm new file mode 100644 index 0000000..b4876e9 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Guide/Flags.elm @@ -0,0 +1,17 @@ +module Generated.Guide.Flags exposing + ( Elm + , ElmSpa + , Programming + ) + + +type alias Elm = + () + + +type alias ElmSpa = + () + + +type alias Programming = + () diff --git a/elm-stuff/.elm-spa/Generated/Guide/Pages.elm b/elm-stuff/.elm-spa/Generated/Guide/Pages.elm new file mode 100644 index 0000000..a22c044 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Guide/Pages.elm @@ -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 diff --git a/elm-stuff/.elm-spa/Generated/Guide/Routes.elm b/elm-stuff/.elm-spa/Generated/Guide/Routes.elm new file mode 100644 index 0000000..7b9b1d6 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Guide/Routes.elm @@ -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_ diff --git a/elm-stuff/.elm-spa/Generated/Pages.elm b/elm-stuff/.elm-spa/Generated/Pages.elm new file mode 100644 index 0000000..678320f --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Pages.elm @@ -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 diff --git a/elm-stuff/.elm-spa/Generated/Routes.elm b/elm-stuff/.elm-spa/Generated/Routes.elm new file mode 100644 index 0000000..d6e63d8 --- /dev/null +++ b/elm-stuff/.elm-spa/Generated/Routes.elm @@ -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_ diff --git a/elm-stuff/.elm-spa/README.md b/elm-stuff/.elm-spa/README.md new file mode 100644 index 0000000..0253509 --- /dev/null +++ b/elm-stuff/.elm-spa/README.md @@ -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. diff --git a/elm.json b/elm.json new file mode 100644 index 0000000..cdddb6f --- /dev/null +++ b/elm.json @@ -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": {} + } +} diff --git a/example/Components/Button.elm b/example/Components/Button.elm new file mode 100644 index 0000000..e3a4eee --- /dev/null +++ b/example/Components/Button.elm @@ -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 diff --git a/example/Components/Hero.elm b/example/Components/Hero.elm new file mode 100644 index 0000000..8197d33 --- /dev/null +++ b/example/Components/Hero.elm @@ -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 + ] diff --git a/example/Components/Section.elm b/example/Components/Section.elm new file mode 100644 index 0000000..3559430 --- /dev/null +++ b/example/Components/Section.elm @@ -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) + ] diff --git a/example/Components/Styles.elm b/example/Components/Styles.elm new file mode 100644 index 0000000..56458e8 --- /dev/null +++ b/example/Components/Styles.elm @@ -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) diff --git a/example/Global.elm b/example/Global.elm new file mode 100644 index 0000000..619a815 --- /dev/null +++ b/example/Global.elm @@ -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 diff --git a/example/Layout.elm b/example/Layout.elm new file mode 100644 index 0000000..4f555f1 --- /dev/null +++ b/example/Layout.elm @@ -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") + ) diff --git a/example/Layouts/Docs.elm b/example/Layouts/Docs.elm new file mode 100644 index 0000000..dbadd78 --- /dev/null +++ b/example/Layouts/Docs.elm @@ -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 diff --git a/example/Layouts/Guide.elm b/example/Layouts/Guide.elm new file mode 100644 index 0000000..bba52fb --- /dev/null +++ b/example/Layouts/Guide.elm @@ -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 diff --git a/example/Layouts/Guide/Dynamic.elm b/example/Layouts/Guide/Dynamic.elm new file mode 100644 index 0000000..ef5fef8 --- /dev/null +++ b/example/Layouts/Guide/Dynamic.elm @@ -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 diff --git a/example/Main.elm b/example/Main.elm new file mode 100644 index 0000000..9d896a7 --- /dev/null +++ b/example/Main.elm @@ -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 + } diff --git a/example/Pages/Docs.elm b/example/Pages/Docs.elm new file mode 100644 index 0000000..511ff0e --- /dev/null +++ b/example/Pages/Docs.elm @@ -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" } + ] + } + ] diff --git a/example/Pages/Docs/Dynamic.elm b/example/Pages/Docs/Dynamic.elm new file mode 100644 index 0000000..719f21c --- /dev/null +++ b/example/Pages/Docs/Dynamic.elm @@ -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 ] + ] diff --git a/example/Pages/Docs/Static.elm b/example/Pages/Docs/Static.elm new file mode 100644 index 0000000..8f15585 --- /dev/null +++ b/example/Pages/Docs/Static.elm @@ -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 = [] + } + ] diff --git a/example/Pages/Guide.elm b/example/Pages/Guide.elm new file mode 100644 index 0000000..de419f0 --- /dev/null +++ b/example/Pages/Guide.elm @@ -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" } + ] + } + ] diff --git a/example/Pages/Guide/Dynamic/Intro.elm b/example/Pages/Guide/Dynamic/Intro.elm new file mode 100644 index 0000000..677fb97 --- /dev/null +++ b/example/Pages/Guide/Dynamic/Intro.elm @@ -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 = [] + } + ] diff --git a/example/Pages/Guide/Elm.elm b/example/Pages/Guide/Elm.elm new file mode 100644 index 0000000..e2c567e --- /dev/null +++ b/example/Pages/Guide/Elm.elm @@ -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 = [] + } + ] diff --git a/example/Pages/Guide/ElmSpa.elm b/example/Pages/Guide/ElmSpa.elm new file mode 100644 index 0000000..26b4a9a --- /dev/null +++ b/example/Pages/Guide/ElmSpa.elm @@ -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 = [] + } + ] diff --git a/example/Pages/Guide/Programming.elm b/example/Pages/Guide/Programming.elm new file mode 100644 index 0000000..d528c0b --- /dev/null +++ b/example/Pages/Guide/Programming.elm @@ -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 = [] + } + ] diff --git a/example/Pages/NotFound.elm b/example/Pages/NotFound.elm new file mode 100644 index 0000000..01b6d3d --- /dev/null +++ b/example/Pages/NotFound.elm @@ -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 "/" } + ] + } + ] diff --git a/example/Pages/SignIn.elm b/example/Pages/SignIn.elm new file mode 100644 index 0000000..4c19f04 --- /dev/null +++ b/example/Pages/SignIn.elm @@ -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 + } diff --git a/example/Pages/Top.elm b/example/Pages/Top.elm new file mode 100644 index 0000000..96ad9c8 --- /dev/null +++ b/example/Pages/Top.elm @@ -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" } + ] + ] + ] diff --git a/example/Ports.elm b/example/Ports.elm new file mode 100644 index 0000000..0b41dad --- /dev/null +++ b/example/Ports.elm @@ -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 + } diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..8b5893d --- /dev/null +++ b/example/README.md @@ -0,0 +1,5 @@ +# src + +```elm + +``` diff --git a/example/Utils/Page.elm b/example/Utils/Page.elm new file mode 100644 index 0000000..8cf7e38 --- /dev/null +++ b/example/Utils/Page.elm @@ -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 + } diff --git a/index.html b/index.html new file mode 100644 index 0000000..b198a4e --- /dev/null +++ b/index.html @@ -0,0 +1,23 @@ + + + + + + + elm-spa + + + + + + + + + + + diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..cab849f --- /dev/null +++ b/netlify.toml @@ -0,0 +1,6 @@ +# sends all routes to /index.html +# (so you can handle 404s there!) +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..36042bc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1256 @@ +{ + "name": "elm-spa-elm-ui", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "chokidar-cli": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-2.1.0.tgz", + "integrity": "sha512-6n21AVpW6ywuEPoxJcLXMA2p4T+SLjWsXKny/9yTWFz0kKxESI3eUylpeV97LylING/27T/RVTY0f2/0QaWq9Q==", + "requires": { + "chokidar": "^3.2.3", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "yargs": "^13.3.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crocks": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/crocks/-/crocks-0.12.1.tgz", + "integrity": "sha512-2qCRJwBmPlRQXzd50k9gt9PaItultOP8lj/cKSH2Eai9aeBuNqAnDuyolAm9TGn6Pw/4BgbxtPJLU1S+tQ4WMQ==" + }, + "cross-Appwn": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cross-Appwn/-/cross-Appwn-5.0.1.tgz", + "integrity": "sha1-o7uzAtsil8vqPATt82lB9GE6o5k=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "elm": { + "version": "0.19.1-3", + "resolved": "https://registry.npmjs.org/elm/-/elm-0.19.1-3.tgz", + "integrity": "sha512-6y36ewCcVmTOx8lj7cKJs3bhI5qMfoVEigePZ9PhEUNKpwjjML/pU2u2YSpHVAznuCcojoF6KIsrS1Ci7GtVaQ==", + "requires": { + "request": "^2.88.0" + } + }, + "elm-hot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/elm-hot/-/elm-hot-1.1.1.tgz", + "integrity": "sha512-ZHjoHd2Ev6riNXNQirj3J+GKKXXwedAUikfFBYzlVL/+3CdGs96cpZ7nhAk4c5l//Qa9ymltrqX36mOlr0pPFA==" + }, + "elm-live": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/elm-live/-/elm-live-4.0.1.tgz", + "integrity": "sha512-IlonaC1pO/QoXlOrwwrJaxyvpJAT8QDSfzenkChbhU1PC1fJetkj2TwZfki+y1ZxpSMTnMSomMraOdWA6DO3VQ==", + "requires": { + "chalk": "^1.1.1", + "chokidar": "3.0.2", + "commander": "2.17.1", + "crocks": "0.12.1", + "cross-Appwn": "5.0.1", + "elm-hot": "1.1.1", + "finalhandler": "1.1.2", + "http-proxy": "1.17.0", + "internal-ip": "4.3.0", + "mime": "2.4.3", + "open": "6.4.0", + "pem": "1.14.2", + "serve-static": "1.14.1", + "ws": "7.1.1" + }, + "dependencies": { + "chokidar": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "requires": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "fsevents": "^2.0.6", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", + "normalize-path": "^3.0.0", + "readdirp": "^3.1.1" + } + } + } + }, + "elm-spa": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/elm-spa/-/elm-spa-1.1.0.tgz", + "integrity": "sha512-/K/og+LQBaqH3ft0OYfa4H7Ecr5jDrG1HJwJdUStZ7TIV1fMJdIqd0avGQoyPa6yl1g6w0wh352P5N2WCruAjA==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es6-promisify": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.0.2.tgz", + "integrity": "sha512-eO6vFm0JvqGzjWIQA6QVKjxpmELfhWbDUWHm1rPfIbn55mhKPiAa5xpLmQWJrNa629ZIeQ8ZvMAi13kvrjK6Mg==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-Appwn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-Appwn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-Appwn/-/cross-Appwn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "follow-redirects": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", + "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "requires": { + "debug": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fsevents": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.1.tgz", + "integrity": "sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==", + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, + "mime": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", + "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "pem": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/pem/-/pem-1.14.2.tgz", + "integrity": "sha512-TOnPtq3ZFnCniOZ+rka4pk8UIze9xG1qI+wNE7EmkiR/cg+53uVvk5QbkWZ7M6RsuOxzz62FW1hlAobJr/lTOA==", + "requires": { + "es6-promisify": "^6.0.0", + "md5": "^2.2.1", + "os-tmpdir": "^1.0.1", + "which": "^1.3.1" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.0.tgz", + "integrity": "sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw==" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "requires": { + "picomatch": "^2.0.4" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.1.tgz", + "integrity": "sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==", + "requires": { + "async-limiter": "^1.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..99494fe --- /dev/null +++ b/package.json @@ -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": [] +} diff --git a/public/ports.js b/public/ports.js new file mode 100644 index 0000000..9dd8fa0 --- /dev/null +++ b/public/ports.js @@ -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` + ) + } + }) + } + } +}) diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..99f62ca --- /dev/null +++ b/public/styles.css @@ -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); +} \ No newline at end of file diff --git a/sandbox.config.json b/sandbox.config.json new file mode 100644 index 0000000..28d9158 --- /dev/null +++ b/sandbox.config.json @@ -0,0 +1,8 @@ +{ + "infiniteLoopProtection": true, + "hardReloadOnChange": false, + "view": "browser", + "container": { + "port": 8080 + } +} diff --git a/src/App.elm b/src/App.elm new file mode 100644 index 0000000..6c4f573 --- /dev/null +++ b/src/App.elm @@ -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 + ] + } diff --git a/src/App/Page.elm b/src/App/Page.elm new file mode 100644 index 0000000..9688ad7 --- /dev/null +++ b/src/App/Page.elm @@ -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 ) diff --git a/src/App/Route.elm b/src/App/Route.elm new file mode 100644 index 0000000..d06c824 --- /dev/null +++ b/src/App/Route.elm @@ -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) diff --git a/src/App/Types.elm b/src/App/Types.elm new file mode 100644 index 0000000..fac8b61 --- /dev/null +++ b/src/App/Types.elm @@ -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 diff --git a/src/Internals/Page.elm b/src/Internals/Page.elm new file mode 100644 index 0000000..bb48696 --- /dev/null +++ b/src/Internals/Page.elm @@ -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 + } diff --git a/src/Internals/Utils.elm b/src/Internals/Utils.elm new file mode 100644 index 0000000..9c53b47 --- /dev/null +++ b/src/Internals/Utils.elm @@ -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 diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..dda173a --- /dev/null +++ b/src/README.md @@ -0,0 +1,2 @@ +# ryannhg/elm-app +> the elm package that makes building `elm-spa` super easy! \ No newline at end of file