starts with a dedicated Api module, wip...
This commit is contained in:
173
src/Api.elm
Normal file
173
src/Api.elm
Normal file
@@ -0,0 +1,173 @@
|
||||
module Api exposing (..)
|
||||
|
||||
import Http
|
||||
import Json.Decode as D
|
||||
import Json.Decode exposing (Decoder, int, string)
|
||||
import Json.Encode as E
|
||||
|
||||
type alias HttpResult a = (Result Http.Error a)
|
||||
|
||||
type alias Response =
|
||||
{ value: Maybe String
|
||||
, notification: Maybe String
|
||||
, updates : Maybe (List Update)
|
||||
, errors : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type Update
|
||||
= ItemRemoved Item
|
||||
| ItemAdded Item
|
||||
| WealthUpdated Wealth
|
||||
| ClaimAdded ()
|
||||
| ClaimRemoved ()
|
||||
|
||||
type Msg
|
||||
= GotPlayer (HttpResult Player)
|
||||
| GotClaims Int (HttpResult Claims)
|
||||
| GotLoot ToChest (HttpResult Loot)
|
||||
| GotActionResult (HttpResult Response)
|
||||
|
||||
-- PLAYERS
|
||||
--
|
||||
|
||||
fetchPlayer : Int -> Cmd Msg
|
||||
fetchPlayer id =
|
||||
Http.get
|
||||
{ url = "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/"
|
||||
, expect = Http.expectJson Main.GotPlayer (valueDecoder playerDecoder )
|
||||
}
|
||||
|
||||
playerDecoder : Decoder Player
|
||||
playerDecoder =
|
||||
D.map4 Player
|
||||
(D.field "id" int)
|
||||
(D.field "name" string)
|
||||
(D.field "debt" int)
|
||||
wealthDecoder
|
||||
|
||||
wealthDecoder : Decoder Wealth
|
||||
wealthDecoder =
|
||||
D.map4 Wealth
|
||||
(D.field "cp" int)
|
||||
(D.field "sp" int)
|
||||
(D.field "gp" int)
|
||||
(D.field "pp" int)
|
||||
|
||||
-- LOOT
|
||||
|
||||
-- Location of a loot
|
||||
type ToChest
|
||||
= OfPlayer Int
|
||||
| OfGroup
|
||||
| OfShop
|
||||
|
||||
itemDecoder =
|
||||
D.map3 Item
|
||||
(D.field "id" int)
|
||||
(D.field "name" string)
|
||||
(D.field "base_price" int)
|
||||
|
||||
lootDecoder : Decoder Loot
|
||||
lootDecoder =
|
||||
Json.Decode.list itemDecoder
|
||||
|
||||
fetchLoot : Main.ToChest -> Cmd Msg
|
||||
fetchLoot dest =
|
||||
let
|
||||
url = case dest of
|
||||
Main.OfPlayer id -> "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/loot"
|
||||
Main.OfShop -> "http://localhost:8088/api/items"
|
||||
Main.OfGroup -> "http://localhost:8088/api/players/0/loot"
|
||||
in
|
||||
Http.get
|
||||
{ url = url
|
||||
, expect = Http.expectJson (GotLoot dest) (valueDecoder lootDecoder)}
|
||||
|
||||
-- CLAIMS
|
||||
|
||||
claimDecoder =
|
||||
D.map3 Claim
|
||||
(D.field "id" int)
|
||||
(D.field "player_id" int)
|
||||
(D.field "loot_id" int)
|
||||
|
||||
fetchClaims : Int -> Cmd Msg
|
||||
fetchClaims playerId =
|
||||
Http.get
|
||||
{ url = "http://localhost:8088/api/claims"
|
||||
, expect = valueDecoder (D.list claimDecoder)
|
||||
|> Http.expectJson (GotClaims playerId)
|
||||
}
|
||||
|
||||
-- API Response
|
||||
--
|
||||
valueDecoder : Decoder a -> Decoder a
|
||||
valueDecoder thenDecoder =
|
||||
D.field "value" thenDecoder
|
||||
|
||||
-- TODO: update server to produce better json
|
||||
-- like an object with list of updates of the same type
|
||||
-- { ItemRemoved : [..], Wealth : [ .. ], .. }
|
||||
updatesDecoder : Decoder DbUpdate
|
||||
updatesDecoder =
|
||||
-- We expect one update but do not know it's kind
|
||||
Json.Decode.oneOf
|
||||
[ (field "ItemRemoved" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemRemoved i)))
|
||||
, (field "ItemAdded" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemAdded i)))
|
||||
, (field "Wealth" (wealthDecoder |> Json.Decode.andThen (\i -> succeed <| WealthUpdated i)))
|
||||
, (field "ClaimRemoved" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimRemoved i)))
|
||||
, (field "ClaimAdded" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimAdded i)))
|
||||
]
|
||||
|
||||
|
||||
apiResponseDecoder : Decoder ApiResponse
|
||||
apiResponseDecoder =
|
||||
Json.Decode.map4 ApiResponse
|
||||
(D.maybe (field "value" string))
|
||||
(Json.Decode.maybe (field "notification" string))
|
||||
(Json.Decode.maybe (field "updates" (Json.Decode.list updatesDecoder)))
|
||||
(Json.Decode.maybe (field "errors" string))
|
||||
|
||||
undoLastAction id = Http.request
|
||||
{ url = "http://localhost:8088/api/players/" ++ String.fromInt id ++"/events/last"
|
||||
, method = "DELETE"
|
||||
, headers = []
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectJson GotActionResult apiResponseDecoder
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
sendRequest : Maybe ViewMode -> Model -> Cmd Msg
|
||||
sendRequest activeMode model =
|
||||
case activeMode of
|
||||
Nothing -> Cmd.none
|
||||
Just mode ->
|
||||
let
|
||||
(endpoint, method) = case mode of
|
||||
Add ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
|
||||
, "POST"
|
||||
)
|
||||
Buy ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
|
||||
, "PUT"
|
||||
)
|
||||
Sell ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
|
||||
, "DELETE"
|
||||
)
|
||||
Grab ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/claims"
|
||||
, "POST")
|
||||
in
|
||||
Http.request
|
||||
{ method = method
|
||||
, headers = []
|
||||
, url = endpoint
|
||||
, body = Http.jsonBody <| buildPayload mode model
|
||||
, expect = Http.expectJson GotActionResult apiResponseDecoder
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
285
src/Main.elm
285
src/Main.elm
@@ -2,18 +2,15 @@ module Main exposing (..)
|
||||
|
||||
import Browser
|
||||
import Browser.Navigation as Nav
|
||||
import Platform.Cmd exposing (Cmd)
|
||||
import Url
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (..)
|
||||
import Svg.Attributes
|
||||
import Http
|
||||
import Json.Decode exposing (Decoder, field, list, string, int, succeed)
|
||||
import Json.Encode as E
|
||||
import Url.Parser as P exposing (Parser, (</>), oneOf, s)
|
||||
import Set exposing (Set)
|
||||
|
||||
import Api
|
||||
|
||||
-- Main
|
||||
|
||||
@@ -32,8 +29,6 @@ main =
|
||||
|
||||
type alias Selection = Set Int
|
||||
|
||||
emptySelection = []
|
||||
|
||||
type alias State =
|
||||
{ navKey : Nav.Key
|
||||
, route : Route
|
||||
@@ -75,13 +70,16 @@ fetchInitialData : Int -> Cmd Msg
|
||||
fetchInitialData playerId =
|
||||
Cmd.batch
|
||||
[ initPlayer playerId
|
||||
, fetchShopInventory
|
||||
, fetchGroupLoot
|
||||
, Cmd.map ApiMsg <| Api.fetchLoot Api.OfShop
|
||||
, Cmd.map ApiMsg <| Api.fetchLoot Api.OfGroup
|
||||
]
|
||||
|
||||
---
|
||||
-- PLAYER
|
||||
-- MODELS
|
||||
---
|
||||
|
||||
-- Player
|
||||
|
||||
type alias Player =
|
||||
{ id: Int
|
||||
, name: String
|
||||
@@ -92,23 +90,13 @@ type alias Player =
|
||||
blankPlayer =
|
||||
Player 0 "Loot-a-lot" 0 (Wealth 0 0 0 0)
|
||||
|
||||
|
||||
initPlayer id =
|
||||
Cmd.batch [fetchPlayer id, fetchLoot id, fetchClaims id]
|
||||
|
||||
fetchPlayer : Int -> Cmd Msg
|
||||
fetchPlayer id =
|
||||
Http.get
|
||||
{ url = "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/"
|
||||
, expect = Http.expectJson GotPlayer (valueDecoder playerDecoder )
|
||||
}
|
||||
|
||||
playerDecoder : Decoder Player
|
||||
playerDecoder =
|
||||
Json.Decode.map4 Player
|
||||
(field "id" int)
|
||||
(field "name" string)
|
||||
(field "debt" int)
|
||||
wealthDecoder
|
||||
Cmd.batch
|
||||
[ Cmd.map ApiMsg <| Api.fetchPlayer id
|
||||
, Cmd.map ApiMsg <| Api.fetchLoot (OfPlayer id)
|
||||
, Cmd.map ApiMsg <| Api.fetchClaims id
|
||||
]
|
||||
|
||||
type alias Wealth =
|
||||
{ cp: Int
|
||||
@@ -117,13 +105,9 @@ type alias Wealth =
|
||||
, pp: Int
|
||||
}
|
||||
|
||||
wealthDecoder : Decoder Wealth
|
||||
wealthDecoder =
|
||||
Json.Decode.map4 Wealth
|
||||
(field "cp" int)
|
||||
(field "sp" int)
|
||||
(field "gp" int)
|
||||
(field "pp" int)
|
||||
-- Loot
|
||||
|
||||
type alias Loot = List Item
|
||||
|
||||
type alias Item =
|
||||
{ id: Int
|
||||
@@ -131,38 +115,7 @@ type alias Item =
|
||||
, base_price: Int
|
||||
}
|
||||
|
||||
itemDecoder =
|
||||
Json.Decode.map3 Item
|
||||
(field "id" int)
|
||||
(field "name" string)
|
||||
(field "base_price" int)
|
||||
|
||||
type alias Loot =
|
||||
List Item
|
||||
|
||||
lootDecoder : Decoder Loot
|
||||
lootDecoder =
|
||||
Json.Decode.list itemDecoder
|
||||
|
||||
fetchLoot id =
|
||||
Http.get
|
||||
{ url = "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/loot"
|
||||
, expect = Http.expectJson (GotLoot OfPlayer) (valueDecoder lootDecoder)}
|
||||
|
||||
fetchShopInventory =
|
||||
Http.get
|
||||
{ url = "http://localhost:8088/api/items"
|
||||
, expect = Http.expectJson (GotLoot OfShop) (valueDecoder lootDecoder)}
|
||||
|
||||
fetchGroupLoot =
|
||||
Http.get
|
||||
{ url = "http://localhost:8088/api/players/0/loot"
|
||||
, expect = Http.expectJson (GotLoot OfGroup) (valueDecoder lootDecoder)}
|
||||
|
||||
type ToChest
|
||||
= OfPlayer
|
||||
| OfGroup
|
||||
| OfShop
|
||||
-- Claims
|
||||
|
||||
type alias Claims = List Claim
|
||||
|
||||
@@ -172,44 +125,13 @@ type alias Claim =
|
||||
, loot_id: Int
|
||||
}
|
||||
|
||||
claimDecoder =
|
||||
Json.Decode.map3 Claim
|
||||
(field "id" int)
|
||||
(field "player_id" int)
|
||||
(field "loot_id" int)
|
||||
|
||||
fetchClaims : Int -> Cmd Msg
|
||||
fetchClaims playerId =
|
||||
Http.get
|
||||
{ url = "http://localhost:8088/api/claims"
|
||||
, expect = valueDecoder (Json.Decode.list claimDecoder)
|
||||
|> Http.expectJson (GotClaims playerId)
|
||||
}
|
||||
-- API Response
|
||||
--
|
||||
valueDecoder : Decoder a -> Decoder a
|
||||
valueDecoder thenDecoder =
|
||||
field "value" thenDecoder
|
||||
|
||||
-- UPDATE
|
||||
|
||||
type alias HttpResult a = (Result Http.Error a)
|
||||
|
||||
type alias ApiResponse =
|
||||
{ value : Maybe String
|
||||
, notification : Maybe String
|
||||
, updates : Maybe (List DbUpdate)
|
||||
, errors : Maybe String
|
||||
}
|
||||
|
||||
type Msg
|
||||
= LinkClicked Browser.UrlRequest
|
||||
| UrlChanged Url.Url
|
||||
| ApiMsg Api.Msg
|
||||
| PlayerChanged Int
|
||||
| GotPlayer (HttpResult Player)
|
||||
| GotClaims Int (HttpResult Claims)
|
||||
| GotLoot ToChest (HttpResult Loot)
|
||||
| GotActionResult (HttpResult ApiResponse)
|
||||
| LootViewItemSwitched Int
|
||||
| ModeSwitched (Maybe ViewMode)
|
||||
| ConfirmAction
|
||||
@@ -248,35 +170,50 @@ update msg model =
|
||||
PlayerChanged newId ->
|
||||
( { model | player = blankPlayer }, initPlayer newId )
|
||||
|
||||
GotPlayer result ->
|
||||
case result of
|
||||
Ok player ->
|
||||
( { model | player = player }
|
||||
, Cmd.none
|
||||
)
|
||||
Err error ->
|
||||
( setError ("Fetching player... " ++ printError error) model
|
||||
, Cmd.none
|
||||
)
|
||||
ApiMsg apiMsg -> case apiMsg of
|
||||
GotActionResult response ->
|
||||
case response of
|
||||
Ok result ->
|
||||
let
|
||||
updates = Maybe.withDefault [] result.updates
|
||||
notification = result.notification
|
||||
errors = Maybe.withDefault "" result.errors
|
||||
in
|
||||
List.foldl applyUpdate model updates
|
||||
|> setNotification notification
|
||||
|> setError errors
|
||||
|> update (ModeSwitched Nothing)
|
||||
Err r -> (setError (Debug.toString r) model, Cmd.none)
|
||||
|
||||
GotClaims id result ->
|
||||
case result of
|
||||
Ok claims -> ( { model | claims = List.filter (\c -> c.player_id == id) claims}, Cmd.none )
|
||||
Err error -> ( setError ("Fetching claims..." ++ Debug.toString error) model, Cmd.none)
|
||||
GotPlayer result ->
|
||||
case result of
|
||||
Ok player ->
|
||||
( { model | player = player }
|
||||
, Cmd.none
|
||||
)
|
||||
Err error ->
|
||||
( setError ("Fetching player... " ++ printError error) model
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GotLoot dest result ->
|
||||
case result of
|
||||
Ok loot ->
|
||||
( case dest of
|
||||
OfPlayer -> { model | loot = Just loot}
|
||||
OfGroup -> { model | groupLoot = Just loot}
|
||||
OfShop -> { model | merchantItems = Just loot}
|
||||
, Cmd.none
|
||||
)
|
||||
Err error ->
|
||||
( setError ("Fetching loot... " ++ printError error) model
|
||||
, Cmd.none
|
||||
)
|
||||
GotClaims id result ->
|
||||
case result of
|
||||
Ok claims -> ( { model | claims = List.filter (\c -> c.player_id == id) claims}, Cmd.none )
|
||||
Err error -> ( setError ("Fetching claims..." ++ Debug.toString error) model, Cmd.none)
|
||||
|
||||
GotLoot dest result ->
|
||||
case result of
|
||||
Ok loot ->
|
||||
( case dest of
|
||||
Api.OfPlayer _ -> { model | loot = Just loot}
|
||||
Api.OfGroup -> { model | groupLoot = Just loot}
|
||||
Api.OfShop -> { model | merchantItems = Just loot}
|
||||
, Cmd.none
|
||||
)
|
||||
Err error ->
|
||||
( setError ("Fetching loot... " ++ printError error) model
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
LootViewItemSwitched id ->
|
||||
let
|
||||
@@ -293,17 +230,16 @@ update msg model =
|
||||
in
|
||||
( { model | state =
|
||||
{ state | activeMode = newMode
|
||||
, selection = case newMode of
|
||||
Nothing ->
|
||||
Nothing
|
||||
, selection =
|
||||
case newMode of
|
||||
Nothing ->
|
||||
Nothing
|
||||
|
||||
Just Grab -> -- Currently claimed object are initially selected
|
||||
Just ( Set.fromList
|
||||
<| List.map (\c -> c.loot_id) model.claims
|
||||
)
|
||||
Just Grab -> -- Currently claimed object are initially selected
|
||||
Just ( Set.fromList <| List.map (\c -> c.loot_id) model.claims)
|
||||
|
||||
Just others ->
|
||||
Just Set.empty
|
||||
Just others ->
|
||||
Just Set.empty
|
||||
}}
|
||||
, Cmd.none )
|
||||
|
||||
@@ -311,34 +247,11 @@ update msg model =
|
||||
let
|
||||
currentMode = model.state.activeMode
|
||||
in
|
||||
(model, sendRequest currentMode model)
|
||||
(model, Cmd.map ApiMsg Api.sendRequest currentMode model)
|
||||
|
||||
UndoLastAction ->
|
||||
let playerId = String.fromInt model.player.id
|
||||
in
|
||||
(model, Http.request
|
||||
{ url = "http://localhost:8088/api/players/" ++ playerId ++"/events/last"
|
||||
, method = "DELETE"
|
||||
, headers = []
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectJson GotActionResult apiResponseDecoder
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
})
|
||||
(model, Cmd.map ApiMsg Api.undoLastAction model.player.id)
|
||||
|
||||
GotActionResult response ->
|
||||
case response of
|
||||
Ok result ->
|
||||
let
|
||||
updates = Maybe.withDefault [] result.updates
|
||||
notification = result.notification
|
||||
errors = Maybe.withDefault "" result.errors
|
||||
in
|
||||
List.foldl applyUpdate model updates
|
||||
|> setNotification notification
|
||||
|> setError errors
|
||||
|> update (ModeSwitched Nothing)
|
||||
Err r -> (setError (Debug.toString r) model, Cmd.none)
|
||||
ClearNotification ->
|
||||
( { model | notification = Nothing }, Cmd.none )
|
||||
|
||||
@@ -409,64 +322,6 @@ applyUpdate u model =
|
||||
ClaimAdded _ -> model
|
||||
ClaimRemoved _ -> model
|
||||
|
||||
-- TODO: update server to produce better json
|
||||
-- like an object with list of updates of the same type
|
||||
-- { ItemRemoved : [..], Wealth : [ .. ], .. }
|
||||
updatesDecoder : Decoder DbUpdate
|
||||
updatesDecoder =
|
||||
-- We expect one update but do not know it's kind
|
||||
Json.Decode.oneOf
|
||||
[ (field "ItemRemoved" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemRemoved i)))
|
||||
, (field "ItemAdded" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemAdded i)))
|
||||
, (field "Wealth" (wealthDecoder |> Json.Decode.andThen (\i -> succeed <| WealthUpdated i)))
|
||||
, (field "ClaimRemoved" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimRemoved i)))
|
||||
, (field "ClaimAdded" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimAdded i)))
|
||||
]
|
||||
|
||||
|
||||
apiResponseDecoder : Decoder ApiResponse
|
||||
apiResponseDecoder =
|
||||
Json.Decode.map4 ApiResponse
|
||||
(Json.Decode.maybe (field "value" string))
|
||||
(Json.Decode.maybe (field "notification" string))
|
||||
(Json.Decode.maybe (field "updates" (Json.Decode.list updatesDecoder)))
|
||||
(Json.Decode.maybe (field "errors" string))
|
||||
|
||||
|
||||
sendRequest : Maybe ViewMode -> Model -> Cmd Msg
|
||||
sendRequest activeMode model =
|
||||
case activeMode of
|
||||
Nothing -> Cmd.none
|
||||
Just mode ->
|
||||
let
|
||||
(endpoint, method) = case mode of
|
||||
Add ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
|
||||
, "POST"
|
||||
)
|
||||
Buy ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
|
||||
, "PUT"
|
||||
)
|
||||
Sell ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
|
||||
, "DELETE"
|
||||
)
|
||||
Grab ->
|
||||
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/claims"
|
||||
, "POST")
|
||||
in
|
||||
Http.request
|
||||
{ method = method
|
||||
, headers = []
|
||||
, url = endpoint
|
||||
, body = Http.jsonBody <| buildPayload mode model
|
||||
, expect = Http.expectJson GotActionResult apiResponseDecoder
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ERRORS
|
||||
|
||||
|
||||
Reference in New Issue
Block a user