work on docs and add examples

This commit is contained in:
Ryan Haskell-Glatz 2021-04-26 00:55:48 -05:00
parent 1ec47af300
commit bde2430529
34 changed files with 327 additions and 331 deletions

View File

@ -17,7 +17,7 @@ __Great news:__ This is exactly what we can do in __elm-spa__!
## Protected pages
At the end of the [pages docs](/guide/pages#pageprotected), we learned that there are also `protected` versions of every __page type__.
At the end of the [pages docs](/guide/03-pages#pageprotected), we learned that there are also `protected` versions of every __page type__.
These protected pages have slightly different signatures:

View File

@ -10,7 +10,7 @@ The goal of this guide is to help you solve any problems you might run into when
Here are some of the benefits for using __elm-spa__:
1. __Automatic routing__ - routes for your web app are automatically generated based on file names. No need to maintain URL routing logic or wire pages together manually.
1. __User authentication__ - provides an easy way to guarantee certain pages are only visible to signed-in users. Checkout the [user authentication](/examples/user-authentication) section for more details!
1. __User authentication__ - provides an easy way to guarantee certain pages are only visible to signed-in users. You can check out the [user authentication](/examples/04-authentication) example for more details!
1. __Zero configuration__ - Includes a hot-reloading dev server, build tool, and everything you need in one CLI tool! No need for webpack, uglify, or other NPM packages.
@ -19,35 +19,34 @@ Here are some of the benefits for using __elm-spa__:
If you already have [NodeJS](https://nodejs.org) installed, getting started with __elm-spa__ is easy:
```terminal
mkdir our-cool-app
cd our-cool-app
npx elm-spa new
```
This will create a new project in the `our-cool-app` folder! It will only create __three__ new files:
This will create a new project in the current folder. Even better: this command only creates __three__ files:
```bash
our-cool-app/
- elm.json # project dependencies
- src/Pages/Home_.elm # our homepage
- public/index.html # entrypoint to your application
elm.json # project dependencies
src/Pages/Home_.elm # our homepage
public/index.html # entrypoint to your application
```
Let's run it at `http://localhost:1234`:
Let's use __elm-spa__ to spin up a dev server:
```terminal
npx elm-spa server
```
If you see "Hello, world!" at [http://localhost:1234](http://localhost:1234), you did it!
## Installation
So far, we've used [npx](https://www.npmjs.com/package/npx) so we don't need to install __elm-spa__ directly. If you'd like to run commands from the terminal, without the `npx` prefix, you can install __elm-spa__ like this:
So far, we've been using [npx](https://www.npmjs.com/package/npx) so we can run __elm-spa__ directly from the command line. If you'd like to run commands from the terminal without the `npx` prefix, you can install __elm-spa__ like this:
```terminal
npm install -g elm-spa@latest
```
You can verify the install succeeded by running `elm-spa help` from your terminal:
To verify the install succeeded, run `elm-spa help` from your terminal:
```terminal
elm-spa help
@ -55,17 +54,20 @@ elm-spa help
elm-spa version 6.0.0
Commands:
elm-spa new . . . . . . . . create a new project
elm-spa add <url> . . . . . . . create a new page
elm-spa build . . . . . one-time production build
elm-spa watch . . . . . . runs build as you code
elm-spa server . . . . . start a live dev server
elm-spa new . . . . . . . . . create a new project
elm-spa add <url> . . . . . . . . create a new page
elm-spa build . . . . . . one-time production build
elm-spa server . . . . . . start a live dev server
Other commands:
elm-spa gen . . . . generates code without elm make
elm-spa watch . . . . runs elm-spa gen as you code
Visit https://elm-spa.dev for more!
```
That output means you can run the `elm-spa` CLI without needing `npx`
---
__Ready for more?__
Let's check out [the CLI](/guide/cli) to learn more about those five commands!
__Next up:__ [The CLI](/guide/01-cli)

View File

@ -1,12 +1,12 @@
# The CLI
At the end of the last section, we installed the __elm-spa__ CLI using [npm](https://npmjs.org) like this:
To install the __elm-spa__ CLI via [npm](https://npmjs.org) run this command:
```terminal
npm install -g elm-spa@latest
```
This gave us the ability to run a few commands:
This CLI gives us these six commands:
1. [__elm-spa new__](#elm-spa-new) - creates a new project
1. [__elm-spa server__](#elm-spa-server) - runs a dev server as you code
@ -15,11 +15,11 @@ This gave us the ability to run a few commands:
1. [__elm-spa gen__](#elm-spa-gen) - generates files, without elm make
1. [__elm-spa watch__](#elm-spa-watch) - generates files as you code
What do these do? This section of the guide dives into more detail on each command!
What do these do? Let's dive into each in detail!
## elm-spa new
When you want to create a new project, you can use the `elm-spa new` command. This creates a new project in the current folder:
When you want to create a new project, use the `elm-spa new` command. This creates a new project in the current folder:
```terminal
elm-spa new
@ -30,7 +30,7 @@ New project created in:
/Users/ryan/code/my-new-app
```
This command only creates __three__ files:
The `new` command creates __three__ files:
Filename | Description
--- | ---
@ -40,15 +40,15 @@ Filename | Description
## elm-spa server
The first thing you'll want to do after creating a new project is try it out in the browser! The `elm-spa server` is all you need to see your app in action:
The first thing you'll want to do after creating a new project is see it in the browser! The `elm-spa server` command is all you need to see the app in action:
```terminal
elm-spa server
```
This will start a development server for your project at [http://localhost:1234](http://localhost:1234).
This command starts a development server for your project at [http://localhost:1234](http://localhost:1234).
When we edit our code, `elm-spa server` automatically compiles your application.
> When you edit your code, `elm-spa server` automatically compiles your application.
## elm-spa add
@ -71,12 +71,11 @@ elm-spa add /settings # src/Pages/Settings.elm
elm-spa add /people/:id # src/Pages/People/Id_.elm
```
We'll cover this in more detail in the [routing section](./routing)
We'll cover this in more detail in the [routing section](./02-routing)
### using page templates
The `elm-spa add` command also accepts an optional `template` argument too for common
pages you might create.
The `elm-spa add` command also accepts an optional `template` argument too for common pages you might create.
```bash
elm-spa add /example static
@ -84,9 +83,11 @@ elm-spa add /example sandbox
elm-spa add /example element
```
We'll explore those page types in the [pages section](./03-pages)
## elm-spa build
The `server` and `watch` commands are great for development, but for __production__, we'll want the `elm-spa build` command.
The `elm-spa server` command is great for development, but for __production__, you'll want the `elm-spa build` command.
```terminal
elm-spa build
@ -94,6 +95,12 @@ elm-spa build
This compiles your app into __an optimized and minified JS file__. This makes it great for serving your application in the real world!
### A note on hosting
By default, the `public` folder can be statically served. Hosting platforms like [Netlify](https://netlify.com) make this free and easy.
Because this is a single page application, be sure to setup redirects to `public/index.html`. Here's an [example of how to do this with Netlify](https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps).
## elm-spa gen
If you are working with another dev server, you won't need the `.js` file generated by the `build` command. To only generate __elm-spa__ files, use the `elm-spa gen` command:
@ -102,7 +109,7 @@ If you are working with another dev server, you won't need the `.js` file genera
elm-spa gen
```
This will generate files in the `.elm-spa` folder, but allow your custom workflow to define it's own way of compiling Elm. This is a great command to combine __elm-spa__ with another tool like [Vite](/examples/05-npm) or [Parcel](https://parceljs.org/elm.html).
This will generate code in the `.elm-spa` folder, but allow your custom workflow to define it's own way of compiling Elm. This is a great command to combine __elm-spa__ with another tool like [Vite](/examples/05-npm) or [Parcel](https://parceljs.org/elm.html).
## elm-spa watch
@ -115,3 +122,6 @@ elm-spa watch
This will automatically generate code and compile your Elm files on save, but without the server.
---
__Next up__: [Routing](./02-routing)

View File

@ -16,7 +16,7 @@ In this section, we'll cover the different kinds of routes you'll find in every
## The homepage
`Home_.elm` is a reserved filename that handles requests to your homepage. The easiest way to add a new homepage is with the [`elm-spa add`](/guide/cli#elm-spa-add) covered in the CLI docs:
`Home_.elm` is a reserved filename that handles requests to your homepage. The easiest way to add a new homepage is with the [`elm-spa add`](/guide/01-cli#elm-spa-add) covered in the CLI docs:
```terminal
elm-spa add /
@ -128,3 +128,6 @@ Once you have a `NotFound.elm` within your `src/Pages` folder, __elm-spa__ will
The technique of moving a file from the `.elm-spa/defaults` folder is known as "ejecting a default file". Throughout the guide, we'll find more examples of files that we might want to move into our `src` folder.
---
__Next up:__ [Pages](./03-pages)

View File

@ -1,6 +1,6 @@
# Pages
In __elm-spa__, every URL connects to a single page. Let's take a closer look at the homepage we created earlier with the `elm-spa new` command:
In __elm-spa__, every URL connects to a single page. Let's take a closer look at the homepage created with the `elm-spa new` command:
```elm
module Pages.Home_ exposing (view)
@ -17,13 +17,13 @@ view =
This homepage renders __"Homepage"__ in the browser tab, and __"Hello, world!"__ onto the page.
Because the file is named `Home_.elm`, we know it's the homepage. Visiting `http://localhost:1234` in a web browser will render the page.
Because the file is named `Home_.elm`, elm-spa knows this is the homepage. Visiting `http://localhost:1234` in a web browser will render this view.
A `view` function is perfect when all you need is to render some HTML on the screen. But many web pages in the real world do more interesting things!
### Upgrading "Hello, world!"
Let's start by adding a `page` function, the first step in our journey from "Hello, world!" to the real world:
Let's start by adding a new `page` function:
```elm
module Pages.Home_ exposing (page)
@ -47,20 +47,20 @@ view =
}
```
We haven't changed our original code much- except we've added a new `page` function that:
We haven't changed the original code much- except we've added a new `page` function that:
1. Accepts 2 inputs: `Shared.Model` and `Request`
2. Returns a `Page` value
3. Has been __exposed__ at the top of the file.
> Exposing `page` from this module lets __elm-spa__ know to use it instead of the plain `view` function from before.
> Exposing `page` from this module lets __elm-spa__ know we want to use `page` instead of the plain `view` function from before.
The `view` function we had before is passed into `page`, so our user still sees __"Hello, world!"__ when they visit the homepage. However, this page now has access to two new bits of information!
The `view` function from before is now passed into `page`. In the web browser, we still see __"Hello, world!"__. However, this page now has access to two new bits of information!
1. `Shared.Model` is our global application state, which might contain the signed-in user, settings, or other things that should persist as we move from one page to another.
2. `Request` is a record with access to the current route, query parameters, and any other information about the current URL.
You can rely on the fact that the `page` will always be passed the latest `Shared.Model` and `Request` value. If we want either of these values to be available in our `view` function, we can pass them in:
You can rely on the fact that the `page` will always be passed the latest `Shared.Model` and `Request` value. If we want either of these values to be available in our `view` function, we pass them in like so:
```elm
page : Shared.Model -> Request -> Page
@ -82,7 +82,7 @@ view req =
}
```
If we are running `elm-spa server`, this will print __"Hello, localhost!"__ on our screen.
Now the browser should display __"Hello, localhost!"__
### Beyond static pages
@ -90,7 +90,7 @@ You might have noticed `Page.static` earlier in our page function. This is one o
The rest of this section will introduce you to the other __page types__ exposed by the `Page` module, so you know which one to reach for.
> Always choose the __simplest__ page type for the job and reach for the more advanced ones when your page needs the extra features!
> Always choose the __simplest__ page type for the job and reach for the more advanced ones when your page _really_ needs the extra features!
- __[Page.static](#pagestatic)__ - for pages that only render a view.
- __[Page.sandbox](#pagesandbox)__ - for pages that need to keep track of state.
@ -104,7 +104,7 @@ The rest of this section will introduce you to the other __page types__ exposed
elm-spa add /example static
```
This was the page type we took a look at earlier, perfect for pages that render static HTML, but might need access to the `Shared.Model` or `Request` values.
This was the page type we looked at above. It is perfect for pages that render static HTML, but might need access to the `Shared.Model` or `Request` values.
```elm
module Pages.Example exposing (page)
@ -129,7 +129,7 @@ elm-spa add /example sandbox
This is the first __page type__ that introduces [the Elm architecture](https://guide.elm-lang.org/architecture/), which uses `Model` to store the current page state and `Msg` to define what actions users can take on this page.
It's time to upgrade to `Page.sandbox` when you need to track state on the page. Here are a few examples of things you'd store in page state:
It's time to upgrade to `Page.sandbox` when you __need to track state__ on the page. Here are a few examples of things you'd store in page state:
- The current slide of a carousel
- The selected tab section to view
@ -158,7 +158,7 @@ update : Msg -> Model -> Model
view : Model -> View Msg
```
> Our `page` function now returns `Page.With Model Msg` instead of `Page`. This is because our page is now __stateful__.
> Our `page` function now returns `Page.With Model Msg` instead of `Page`. This is because our page is now stateful.
_( Inspired by [__Browser.sandbox__](https://package.elm-lang.org/packages/elm/browser/latest/Browser#sandbox) )_
@ -251,3 +251,7 @@ Page.protected.sandbox
```
When you are ready for user authentication, you can learn more about using `Page.protected` in the [authentication guide](/examples/04-authentication).
---
__Next up:__ [Requests](./04-requests)

View File

@ -8,11 +8,11 @@ page shared req =
...
```
This might be useful when you need to show the active link in your navbar, or navigate to a different page programmicatically. Let's look at the properties on `req` that you might find useful!
This might be useful when you need to show the active link in your navbar, or navigate to a page programmatically. Let's look at the properties on `req` that you might find useful!
## req.params
Every [dynamic route](/guide/routing#dynamic-routes) has parameters that you'll want to get access to. For [static routes](/guide/routing@static-routes), those parameters will be `()`:
Every [dynamic route](/guide/02-routing#dynamic-routes) has parameters that you'll want to get access to. For [static routes](/guide/02-routing#static-routes), those parameters will be `()`:
URL | Request
--- | ---
@ -64,7 +64,7 @@ req.route == Gen.Route.People.Name_ { name = "ryan" }
## req.url
If you need the `port`, `hostname`, or anything else it is available at `req.url`, which contains the original [elm/url](https://package.elm-lang.org/packages/elm/url/latest/Url) URL value.
If you need the `port`, `fragment`, or anything else, `req.url` contains the original [elm/url](https://package.elm-lang.org/packages/elm/url/latest/Url) URL value.
```elm
type alias Url =
@ -77,14 +77,24 @@ type alias Url =
}
```
This is less commonly used than `req.params` and `req.query`, but can be useful in certain cases.
This is less commonly used than `req.params` and `req.query`, but is useful in specific cases.
## Programmatic Navigation
Most of the time, navigation in Elm is as easy as giving an `href` attribute to an anchor tag:
```elm
a [ href "/guide" ] [ text "elm-spa guide" ]
link =
a [ href "/guide" ] [ text "Guide" ]
```
With the generated route code, we can even prevent the need for string URLs. This is great for refactoring and catching typos:
```elm
import Gen.Route as Route
link =
a [ href (Route.toHref Route.Guide) ] [ text "Guide" ]
```
Other times, you'll want to do __programmatic navigation__ navigating to another page after some event completes. Maybe you want to __redirect__ to a sign in page, or head to the __dashboard after signing in successfully__.
@ -105,3 +115,6 @@ update req msg model =
When the `SignedIn` message is fired, this code will redirect the user to the `Dashboard` route.
---
__Next up:__ [Shared state](./05-shared-state)

View File

@ -1,10 +1,10 @@
# Shared state
With __elm-spa__, any time we move from one page to another, the `init` function for that new page is called. This means that the state of the previous page you were looking at has been replaced by the new page.
With __elm-spa__, any time we move from one page to another, the `init` function for that new page is called. This means that the state of the previous page you were looking at has been replaced by the new page's state.
So if we sign in a user at the `SignIn` page, we'll need a place to store the user before navigating over to the `Dashboard`.
So if we sign in a user on the `SignIn` page, we'll need a place to store the user before navigating over to the `Dashboard`.
This is where the `Shared` module comes in the perfect place to store things that every page needs to access!
This is where the `Shared` module comes in the perfect place to store data that every page needs to access!
### Ejecting the default file
@ -52,7 +52,7 @@ type alias User =
}
```
As we saw in the [pages guide](/guide/pages), this `Shared.Model` will be passed into every page so we can check if `shared.user` has a value or not!
As we saw in the [pages guide](/guide/03-pages), this `Shared.Model` will be passed into every page so we can check if `shared.user` has a value or not!
## Shared.init

View File

@ -38,7 +38,7 @@ view =
}
]
, alternatingMarkdownSections
[ ( "🌳"
[ ( "💻"
, """
## Build reliable applications with Elm
@ -66,7 +66,7 @@ With __elm-spa__, routing is automatically generated for you based on a standard
, """
## User authentication
The latest release comes with an easy way to setup user authentication. Use the `Page.protected` API to easily guarantee only logged-in users can view certain pages.
The latest release comes with a simple way to setup user authentication. Use the `Page.protected` API to easily guarantee only logged-in users can view certain pages.
"""
, [ ( "See it in action", Gen.Route.Examples__Section_ { section = "04-authentication" } )
]

View File

@ -1,28 +0,0 @@
# examples/03-user-auth
> 🌳 built with [elm-spa](https://elm-spa.dev)
## dependencies
This project requires the latest LTS version of [Node.js](https://nodejs.org/)
```bash
npm install -g elm elm-spa
```
## running locally
```bash
elm-spa server # starts this app at http:/localhost:1234
```
### other commands
```bash
elm-spa add # add a new page to the application
elm-spa build # production build
elm-spa watch # runs build as you code (without the server)
```
## learn more
You can learn more at [elm-spa.dev](https://elm-spa.dev)

View File

@ -1,27 +0,0 @@
{
"type": "application",
"source-directories": [
"src",
".elm-spa/defaults",
".elm-spa/generated"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/json": "1.1.3",
"elm/url": "1.0.0",
"ryannhg/elm-spa": "5.3.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@ -1,57 +0,0 @@
module Pages.Dashboard exposing (Model, Msg, page)
import Gen.Params.Dashboard exposing (Params)
import Page exposing (Page)
import Request exposing (Request)
import Shared
import View exposing (View)
type alias User =
()
page : Shared.Model -> Request Params -> Page Model Msg
page shared req =
Page.protected.sandbox
{ init = init
, update = update
, view = view
}
-- INIT
type alias Model =
{}
init : User -> Model
init user =
{}
-- UPDATE
type Msg
= ReplaceMe
update : User -> Msg -> Model -> Model
update user msg model =
case msg of
ReplaceMe ->
model
-- VIEW
view : User -> Model -> View Msg
view user model =
View.placeholder "Dashboard"

View File

@ -1,8 +0,0 @@
module Pages.Home_ exposing (view)
import View exposing (View)
view : View Never
view =
View.placeholder "Home_"

View File

@ -1,9 +0,0 @@
module Pages.SignIn exposing (view)
import View exposing (View)
view : View Never
view =
View.placeholder "SignIn"

View File

@ -6,6 +6,6 @@
</head>
<body>
<script src="/dist/elm.js"></script>
<script> Elm.Main.init() </script>
<script src="/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,8 @@
const app = Elm.Main.init({
flags: JSON.parse(localStorage.getItem('storage'))
})
app.ports.save_.subscribe(storage => {
localStorage.setItem('storage', JSON.stringify(storage))
app.ports.load_.send(storage)
})

View File

@ -10,6 +10,7 @@ module Auth exposing
-}
import Domain.User
import ElmSpa.Internals.Page as ElmSpa
import Gen.Route exposing (Route)
import Request exposing (Request)
@ -17,12 +18,12 @@ import Shared
type alias User =
Shared.User
Domain.User.User
beforeProtectedInit : Shared.Model -> Request -> ElmSpa.Protected User Route
beforeProtectedInit shared req =
case shared.user of
beforeProtectedInit { storage } _ =
case storage.user of
Just user ->
ElmSpa.Provide user

View File

@ -0,0 +1,22 @@
module Domain.User exposing (User, decoder, encode)
import Json.Decode as Json
import Json.Encode as Encode
type alias User =
{ name : String
}
decoder : Json.Decoder User
decoder =
Json.map User
(Json.field "name" Json.string)
encode : User -> Json.Value
encode user =
Encode.object
[ ( "name", Encode.string user.name )
]

View File

@ -1,22 +1,22 @@
module Pages.Home_ exposing (Model, Msg, page, view)
import Auth
import Effect exposing (Effect)
import Html
import Html.Events as Events
import Page
import Request exposing (Request)
import Shared
import Storage exposing (Storage)
import UI
import View exposing (View)
page : Shared.Model -> Request -> Page.With Model Msg
page _ _ =
Page.protected.advanced <|
page shared _ =
Page.protected.element <|
\user ->
{ init = init
, update = update
, update = update shared.storage
, view = view user
, subscriptions = \_ -> Sub.none
}
@ -30,9 +30,9 @@ type alias Model =
{}
init : ( Model, Effect Msg )
init : ( Model, Cmd Msg )
init =
( {}, Effect.none )
( {}, Cmd.none )
@ -43,12 +43,12 @@ type Msg
= ClickedSignOut
update : Msg -> Model -> ( Model, Effect Msg )
update msg model =
update : Storage -> Msg -> Model -> ( Model, Cmd Msg )
update storage msg model =
case msg of
ClickedSignOut ->
( model
, Effect.fromShared Shared.SignedOut
, Storage.signOut storage
)

View File

@ -1,6 +1,5 @@
module Pages.SignIn exposing (Model, Msg, page)
import Effect exposing (Effect)
import Gen.Params.SignIn exposing (Params)
import Html
import Html.Attributes as Attr
@ -8,15 +7,16 @@ import Html.Events as Events
import Page
import Request
import Shared
import Storage exposing (Storage)
import UI
import View exposing (View)
page : Shared.Model -> Request.With Params -> Page.With Model Msg
page shared req =
Page.advanced
Page.element
{ init = init
, update = update
, update = update shared.storage
, view = view
, subscriptions = subscriptions
}
@ -30,10 +30,10 @@ type alias Model =
{ name : String }
init : ( Model, Effect Msg )
init : ( Model, Cmd Msg )
init =
( { name = "" }
, Effect.none
, Cmd.none
)
@ -46,17 +46,17 @@ type Msg
| SubmittedSignInForm
update : Msg -> Model -> ( Model, Effect Msg )
update msg model =
update : Storage -> Msg -> Model -> ( Model, Cmd Msg )
update storage msg model =
case msg of
UpdatedName name ->
( { model | name = name }
, Effect.none
, Cmd.none
)
SubmittedSignInForm ->
( model
, Effect.fromShared (Shared.SignedIn model.name)
, Storage.signIn { name = model.name } storage
)

View File

@ -0,0 +1,59 @@
module Shared exposing
( Flags
, Model
, Msg
, init
, subscriptions
, update
)
import Gen.Route
import Json.Decode as Json
import Request exposing (Request)
import Storage exposing (Storage)
type alias Flags =
Json.Value
type alias Model =
{ storage : Storage
}
init : Request -> Flags -> ( Model, Cmd Msg )
init req flags =
let
model =
{ storage = Storage.fromJson flags }
in
( model
, if model.storage.user /= Nothing && req.route == Gen.Route.SignIn then
Request.replaceRoute Gen.Route.SignIn req
else
Cmd.none
)
type Msg
= StorageUpdated Storage
update : Request -> Msg -> Model -> ( Model, Cmd Msg )
update req msg model =
case msg of
StorageUpdated storage ->
( { model | storage = storage }
, if Gen.Route.SignIn == req.route then
Request.pushRoute Gen.Route.Home_ req
else
Cmd.none
)
subscriptions : Request -> Model -> Sub Msg
subscriptions _ _ =
Storage.load StorageUpdated

View File

@ -0,0 +1,85 @@
port module Storage exposing
( Storage, load
, signIn, signOut
, fromJson
)
{-|
@docs Storage, save, load
@docs signIn, signOut
-}
import Domain.User as User exposing (User)
import Json.Decode as Json
import Json.Encode as Encode
type alias Storage =
{ user : Maybe User
}
fromJson : Json.Value -> Storage
fromJson json =
json
|> Json.decodeValue decoder
|> Result.withDefault init
init : Storage
init =
{ user = Nothing
}
decoder : Json.Decoder Storage
decoder =
Json.map Storage
(Json.field "user" (Json.maybe User.decoder))
save : Storage -> Json.Value
save storage =
Encode.object
[ ( "user"
, storage.user
|> Maybe.map User.encode
|> Maybe.withDefault Encode.null
)
]
-- UPDATING STORAGE
signIn : User -> Storage -> Cmd msg
signIn user storage =
saveToLocalStorage { storage | user = Just user }
signOut : Storage -> Cmd msg
signOut storage =
saveToLocalStorage { storage | user = Nothing }
-- PORTS
saveToLocalStorage : Storage -> Cmd msg
saveToLocalStorage =
save >> save_
port save_ : Json.Value -> Cmd msg
load : (Storage -> msg) -> Sub msg
load fromStorage =
load_ (fromJson >> fromStorage)
port load_ : (Json.Value -> msg) -> Sub msg

View File

@ -12,9 +12,9 @@ layout children =
viewLink label route =
Html.a [ Attr.href (Route.toHref route) ] [ Html.text label ]
in
[ Html.div [ Attr.class "container" ]
[ Html.header [ Attr.class "navbar" ]
[ Html.strong [ Attr.class "brand" ] [ viewLink "Home" Route.Home_ ]
[ Html.div [ Attr.style "margin" "2rem" ]
[ Html.header [ Attr.style "margin-bottom" "1rem" ]
[ Html.strong [ Attr.style "margin-right" "1rem" ] [ viewLink "Home" Route.Home_ ]
, viewLink "Sign in" Route.SignIn
]
, Html.main_ [] children

View File

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

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<script src="/dist/elm.js"></script>
<script> Elm.Main.init() </script>
</body>
</html>

View File

@ -1,29 +0,0 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.container {
max-width: 960px;
margin: 1rem auto;
}
.navbar {
display: flex;
align-items: center;
}
.navbar .brand {
font-size: 1.5rem;
}
.navbar .splitter { flex: 1 1 auto; }
.navbar a {
margin-right: 16px;
}
main { margin-top: 3rem; }
h1, h2 {
margin-top: 3rem;
}

View File

@ -1,66 +0,0 @@
module Shared exposing
( Flags
, Model
, Msg(..)
, User
, init
, subscriptions
, update
)
import Gen.Route
import Json.Decode as Json
import Request exposing (Request)
type alias User =
{ name : String
}
-- INIT
type alias Flags =
Json.Value
type alias Model =
{ user : Maybe User
}
init : Request -> Flags -> ( Model, Cmd Msg )
init _ _ =
( { user = Nothing }
, Cmd.none
)
-- UPDATE
type Msg
= SignedIn String
| SignedOut
update : Request -> Msg -> Model -> ( Model, Cmd Msg )
update req msg model =
case msg of
SignedIn name ->
( { model | user = Just { name = name } }
, Request.pushRoute Gen.Route.Home_ req
)
SignedOut ->
( { model | user = Nothing }
, Cmd.none
)
subscriptions : Request -> Model -> Sub Msg
subscriptions _ _ =
Sub.none

View File

@ -12,5 +12,8 @@ export default {
server: Server.run,
gen: Build.gen,
watch: Watch.run,
help: Help.run
help: Help.run,
// Aliases for Elm folks
init: New.run,
make: Build.build,
}

View File

@ -2,14 +2,35 @@ import path from "path"
import fs from "fs"
import config from "../config"
import * as File from '../file'
import { bold, reset } from "../terminal"
import { bold, check, colors, dim, dot, reset, warn } from "../terminal"
import { createInterface } from "readline"
// Scaffold a new elm-spa project
export default {
run: () => {
const dest = process.cwd()
File.copy(config.folders.init, dest)
try { fs.renameSync(path.join(dest, '_gitignore'), path.join(dest, '.gitignore')) } catch (_) {}
return ` ${bold}New project created in:${reset}\n ${process.cwd()}\n`
run: async () => {
return new Promise(offerToInitializeProject)
}
}
const offerToInitializeProject = (resolve: (value: unknown) => void, reject: (reason: unknown) => void) => {
const rl = createInterface({
input: process.stdin,
output: process.stdout
})
rl.question(`\n May I create a ${colors.cyan}new project${reset} in the ${colors.yellow}current folder${reset}? ${dim}[y/n]${reset} `, answer => {
if (answer.toLowerCase() === 'n') {
reject(` ${bold}No changes made!${reset}`)
} else {
resolve(initializeNewProject())
rl.close()
}
})
}
const initializeNewProject = () => {
const dest = process.cwd()
File.copy(config.folders.init, dest)
try { fs.renameSync(path.join(dest, '_gitignore'), path.join(dest, '.gitignore')) } catch (_) {}
return ` ${check} ${bold}New project created in:${reset}\n ${process.cwd()}\n`
}

View File

@ -10,7 +10,10 @@ const commands : Commands = {
gen: CLI.gen,
watch: CLI.watch,
server: CLI.server,
help: CLI.help
help: CLI.help,
// Aliases for Elm folks
init: CLI.new,
make: CLI.build,
}
const command : string | undefined = process.argv[2]

View File

@ -6,4 +6,7 @@ export type Commands = {
watch: () => any
server: () => any
help: () => any
// Aliases for Elm folks
init: () => any
make: () => any
}