Initial import

This commit is contained in:
3wc 2021-04-17 23:42:28 +02:00
commit cf706be388
18 changed files with 5879 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Folders to ignore
elm-stuff
node_modules
public/dist
src/Spa/Generated
# MacOS weird stuff
.DS_Store

30
README.md Normal file
View File

@ -0,0 +1,30 @@
# abra-apps
Created using [`elm-spa`](https://elm-spa.dev)
## local development
You can get this site up and running with one command:
```
npm start
```
### other commands to know
There are a handful of commands in the [package.json](./package.json).
Command | Description
:-- | :--
`npm run dev` | Run a dev server and automatically build changes.
`npm run test:watch` | Run tests as you code.
`npm run build` | Build the site for production.
`npm run test` | Run the test suite once, great for CI
## deploying
After you run `npm run build`, the contents of the `public` folder can be hosted as a static site. If you haven't hosted a static site before, I'd recommend using [Netlify](https://netlify.com) (it's free!)
__Build command:__ `npm run build`
__Publish directory:__ `public`

33
elm.json Normal file
View File

@ -0,0 +1,33 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
"elm/url": "1.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {
"avh4/elm-program-test": "3.2.0",
"elm-explorations/test": "1.2.2"
},
"indirect": {
"avh4/elm-fifo": "1.0.4",
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/random": "1.0.0"
}
}
}

2446
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "our-elm-spa-app",
"version": "1.0.0",
"description": "A project created with elm-spa",
"scripts": {
"start": "npm install && npm run build:dev && npm run dev",
"test": "elm-test",
"test:watch": "elm-test --watch",
"build": "run-s build:elm-spa build:elm",
"build:dev": "run-s build:elm-spa build:dev:elm",
"dev": "run-p dev:elm-spa dev:elm",
"build:elm": "elm make src/Main.elm --optimize --output=public/dist/elm.compiled.js",
"build:dev:elm": "elm make src/Main.elm --debug --output=public/dist/elm.compiled.js || true",
"build:elm-spa": "elm-spa build .",
"dev:elm": "elm-live src/Main.elm -u -d public -- --debug --output=public/dist/elm.compiled.js",
"dev:elm-spa": "chokidar src/Pages -c \"elm-spa build .\""
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"chokidar-cli": "2.1.0",
"elm": "0.19.1-3",
"elm-live": "4.0.2",
"elm-spa": "5.0.4",
"elm-test": "0.19.1-revision2",
"npm-run-all": "4.1.5"
}
}

1358
public/abra-apps-list.json Normal file

File diff suppressed because it is too large Load Diff

1314
public/abra-apps.json Normal file

File diff suppressed because it is too large Load Diff

16
public/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- CSS goes here -->
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<!-- JavaScript goes here -->
<script src="/dist/elm.compiled.js"></script>
<script src="/main.js"></script>
</body>
</html>

9
public/main.js Normal file
View File

@ -0,0 +1,9 @@
// Initial data passed to Elm (should match `Flags` defined in `Shared.elm`)
// https://guide.elm-lang.org/interop/flags.html
var flags = null
// Start our Elm application
var app = Elm.Main.init({ flags: flags })
// Ports go here
// https://guide.elm-lang.org/interop/ports.html

12
public/style.css Normal file
View File

@ -0,0 +1,12 @@
body {
font-family: sans-serif;
margin: 20px
}
.navbar {
display: flex;
}
.navbar > *:not(:last-child) { margin-right: 20px; }
.page { margin-top: 20px; }

148
src/Main.elm Normal file
View File

@ -0,0 +1,148 @@
module Main exposing (main)
import Browser
import Browser.Navigation as Nav
import Shared exposing (Flags)
import Spa.Document as Document exposing (Document)
import Spa.Generated.Pages as Pages
import Spa.Generated.Route as Route exposing (Route)
import Url exposing (Url)
main : Program Flags Model Msg
main =
Browser.application
{ init = init
, update = update
, subscriptions = subscriptions
, view = view >> Document.toBrowserDocument
, onUrlRequest = LinkClicked
, onUrlChange = UrlChanged
}
-- INIT
type alias Model =
{ shared : Shared.Model
, page : Pages.Model
}
init : Flags -> Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
let
( shared, sharedCmd ) =
Shared.init flags url key
( page, pageCmd ) =
Pages.init (fromUrl url) shared
savedShare =
Pages.save page shared
in
( Model savedShare page
, Cmd.batch
[ Cmd.map Shared sharedCmd
, Cmd.map Pages pageCmd
]
)
-- UPDATE
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url
| Shared Shared.Msg
| Pages Pages.Msg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkClicked (Browser.Internal url) ->
( model
, Nav.pushUrl model.shared.key (Url.toString url)
)
LinkClicked (Browser.External href) ->
( model
, Nav.load href
)
UrlChanged url ->
let
original =
model.shared
shared =
{ original | url = url }
( page, pageCmd ) =
Pages.init (fromUrl url) shared
in
( { model | page = page, shared = Pages.save page shared }
, Cmd.map Pages pageCmd
)
Shared sharedMsg ->
let
( shared, sharedCmd ) =
Shared.update sharedMsg model.shared
( page, pageCmd ) =
Pages.load model.page shared
in
( { model | page = page, shared = shared }
, Cmd.batch
[ Cmd.map Shared sharedCmd
, Cmd.map Pages pageCmd
]
)
Pages pageMsg ->
let
( page, pageCmd ) =
Pages.update pageMsg model.page
shared =
Pages.save page model.shared
in
( { model | page = page, shared = shared }
, Cmd.map Pages pageCmd
)
view : Model -> Document Msg
view model =
Shared.view
{ page =
Pages.view model.page
|> Document.map Pages
, toMsg = Shared
}
model.shared
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Shared.subscriptions model.shared
|> Sub.map Shared
, Pages.subscriptions model.page
|> Sub.map Pages
]
-- URL
fromUrl : Url -> Route
fromUrl =
Route.fromUrl >> Maybe.withDefault Route.NotFound

38
src/Pages/About.elm Normal file
View File

@ -0,0 +1,38 @@
module Pages.About exposing (Model, Msg, Params, page)
import Html exposing (..)
import Spa.Document exposing (Document)
import Spa.Page as Page exposing (Page)
import Spa.Url exposing (Url)
type alias Params =
()
type alias Model =
Url Params
type alias Msg =
Never
page : Page Params Model Msg
page =
Page.static
{ view = view
}
-- VIEW
view : Url Params -> Document Msg
view { params } =
{ title = "about"
, body =
[ text "about"
]
}

38
src/Pages/NotFound.elm Normal file
View File

@ -0,0 +1,38 @@
module Pages.NotFound exposing (Model, Msg, Params, page)
import Html exposing (..)
import Spa.Document exposing (Document)
import Spa.Page as Page exposing (Page)
import Spa.Url exposing (Url)
type alias Params =
()
type alias Model =
Url Params
type alias Msg =
Never
page : Page Params Model Msg
page =
Page.static
{ view = view
}
-- VIEW
view : Url Params -> Document Msg
view { params } =
{ title = "404"
, body =
[ text "Not found"
]
}

147
src/Pages/Top.elm Normal file
View File

@ -0,0 +1,147 @@
module Pages.Top exposing (Model, Msg, Params, page)
import Html exposing (Html, button, div, h2, h5, img, text, ul, li, a)
import Html.Attributes exposing (src, style, class)
import Html.Events exposing (onClick)
import Http
import Json.Decode as Decode
import Spa.Document exposing (Document)
import Spa.Page as Page exposing (Page)
import Spa.Url as Url exposing (Url)
page : Page Params Model Msg
page =
Page.element
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
-- INIT
type alias Params =
()
type alias App =
{ name : String
, category : String
, repository : Maybe String
, versions : Maybe (List String)
}
type Model
= Failure
| Loading
| Success (List App)
init : Url Params -> ( Model, Cmd Msg )
init { params } =
( Loading, loadApps )
-- UPDATE
type Msg
= MorePlease
| GotApps (Result Http.Error (List App))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MorePlease ->
( Loading, loadApps )
GotApps result ->
case result of
Ok apps ->
( Success apps, Cmd.none )
Err _ ->
( Failure, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Document Msg
view model =
{ title = "Examples.Cats"
, body = [ body model ]
}
body : Model -> Html Msg
body model =
div []
[ viewApps model
]
viewAppName : App -> Html Msg
viewAppName app =
div [ class "col-4" ]
[ div [ class "card" ]
[ div [ class "card-body" ]
[ h5 [ class "card-title" ] [ text app.name ]
, div [ class "card-body" ]
[ text app.category
]
]
]
]
viewApps : Model -> Html Msg
viewApps model =
case model of
Failure ->
div [ ]
[ text "I could not load a random cat for some reason. "
, button [ onClick MorePlease ] [ text "Try Again!" ]
]
Loading ->
text "Loading..."
Success apps ->
div []
[ div [ class "row" ]
(List.map viewAppName (List.sortBy .name apps))
]
-- HTTP
loadApps : Cmd Msg
loadApps =
Http.get
{ url = "http://localhost:8000/abra-apps-list.json"
, expect = Http.expectJson GotApps appListDecoder
}
appDecoder : Decode.Decoder App
appDecoder =
Decode.map4
App
(Decode.field "name" Decode.string)
(Decode.field "category" Decode.string)
(Decode.succeed Nothing)
(Decode.succeed Nothing)
appListDecoder : Decode.Decoder (List App)
appListDecoder =
Decode.list appDecoder

81
src/Shared.elm Normal file
View File

@ -0,0 +1,81 @@
module Shared exposing
( Flags
, Model
, Msg
, init
, subscriptions
, update
, view
)
import Browser.Navigation exposing (Key)
import Html exposing (..)
import Html.Attributes exposing (class, href)
import Spa.Document exposing (Document)
import Spa.Generated.Route as Route
import Url exposing (Url)
-- INIT
type alias Flags =
()
type alias Model =
{ url : Url
, key : Key
}
init : Flags -> Url -> Key -> ( Model, Cmd Msg )
init flags url key =
( Model url key
, Cmd.none
)
-- UPDATE
type Msg
= ReplaceMe
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ReplaceMe ->
( model, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view :
{ page : Document msg, toMsg : Msg -> msg }
-> Model
-> Document msg
view { page, toMsg } model =
{ title = page.title
, body =
[ div []
[ nav [ class "navbar navbar-expand-lg navbar-dark bg-dark" ]
[ a [ class "navbar-brand", href (Route.toString Route.Top) ] [ text "abra apps" ]
, ul [ class "navbar-nav" ]
[ li [ class "nav-tem" ] [ a [ class "nav-link", href (Route.toString Route.About) ] [ text "about" ] ]
]
]
, div [ class "container-fluid" ] page.body
]
]
}

28
src/Spa/Document.elm Normal file
View File

@ -0,0 +1,28 @@
module Spa.Document exposing
( Document
, map
, toBrowserDocument
)
import Browser
import Html exposing (Html)
type alias Document msg =
{ title : String
, body : List (Html msg)
}
map : (msg1 -> msg2) -> Document msg1 -> Document msg2
map fn doc =
{ title = doc.title
, body = List.map (Html.map fn) doc.body
}
toBrowserDocument : Document msg -> Browser.Document msg
toBrowserDocument doc =
{ title = doc.title
, body = doc.body
}

91
src/Spa/Page.elm Normal file
View File

@ -0,0 +1,91 @@
module Spa.Page exposing
( Page
, static, sandbox, element, application
)
{-|
@docs Page
@docs static, sandbox, element, application
@docs Upgraded, Bundle, upgrade
-}
import Shared
import Spa.Document exposing (Document)
import Spa.Url exposing (Url)
type alias Page params model msg =
{ init : Shared.Model -> Url params -> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, view : model -> Document msg
, subscriptions : model -> Sub msg
, save : model -> Shared.Model -> Shared.Model
, load : Shared.Model -> model -> ( model, Cmd msg )
}
static :
{ view : Url params -> Document msg
}
-> Page params (Url params) msg
static page =
{ init = \_ url -> ( url, Cmd.none )
, update = \_ model -> ( model, Cmd.none )
, view = page.view
, subscriptions = \_ -> Sub.none
, save = always identity
, load = always (identity >> ignoreEffect)
}
sandbox :
{ init : Url params -> model
, update : msg -> model -> model
, view : model -> Document msg
}
-> Page params model msg
sandbox page =
{ init = \_ url -> ( page.init url, Cmd.none )
, update = \msg model -> ( page.update msg model, Cmd.none )
, view = page.view
, subscriptions = \_ -> Sub.none
, save = always identity
, load = always (identity >> ignoreEffect)
}
element :
{ init : Url params -> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, view : model -> Document msg
, subscriptions : model -> Sub msg
}
-> Page params model msg
element page =
{ init = \_ params -> page.init params
, update = \msg model -> page.update msg model
, view = page.view
, subscriptions = page.subscriptions
, save = always identity
, load = always (identity >> ignoreEffect)
}
application :
{ init : Shared.Model -> Url params -> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, view : model -> Document msg
, subscriptions : model -> Sub msg
, save : model -> Shared.Model -> Shared.Model
, load : Shared.Model -> model -> ( model, Cmd msg )
}
-> Page params model msg
application page =
page
ignoreEffect : model -> ( model, Cmd msg )
ignoreEffect model =
( model, Cmd.none )

53
src/Spa/Url.elm Normal file
View File

@ -0,0 +1,53 @@
module Spa.Url exposing (Url, create)
import Browser.Navigation exposing (Key)
import Dict exposing (Dict)
import Url
type alias Url params =
{ key : Key
, params : params
, query : Dict String String
, rawUrl : Url.Url
}
create : params -> Key -> Url.Url -> Url params
create params key url =
{ key = key
, params = params
, rawUrl = url
, query =
url.query
|> Maybe.map toQueryDict
|> Maybe.withDefault Dict.empty
}
toQueryDict : String -> Dict String String
toQueryDict queryString =
let
second : List a -> Maybe a
second =
List.drop 1 >> List.head
toTuple : List String -> Maybe ( String, String )
toTuple list =
Maybe.map
(\first ->
( first
, second list |> Maybe.withDefault ""
)
)
(List.head list)
decode =
Url.percentDecode >> Maybe.withDefault ""
in
queryString
|> String.split "&"
|> List.map (String.split "=")
|> List.filterMap toTuple
|> List.map (Tuple.mapBoth decode decode)
|> Dict.fromList