mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-11-22 03:12:01 +03:00
work on docs and add examples
This commit is contained in:
parent
1ec47af300
commit
bde2430529
@ -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:
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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
|
||||
|
@ -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" } )
|
||||
]
|
||||
|
@ -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)
|
@ -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": {}
|
||||
}
|
||||
}
|
@ -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"
|
@ -1,8 +0,0 @@
|
||||
module Pages.Home_ exposing (view)
|
||||
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
view : View Never
|
||||
view =
|
||||
View.placeholder "Home_"
|
@ -1,9 +0,0 @@
|
||||
module Pages.SignIn exposing (view)
|
||||
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
view : View Never
|
||||
view =
|
||||
View.placeholder "SignIn"
|
||||
|
@ -6,6 +6,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="/dist/elm.js"></script>
|
||||
<script> Elm.Main.init() </script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
8
examples/04-authentication/public/main.js
Normal file
8
examples/04-authentication/public/main.js
Normal 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)
|
||||
})
|
@ -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
|
||||
|
22
examples/04-authentication/src/Domain/User.elm
Normal file
22
examples/04-authentication/src/Domain/User.elm
Normal 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 )
|
||||
]
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
59
examples/04-authentication/src/Shared.elm
Normal file
59
examples/04-authentication/src/Shared.elm
Normal 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
|
85
examples/04-authentication/src/Storage.elm
Normal file
85
examples/04-authentication/src/Storage.elm
Normal 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
|
@ -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
|
5
examples/04-demo/.gitignore
vendored
5
examples/04-demo/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
.DS_Store
|
||||
.elm-spa
|
||||
elm-stuff
|
||||
node_modules
|
||||
dist
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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
|
@ -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,
|
||||
}
|
@ -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`
|
||||
}
|
@ -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]
|
||||
|
@ -6,4 +6,7 @@ export type Commands = {
|
||||
watch: () => any
|
||||
server: () => any
|
||||
help: () => any
|
||||
// Aliases for Elm folks
|
||||
init: () => any
|
||||
make: () => any
|
||||
}
|
Loading…
Reference in New Issue
Block a user