Add an elm frontend for the tasks api.
This commit is contained in:
parent
f507825f5f
commit
13e07e42df
|
@ -2,3 +2,5 @@ target/
|
|||
|
||||
.env
|
||||
*.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::http::header;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use serde::Deserialize;
|
||||
|
@ -8,7 +9,11 @@ use crate::{global::AppState, models::Task};
|
|||
pub async fn list(state: State<AppState>) -> impl IntoResponse {
|
||||
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)]
|
||||
|
@ -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();
|
||||
|
||||
(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