diff --git a/docs/public/content/examples/04-authentication.md b/docs/public/content/examples/04-authentication.md index 185ee84..c4c3aba 100644 --- a/docs/public/content/examples/04-authentication.md +++ b/docs/public/content/examples/04-authentication.md @@ -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: diff --git a/docs/public/content/guide.md b/docs/public/content/guide.md index 23fec43..ef67a61 100644 --- a/docs/public/content/guide.md +++ b/docs/public/content/guide.md @@ -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 . . . . . . . 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 . . . . . . . . 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) diff --git a/docs/public/content/guide/cli.md b/docs/public/content/guide/01-cli.md similarity index 59% rename from docs/public/content/guide/cli.md rename to docs/public/content/guide/01-cli.md index 03e966b..ce075f5 100644 --- a/docs/public/content/guide/cli.md +++ b/docs/public/content/guide/01-cli.md @@ -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) \ No newline at end of file diff --git a/docs/public/content/guide/routing.md b/docs/public/content/guide/02-routing.md similarity index 97% rename from docs/public/content/guide/routing.md rename to docs/public/content/guide/02-routing.md index 476c4a3..7bbaeb6 100644 --- a/docs/public/content/guide/routing.md +++ b/docs/public/content/guide/02-routing.md @@ -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) \ No newline at end of file diff --git a/docs/public/content/guide/pages.md b/docs/public/content/guide/03-pages.md similarity index 83% rename from docs/public/content/guide/pages.md rename to docs/public/content/guide/03-pages.md index f9cac1c..a3a9820 100644 --- a/docs/public/content/guide/pages.md +++ b/docs/public/content/guide/03-pages.md @@ -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) \ No newline at end of file diff --git a/docs/public/content/guide/url-requests.md b/docs/public/content/guide/04-requests.md similarity index 75% rename from docs/public/content/guide/url-requests.md rename to docs/public/content/guide/04-requests.md index 0577d28..8d73cb0 100644 --- a/docs/public/content/guide/url-requests.md +++ b/docs/public/content/guide/04-requests.md @@ -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) \ No newline at end of file diff --git a/docs/public/content/guide/shared-state.md b/docs/public/content/guide/05-shared-state.md similarity index 90% rename from docs/public/content/guide/shared-state.md rename to docs/public/content/guide/05-shared-state.md index cea5b7f..bfaece6 100644 --- a/docs/public/content/guide/shared-state.md +++ b/docs/public/content/guide/05-shared-state.md @@ -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 diff --git a/docs/public/content/guide/views.md b/docs/public/content/guide/06-views.md similarity index 100% rename from docs/public/content/guide/views.md rename to docs/public/content/guide/06-views.md diff --git a/docs/src/Pages/Home_.elm b/docs/src/Pages/Home_.elm index 782baca..0bbea18 100644 --- a/docs/src/Pages/Home_.elm +++ b/docs/src/Pages/Home_.elm @@ -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" } ) ] diff --git a/examples/03-user-auth/README.md b/examples/03-user-auth/README.md deleted file mode 100644 index c8b3a38..0000000 --- a/examples/03-user-auth/README.md +++ /dev/null @@ -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) diff --git a/examples/03-user-auth/elm.json b/examples/03-user-auth/elm.json deleted file mode 100644 index e694c97..0000000 --- a/examples/03-user-auth/elm.json +++ /dev/null @@ -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": {} - } -} diff --git a/examples/03-user-auth/src/Pages/Dashboard.elm b/examples/03-user-auth/src/Pages/Dashboard.elm deleted file mode 100644 index d431b0b..0000000 --- a/examples/03-user-auth/src/Pages/Dashboard.elm +++ /dev/null @@ -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" diff --git a/examples/03-user-auth/src/Pages/Home_.elm b/examples/03-user-auth/src/Pages/Home_.elm deleted file mode 100644 index 5934b12..0000000 --- a/examples/03-user-auth/src/Pages/Home_.elm +++ /dev/null @@ -1,8 +0,0 @@ -module Pages.Home_ exposing (view) - -import View exposing (View) - - -view : View Never -view = - View.placeholder "Home_" diff --git a/examples/03-user-auth/src/Pages/SignIn.elm b/examples/03-user-auth/src/Pages/SignIn.elm deleted file mode 100644 index e130dfa..0000000 --- a/examples/03-user-auth/src/Pages/SignIn.elm +++ /dev/null @@ -1,9 +0,0 @@ -module Pages.SignIn exposing (view) - -import View exposing (View) - - -view : View Never -view = - View.placeholder "SignIn" - diff --git a/examples/03-user-auth/.gitignore b/examples/04-authentication/.gitignore similarity index 100% rename from examples/03-user-auth/.gitignore rename to examples/04-authentication/.gitignore diff --git a/examples/04-demo/README.md b/examples/04-authentication/README.md similarity index 100% rename from examples/04-demo/README.md rename to examples/04-authentication/README.md diff --git a/examples/04-demo/elm.json b/examples/04-authentication/elm.json similarity index 100% rename from examples/04-demo/elm.json rename to examples/04-authentication/elm.json diff --git a/examples/03-user-auth/public/index.html b/examples/04-authentication/public/index.html similarity index 84% rename from examples/03-user-auth/public/index.html rename to examples/04-authentication/public/index.html index a113e35..486f3e7 100644 --- a/examples/03-user-auth/public/index.html +++ b/examples/04-authentication/public/index.html @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/examples/04-authentication/public/main.js b/examples/04-authentication/public/main.js new file mode 100644 index 0000000..3a66a3e --- /dev/null +++ b/examples/04-authentication/public/main.js @@ -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) +}) \ No newline at end of file diff --git a/examples/04-demo/src/Auth.elm b/examples/04-authentication/src/Auth.elm similarity index 81% rename from examples/04-demo/src/Auth.elm rename to examples/04-authentication/src/Auth.elm index 4273ab7..01cd505 100644 --- a/examples/04-demo/src/Auth.elm +++ b/examples/04-authentication/src/Auth.elm @@ -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 diff --git a/examples/04-authentication/src/Domain/User.elm b/examples/04-authentication/src/Domain/User.elm new file mode 100644 index 0000000..e81fca4 --- /dev/null +++ b/examples/04-authentication/src/Domain/User.elm @@ -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 ) + ] diff --git a/examples/04-demo/src/Pages/Home_.elm b/examples/04-authentication/src/Pages/Home_.elm similarity index 74% rename from examples/04-demo/src/Pages/Home_.elm rename to examples/04-authentication/src/Pages/Home_.elm index 07122f9..7f48d57 100644 --- a/examples/04-demo/src/Pages/Home_.elm +++ b/examples/04-authentication/src/Pages/Home_.elm @@ -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 ) diff --git a/examples/04-demo/src/Pages/SignIn.elm b/examples/04-authentication/src/Pages/SignIn.elm similarity index 83% rename from examples/04-demo/src/Pages/SignIn.elm rename to examples/04-authentication/src/Pages/SignIn.elm index ed8bda4..a19bbc2 100644 --- a/examples/04-demo/src/Pages/SignIn.elm +++ b/examples/04-authentication/src/Pages/SignIn.elm @@ -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 ) diff --git a/examples/04-authentication/src/Shared.elm b/examples/04-authentication/src/Shared.elm new file mode 100644 index 0000000..d7eb01d --- /dev/null +++ b/examples/04-authentication/src/Shared.elm @@ -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 diff --git a/examples/04-authentication/src/Storage.elm b/examples/04-authentication/src/Storage.elm new file mode 100644 index 0000000..12ec607 --- /dev/null +++ b/examples/04-authentication/src/Storage.elm @@ -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 diff --git a/examples/04-demo/src/UI.elm b/examples/04-authentication/src/UI.elm similarity index 73% rename from examples/04-demo/src/UI.elm rename to examples/04-authentication/src/UI.elm index 7b2aa94..97d199b 100644 --- a/examples/04-demo/src/UI.elm +++ b/examples/04-authentication/src/UI.elm @@ -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 diff --git a/examples/04-demo/.gitignore b/examples/04-demo/.gitignore deleted file mode 100644 index ce6402f..0000000 --- a/examples/04-demo/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.DS_Store -.elm-spa -elm-stuff -node_modules -dist \ No newline at end of file diff --git a/examples/04-demo/public/index.html b/examples/04-demo/public/index.html deleted file mode 100644 index 3299e60..0000000 --- a/examples/04-demo/public/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/04-demo/public/style.css b/examples/04-demo/public/style.css deleted file mode 100644 index 7f09426..0000000 --- a/examples/04-demo/public/style.css +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/examples/04-demo/src/Shared.elm b/examples/04-demo/src/Shared.elm deleted file mode 100644 index a6918be..0000000 --- a/examples/04-demo/src/Shared.elm +++ /dev/null @@ -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 diff --git a/src/cli/src/cli.ts b/src/cli/src/cli.ts index 95a38d9..d3c5b33 100644 --- a/src/cli/src/cli.ts +++ b/src/cli/src/cli.ts @@ -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, } \ No newline at end of file diff --git a/src/cli/src/cli/init.ts b/src/cli/src/cli/init.ts index 0a96030..d60b482 100644 --- a/src/cli/src/cli/init.ts +++ b/src/cli/src/cli/init.ts @@ -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` } \ No newline at end of file diff --git a/src/cli/src/index.ts b/src/cli/src/index.ts index 81f4ab5..3f3887c 100644 --- a/src/cli/src/index.ts +++ b/src/cli/src/index.ts @@ -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] diff --git a/src/cli/src/types.ts b/src/cli/src/types.ts index 311766d..54caba6 100644 --- a/src/cli/src/types.ts +++ b/src/cli/src/types.ts @@ -6,4 +6,7 @@ export type Commands = { watch: () => any server: () => any help: () => any + // Aliases for Elm folks + init: () => any + make: () => any } \ No newline at end of file