Add an elm frontend for the tasks api.
This commit is contained in:
parent
f507825f5f
commit
13e07e42df
|
@ -2,3 +2,5 @@ target/
|
||||||
|
|
||||||
.env
|
.env
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
frontend/elm-stuff
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"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/bytes": "1.0.8",
|
||||||
|
"elm/file": "1.0.5",
|
||||||
|
"elm/time": "1.0.0",
|
||||||
|
"elm/virtual-dom": "1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-dependencies": {
|
||||||
|
"direct": {},
|
||||||
|
"indirect": {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
module Dashboard exposing (Model, Msg, Task, init, update, view)
|
||||||
|
|
||||||
|
import Browser exposing (Document)
|
||||||
|
import Browser.Navigation as Nav
|
||||||
|
import Html exposing (Html, div, text)
|
||||||
|
import Http
|
||||||
|
import Json.Decode exposing (Decoder, field, int, list, map3, maybe, string)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Task =
|
||||||
|
{ id : Int
|
||||||
|
, title : String
|
||||||
|
, description : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ navKey : Nav.Key
|
||||||
|
, tasks : Status (List Task)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Status a
|
||||||
|
= Failure
|
||||||
|
| Loading
|
||||||
|
| Success a
|
||||||
|
|
||||||
|
|
||||||
|
init : Nav.Key -> ( Model, Cmd Msg )
|
||||||
|
init navKey =
|
||||||
|
( Model navKey Loading
|
||||||
|
, Http.get
|
||||||
|
{ url = "http://localhost:3000/tasks"
|
||||||
|
, expect = Http.expectJson GotTasks taskDecoder
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= GotTasks (Result Http.Error (List Task))
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
GotTasks result ->
|
||||||
|
case result of
|
||||||
|
Ok tasks ->
|
||||||
|
( { model | tasks = Success tasks }, Cmd.none )
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
( { model | tasks = Failure }, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
taskDecoder : Decoder (List Task)
|
||||||
|
taskDecoder =
|
||||||
|
list
|
||||||
|
(map3 Task
|
||||||
|
(field "id" int)
|
||||||
|
(field "title" string)
|
||||||
|
(maybe
|
||||||
|
(field "description"
|
||||||
|
string
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Document Msg
|
||||||
|
view model =
|
||||||
|
{ title = "Dashboard"
|
||||||
|
, body = [ viewBody model ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
viewBody : Model -> Html Msg
|
||||||
|
viewBody model =
|
||||||
|
case model.tasks of
|
||||||
|
Failure ->
|
||||||
|
div [] [ text "Failed to load" ]
|
||||||
|
|
||||||
|
Loading ->
|
||||||
|
div [] [ text "Loading" ]
|
||||||
|
|
||||||
|
Success tasks ->
|
||||||
|
div [] [ text ("Loaded " ++ String.fromInt (List.length tasks) ++ " tasks") ]
|
|
@ -0,0 +1,129 @@
|
||||||
|
module Main exposing (..)
|
||||||
|
|
||||||
|
import Browser
|
||||||
|
import Browser.Navigation as Nav
|
||||||
|
import Dashboard
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Url
|
||||||
|
import Url.Parser as Parser exposing (Parser, oneOf)
|
||||||
|
|
||||||
|
|
||||||
|
main : Program () Model Msg
|
||||||
|
main =
|
||||||
|
Browser.application
|
||||||
|
{ init = init
|
||||||
|
, view = view
|
||||||
|
, update = update
|
||||||
|
, subscriptions = subscriptions
|
||||||
|
, onUrlChange = UrlChanged
|
||||||
|
, onUrlRequest = LinkClicked
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Model
|
||||||
|
= NotFound Nav.Key
|
||||||
|
| Dashboard Dashboard.Model
|
||||||
|
|
||||||
|
|
||||||
|
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
|
||||||
|
init _ url key =
|
||||||
|
stepUrl url (NotFound key)
|
||||||
|
|
||||||
|
|
||||||
|
type Route
|
||||||
|
= DashboardRoute
|
||||||
|
|
||||||
|
|
||||||
|
parser : Parser (Route -> a) a
|
||||||
|
parser =
|
||||||
|
oneOf
|
||||||
|
[ Parser.map DashboardRoute (Parser.s "tasks")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
stepUrl : Url.Url -> Model -> ( Model, Cmd Msg )
|
||||||
|
stepUrl url model =
|
||||||
|
case Parser.parse parser url of
|
||||||
|
Just DashboardRoute ->
|
||||||
|
Dashboard.init (toNavKey model)
|
||||||
|
|> mapModelAndMsg Dashboard GotDashboardMsg
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( NotFound (toNavKey model), Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
mapModelAndMsg : (subModel -> Model) -> (subMsg -> Msg) -> ( subModel, Cmd subMsg ) -> ( Model, Cmd Msg )
|
||||||
|
mapModelAndMsg toModel toMsg ( subModel, subCmd ) =
|
||||||
|
( toModel subModel
|
||||||
|
, Cmd.map toMsg subCmd
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= LinkClicked Browser.UrlRequest
|
||||||
|
| UrlChanged Url.Url
|
||||||
|
| GotDashboardMsg Dashboard.Msg
|
||||||
|
|
||||||
|
|
||||||
|
toNavKey : Model -> Nav.Key
|
||||||
|
toNavKey model =
|
||||||
|
case model of
|
||||||
|
NotFound key ->
|
||||||
|
key
|
||||||
|
|
||||||
|
Dashboard dashboard ->
|
||||||
|
dashboard.navKey
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case ( msg, model ) of
|
||||||
|
( LinkClicked urlRequest, _ ) ->
|
||||||
|
case urlRequest of
|
||||||
|
Browser.Internal url ->
|
||||||
|
( model, Nav.pushUrl (toNavKey model) (Url.toString url) )
|
||||||
|
|
||||||
|
Browser.External href ->
|
||||||
|
( model, Nav.load href )
|
||||||
|
|
||||||
|
( UrlChanged url, _ ) ->
|
||||||
|
stepUrl url model
|
||||||
|
|
||||||
|
( GotDashboardMsg dashboardMsg, Dashboard dashboard ) ->
|
||||||
|
Dashboard.update dashboardMsg dashboard
|
||||||
|
|> mapModelAndMsg Dashboard GotDashboardMsg
|
||||||
|
|
||||||
|
( _, _ ) ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions : Model -> Sub Msg
|
||||||
|
subscriptions _ =
|
||||||
|
Sub.none
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Browser.Document Msg
|
||||||
|
view model =
|
||||||
|
let
|
||||||
|
viewPage toMsg content =
|
||||||
|
{ title = content.title
|
||||||
|
, body = List.map (Html.map toMsg) content.body
|
||||||
|
}
|
||||||
|
in
|
||||||
|
case model of
|
||||||
|
NotFound _ ->
|
||||||
|
{ title = "Not Found"
|
||||||
|
, body =
|
||||||
|
[ text "This page was not found!"
|
||||||
|
, viewLink "/tasks"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Dashboard dashboard ->
|
||||||
|
viewPage GotDashboardMsg (Dashboard.view dashboard)
|
||||||
|
|
||||||
|
|
||||||
|
viewLink : String -> Html msg
|
||||||
|
viewLink path =
|
||||||
|
li [] [ a [ href path ] [ text path ] ]
|
|
@ -1,4 +1,5 @@
|
||||||
use axum::extract::{Json, State};
|
use axum::extract::{Json, State};
|
||||||
|
use axum::http::header;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -8,7 +9,11 @@ use crate::{global::AppState, models::Task};
|
||||||
pub async fn list(state: State<AppState>) -> impl IntoResponse {
|
pub async fn list(state: State<AppState>) -> impl IntoResponse {
|
||||||
let tasks = Task::all(&state.db_pool).await.unwrap();
|
let tasks = Task::all(&state.db_pool).await.unwrap();
|
||||||
|
|
||||||
(StatusCode::OK, serde_json::to_string(&tasks).unwrap())
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
|
||||||
|
serde_json::to_string(&tasks).unwrap(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -22,5 +27,9 @@ pub async fn create(state: State<AppState>, Json(req): Json<NewTask>) -> impl In
|
||||||
|
|
||||||
let new_task = task.insert(&state.db_pool).await.unwrap();
|
let new_task = task.insert(&state.db_pool).await.unwrap();
|
||||||
|
|
||||||
(StatusCode::OK, serde_json::to_string(&new_task).unwrap())
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
|
||||||
|
serde_json::to_string(&new_task).unwrap(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue