restructure the code, learning from spa-example-app

This commit is contained in:
2019-11-10 23:18:19 +01:00
parent eb29c5a24f
commit 5725d81236
7 changed files with 609 additions and 549 deletions

View File

@@ -1,53 +1,435 @@
module Chest exposing (..) module Chest exposing (..)
import Api exposing (Claims, HttpResult, Item, Loot)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onCheck) import Html.Events exposing (onCheck)
import Api exposing (HttpResult, Claims, Item, Loot)
import Modes
import Route exposing (..) import Route exposing (..)
import Set exposing (Set) import Set exposing (Set)
import Utils exposing (..) import Utils exposing (..)
-- MODEL -- MODEL
type alias State =
{ menuOpen : Bool
, mode : ActionMode
, error : Maybe String
, notification : Maybe String
}
type alias Selection =
Set Int
type ActionMode
= Sell
| Buy
| Grab
| Add
| NoMode
type alias Model = type alias Model =
{ loot : Loot { state : State
, shown : Route.ChestContent
, playerLoot : Loot
, groupLoot : Loot , groupLoot : Loot
, merchantItems : Loot , merchantLoot : Loot
, newLoot : Loot , newLoot : Loot
, selection : Maybe Selection , selection : Maybe Selection
, claims : Claims , claims : Claims
} }
init : Int -> ( Model, Cmd Msg )
init playerId =
( Model
(State False Modes.None Nothing Nothing)
[]
[]
[]
[]
Nothing
[]
, Api.fetchClaims (GotClaims playerId)
)
viewNotification : Maybe String -> Html Msg
viewNotification notification =
case notification of
Just t ->
div [ class "notification is-success is-marginless" ]
[ button [ class "delete", onClick ClearNotification ] []
, text t
]
Nothing ->
text ""
-- DEBUG SECTION
viewDebugSection : Model -> Html Msg
viewDebugSection model =
div [ class "panel is-danger" ]
[ p [ class "panel-heading" ] [ text "Debug" ]
, debugSwitchPlayers
, p [ class "panel-block has-text-danger" ] [ text <| Maybe.withDefault "" model.state.error ]
, p [ class "panel-block" ] [ text ("Route : " ++ Debug.toString model.route) ]
, p [ class "panel-block" ] [ text ("Active Mode : " ++ Debug.toString model.mode) ]
, p [ class "panel-block" ] [ text ("Selection : " ++ Debug.toString model.chest.selection) ]
, p [ class "panel-block" ] [ text ("Claims : " ++ Debug.toString model.chest.claims) ]
, p [] debugSandbox
]
stackedIcon name =
span [ class "icon is-medium" ]
[ span [ class "fa-stack" ]
[ i [ class "fas fa-circle fa-stack-2x" ] []
, i [ class (name ++ " fa-inverse fa-stack-1x") ] []
, text ""
]
]
debugSandbox =
[ stackedIcon "fas fa-coins"
, stackedIcon "fab fa-d-and-d"
, stackedIcon "fas fa-praying-hands"
, stackedIcon "fas fa-gem"
, stackedIcon "fas fa-pen"
, stackedIcon "fas fa-percentage"
, stackedIcon "fas fa-store-alt"
, stackedIcon "fas fa-cart-plus"
, stackedIcon "fas fa-angry"
, stackedIcon "fas fa-plus"
, stackedIcon "fas fa-tools"
, stackedIcon "fas fa-search"
]
debugSwitchPlayers : Html Msg
debugSwitchPlayers =
div [ class "panel-tabs" ]
[ a [ onClick (PlayerChanged 0) ] [ text "Groupe" ]
, a [ onClick (PlayerChanged 1) ] [ text "Lomion" ]
, a [ onClick (PlayerChanged 2) ] [ text "Fefi" ]
]
-- HEADER SECTION
viewHeaderBar : String -> Model -> Html Msg
viewHeaderBar title model =
nav [ class "navbar container", class "is-info" ]
[ div [ class "navbar-brand" ]
[ a [ class "navbar-item", href "/" ]
[ renderIcon { icon = "fab fa-d-and-d", size = "medium", ratio = "2x" }
, span [] [ text model.player.name ]
]
, a [ class "navbar-burger is-active" ]
[ span [ attribute "aria-hidden" "true" ] []
, span [ attribute "aria-hidden" "true" ] []
, span [ attribute "aria-hidden" "true" ] []
]
]
, div [ class "navbar-menu is-active" ]
[ div [ class "navbar-end" ]
[ a [ class "navbar-item", href "/marchand" ]
[ renderIcon { icon = "fas fa-store-alt", ratio = "1x", size = "medium" }
, span [] [ text "Marchand" ]
]
, a
[ class "navbar-item"
, href
(if model.player.id == 0 then
"/nouveau-tresor"
else
"/coffre"
)
]
[ renderIcon { icon = "fas fa-gem", ratio = "1x", size = "medium" }
, span []
[ text
(if model.player.id == 0 then
"Nouveau loot"
else
"Coffre de groupe"
)
]
]
]
]
]
-- PLAYER BAR
viewPlayerBar : Player -> List (Html Msg) -> Html Msg
viewPlayerBar player actionControls =
section [ class "level container is-mobile box" ]
[ div [ class "level-left" ]
([ div [ class "level-item" ]
[ span [ class "icon is-large" ]
[ i [ class "fas fa-2x fa-piggy-bank" ] [] ]
]
]
++ viewWealth player.wealth
++ (if player.debt > 0 then
[ div [ class "level-item" ]
[ p [ class "heading is-size-4 has-text-danger" ]
[ text ("Dette : " ++ String.fromInt player.debt ++ "po") ]
]
]
else
[]
)
)
, div [ class "level-right" ] actionControls
]
viewWealth : Wealth -> List (Html Msg)
viewWealth wealth =
[ showWealthField "pp" <| String.fromInt wealth.pp
, showWealthField "gp" <| String.padLeft 2 '0' <| String.fromInt wealth.gp
, showWealthField "sp" <| String.fromInt wealth.sp
, showWealthField "cp" <| String.fromInt wealth.cp
]
showWealthField : String -> String -> Html Msg
showWealthField name value =
div [ class "level-item" ]
[ p [ class "has-text-right" ]
[ strong [ class "heading is-marginless" ] [ text name ]
, span [ class <| "is-size-4" ] [ text value ]
]
]
-- UPDATE
initPlayer id =
Cmd.map ApiMsg <| Api.fetchPlayer id
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
PlayerChanged newId ->
let
( chest, cmd ) =
Chest.init newId
in
( { model
| player = Api.blankPlayer
, route = PlayerChest
, chest = chest
}
, Cmd.batch
[ initPlayer newId
, Cmd.map ChestMsg cmd
]
)
ApiMsg apiMsg ->
case apiMsg of
Api.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 (ModeMsg (Modes.ModeSwitched Modes.None))
Err r ->
( setError (Debug.toString r) model, Cmd.none )
Api.GotPlayer result ->
case result of
Ok player ->
( { model | player = player }
, Cmd.none
)
Err error ->
( setError ("Fetching player... " ++ Debug.toString error) model
, Cmd.none
)
ModeMsg modeMsg ->
case modeMsg of
Modes.ModeSwitched newMode ->
( { model
| mode = newMode
, chest =
let
( newChest, _ ) =
Chest.update
(Chest.SetSelection
(case newMode of
Modes.None ->
Nothing
Modes.Grab ->
-- Currently claimed object are initially selected
Just (Set.fromList <| List.map (\c -> c.loot_id) model.chest.claims)
others ->
Just Set.empty
)
)
model.chest
in
newChest
}
, Cmd.none
)
Modes.ConfirmAction ->
case model.mode of
-- This should not happen, so we ignore it
Modes.None ->
( model, Cmd.none )
mode ->
let
items =
Chest.getSelected model.route model.chest
in
( model
, Cmd.map ApiMsg <|
Api.sendRequest
mode
(String.fromInt model.player.id)
items
)
ClearNotification ->
( setNotification Nothing model, Cmd.none )
setNotification : Maybe String -> Model -> Model
setNotification notification model =
let
state =
model.state
in
{ model
| state =
{ state | notification = notification }
}
setLoot : Loot -> Model -> Model
setLoot items model =
let
chest =
model.chest
in
{ model | chest = { chest | loot = items } }
setClaims : Claims -> Model -> Model
setClaims claims model =
let
chest =
model.chest
in
{ model | chest = { chest | claims = claims } }
-- DbUpdates always refer to the active player's loot
applyUpdate : Api.Update -> Model -> Model
applyUpdate u model =
case u of
Api.ItemRemoved item ->
model
|> setLoot (List.filter (\i -> i.id /= item.id) model.chest.loot)
Api.ItemAdded item ->
model |> setLoot (item :: model.chest.loot)
Api.WealthUpdated diff ->
let
player =
model.player
wealth =
player.wealth
in
{ model
| player =
{ player
| wealth =
Wealth
(wealth.cp + diff.cp)
(wealth.sp + diff.sp)
(wealth.gp + diff.gp)
(wealth.pp + diff.pp)
}
}
Api.ClaimAdded claim ->
model |> setClaims (claim :: model.chest.claims)
Api.ClaimRemoved claim ->
model
|> setClaims (List.filter (\c -> c.id /= claim.id) model.chest.claims)
-- ERRORS
setError : String -> Model -> Model
setError error model =
let
state =
model.state
in
{ model
| state =
{ state | error = Just error }
}
type ToChest type ToChest
= OfPlayer Int = OfPlayer Int
| OfGroup | OfGroup
| OfShop | OfShop
type alias Selection =
Set Int
init : Int -> (Model, Cmd Msg)
init playerId =
( { loot = []
, groupLoot = []
, merchantItems = []
, newLoot = []
, selection = Nothing
, claims = []
}
, Cmd.batch
[ fetchLoot OfShop
, fetchLoot OfGroup
, fetchLoot (OfPlayer playerId)
, Api.fetchClaims (GotClaims playerId)
]
)
fetchLoot : ToChest -> Cmd Msg fetchLoot : ToChest -> Cmd Msg
fetchLoot dest = fetchLoot dest =
@@ -63,13 +445,20 @@ fetchLoot dest =
OfGroup -> OfGroup ->
"http://localhost:8088/api/players/0/loot" "http://localhost:8088/api/players/0/loot"
in in
Api.fetchLoot url (GotLoot dest) Api.fetchLoot url (GotLoot dest)
-- VIEW -- VIEW
view : Modes.Model -> Route.Route -> Model -> Html Msg view : Modes.Model -> Route.Route -> Model -> Html Msg
view mode route model = view mode route model =
let let
renderControls =
Modes.viewControls model.mode model.route
|> List.map (Html.map ModeMsg)
( header, shownItems ) = ( header, shownItems ) =
case route of case route of
Route.PlayerChest -> Route.PlayerChest ->
@@ -84,7 +473,6 @@ view mode route model =
Route.NewLoot -> Route.NewLoot ->
( "Nouveau trésor :)", [] ) ( "Nouveau trésor :)", [] )
isSelected = isSelected =
itemInSelection model.selection itemInSelection model.selection
@@ -94,7 +482,8 @@ view mode route model =
case route of case route of
Route.GroupLoot -> Route.GroupLoot ->
let let
isClaimed = itemInClaims model.claims isClaimed =
itemInClaims model.claims
in in
-- Claim controls for Group chest -- Claim controls for Group chest
Just (claimedItemRenderer isClaimed) Just (claimedItemRenderer isClaimed)
@@ -105,38 +494,50 @@ view mode route model =
activeMode -> activeMode ->
Just (rowRendererForMode isSelected activeMode) Just (rowRendererForMode isSelected activeMode)
in in
article [ viewHeaderBar player.name model
[ class "section" ] , viewPlayerBar model.player renderControls
[ div [ class "columns"] , main_
[ div [ class "column is-one-third" ] [ p [ class "title" ] [ text header ] ] [ class "container" ]
, div [ class "column" ] [ viewSearchBar ] [ viewNotification model.state.notification
] , article
, table [ class "table is-fullwidth is-striped is-hoverable" ] [ class "section" ]
[ thead [ class "table-header" ] [ div [ class "columns" ]
[ th [] [ text "Nom" ] ] [ div [ class "column is-one-third" ] [ p [ class "title" ] [ text header ] ]
, tbody [] <| List.map (viewItemTableRow isSelected rowRenderer) shownItems , div [ class "column" ] [ viewSearchBar ]
]
, table [ class "table is-fullwidth is-striped is-hoverable" ]
[ thead [ class "table-header" ]
[ th [] [ text "Nom" ] ]
, tbody [] <| List.map (viewItemTableRow isSelected rowRenderer) shownItems
]
] ]
] ]
, hr [] []
, section [ class "container" ] [ viewDebugSection model ]
]
-- Renderers -- Renderers
-- --
-- Item -> Html Msg -- Item -> Html Msg
claimedItemRenderer : (Item -> Bool) -> Item -> Html Msg claimedItemRenderer : (Item -> Bool) -> Item -> Html Msg
claimedItemRenderer isClaimed item = claimedItemRenderer isClaimed item =
case isClaimed item of case isClaimed item of
True -> True ->
renderIcon renderIcon
{ icon = "fas fa-praying-hands" { icon = "fas fa-praying-hands"
, size = "small" , size = "small"
, ratio = "1x" , ratio = "1x"
} }
False -> False ->
text "" text ""
rowRendererForMode: (Item -> Bool) -> Modes.Model -> Item -> Html Msg rowRendererForMode : (Item -> Bool) -> Modes.Model -> Item -> Html Msg
rowRendererForMode isSelected mode item = rowRendererForMode isSelected mode item =
let let
canSelect = canSelect =
@@ -156,7 +557,8 @@ rowRendererForMode isSelected mode item =
Modes.Add -> Modes.Add ->
p [ class "level-item" ] [ text "New !" ] p [ class "level-item" ] [ text "New !" ]
Modes.None -> text "" Modes.None ->
text ""
in in
div [ class "level-right" ] <| div [ class "level-right" ] <|
renderInfo renderInfo
@@ -192,6 +594,8 @@ viewItemTableRow isSelected rowRenderer item =
] ]
] ]
-- Search Bar -- Search Bar
@@ -199,19 +603,75 @@ viewSearchBar : Html Msg
viewSearchBar = viewSearchBar =
div [ class "field" ] div [ class "field" ]
[ p [ class "control has-icons-left" ] [ p [ class "control has-icons-left" ]
[ input [ class "input" ] [] [ input [ class "input" ] []
, span [ class "icon is-left" ] [ i [ class "fas fa-search" ] [] ] , span [ class "icon is-left" ] [ i [ class "fas fa-search" ] [] ]
] ]
] ]
-- ACTION MODES
--
canSelectIn : ActionMode -> Bool
canSelectIn mode =
case mode of
Sell ->
True
Buy ->
True
Grab ->
True
Add ->
False
NoMode ->
False
viewControls : ActionMode -> Route.Route -> List (Html Msg)
viewControls mode route =
case mode of
None ->
case route of
Route.PlayerChest ->
[ actionButton (ModeSwitched Sell) "Vendre" "coins" "danger" ]
Route.GroupLoot ->
[ actionButton (ModeSwitched Grab) "Demander" "praying-hands" "primary" ]
Route.Merchant ->
[ actionButton (ModeSwitched Buy) "Acheter" "coins" "success" ]
Route.NewLoot ->
[ actionButton (ModeSwitched Add) "Nouveau loot" "plus" "primary" ]
m ->
[ actionButton ConfirmAction "Valider" "check" "primary"
, actionButton (ModeSwitched None) "Annuler" "times" "danger"
]
-- UPDATE -- UPDATE
type Msg
= ApiMsg Api.Msg
| ModeMsg Modes.Msg
| PlayerChanged Int
| ClearNotification
type Msg type Msg
= SetSelection (Maybe Selection) = SetSelection (Maybe Selection)
| GotLoot ToChest (HttpResult Loot) | GotLoot ToChest (HttpResult Loot)
| GotClaims Int (HttpResult Claims) | GotClaims Int (HttpResult Claims)
| SwitchSelectionState Int | SwitchSelectionState Int
| ModeSwitched ActionMode
| ConfirmAction
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
@@ -226,10 +686,11 @@ update msg model =
GotClaims id result -> GotClaims id result ->
case result of case result of
Ok claims -> Ok claims ->
( { model | claims = ( { model
List.filter | claims =
(\c -> c.player_id == id) List.filter
claims (\c -> c.player_id == id)
claims
} }
, Cmd.none , Cmd.none
) )
@@ -253,12 +714,14 @@ update msg model =
) )
Err error -> Err error ->
( model , Cmd.none) ( model, Cmd.none )
-- Selection -- Selection
-- Get list of selected items -- Get list of selected items
getSelected : Route -> Model -> Loot getSelected : Route -> Model -> Loot
getSelected route model = getSelected route model =
targetItemsFor route model targetItemsFor route model

View File

@@ -8,13 +8,12 @@ import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (..) import Html.Events exposing (..)
import Json.Encode as E import Json.Encode as E
import Modes
import Route exposing (..) import Route exposing (..)
import Set exposing (Set) import Set exposing (Set)
import Svg.Attributes import Svg.Attributes
import Url import Url
import Utils exposing (..) import Utils exposing (..)
import Session exposing (..)
-- Main -- Main
@@ -35,55 +34,35 @@ main =
-- Model -- Model
type Model
= Chest Chest.Model
| Admin Admin.Model
| About
type alias State = -- This is not what we really want.
{ menuOpen : Bool -- The flags will be a Maybe Int (id of logged in player), so
, error : Maybe String -- in case there is no player logged in, we need to display
, notification : Maybe String -- a "Home" page
} -- This mean Chest cannot be initiated right away, and many model
-- fields are useless.
--
type alias Model = -- A User can :
{ state : State -- - not be logged in -> See About page
, navKey : Nav.Key -- - just loggend in -> See Loading page then Chest
, route : Route -- - coming back being still logged in -> See Chest (or same as above)
, mode : Modes.Model init : Maybe Int -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
, player : Player
, chest : Chest.Model
}
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key = init flags url key =
let case flags of
route = Just id ->
case routeParser url of let
Just r -> session =
r Session.LoggedIn key <| Session.User.Player id
in
(Chest <| Chest.init id, Cmd.none)
Nothing -> Nothing ->
PlayerChest (About, Cmd.none)
(chest, cmd) =
Chest.init 0
in
( Model
(State False Nothing Nothing)
key
route
Modes.init
Api.blankPlayer
chest
, Cmd.batch
[ initPlayer 0
, Cmd.map ChestMsg cmd
]
)
initPlayer id =
Cmd.map ApiMsg <| Api.fetchPlayer id
--- ---
@@ -93,206 +72,26 @@ initPlayer id =
view : Model -> Browser.Document Msg view : Model -> Browser.Document Msg
view model = view model =
let let
renderControls = (title, body) =
Modes.viewControls model.mode model.route case model of
|> List.map (Html.map ModeMsg) Chest chest ->
("Loot-a-lot", Chest.view chest)
Admin session ->
("Administration", Admin.view session)
About ->
("A propos", p [] ["A propos"])
in in
{ title = "Loot-a-lot in ELM" { title = title
, body = , body = body }
[ viewHeaderBar model
, viewPlayerBar model.player renderControls
, main_
[ class "container" ]
[ viewNotification model.state.notification
, Chest.view
model.mode
model.route
model.chest
|> Html.map ChestMsg
]
, hr [] []
, section [ class "container" ] [ viewDebugSection model ]
]
}
viewNotification : Maybe String -> Html Msg
viewNotification notification =
case notification of
Just t ->
div [ class "notification is-success is-marginless" ]
[ button [ class "delete", onClick ClearNotification ] []
, text t
]
Nothing ->
text ""
-- DEBUG SECTION
viewDebugSection : Model -> Html Msg
viewDebugSection model =
div [ class "panel is-danger" ]
[ p [ class "panel-heading" ] [ text "Debug" ]
, debugSwitchPlayers
, p [ class "panel-block has-text-danger" ] [ text <| Maybe.withDefault "" model.state.error ]
, p [ class "panel-block" ] [ text ("Route : " ++ Debug.toString model.route) ]
, p [ class "panel-block" ] [ text ("Active Mode : " ++ Debug.toString model.mode) ]
, p [ class "panel-block" ] [ text ("Selection : " ++ Debug.toString model.chest.selection) ]
, p [ class "panel-block" ] [ text ("Claims : " ++ Debug.toString model.chest.claims) ]
, p [] debugSandbox
]
stackedIcon name =
span [ class "icon is-medium" ]
[ span [ class "fa-stack" ]
[ i [ class "fas fa-circle fa-stack-2x" ] []
, i [ class (name ++ " fa-inverse fa-stack-1x") ] []
, text ""
]
]
debugSandbox =
[ stackedIcon "fas fa-coins"
, stackedIcon "fab fa-d-and-d"
, stackedIcon "fas fa-praying-hands"
, stackedIcon "fas fa-gem"
, stackedIcon "fas fa-pen"
, stackedIcon "fas fa-percentage"
, stackedIcon "fas fa-store-alt"
, stackedIcon "fas fa-cart-plus"
, stackedIcon "fas fa-angry"
, stackedIcon "fas fa-plus"
, stackedIcon "fas fa-tools"
, stackedIcon "fas fa-search"
]
debugSwitchPlayers : Html Msg
debugSwitchPlayers =
div [ class "panel-tabs" ]
[ a [ onClick (PlayerChanged 0) ] [ text "Groupe" ]
, a [ onClick (PlayerChanged 1) ] [ text "Lomion" ]
, a [ onClick (PlayerChanged 2) ] [ text "Fefi" ]
]
-- HEADER SECTION
viewHeaderBar : Model -> Html Msg
viewHeaderBar model =
nav [ class "navbar container", class "is-info" ]
[ div [ class "navbar-brand" ]
[ a [ class "navbar-item", href "/" ]
[ renderIcon { icon = "fab fa-d-and-d", size = "medium", ratio = "2x" }
, span [] [ text model.player.name ]
]
, a [ class "navbar-burger is-active" ]
[ span [ attribute "aria-hidden" "true" ] []
, span [ attribute "aria-hidden" "true" ] []
, span [ attribute "aria-hidden" "true" ] []
]
]
, div [ class "navbar-menu is-active" ]
[ div [ class "navbar-end" ]
[ a [ class "navbar-item", href "/marchand" ]
[ renderIcon { icon = "fas fa-store-alt", ratio = "1x", size = "medium" }
, span [] [ text "Marchand" ]
]
, a
[ class "navbar-item"
, href
(if model.player.id == 0 then
"/nouveau-tresor"
else
"/coffre"
)
]
[ renderIcon { icon = "fas fa-gem", ratio = "1x", size = "medium" }
, span []
[ text
(if model.player.id == 0 then
"Nouveau loot"
else
"Coffre de groupe"
)
]
]
]
]
]
-- PLAYER BAR
viewPlayerBar : Player -> List (Html Msg) -> Html Msg
viewPlayerBar player actionControls =
section [ class "level container is-mobile box" ]
[ div [ class "level-left" ]
([ div [ class "level-item" ]
[ span [ class "icon is-large" ]
[ i [ class "fas fa-2x fa-piggy-bank" ] [] ]
]
]
++ viewWealth player.wealth
++ (if player.debt > 0 then
[ div [ class "level-item" ]
[ p [ class "heading is-size-4 has-text-danger" ]
[ text ("Dette : " ++ String.fromInt player.debt ++ "po") ]
]
]
else
[]
)
)
, div [ class "level-right" ] actionControls
]
viewWealth : Wealth -> List (Html Msg)
viewWealth wealth =
[ showWealthField "pp" <| String.fromInt wealth.pp
, showWealthField "gp" <| String.padLeft 2 '0' <| String.fromInt wealth.gp
, showWealthField "sp" <| String.fromInt wealth.sp
, showWealthField "cp" <| String.fromInt wealth.cp
]
showWealthField : String -> String -> Html Msg
showWealthField name value =
div [ class "level-item" ]
[ p [ class "has-text-right" ]
[ strong [ class "heading is-marginless" ] [ text name ]
, span [ class <| "is-size-4" ] [ text value ]
]
]
-- UPDATE
type Msg type Msg
= LinkClicked Browser.UrlRequest = UrlChanged Url.Url
| UrlChanged Url.Url | LinkClicked Browser.UrlRequest
| ApiMsg Api.Msg | GotChestMsg Chest.Msg
| ChestMsg Chest.Msg | GotAdminMsg Admin.Msg
| ModeMsg Modes.Msg
| PlayerChanged Int
| ClearNotification
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
case msg of case msg of
LinkClicked urlRequest -> LinkClicked urlRequest ->
@@ -326,188 +125,12 @@ update msg model =
Nothing -> Nothing ->
( setError "Invalid route" model, Cmd.none ) ( setError "Invalid route" model, Cmd.none )
PlayerChanged newId -> GotChestMsg chestMsg ->
( { model | player = Api.blankPlayer }, initPlayer newId )
ChestMsg chestMsg ->
let let
( chest, _ ) = ( chest, cmd ) =
Chest.update chestMsg model.chest Chest.update chestMsg model.chest
in in
( { model | chest = chest }, Cmd.none ) ( Chest chest, Cmd.map GotChestMsg cmd )
ApiMsg apiMsg ->
case apiMsg of
Api.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 (ModeMsg (Modes.ModeSwitched Modes.None))
Err r ->
( setError (Debug.toString r) model, Cmd.none )
Api.GotPlayer result ->
case result of
Ok player ->
( { model | player = player }
, Cmd.none
)
Err error ->
( setError ("Fetching player... " ++ Debug.toString error) model
, Cmd.none
)
ModeMsg modeMsg ->
case modeMsg of
Modes.ModeSwitched newMode ->
( { model
| mode = newMode
, chest =
let
( newChest, _ ) =
Chest.update
(Chest.SetSelection
(case newMode of
Modes.None ->
Nothing
Modes.Grab ->
-- Currently claimed object are initially selected
Just (Set.fromList <| List.map (\c -> c.loot_id) model.chest.claims)
others ->
Just Set.empty
)
)
model.chest
in
newChest
}
, Cmd.none
)
Modes.ConfirmAction ->
case model.mode of
-- This should not happen, so we ignore it
Modes.None ->
(model, Cmd.none)
mode ->
let
items =
Chest.getSelected model.route model.chest
in
( model
, Cmd.map ApiMsg <|
Api.sendRequest
mode
(String.fromInt model.player.id)
items
)
ClearNotification ->
( setNotification Nothing model, Cmd.none )
setNotification : Maybe String -> Model -> Model
setNotification notification model =
let
state =
model.state
in
{ model
| state =
{ state | notification = notification }
}
setLoot : Loot -> Model -> Model
setLoot items model =
let
chest =
model.chest
in
{ model | chest = { chest | loot = items } }
setClaims : Claims -> Model -> Model
setClaims claims model =
let
chest = model.chest
in
{ model | chest = { chest | claims = claims } }
-- DbUpdates always refer to the active player's loot
applyUpdate : Api.Update -> Model -> Model
applyUpdate u model =
case u of
Api.ItemRemoved item ->
model
|> setLoot (List.filter (\i -> i.id /= item.id) model.chest.loot)
Api.ItemAdded item ->
model |> setLoot (item :: model.chest.loot)
Api.WealthUpdated diff ->
let
player =
model.player
wealth =
player.wealth
in
{ model
| player =
{ player
| wealth =
Wealth
(wealth.cp + diff.cp)
(wealth.sp + diff.sp)
(wealth.gp + diff.gp)
(wealth.pp + diff.pp)
}
}
Api.ClaimAdded claim ->
model |> setClaims (claim :: model.chest.claims)
Api.ClaimRemoved claim ->
model
|> setClaims (List.filter (\c -> c.id /= claim.id) model.chest.claims)
-- ERRORS
setError : String -> Model -> Model
setError error model =
let
state =
model.state
in
{ model
| state =
{ state | error = Just error }
}
-- STATE Utils -- STATE Utils
-- SUBSCRIPTIONS -- SUBSCRIPTIONS

View File

@@ -1,59 +0,0 @@
module Modes exposing (..)
import Route
import Html exposing (..)
import Html.Attributes exposing (..)
import Utils exposing(actionButton)
type Model
= Sell
| Buy
| Grab
| Add
| None
init =
None
type Msg
= ModeSwitched Model
| ConfirmAction
canSelectIn : Model -> Bool
canSelectIn mode =
case mode of
Sell ->
True
Buy ->
True
Grab ->
True
Add ->
False
None ->
False
viewControls : Model -> Route.Route -> List (Html Msg)
viewControls mode route =
case mode of
None ->
case route of
Route.PlayerChest ->
[ actionButton (ModeSwitched Sell) "Vendre" "coins" "danger" ]
Route.GroupLoot ->
[ actionButton (ModeSwitched Grab) "Demander" "praying-hands" "primary" ]
Route.Merchant ->
[ actionButton (ModeSwitched Buy) "Acheter" "coins" "success" ]
Route.NewLoot ->
[ actionButton (ModeSwitched Add) "Nouveau loot" "plus" "primary" ]
m ->
[ actionButton ConfirmAction "Valider" "check" "primary"
, actionButton (ModeSwitched None) "Annuler" "times" "danger"
]

6
src/Page/Chest.elm Normal file
View File

@@ -0,0 +1,6 @@
module Page.Chest exposing (..)
-- Put the rest of Chest here
init =
()

7
src/Page/LoggedOut.elm Normal file
View File

@@ -0,0 +1,7 @@
module Page.LoggedOut exposing (view)
import Html exposing (..)
import Html.Attributes exposing (..)
view =
p [ class "header is-1" ] [ text "Loot-a-lot" ]

View File

@@ -2,24 +2,32 @@ module Route exposing(..)
import Url import Url
import Url.Parser as P exposing (Parser, (</>), oneOf, s) import Url.Parser as P exposing (Parser, (</>), oneOf, s)
---
-- ROUTES
---
type Route -- ROUTES
= PlayerChest
| Merchant type ChestContent
= PlayerLoot
| MerchantLoot
| GroupLoot | GroupLoot
| NewLoot | NewLoot
routeParser : Url.Url -> Maybe Route type Route
routeParser url = = Home ChestContent
P.parse | About
(oneOf | Admin
[ P.map GroupLoot (P.s "coffre")
, P.map PlayerChest P.top
, P.map Merchant (P.s "marchand") parser : P.Parser (Route -> a) a
, P.map NewLoot (P.s "nouveau-tresor") parser =
oneOf
[ P.map (Home PlayerLoot) P.top
, P.map (Home GroupLoot) (P.s "coffre")
, P.map (Home MerchantLoot) (P.s "marchand")
, P.map (Home NewLoot) (P.s "nouveau-tresor")
, P.map About (P.s "about")
, P.map Admin (P.s "admin")
] ]
)
url fromUrl : Url.Url -> Maybe Route
fromUrl url =
P.parse parser url

12
src/Session.elm Normal file
View File

@@ -0,0 +1,12 @@
module Session exposing (..)
import Browser.Navigation as Nav
type User
= Player Int
| Admin
type Model
= LoggedIn Nav.Key User
| LoggedOut Nav.Key