Get game passwords and public/private games working, errors better represented client-side, HttpData rework.
This commit is contained in:
@@ -16,7 +16,7 @@ import MassiveDecks.Card.Source.Cardcast as Cardcast
|
||||
import MassiveDecks.Card.Source.Fake as Fake
|
||||
import MassiveDecks.Card.Source.Methods exposing (..)
|
||||
import MassiveDecks.Card.Source.Model exposing (..)
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form.Message exposing (Message)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
@@ -52,7 +52,7 @@ name source =
|
||||
() |> (methods source |> .name)
|
||||
|
||||
|
||||
validate : Source -> Maybe (Components.Message msg)
|
||||
validate : Source -> Maybe (Message msg)
|
||||
validate source =
|
||||
() |> (methods source |> .problem)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import Html.Events as HtmlE
|
||||
import MassiveDecks.Card.Source.Cardcast.Model exposing (..)
|
||||
import MassiveDecks.Card.Source.Methods as Source
|
||||
import MassiveDecks.Card.Source.Model as Source exposing (Source)
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form.Message as Message exposing (Message)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
@@ -46,10 +46,10 @@ equals (PlayCode pc) source =
|
||||
False
|
||||
|
||||
|
||||
problem : PlayCode -> Maybe (Components.Message msg)
|
||||
problem : PlayCode -> Maybe (Message msg)
|
||||
problem (PlayCode pc) =
|
||||
if String.isEmpty pc then
|
||||
Strings.CardcastEmptyPlayCode |> Components.info |> Just
|
||||
Strings.CardcastEmptyPlayCode |> Message.info |> Just
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
@@ -2,14 +2,14 @@ module MassiveDecks.Card.Source.Methods exposing (Methods)
|
||||
|
||||
import Html exposing (Html)
|
||||
import MassiveDecks.Card.Source.Model exposing (..)
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form.Message exposing (Message)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
|
||||
|
||||
{-| A collection of methods applied to the source data.
|
||||
-}
|
||||
type alias Methods msg =
|
||||
{ problem : () -> Maybe (Components.Message msg)
|
||||
{ problem : () -> Maybe (Message msg)
|
||||
, details : () -> Details
|
||||
, tooltip : () -> Maybe ( String, Html msg )
|
||||
, logo : () -> Maybe (Html msg)
|
||||
|
||||
@@ -1,109 +1,28 @@
|
||||
module MassiveDecks.Components exposing
|
||||
( Fix
|
||||
, Message
|
||||
, Severity(..)
|
||||
, error
|
||||
, errorWithFix
|
||||
, floatingActionButton
|
||||
, formSection
|
||||
( floatingActionButton
|
||||
, iconButton
|
||||
, iconButtonStyled
|
||||
, info
|
||||
, linkButton
|
||||
, message
|
||||
, warning
|
||||
)
|
||||
|
||||
{-| Reusable interface elements.
|
||||
-}
|
||||
|
||||
import FontAwesome.Attributes as Icon
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import FontAwesome.Solid as Icon
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Attributes.Aria as Aria
|
||||
import Html.Events as HtmlE
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
{-| A section containing inputs and messages..
|
||||
{-| Something that looks like a link but is actually a button suitable for handling events on click.
|
||||
-}
|
||||
formSection : Shared -> String -> Html msg -> List (Message msg) -> Html msg
|
||||
formSection shared id component messages =
|
||||
Html.div [ HtmlA.id id, HtmlA.class "form-section" ] (component :: (messages |> List.filterMap (message shared)))
|
||||
|
||||
|
||||
type alias Fix msg =
|
||||
{ text : MdString
|
||||
, msg : msg
|
||||
}
|
||||
|
||||
|
||||
type Severity
|
||||
= Info
|
||||
| Warning
|
||||
| Error
|
||||
|
||||
|
||||
type alias Message msg =
|
||||
Maybe (InternalMessage msg)
|
||||
|
||||
|
||||
message : Shared -> Message msg -> Maybe (Html msg)
|
||||
message shared msg =
|
||||
msg |> Maybe.map (internalMessage shared)
|
||||
|
||||
|
||||
info : MdString -> Message msg
|
||||
info mdString =
|
||||
Just
|
||||
{ severity = Info
|
||||
, description = mdString
|
||||
, fix = Nothing
|
||||
}
|
||||
|
||||
|
||||
warning : MdString -> Message msg
|
||||
warning mdString =
|
||||
Just
|
||||
{ severity = Warning
|
||||
, description = mdString
|
||||
, fix = Nothing
|
||||
}
|
||||
|
||||
|
||||
error : MdString -> Message msg
|
||||
error mdString =
|
||||
Just
|
||||
{ severity = Error
|
||||
, description = mdString
|
||||
, fix = Nothing
|
||||
}
|
||||
|
||||
|
||||
errorWithFix : MdString -> MdString -> msg -> Message msg
|
||||
errorWithFix errorString fixString fix =
|
||||
Just
|
||||
{ severity = Error
|
||||
, description = errorString
|
||||
, fix = Just { text = fixString, msg = fix }
|
||||
}
|
||||
|
||||
|
||||
linkButton : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
linkButton attrs contents =
|
||||
Html.span (HtmlA.class "link-button" :: Aria.role "button" :: HtmlA.tabindex 0 :: attrs) contents
|
||||
|
||||
|
||||
|
||||
--Html.button (HtmlA.class "link-button" :: attrs) contents
|
||||
|
||||
|
||||
{-| A button that is just an icon.
|
||||
-}
|
||||
iconButton : List (Html.Attribute msg) -> Icon -> Html msg
|
||||
@@ -124,42 +43,3 @@ Only one of these should exist on screen at any time.
|
||||
floatingActionButton : List (Html.Attribute msg) -> Icon -> Html msg
|
||||
floatingActionButton attrs icon =
|
||||
Wl.button (List.concat [ [ WlA.fab ], attrs ]) [ Icon.view icon ]
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
type alias InternalMessage msg =
|
||||
{ severity : Severity
|
||||
, description : MdString
|
||||
, fix : Maybe (Fix msg)
|
||||
}
|
||||
|
||||
|
||||
internalMessage : Shared -> InternalMessage msg -> Html msg
|
||||
internalMessage shared { severity, description, fix } =
|
||||
let
|
||||
( class, icon ) =
|
||||
case severity of
|
||||
Info ->
|
||||
( "info", Icon.infoCircle )
|
||||
|
||||
Warning ->
|
||||
( "warning", Icon.exclamationTriangle )
|
||||
|
||||
Error ->
|
||||
( "inline-error", Icon.exclamationCircle )
|
||||
|
||||
fixLink =
|
||||
case fix of
|
||||
Just { text, msg } ->
|
||||
[ Html.text " ", linkButton [ msg |> HtmlE.onClick ] [ text |> Lang.html shared ] ]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
in
|
||||
Html.span [ HtmlA.class class ]
|
||||
[ Icon.viewStyled [ Icon.fw ] icon
|
||||
, Html.span [] ((description |> Lang.html shared) :: fixLink)
|
||||
]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
module MassiveDecks.Components.Form exposing (section)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import MassiveDecks.Components.Form.Message as Message exposing (Message)
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
|
||||
|
||||
{-| A section containing inputs and messages..
|
||||
-}
|
||||
section : Shared -> String -> Html msg -> List (Message msg) -> Html msg
|
||||
section shared id component messages =
|
||||
Html.div [ HtmlA.id id, HtmlA.class "form-section" ]
|
||||
(component :: (messages |> List.filterMap (Message.view shared)))
|
||||
@@ -0,0 +1,123 @@
|
||||
module MassiveDecks.Components.Form.Message exposing
|
||||
( Fix
|
||||
, Message
|
||||
, Severity(..)
|
||||
, error
|
||||
, errorWithFix
|
||||
, info
|
||||
, none
|
||||
, view
|
||||
, warning
|
||||
)
|
||||
|
||||
import FontAwesome.Attributes as Icon
|
||||
import FontAwesome.Icon as Icon
|
||||
import FontAwesome.Solid as Icon
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
|
||||
|
||||
type alias Fix msg =
|
||||
{ text : MdString
|
||||
, msg : msg
|
||||
}
|
||||
|
||||
|
||||
type Severity
|
||||
= Info
|
||||
| Warning
|
||||
| Error
|
||||
|
||||
|
||||
type alias Message msg =
|
||||
Maybe (InternalMessage msg)
|
||||
|
||||
|
||||
view : Shared -> Message msg -> Maybe (Html msg)
|
||||
view shared msg =
|
||||
msg |> Maybe.map (internalMessage shared)
|
||||
|
||||
|
||||
info : MdString -> Message msg
|
||||
info mdString =
|
||||
Just
|
||||
{ severity = Info
|
||||
, description = mdString
|
||||
, fix = Nothing
|
||||
}
|
||||
|
||||
|
||||
warning : MdString -> Message msg
|
||||
warning mdString =
|
||||
Just
|
||||
{ severity = Warning
|
||||
, description = mdString
|
||||
, fix = Nothing
|
||||
}
|
||||
|
||||
|
||||
error : MdString -> Message msg
|
||||
error mdString =
|
||||
Just
|
||||
{ severity = Error
|
||||
, description = mdString
|
||||
, fix = Nothing
|
||||
}
|
||||
|
||||
|
||||
errorWithFix : MdString -> MdString -> msg -> Message msg
|
||||
errorWithFix errorString fixString fix =
|
||||
Just
|
||||
{ severity = Error
|
||||
, description = errorString
|
||||
, fix = Just { text = fixString, msg = fix }
|
||||
}
|
||||
|
||||
|
||||
none : Message msg
|
||||
none =
|
||||
Nothing
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
type alias InternalMessage msg =
|
||||
{ severity : Severity
|
||||
, description : MdString
|
||||
, fix : Maybe (Fix msg)
|
||||
}
|
||||
|
||||
|
||||
internalMessage : Shared -> InternalMessage msg -> Html msg
|
||||
internalMessage shared { severity, description, fix } =
|
||||
let
|
||||
( class, icon ) =
|
||||
case severity of
|
||||
Info ->
|
||||
( "info", Icon.infoCircle )
|
||||
|
||||
Warning ->
|
||||
( "warning", Icon.exclamationTriangle )
|
||||
|
||||
Error ->
|
||||
( "inline-error", Icon.exclamationCircle )
|
||||
|
||||
fixLink =
|
||||
case fix of
|
||||
Just { text, msg } ->
|
||||
[ Html.text " ", Components.linkButton [ msg |> HtmlE.onClick ] [ text |> Lang.html shared ] ]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
in
|
||||
Html.span [ HtmlA.class class ]
|
||||
[ Icon.viewStyled [ Icon.fw ] icon
|
||||
, Html.span [] ((description |> Lang.html shared) :: fixLink)
|
||||
]
|
||||
@@ -2,7 +2,6 @@ module MassiveDecks.Error exposing (view)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Http
|
||||
import Json.Decode as Json
|
||||
import MassiveDecks.Error.Model exposing (..)
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
@@ -104,13 +103,10 @@ render error =
|
||||
case error of
|
||||
Http httpError ->
|
||||
case httpError of
|
||||
Http.NetworkError ->
|
||||
NetworkError ->
|
||||
Model Strings.NetworkError Nothing
|
||||
|
||||
Http.BadBody message ->
|
||||
Model Strings.BadPayloadError ("Decoding error: " ++ message |> Just)
|
||||
|
||||
Http.BadStatus code ->
|
||||
BadStatus code ->
|
||||
case code of
|
||||
504 ->
|
||||
Model Strings.ServerDownError Nothing
|
||||
@@ -118,10 +114,10 @@ render error =
|
||||
_ ->
|
||||
Model Strings.BadStatusError (code |> String.fromInt |> Just)
|
||||
|
||||
Http.Timeout ->
|
||||
Timeout ->
|
||||
Model Strings.TimeoutError Nothing
|
||||
|
||||
Http.BadUrl url ->
|
||||
BadUrl url ->
|
||||
Model Strings.BadUrlError (Just ("Url: " ++ url))
|
||||
|
||||
Token tokenError ->
|
||||
@@ -141,6 +137,3 @@ render error =
|
||||
|
||||
Json jsonError ->
|
||||
Model Strings.BadPayloadError (jsonError |> Json.errorToString |> Just)
|
||||
|
||||
Generic string ->
|
||||
Model string Nothing
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
module MassiveDecks.Error.Model exposing
|
||||
( Error(..)
|
||||
, HttpError(..)
|
||||
, Overlay
|
||||
)
|
||||
|
||||
import Http
|
||||
import Json.Decode as Json
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
|
||||
|
||||
{-| A generic error for the application as a whole.
|
||||
-}
|
||||
type Error
|
||||
= Http Http.Error
|
||||
= Http HttpError
|
||||
| Json Json.Error
|
||||
| Token Lobby.TokenDecodingError
|
||||
| Generic MdString
|
||||
|
||||
|
||||
{-| An error from an HTTP request.
|
||||
-}
|
||||
type HttpError
|
||||
= BadUrl String
|
||||
| Timeout
|
||||
| NetworkError
|
||||
| BadStatus Int
|
||||
|
||||
|
||||
{-| An overlay displaying a number of errors.
|
||||
|
||||
@@ -7,9 +7,11 @@ module MassiveDecks.Game.Player exposing
|
||||
, isCzar
|
||||
, playState
|
||||
, role
|
||||
, roleDescription
|
||||
)
|
||||
|
||||
import MassiveDecks.Game.Round as Round exposing (Round)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.User as User
|
||||
import Set
|
||||
|
||||
@@ -42,6 +44,16 @@ type Role
|
||||
| RPlayer
|
||||
|
||||
|
||||
roleDescription : Role -> MdString
|
||||
roleDescription toDescribe =
|
||||
case toDescribe of
|
||||
RCzar ->
|
||||
Strings.Czar
|
||||
|
||||
RPlayer ->
|
||||
Strings.Player
|
||||
|
||||
|
||||
{-| The state of a player in regards to playing into a round.
|
||||
-}
|
||||
type PlayState
|
||||
|
||||
@@ -8,6 +8,7 @@ module MassiveDecks.Game.Round exposing
|
||||
, Playing
|
||||
, Revealing
|
||||
, Round(..)
|
||||
, Stage(..)
|
||||
, complete
|
||||
, data
|
||||
, idDecoder
|
||||
@@ -15,12 +16,15 @@ module MassiveDecks.Game.Round exposing
|
||||
, noPick
|
||||
, playing
|
||||
, revealing
|
||||
, stage
|
||||
, stageDescription
|
||||
)
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Json.Decode as Json
|
||||
import MassiveDecks.Card.Model as Card
|
||||
import MassiveDecks.Card.Play as Play exposing (Play)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.User as User
|
||||
import Set exposing (Set)
|
||||
|
||||
@@ -36,6 +40,51 @@ idDecoder =
|
||||
Json.string |> Json.map Id
|
||||
|
||||
|
||||
{-| The stage of the round.
|
||||
-}
|
||||
type Stage
|
||||
= SPlaying
|
||||
| SRevealing
|
||||
| SJudging
|
||||
| SComplete
|
||||
|
||||
|
||||
{-| Get the stage of the given round.
|
||||
-}
|
||||
stage : Round -> Stage
|
||||
stage round =
|
||||
case round of
|
||||
P _ ->
|
||||
SPlaying
|
||||
|
||||
R _ ->
|
||||
SRevealing
|
||||
|
||||
J _ ->
|
||||
SJudging
|
||||
|
||||
C _ ->
|
||||
SComplete
|
||||
|
||||
|
||||
{-| A description of the given stage.
|
||||
-}
|
||||
stageDescription : Stage -> MdString
|
||||
stageDescription toDescribe =
|
||||
case toDescribe of
|
||||
SPlaying ->
|
||||
Strings.Playing
|
||||
|
||||
SRevealing ->
|
||||
Strings.Revealing
|
||||
|
||||
SJudging ->
|
||||
Strings.Judging
|
||||
|
||||
SComplete ->
|
||||
Strings.Complete
|
||||
|
||||
|
||||
{-| A round during a game.
|
||||
-}
|
||||
type Round
|
||||
|
||||
@@ -10,6 +10,7 @@ module MassiveDecks.Models.Decoders exposing
|
||||
, lobbyState
|
||||
, lobbySummary
|
||||
, lobbyToken
|
||||
, mdError
|
||||
, privilege
|
||||
, revealingRound
|
||||
, settings
|
||||
@@ -44,28 +45,34 @@ import MassiveDecks.User as User exposing (User)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
unknownValue : String -> String -> Json.Decoder a
|
||||
unknownValue name value =
|
||||
("Unknown " ++ name ++ ": \"" ++ value ++ "\".") |> Json.fail
|
||||
|
||||
|
||||
castStatus : Json.Decoder Cast.Status
|
||||
castStatus =
|
||||
Json.field "status" Json.string
|
||||
|> Json.andThen
|
||||
(\status ->
|
||||
case status of
|
||||
"NoDevicesAvailable" ->
|
||||
Json.succeed Cast.NoDevicesAvailable
|
||||
Json.field "status" Json.string |> Json.andThen castStatusByName
|
||||
|
||||
"NotConnected" ->
|
||||
Json.succeed Cast.NotConnected
|
||||
|
||||
"Connecting" ->
|
||||
Json.succeed Cast.Connecting
|
||||
castStatusByName : String -> Json.Decoder Cast.Status
|
||||
castStatusByName name =
|
||||
case name of
|
||||
"NoDevicesAvailable" ->
|
||||
Json.succeed Cast.NoDevicesAvailable
|
||||
|
||||
"Connected" ->
|
||||
Json.map Cast.Connected
|
||||
(Json.field "name" Json.string)
|
||||
"NotConnected" ->
|
||||
Json.succeed Cast.NotConnected
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown cast status: " ++ status)
|
||||
)
|
||||
"Connecting" ->
|
||||
Json.succeed Cast.Connecting
|
||||
|
||||
"Connected" ->
|
||||
Json.map Cast.Connected
|
||||
(Json.field "name" Json.string)
|
||||
|
||||
_ ->
|
||||
unknownValue "cast status" name
|
||||
|
||||
|
||||
castFlags : Json.Decoder Cast.Flags
|
||||
@@ -115,7 +122,7 @@ sourceByName name =
|
||||
Json.field "playCode" Json.string |> Json.map (Cardcast.playCode >> Source.Cardcast)
|
||||
|
||||
_ ->
|
||||
"Unknown source \"" ++ name ++ "\"" |> Json.fail
|
||||
unknownValue "source" name
|
||||
|
||||
|
||||
tokenValidity : Json.Decoder (Dict Lobby.Token Bool)
|
||||
@@ -130,16 +137,15 @@ lobbyToken =
|
||||
|
||||
language : Json.Decoder Language
|
||||
language =
|
||||
Json.string
|
||||
|> Json.andThen
|
||||
(\code ->
|
||||
case Lang.fromCode code of
|
||||
Just lang ->
|
||||
Json.succeed lang
|
||||
Json.string |> Json.andThen languageFromCode
|
||||
|
||||
Nothing ->
|
||||
Json.fail ("Unknown language code: '" ++ code ++ "'.")
|
||||
)
|
||||
|
||||
languageFromCode : String -> Json.Decoder Language
|
||||
languageFromCode code =
|
||||
code
|
||||
|> Lang.fromCode
|
||||
|> Maybe.map Json.succeed
|
||||
|> Maybe.withDefault (unknownValue "language code" code)
|
||||
|
||||
|
||||
lobby : Json.Decoder Lobby
|
||||
@@ -150,7 +156,6 @@ lobby =
|
||||
(Json.field "users" users)
|
||||
(Json.field "owner" userId)
|
||||
(Json.field "config" config)
|
||||
--(Json.field "game" game |> Json.map (Game.emptyModel >> Just))
|
||||
(Json.maybe (Json.field "game" game |> Json.map Game.emptyModel))
|
||||
|
||||
|
||||
@@ -167,11 +172,12 @@ game =
|
||||
|
||||
config : Json.Decoder Configure.Config
|
||||
config =
|
||||
Json.map4 Configure.Config
|
||||
Json.map5 Configure.Config
|
||||
(Json.field "rules" rules)
|
||||
(Json.field "decks" (Json.list deck))
|
||||
(Json.maybe (Json.field "password" Json.string))
|
||||
(Json.field "version" Json.string)
|
||||
(Json.maybe (Json.field "public" Json.bool) |> Json.map (Maybe.withDefault False))
|
||||
|
||||
|
||||
deck : Json.Decoder Configure.Deck
|
||||
@@ -238,19 +244,20 @@ player =
|
||||
|
||||
control : Json.Decoder Player.Control
|
||||
control =
|
||||
Json.string
|
||||
|> Json.andThen
|
||||
(\name ->
|
||||
case name of
|
||||
"Human" ->
|
||||
Json.succeed Player.Human
|
||||
Json.string |> Json.andThen controlByName
|
||||
|
||||
"Computer" ->
|
||||
Json.succeed Player.Computer
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown controller: " ++ name)
|
||||
)
|
||||
controlByName : String -> Json.Decoder Player.Control
|
||||
controlByName name =
|
||||
case name of
|
||||
"Human" ->
|
||||
Json.succeed Player.Human
|
||||
|
||||
"Computer" ->
|
||||
Json.succeed Player.Computer
|
||||
|
||||
_ ->
|
||||
unknownValue "user controller" name
|
||||
|
||||
|
||||
score : Json.Decoder Player.Score
|
||||
@@ -275,79 +282,84 @@ user =
|
||||
|
||||
userConnection : Json.Decoder User.Connection
|
||||
userConnection =
|
||||
Json.string
|
||||
|> Json.andThen
|
||||
(\name ->
|
||||
case name of
|
||||
"Connected" ->
|
||||
Json.succeed User.Connected
|
||||
Json.string |> Json.andThen userConnectionByName
|
||||
|
||||
"Disconnected" ->
|
||||
Json.succeed User.Disconnected
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown connection state: " ++ name)
|
||||
)
|
||||
userConnectionByName : String -> Json.Decoder User.Connection
|
||||
userConnectionByName name =
|
||||
case name of
|
||||
"Connected" ->
|
||||
Json.succeed User.Connected
|
||||
|
||||
"Disconnected" ->
|
||||
Json.succeed User.Disconnected
|
||||
|
||||
_ ->
|
||||
unknownValue "connection state" name
|
||||
|
||||
|
||||
userPresence : Json.Decoder User.Presence
|
||||
userPresence =
|
||||
Json.string
|
||||
|> Json.andThen
|
||||
(\name ->
|
||||
case name of
|
||||
"Joined" ->
|
||||
Json.succeed User.Joined
|
||||
Json.string |> Json.andThen userPresenceByName
|
||||
|
||||
"Left" ->
|
||||
Json.succeed User.Left
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown presence state: " ++ name)
|
||||
)
|
||||
userPresenceByName : String -> Json.Decoder User.Presence
|
||||
userPresenceByName name =
|
||||
case name of
|
||||
"Joined" ->
|
||||
Json.succeed User.Joined
|
||||
|
||||
"Left" ->
|
||||
Json.succeed User.Left
|
||||
|
||||
_ ->
|
||||
unknownValue "presence state" name
|
||||
|
||||
|
||||
role : Json.Decoder User.Role
|
||||
role =
|
||||
Json.string
|
||||
|> Json.andThen
|
||||
(\name ->
|
||||
case name of
|
||||
"Spectator" ->
|
||||
Json.succeed User.Spectator
|
||||
Json.string |> Json.andThen roleByName
|
||||
|
||||
"Player" ->
|
||||
Json.succeed User.Player
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown role: " ++ name)
|
||||
)
|
||||
roleByName : String -> Json.Decoder User.Role
|
||||
roleByName name =
|
||||
case name of
|
||||
"Spectator" ->
|
||||
Json.succeed User.Spectator
|
||||
|
||||
"Player" ->
|
||||
Json.succeed User.Player
|
||||
|
||||
_ ->
|
||||
unknownValue "user role" name
|
||||
|
||||
|
||||
lobbyState : Json.Decoder Lobby.State
|
||||
lobbyState =
|
||||
Json.string
|
||||
|> Json.andThen
|
||||
(\name ->
|
||||
case name of
|
||||
"Playing" ->
|
||||
Json.succeed Lobby.Playing
|
||||
Json.string |> Json.andThen lobbyStateByName
|
||||
|
||||
"SettingUp" ->
|
||||
Json.succeed Lobby.SettingUp
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown lobby state: " ++ name)
|
||||
)
|
||||
lobbyStateByName : String -> Json.Decoder Lobby.State
|
||||
lobbyStateByName name =
|
||||
case name of
|
||||
"Playing" ->
|
||||
Json.succeed Lobby.Playing
|
||||
|
||||
"SettingUp" ->
|
||||
Json.succeed Lobby.SettingUp
|
||||
|
||||
_ ->
|
||||
unknownValue "lobby state" name
|
||||
|
||||
|
||||
lobbySummary : Json.Decoder LobbyBrowser.Summary
|
||||
lobbySummary =
|
||||
Json.map4 LobbyBrowser.Summary
|
||||
Json.map5 LobbyBrowser.Summary
|
||||
(Json.field "name" Json.string)
|
||||
(Json.field "gameCode" gameCode)
|
||||
(Json.field "state" lobbyState)
|
||||
(Json.field "users" userSummary)
|
||||
(Json.maybe (Json.field "password" Json.bool) |> Json.map (Maybe.withDefault False))
|
||||
|
||||
|
||||
userId : Json.Decoder User.Id
|
||||
@@ -357,19 +369,20 @@ userId =
|
||||
|
||||
privilege : Json.Decoder User.Privilege
|
||||
privilege =
|
||||
Json.string
|
||||
|> Json.andThen
|
||||
(\name ->
|
||||
case name of
|
||||
"Privileged" ->
|
||||
Json.succeed User.Privileged
|
||||
Json.string |> Json.andThen privilegeByName
|
||||
|
||||
"Unprivileged" ->
|
||||
Json.succeed User.Unprivileged
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown privilege level '" ++ name ++ "'.")
|
||||
)
|
||||
privilegeByName : String -> Json.Decoder User.Privilege
|
||||
privilegeByName name =
|
||||
case name of
|
||||
"Privileged" ->
|
||||
Json.succeed User.Privileged
|
||||
|
||||
"Unprivileged" ->
|
||||
Json.succeed User.Unprivileged
|
||||
|
||||
_ ->
|
||||
unknownValue "privilege level" name
|
||||
|
||||
|
||||
userSummary : Json.Decoder LobbyBrowser.UserSummary
|
||||
@@ -426,6 +439,9 @@ eventByName name =
|
||||
"HouseRuleChanged" ->
|
||||
configured houseRuleChanged
|
||||
|
||||
"PublicSet" ->
|
||||
configured publicSet
|
||||
|
||||
"GameStarted" ->
|
||||
gameStarted
|
||||
|
||||
@@ -448,7 +464,7 @@ eventByName name =
|
||||
gameEvent roundFinished
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown event '" ++ name ++ "'.")
|
||||
unknownValue "event" name
|
||||
|
||||
|
||||
roundFinished : Json.Decoder Events.GameEvent
|
||||
@@ -514,7 +530,7 @@ houseRuleChangeFromName name =
|
||||
maybeHouseRuleChange (Json.field "cost" Json.int) Rules.Reboot Rules.RebootChange
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown house rule (for change) \"" ++ name ++ "\"")
|
||||
unknownValue "house rule (for change)" name
|
||||
|
||||
|
||||
maybeHouseRuleChange :
|
||||
@@ -564,6 +580,12 @@ presence state =
|
||||
(Json.field "user" userId)
|
||||
|
||||
|
||||
publicSet : Json.Decoder Events.ConfigChanged
|
||||
publicSet =
|
||||
Json.map (\public -> Events.PublicSet { public = public })
|
||||
(Json.field "public" Json.bool)
|
||||
|
||||
|
||||
passwordSet : Json.Decoder Events.ConfigChanged
|
||||
passwordSet =
|
||||
Json.map (\password -> Events.PasswordSet { password = password })
|
||||
@@ -620,7 +642,7 @@ deckChangeByName name =
|
||||
Json.field "reason" failReason |> Json.map (\r -> Events.Fail { reason = r })
|
||||
|
||||
_ ->
|
||||
"Unknown deck change \"" ++ name ++ "\"" |> Json.fail
|
||||
unknownValue "deck change" name
|
||||
|
||||
|
||||
failReason : Json.Decoder Source.LoadFailureReason
|
||||
@@ -638,7 +660,7 @@ failReasonByName name =
|
||||
Json.succeed Source.NotFound
|
||||
|
||||
_ ->
|
||||
"Unknown failure reason \"" ++ name ++ "\"" |> Json.fail
|
||||
unknownValue "failure reason" name
|
||||
|
||||
|
||||
play : Json.Decoder Play
|
||||
@@ -721,7 +743,7 @@ transformByName maybeName =
|
||||
Json.succeed Parts.Capitalize
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown transform '" ++ name ++ "'.")
|
||||
unknownValue "transform" name
|
||||
|
||||
|
||||
round : Json.Decoder Round
|
||||
@@ -745,7 +767,7 @@ roundByName name =
|
||||
completeRound |> Json.map Round.C
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown round stage '" ++ name ++ "'.")
|
||||
unknownValue "round stage" name
|
||||
|
||||
|
||||
playerSet : Json.Decoder (Set User.Id)
|
||||
@@ -809,7 +831,7 @@ playerRoleByName name =
|
||||
Json.succeed Player.RPlayer
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown player role: '" ++ name ++ "'.")
|
||||
unknownValue "player role" name
|
||||
|
||||
|
||||
userRole : Json.Decoder User.Role
|
||||
@@ -827,7 +849,7 @@ userRoleByName name =
|
||||
Json.succeed User.Player
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown user role: '" ++ name ++ "'.")
|
||||
unknownValue "user role" name
|
||||
|
||||
|
||||
mdError : Json.Decoder MdError
|
||||
@@ -868,14 +890,38 @@ mdErrorByName name =
|
||||
Json.succeed MdError.OutOfCardsError |> Json.map MdError.Game
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown error: '" ++ name ++ "'.")
|
||||
unknownValue "error" name
|
||||
|
||||
|
||||
stage : Json.Decoder Round.Stage
|
||||
stage =
|
||||
Json.string |> Json.andThen stageByName
|
||||
|
||||
|
||||
stageByName : String -> Json.Decoder Round.Stage
|
||||
stageByName name =
|
||||
case name of
|
||||
"Playing" ->
|
||||
Json.succeed Round.SPlaying
|
||||
|
||||
"Revealing" ->
|
||||
Json.succeed Round.SRevealing
|
||||
|
||||
"Judging" ->
|
||||
Json.succeed Round.SJudging
|
||||
|
||||
"Complete" ->
|
||||
Json.succeed Round.SComplete
|
||||
|
||||
_ ->
|
||||
unknownValue "round stage" name
|
||||
|
||||
|
||||
incorrectRoundStageError : Json.Decoder MdError.ActionExecutionError
|
||||
incorrectRoundStageError =
|
||||
Json.map2 (\s -> \e -> MdError.IncorrectRoundStageError { stage = s, expected = e })
|
||||
(Json.field "stage" Json.string)
|
||||
(Json.field "expected" Json.string)
|
||||
(Json.field "stage" stage)
|
||||
(Json.field "expected" stage)
|
||||
|
||||
|
||||
configEditConflictError : Json.Decoder MdError.ActionExecutionError
|
||||
@@ -917,12 +963,12 @@ authenticationErrorByName name =
|
||||
Json.succeed MdError.InvalidLobbyPassword
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown authentication failure reason: '" ++ name ++ "'.")
|
||||
unknownValue "authentication failure reason" name
|
||||
|
||||
|
||||
lobbyError : Json.Decoder MdError.LobbyNotFoundError
|
||||
lobbyError =
|
||||
Json.field "reason" Json.string |> Json.andThen lobbyErrorByName
|
||||
Json.string |> Json.andThen lobbyErrorByName
|
||||
|
||||
|
||||
lobbyErrorByName : String -> Json.Decoder MdError.LobbyNotFoundError
|
||||
@@ -935,4 +981,4 @@ lobbyErrorByName name =
|
||||
Json.succeed MdError.DoesNotExist
|
||||
|
||||
_ ->
|
||||
Json.fail ("Unknown lobby error reason: '" ++ name ++ "'.")
|
||||
unknownValue "lobby not found error" name
|
||||
|
||||
@@ -86,7 +86,9 @@ language l =
|
||||
userRegistration : User.Registration -> Json.Value
|
||||
userRegistration r =
|
||||
Json.object
|
||||
[ ( "name", r.name |> Json.string ) ]
|
||||
(( "name", r.name |> Json.string )
|
||||
:: (r.password |> Maybe.map (\p -> [ ( "password", p |> Json.string ) ]) |> Maybe.withDefault [])
|
||||
)
|
||||
|
||||
|
||||
houseRuleChange : Rules.HouseRuleChange -> Json.Value
|
||||
|
||||
@@ -4,10 +4,13 @@ module MassiveDecks.Models.MdError exposing
|
||||
, GameStateError(..)
|
||||
, LobbyNotFoundError(..)
|
||||
, MdError(..)
|
||||
, describe
|
||||
)
|
||||
|
||||
import MassiveDecks.Game.Player as Player
|
||||
import MassiveDecks.Game.Round as Round
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.User as User
|
||||
|
||||
|
||||
@@ -21,7 +24,7 @@ type MdError
|
||||
type ActionExecutionError
|
||||
= IncorrectPlayerRole { role : Player.Role, expected : Player.Role }
|
||||
| IncorrectUserRole { role : User.Role, expected : User.Role }
|
||||
| IncorrectRoundStageError { stage : String, expected : String }
|
||||
| IncorrectRoundStageError { stage : Round.Stage, expected : Round.Stage }
|
||||
| ConfigEditConflictError { version : String, expected : String }
|
||||
| Unprivileged
|
||||
| GameNotStarted
|
||||
@@ -40,3 +43,51 @@ type LobbyNotFoundError
|
||||
|
||||
type GameStateError
|
||||
= OutOfCardsError
|
||||
|
||||
|
||||
describe : MdError -> MdString
|
||||
describe error =
|
||||
case error of
|
||||
ActionExecution aee ->
|
||||
case aee of
|
||||
IncorrectPlayerRole { role, expected } ->
|
||||
Strings.IncorrectPlayerRoleError { role = Player.roleDescription role, expected = Player.roleDescription expected }
|
||||
|
||||
IncorrectUserRole { role, expected } ->
|
||||
Strings.IncorrectUserRoleError { role = User.roleDescription role, expected = User.roleDescription expected }
|
||||
|
||||
IncorrectRoundStageError { stage, expected } ->
|
||||
Strings.IncorrectRoundStageError { stage = Round.stageDescription stage, expected = Round.stageDescription expected }
|
||||
|
||||
ConfigEditConflictError _ ->
|
||||
Strings.ConfigEditConflictError
|
||||
|
||||
Unprivileged ->
|
||||
Strings.UnprivilegedError
|
||||
|
||||
GameNotStarted ->
|
||||
Strings.GameNotStartedError
|
||||
|
||||
Authentication ae ->
|
||||
case ae of
|
||||
IncorrectIssuer ->
|
||||
Strings.IncorrectIssuerError
|
||||
|
||||
InvalidAuthentication ->
|
||||
Strings.InvalidAuthenticationError
|
||||
|
||||
InvalidLobbyPassword ->
|
||||
Strings.InvalidLobbyPasswordError
|
||||
|
||||
LobbyNotFound { reason, gameCode } ->
|
||||
case reason of
|
||||
Closed ->
|
||||
Strings.LobbyClosedError { gameCode = GameCode.toString gameCode }
|
||||
|
||||
DoesNotExist ->
|
||||
Strings.LobbyDoesNotExistError { gameCode = GameCode.toString gameCode }
|
||||
|
||||
Game gse ->
|
||||
case gse of
|
||||
OutOfCardsError ->
|
||||
Strings.OutOfCardsError
|
||||
|
||||
@@ -566,7 +566,7 @@ playStateDetail round userId =
|
||||
Just (Round.P p) ->
|
||||
case Player.playState p userId of
|
||||
Player.Playing ->
|
||||
Just Strings.Playing
|
||||
Just Strings.StillPlaying
|
||||
|
||||
Player.Played ->
|
||||
Just Strings.Played
|
||||
|
||||
@@ -6,6 +6,7 @@ module MassiveDecks.Pages.Lobby.Actions exposing
|
||||
, reveal
|
||||
, setHandSize
|
||||
, setPassword
|
||||
, setPublic
|
||||
, setScoreLimit
|
||||
, startGame
|
||||
, submit
|
||||
@@ -56,6 +57,11 @@ changeHouseRule value =
|
||||
configAction "ChangeHouseRule" [ ( "change", value |> Encoders.houseRuleChange ) ]
|
||||
|
||||
|
||||
setPublic : Bool -> String -> Cmd msg
|
||||
setPublic value =
|
||||
configAction "SetPublic" [ ( "public", value |> Json.bool ) ]
|
||||
|
||||
|
||||
submit : List Card.Id -> Cmd msg
|
||||
submit play =
|
||||
action "Submit" [ ( "play", play |> Json.list Json.string ) ]
|
||||
|
||||
@@ -17,6 +17,8 @@ import MassiveDecks.Card.Source as Source
|
||||
import MassiveDecks.Card.Source.Cardcast.Model as Cardcast
|
||||
import MassiveDecks.Card.Source.Model as Source exposing (Source)
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form as Form
|
||||
import MassiveDecks.Components.Form.Message as Message exposing (Message)
|
||||
import MassiveDecks.Game.Rules as Rules
|
||||
import MassiveDecks.Messages as Global
|
||||
import MassiveDecks.Model exposing (..)
|
||||
@@ -24,7 +26,7 @@ import MassiveDecks.Pages.Lobby.Actions as Actions
|
||||
import MassiveDecks.Pages.Lobby.Configure.Messages exposing (..)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Model exposing (..)
|
||||
import MassiveDecks.Pages.Lobby.Events as Events
|
||||
import MassiveDecks.Pages.Lobby.GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.Invite as Invite
|
||||
import MassiveDecks.Pages.Lobby.Messages as Lobby
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby exposing (Lobby)
|
||||
@@ -47,11 +49,13 @@ init =
|
||||
, scoreLimit = Just 25
|
||||
, tab = Decks
|
||||
, password = Nothing
|
||||
, passwordVisible = False
|
||||
, houseRules =
|
||||
{ rando = Nothing
|
||||
, packingHeat = Nothing
|
||||
, reboot = Nothing
|
||||
}
|
||||
, public = False
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +67,9 @@ updateFromConfig config model =
|
||||
, handSize = config.rules.handSize
|
||||
, scoreLimit = config.rules.scoreLimit
|
||||
, password = config.password
|
||||
, passwordVisible = model.passwordVisible
|
||||
, houseRules = config.rules.houseRules
|
||||
, public = config.public
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +112,9 @@ update msg model config =
|
||||
in
|
||||
( { model | password = value }, cmd )
|
||||
|
||||
TogglePasswordVisibility ->
|
||||
( { model | passwordVisible = not model.passwordVisible }, Cmd.none )
|
||||
|
||||
HouseRuleChange target value ->
|
||||
let
|
||||
send =
|
||||
@@ -113,6 +122,13 @@ update msg model config =
|
||||
in
|
||||
( { model | houseRules = model.houseRules |> Rules.apply value }, send )
|
||||
|
||||
PublicChange target value ->
|
||||
let
|
||||
send =
|
||||
ifRemote (Actions.setPublic value config.version) target
|
||||
in
|
||||
( { model | public = value }, send )
|
||||
|
||||
|
||||
view : Shared -> Bool -> Model -> GameCode -> Lobby -> Config -> Html Global.Msg
|
||||
view shared canEdit model gameCode lobby config =
|
||||
@@ -122,7 +138,7 @@ view shared canEdit model gameCode lobby config =
|
||||
[ Html.h2 [] [ lobby.name |> Html.text ]
|
||||
, Html.div []
|
||||
[ Invite.button shared
|
||||
, Strings.GameCode { code = gameCode } |> Lang.html shared
|
||||
, Strings.GameCode { code = GameCode.toString gameCode } |> Lang.html shared
|
||||
]
|
||||
]
|
||||
, Wl.tabGroup [ WlA.align WlA.Center ] (tabs |> List.map (tab shared model.tab))
|
||||
@@ -160,6 +176,11 @@ applyChange configChange oldConfig oldConfigure =
|
||||
, { oldConfigure | houseRules = oldConfigure.houseRules |> Rules.apply change }
|
||||
)
|
||||
|
||||
Events.PublicSet { public } ->
|
||||
( { oldConfig | public = public }
|
||||
, { oldConfigure | public = public }
|
||||
)
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
@@ -178,7 +199,7 @@ startGameSegment shared canEdit lobby config =
|
||||
else
|
||||
[ WlA.disabled ]
|
||||
in
|
||||
Components.formSection shared
|
||||
Form.section shared
|
||||
"start-game"
|
||||
(Wl.button startGameAttrs [ Strings.StartGame |> Lang.html shared ])
|
||||
(startErrors |> Maybe.justIf canEdit |> Maybe.withDefault [])
|
||||
@@ -227,7 +248,7 @@ applyDeckChange event config configure =
|
||||
( newConfig, newConfigure )
|
||||
|
||||
|
||||
startGameProblems : Dict User.Id User -> Config -> List (Components.Message Global.Msg)
|
||||
startGameProblems : Dict User.Id User -> Config -> List (Message Global.Msg)
|
||||
startGameProblems users config =
|
||||
let
|
||||
-- We assume decks will have calls/responses.
|
||||
@@ -242,7 +263,7 @@ startGameProblems users config =
|
||||
|
||||
deckIssues =
|
||||
if noDecks then
|
||||
[ Components.errorWithFix
|
||||
[ Message.errorWithFix
|
||||
Strings.NeedAtLeastOneDeck
|
||||
Strings.NoDecksHint
|
||||
("CAHBS" |> Cardcast.playCode |> Source.Cardcast |> AddDeck |> lift)
|
||||
@@ -250,14 +271,14 @@ startGameProblems users config =
|
||||
]
|
||||
|
||||
else if loadingDecks then
|
||||
[ Strings.WaitForDecks |> Components.info |> Just ]
|
||||
[ Strings.WaitForDecks |> Message.info |> Just ]
|
||||
|
||||
else
|
||||
[ Strings.MissingCardType { cardType = Strings.Call }
|
||||
|> Components.error
|
||||
|> Message.error
|
||||
|> Maybe.justIf ((summaries .calls |> List.sum) < 1)
|
||||
, Strings.MissingCardType { cardType = Strings.Response }
|
||||
|> Components.error
|
||||
|> Message.error
|
||||
|> Maybe.justIf ((summaries .responses |> List.sum) < 1)
|
||||
]
|
||||
|
||||
@@ -265,7 +286,7 @@ startGameProblems users config =
|
||||
users |> Dict.values |> List.filter (\user -> user.role == User.Player) |> List.length
|
||||
|
||||
playerIssues =
|
||||
[ Components.errorWithFix
|
||||
[ Message.errorWithFix
|
||||
Strings.NeedAtLeastThreePlayers
|
||||
Strings.Invite
|
||||
(Lobby.ToggleInviteDialog |> Global.LobbyMsg)
|
||||
@@ -288,7 +309,7 @@ ifRemote cmd target =
|
||||
|
||||
tabs : List Tab
|
||||
tabs =
|
||||
[ Decks, Rules, Game ]
|
||||
[ Decks, Rules, Privacy ]
|
||||
|
||||
|
||||
tab : Shared -> Tab -> Tab -> Html Global.Msg
|
||||
@@ -309,8 +330,8 @@ tabName target =
|
||||
Rules ->
|
||||
Strings.ConfigureRules
|
||||
|
||||
Game ->
|
||||
Strings.ConfigureGame
|
||||
Privacy ->
|
||||
Strings.ConfigurePrivacy
|
||||
|
||||
|
||||
tabContent : Shared -> Bool -> Model -> Lobby -> Config -> Html Global.Msg
|
||||
@@ -322,7 +343,7 @@ tabContent shared canEdit model lobby config =
|
||||
Rules ->
|
||||
configureRules shared canEdit model lobby config
|
||||
|
||||
Game ->
|
||||
Privacy ->
|
||||
configureGameSettings shared canEdit model lobby config
|
||||
|
||||
|
||||
@@ -347,7 +368,7 @@ handSize shared canEdit model config =
|
||||
value =
|
||||
model.handSize
|
||||
in
|
||||
Components.formSection shared
|
||||
Form.section shared
|
||||
"hand-size"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
@@ -372,7 +393,7 @@ handSize shared canEdit model config =
|
||||
(Icon.save |> Maybe.justIf (config.rules.handSize /= value) |> Maybe.withDefault Icon.check)
|
||||
]
|
||||
)
|
||||
[ Components.info Strings.HandSizeDescription ]
|
||||
[ Message.info Strings.HandSizeDescription ]
|
||||
|
||||
|
||||
scoreLimit : Shared -> Bool -> Model -> Config -> Html Global.Msg
|
||||
@@ -384,7 +405,7 @@ scoreLimit shared canEdit model config =
|
||||
value =
|
||||
model.scoreLimit
|
||||
in
|
||||
Components.formSection shared
|
||||
Form.section shared
|
||||
"score-limit"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
@@ -414,7 +435,7 @@ scoreLimit shared canEdit model config =
|
||||
(Icon.save |> Maybe.justIf (config.rules.scoreLimit /= value) |> Maybe.withDefault Icon.check)
|
||||
]
|
||||
)
|
||||
[ Components.info Strings.ScoreLimitDescription ]
|
||||
[ Message.info Strings.ScoreLimitDescription ]
|
||||
|
||||
|
||||
houseRules : Shared -> Bool -> Model -> Lobby -> Config -> Html Global.Msg
|
||||
@@ -460,7 +481,7 @@ houseRule shared id { default, change, title, description, extract, insert } can
|
||||
|> Maybe.withDefault []
|
||||
in
|
||||
Html.div [ HtmlA.classList [ ( "house-rule", True ), ( "enabled", enabled ) ] ]
|
||||
[ Components.formSection
|
||||
[ Form.section
|
||||
shared
|
||||
id
|
||||
(Html.div [ HtmlA.class "multipart" ]
|
||||
@@ -473,7 +494,7 @@ houseRule shared id { default, change, title, description, extract, insert } can
|
||||
(Icon.check |> Maybe.justIf saved |> Maybe.withDefault Icon.save)
|
||||
]
|
||||
)
|
||||
[ Components.info (localValue |> description) ]
|
||||
[ Message.info (localValue |> description) ]
|
||||
, Html.div [ HtmlA.class "house-rule-settings" ] settings
|
||||
]
|
||||
|
||||
@@ -485,7 +506,7 @@ rando shared canEdit model config =
|
||||
|
||||
randoSettings : Shared -> Bool -> Rules.Rando -> (Rules.Rando -> Global.Msg) -> List (Html Global.Msg)
|
||||
randoSettings shared canEdit value localChange =
|
||||
[ Components.formSection
|
||||
[ Form.section
|
||||
shared
|
||||
"rando-number"
|
||||
(Wl.textField
|
||||
@@ -503,7 +524,7 @@ randoSettings shared canEdit value localChange =
|
||||
]
|
||||
[]
|
||||
)
|
||||
[ Strings.HouseRuleRandoCardrissianNumberDescription |> Components.info ]
|
||||
[ Strings.HouseRuleRandoCardrissianNumberDescription |> Message.info ]
|
||||
]
|
||||
|
||||
|
||||
@@ -524,7 +545,7 @@ reboot shared canEdit model config =
|
||||
|
||||
rebootSettings : Shared -> Bool -> Rules.Reboot -> (Rules.Reboot -> Global.Msg) -> List (Html Global.Msg)
|
||||
rebootSettings shared canEdit value localChange =
|
||||
[ Components.formSection
|
||||
[ Form.section
|
||||
shared
|
||||
"reboot-cost"
|
||||
(Wl.textField
|
||||
@@ -542,7 +563,7 @@ rebootSettings shared canEdit value localChange =
|
||||
]
|
||||
[]
|
||||
)
|
||||
[ Strings.HouseRuleRebootCostDescription |> Components.info ]
|
||||
[ Strings.HouseRuleRebootCostDescription |> Message.info ]
|
||||
]
|
||||
|
||||
|
||||
@@ -618,7 +639,7 @@ configureGameSettings shared canEdit model lobby config =
|
||||
|
||||
Nothing ->
|
||||
[ "" |> WlA.value, WlA.disabled ]
|
||||
, [ Strings.GamePassword |> Lang.string shared |> WlA.label
|
||||
, [ Strings.LobbyPassword |> Lang.string shared |> WlA.label
|
||||
, WlA.minLength 1
|
||||
, WlA.outlined
|
||||
, HtmlA.class "primary"
|
||||
@@ -629,6 +650,7 @@ configureGameSettings shared canEdit model lobby config =
|
||||
[ HtmlE.onInput (Just >> PasswordChange Local >> lift)
|
||||
, HtmlE.onBlur (model.password |> PasswordChange Remote |> lift)
|
||||
]
|
||||
, [ WlA.Password |> WlA.type_ ] |> Maybe.justIf (not model.passwordVisible) |> Maybe.withDefault []
|
||||
]
|
||||
|
||||
passwordSwitchAttrs =
|
||||
@@ -645,12 +667,17 @@ configureGameSettings shared canEdit model lobby config =
|
||||
]
|
||||
|
||||
password =
|
||||
Components.formSection
|
||||
Form.section
|
||||
shared
|
||||
"add-deck"
|
||||
"password"
|
||||
(Html.div [ HtmlA.class "multipart" ]
|
||||
[ Wl.switch passwordSwitchAttrs
|
||||
, Wl.textField passwordAttrs []
|
||||
, Components.iconButton
|
||||
[ TogglePasswordVisibility |> lift |> HtmlE.onClick
|
||||
, WlA.disabled |> Maybe.justIf (Maybe.isNothing model.password) |> Maybe.withDefault HtmlA.nothing
|
||||
]
|
||||
(Icon.eyeSlash |> Maybe.justIf model.passwordVisible |> Maybe.withDefault Icon.eye)
|
||||
, Components.iconButton
|
||||
[ WlA.disabled
|
||||
|> Maybe.justIf (not canEdit || config.password == model.password)
|
||||
@@ -659,12 +686,27 @@ configureGameSettings shared canEdit model lobby config =
|
||||
(Icon.save |> Maybe.justIf (config.password /= model.password) |> Maybe.withDefault Icon.check)
|
||||
]
|
||||
)
|
||||
[ Components.info Strings.GamePasswordDescription
|
||||
, Components.warning Strings.PasswordNotSecured
|
||||
[ Message.info Strings.LobbyPasswordDescription
|
||||
, Message.warning Strings.PasswordShared
|
||||
, Message.warning Strings.PasswordNotSecured
|
||||
]
|
||||
|
||||
public =
|
||||
Form.section shared
|
||||
"public"
|
||||
(Html.div [ HtmlA.class "multipart" ]
|
||||
[ Wl.switch
|
||||
[ WlA.disabled |> Maybe.justIf (not canEdit) |> Maybe.withDefault (PublicChange Remote >> lift |> HtmlE.onCheck)
|
||||
, WlA.checked |> Maybe.justIf model.public |> Maybe.withDefault HtmlA.nothing
|
||||
]
|
||||
, Html.span [ HtmlA.class "primary" ] [ Strings.Public |> Lang.html shared ]
|
||||
]
|
||||
)
|
||||
[ Message.info Strings.PublicDescription ]
|
||||
in
|
||||
Html.div [ HtmlA.class "game-settings" ]
|
||||
[ password
|
||||
[ public
|
||||
, password
|
||||
]
|
||||
|
||||
|
||||
@@ -681,7 +723,7 @@ addDeckWidget shared existing deckToAdd =
|
||||
in
|
||||
Html.form
|
||||
[ submit |> Result.map (lift >> HtmlE.onSubmit) |> Result.withDefault HtmlA.nothing ]
|
||||
[ Components.formSection
|
||||
[ Form.section
|
||||
shared
|
||||
"add-deck"
|
||||
(Html.div [ HtmlA.class "multipart" ]
|
||||
@@ -707,12 +749,12 @@ addDeckWidget shared existing deckToAdd =
|
||||
]
|
||||
|
||||
|
||||
submitDeckAction : List Deck -> Source.External -> Result (Components.Message Global.Msg) Msg
|
||||
submitDeckAction : List Deck -> Source.External -> Result (Message Global.Msg) Msg
|
||||
submitDeckAction existing deckToAdd =
|
||||
let
|
||||
potentialProblem =
|
||||
if List.any (.source >> Source.Ex >> Source.equals (Source.Ex deckToAdd)) existing then
|
||||
Strings.DeckAlreadyAdded |> Components.error |> Just
|
||||
Strings.DeckAlreadyAdded |> Message.error |> Just
|
||||
|
||||
else
|
||||
Source.validate (Source.Ex deckToAdd)
|
||||
|
||||
@@ -15,6 +15,8 @@ type Msg
|
||||
| ScoreLimitChange Target (Maybe Int)
|
||||
| PasswordChange Target (Maybe String)
|
||||
| HouseRuleChange Target Rules.HouseRuleChange
|
||||
| PublicChange Target Bool
|
||||
| TogglePasswordVisibility
|
||||
|
||||
|
||||
{-| We don't want to push every tiny change to the server. Instead we only push some changes.
|
||||
|
||||
@@ -13,7 +13,7 @@ import MassiveDecks.Game.Rules as Rules exposing (Rules)
|
||||
type Tab
|
||||
= Decks
|
||||
| Rules
|
||||
| Game
|
||||
| Privacy
|
||||
|
||||
|
||||
type alias DeckError =
|
||||
@@ -28,8 +28,10 @@ type alias Model =
|
||||
, handSize : Int
|
||||
, scoreLimit : Maybe Int
|
||||
, password : Maybe String
|
||||
, passwordVisible : Bool
|
||||
, tab : Tab
|
||||
, houseRules : Rules.HouseRules
|
||||
, public : Bool
|
||||
}
|
||||
|
||||
|
||||
@@ -46,4 +48,5 @@ type alias Config =
|
||||
, decks : List Deck
|
||||
, password : Maybe String
|
||||
, version : String
|
||||
, public : Bool
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ type ConfigChanged
|
||||
| ScoreLimitSet { limit : Maybe Int }
|
||||
| PasswordSet { password : Maybe String }
|
||||
| HouseRuleChanged { change : Rules.HouseRuleChange }
|
||||
| PublicSet { public : Bool }
|
||||
|
||||
|
||||
type DeckChange
|
||||
|
||||
@@ -6,6 +6,8 @@ import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Json.Decode
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form as Form
|
||||
import MassiveDecks.Components.Form.Message as Message
|
||||
import MassiveDecks.Messages as Global
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
@@ -18,7 +20,6 @@ import MassiveDecks.Util.Html as Html
|
||||
import QRCode
|
||||
import Url exposing (Url)
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
{-| A button to show the dialog.
|
||||
@@ -54,8 +55,8 @@ dialog shared gameCode password open =
|
||||
]
|
||||
Icon.times
|
||||
, Wl.card [ onClickNoPropegation Global.NoOp ]
|
||||
[ Strings.InviteExplanation { gameCode = gameCode, password = password } |> Lang.html shared
|
||||
, Components.formSection shared
|
||||
[ Strings.InviteExplanation { gameCode = GameCode.toString gameCode, password = password } |> Lang.html shared
|
||||
, Form.section shared
|
||||
"invite-link"
|
||||
(Html.div [ HtmlA.class "multipart" ]
|
||||
[ Html.input
|
||||
@@ -68,7 +69,7 @@ dialog shared gameCode password open =
|
||||
, Components.iconButton [ "invite-link-field" |> Global.Copy |> HtmlE.onClick ] Icon.copy
|
||||
]
|
||||
)
|
||||
[ Components.info Strings.InviteLinkHelp ]
|
||||
[ Message.info Strings.InviteLinkHelp ]
|
||||
, lobbyUrl |> qr
|
||||
]
|
||||
]
|
||||
@@ -81,7 +82,7 @@ overlay shared gameCode =
|
||||
Html.div [ HtmlA.class "invite" ]
|
||||
[ Html.div [ HtmlA.class "join-info" ]
|
||||
[ Html.p [] [ Strings.JoinTheGame |> Lang.html shared ]
|
||||
, Html.p [] [ Strings.GameCode { code = gameCode } |> Lang.html shared ]
|
||||
, Html.p [] [ Strings.GameCode { code = GameCode.toString gameCode } |> Lang.html shared ]
|
||||
, Html.p [] [ Html.text (stripProtocol shared.origin) ]
|
||||
]
|
||||
, Html.div [ HtmlA.class "qr-code" ] [ url shared gameCode |> qr ]
|
||||
|
||||
@@ -15,13 +15,7 @@ decode token =
|
||||
body
|
||||
|> Base64.decode
|
||||
|> Result.mapError TokenBase64Error
|
||||
|> Result.andThen
|
||||
(\json ->
|
||||
json
|
||||
|> Json.decodeString decodeClaims
|
||||
|> Result.mapError TokenJsonError
|
||||
|> Result.map (\claims -> Auth token claims)
|
||||
)
|
||||
|> Result.andThen (decodeJson token)
|
||||
|
||||
_ ->
|
||||
Err (InvalidTokenStructure token)
|
||||
@@ -31,6 +25,14 @@ decode token =
|
||||
{- Private -}
|
||||
|
||||
|
||||
decodeJson : Token -> String -> Result TokenDecodingError Auth
|
||||
decodeJson token json =
|
||||
json
|
||||
|> Json.decodeString decodeClaims
|
||||
|> Result.mapError TokenJsonError
|
||||
|> Result.map (\claims -> Auth token claims)
|
||||
|
||||
|
||||
decodeClaims : Json.Decoder Claims
|
||||
decodeClaims =
|
||||
Json.map3 Claims
|
||||
|
||||
@@ -18,6 +18,7 @@ import MassiveDecks.Card.Source.Cardcast.Model as Cardcast
|
||||
import MassiveDecks.Card.Source.Model as Source
|
||||
import MassiveDecks.Messages exposing (..)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode
|
||||
import MassiveDecks.Pages.Route as Route
|
||||
import MassiveDecks.Pages.Spectate.Messages as Spectate
|
||||
import MassiveDecks.Pages.Spectate.Model exposing (..)
|
||||
@@ -119,7 +120,7 @@ view shared model =
|
||||
]
|
||||
, Html.div [ HtmlA.class "join-info" ]
|
||||
[ Html.p [] [ Strings.JoinTheGame |> Lang.html shared ]
|
||||
, Html.p [] [ Strings.GameCode { code = model.route.lobby.gameCode } |> Lang.html shared ]
|
||||
, Html.p [] [ Strings.GameCode { code = GameCode.toString model.route.lobby.gameCode } |> Lang.html shared ]
|
||||
, Html.p [] [ Html.text (stripProtocol shared.origin) ]
|
||||
]
|
||||
, Html.div [ HtmlA.class "qr-code" ] qr
|
||||
|
||||
@@ -13,15 +13,17 @@ import FontAwesome.Solid as Icon
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Http
|
||||
import MassiveDecks.Card as Card
|
||||
import MassiveDecks.Card.Model as Card exposing (Card)
|
||||
import MassiveDecks.Card.Parts as Parts
|
||||
import MassiveDecks.Card.Source.Model as Source
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form as Form
|
||||
import MassiveDecks.Components.Form.Message as Message
|
||||
import MassiveDecks.Error as Error
|
||||
import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
import MassiveDecks.Messages as Global
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Models.MdError as MdError exposing (MdError)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Pages.Lobby.Token as Token
|
||||
@@ -34,6 +36,7 @@ import MassiveDecks.Requests.Api as Api
|
||||
import MassiveDecks.Requests.HttpData as HttpData
|
||||
import MassiveDecks.Requests.HttpData.Messages as HttpData
|
||||
import MassiveDecks.Requests.HttpData.Model as HttpData exposing (HttpData)
|
||||
import MassiveDecks.Requests.Request as Request
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util as Util
|
||||
@@ -78,6 +81,7 @@ init shared r =
|
||||
, lobbies = lobbies
|
||||
, newLobbyRequest = HttpData.initLazy
|
||||
, joinLobbyRequest = HttpData.initLazy
|
||||
, password = Nothing
|
||||
}
|
||||
, lobbiesCmd
|
||||
)
|
||||
@@ -106,7 +110,7 @@ update msg model =
|
||||
Just gc ->
|
||||
Util.modelLift (\jlr -> { model | joinLobbyRequest = jlr })
|
||||
(HttpData.update
|
||||
(joinGameRequest gc model.name)
|
||||
(joinGameRequest gc model.name model.password)
|
||||
httpDataMsg
|
||||
model.joinLobbyRequest
|
||||
)
|
||||
@@ -117,6 +121,26 @@ update msg model =
|
||||
LobbyBrowserMsg lbm ->
|
||||
Util.modelLift (\lobbies -> { model | lobbies = lobbies }) (LobbyBrowser.update lbm model.lobbies)
|
||||
|
||||
PasswordChanged newPassword ->
|
||||
( { model | password = Just newPassword }, Cmd.none )
|
||||
|
||||
PasswordWrong ->
|
||||
if Maybe.isJust model.password then
|
||||
let
|
||||
jlr =
|
||||
model.joinLobbyRequest
|
||||
|
||||
newJlr =
|
||||
{ jlr
|
||||
| error = MdError.InvalidLobbyPassword |> MdError.Authentication |> Just
|
||||
, loading = False
|
||||
}
|
||||
in
|
||||
( { model | joinLobbyRequest = newJlr }, Cmd.none )
|
||||
|
||||
else
|
||||
( { model | password = Just "", joinLobbyRequest = HttpData.initLazy }, Cmd.none )
|
||||
|
||||
|
||||
view : Shared -> Model -> List (Html Global.Msg)
|
||||
view shared model =
|
||||
@@ -170,20 +194,38 @@ view shared model =
|
||||
|
||||
startGameRequest : String -> HttpData.Pull Global.Msg
|
||||
startGameRequest name =
|
||||
HttpData.interceptedRequest
|
||||
(Api.newLobby { owner = { name = name } })
|
||||
(Token.decode >> Result.mapError Error.Token)
|
||||
(StartGame >> Global.StartMsg)
|
||||
(Global.JoinLobby name)
|
||||
Api.newLobby
|
||||
((HttpData.Response >> StartGame >> Global.StartMsg)
|
||||
|> Request.intercept Request.passthrough Request.passthrough (Request.replace (Global.JoinLobby name))
|
||||
)
|
||||
{ owner = { name = name, password = Nothing } }
|
||||
|> Http.request
|
||||
|
||||
|
||||
joinGameRequest : GameCode -> String -> HttpData.Pull Global.Msg
|
||||
joinGameRequest gameCode name =
|
||||
HttpData.interceptedRequest
|
||||
(Api.joinLobby gameCode { name = name })
|
||||
(Token.decode >> Result.mapError Error.Token)
|
||||
(StartGame >> Global.StartMsg)
|
||||
(Global.JoinLobby name)
|
||||
joinGameRequest : GameCode -> String -> Maybe String -> HttpData.Pull Global.Msg
|
||||
joinGameRequest gameCode name password =
|
||||
Api.joinLobby
|
||||
((HttpData.Response >> JoinGame >> Global.StartMsg)
|
||||
|> Request.intercept Request.passthrough (Request.maybeReplace onJoinError) (Request.replace (Global.JoinLobby name))
|
||||
)
|
||||
gameCode
|
||||
{ name = name, password = password }
|
||||
|> Http.request
|
||||
|
||||
|
||||
onJoinError : MdError -> Maybe Global.Msg
|
||||
onJoinError error =
|
||||
case error of
|
||||
MdError.Authentication ae ->
|
||||
case ae of
|
||||
MdError.InvalidLobbyPassword ->
|
||||
PasswordWrong |> Global.StartMsg |> Just
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
loadingOrLoaded : Model -> Bool
|
||||
@@ -276,16 +318,13 @@ newContent shared model =
|
||||
Icon.view Icon.play
|
||||
|
||||
error =
|
||||
model.newLobbyRequest.error
|
||||
|> Maybe.map
|
||||
(\e ->
|
||||
[ Error.view shared (Route.Start model.route) e ]
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
model.newLobbyRequest.generalError
|
||||
|> Maybe.map (Error.view shared (Route.Start model.route))
|
||||
|> Maybe.withDefault Html.nothing
|
||||
in
|
||||
Html.div [ HtmlA.class "new-game start-tab" ]
|
||||
(List.concat
|
||||
[ error
|
||||
[ [ error ]
|
||||
, nameField shared model
|
||||
, [ Wl.button
|
||||
[ buttonAttr
|
||||
@@ -315,12 +354,23 @@ joinContent shared model =
|
||||
|
||||
else
|
||||
Icon.view Icon.play
|
||||
|
||||
error =
|
||||
model.joinLobbyRequest.generalError
|
||||
|> Maybe.map (Error.view shared (Route.Start model.route))
|
||||
|> Maybe.withDefault Html.nothing
|
||||
|
||||
maybePasswordField =
|
||||
model.password
|
||||
|> Maybe.map (passwordField shared model.joinLobbyRequest.error)
|
||||
|> Maybe.withDefault []
|
||||
in
|
||||
Html.div [ HtmlA.class "join-game start-tab" ]
|
||||
(List.concat
|
||||
[ rejoinSection shared model
|
||||
[ [ error ]
|
||||
, rejoinSection shared model
|
||||
, nameField shared model
|
||||
, [ Components.formSection shared
|
||||
, [ Form.section shared
|
||||
"game-code-input"
|
||||
(Wl.textField
|
||||
[ HtmlA.class "game-code-input"
|
||||
@@ -331,8 +381,10 @@ joinContent shared model =
|
||||
]
|
||||
[]
|
||||
)
|
||||
[ Components.info Strings.GameCodeHowToAcquire ]
|
||||
, Wl.button
|
||||
[ Message.info Strings.GameCodeHowToAcquire ]
|
||||
]
|
||||
, maybePasswordField
|
||||
, [ Wl.button
|
||||
[ buttonAttr
|
||||
]
|
||||
[ buttonIcon, Strings.PlayGame |> Lang.html shared ]
|
||||
@@ -341,6 +393,24 @@ joinContent shared model =
|
||||
)
|
||||
|
||||
|
||||
passwordField : Shared -> Maybe MdError -> String -> List (Html Global.Msg)
|
||||
passwordField shared error password =
|
||||
[ Form.section shared
|
||||
"password-input"
|
||||
(Wl.textField
|
||||
[ PasswordChanged >> Global.StartMsg |> HtmlE.onInput
|
||||
, WlA.value password
|
||||
, WlA.outlined
|
||||
, Strings.LobbyPassword |> Lang.label shared
|
||||
]
|
||||
[]
|
||||
)
|
||||
[ Message.info Strings.LobbyRequiresPassword
|
||||
, error |> Maybe.map (MdError.describe >> Message.error) |> Maybe.withDefault Message.none
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
rejoinSection : Shared -> Model -> List (Html Global.Msg)
|
||||
rejoinSection shared model =
|
||||
let
|
||||
@@ -364,7 +434,7 @@ rejoinLobby shared result =
|
||||
Ok auth ->
|
||||
Html.li []
|
||||
[ Html.a [ Route.Lobby { gameCode = auth.claims.gc } |> Route.href ]
|
||||
[ Strings.RejoinGame { code = auth.claims.gc } |> Lang.html shared
|
||||
[ Strings.RejoinGame { code = GameCode.toString auth.claims.gc } |> Lang.html shared
|
||||
]
|
||||
]
|
||||
|> Just
|
||||
@@ -386,7 +456,7 @@ nameField shared model =
|
||||
_ =
|
||||
""
|
||||
in
|
||||
[ Components.formSection shared
|
||||
[ Form.section shared
|
||||
"name-input"
|
||||
(Wl.textField
|
||||
[ NameChanged
|
||||
|
||||
@@ -11,6 +11,7 @@ import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Html.Keyed as HtmlK
|
||||
import Http
|
||||
import MassiveDecks.Messages as Global
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode
|
||||
@@ -26,7 +27,9 @@ import MassiveDecks.Requests.HttpData.Messages as HttpData
|
||||
import MassiveDecks.Requests.HttpData.Model as HttpData exposing (HttpData)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString(..))
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.List as List
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
@@ -73,7 +76,7 @@ refresh model =
|
||||
|
||||
requestLobbySummaries : HttpData.Pull Global.Msg
|
||||
requestLobbySummaries =
|
||||
HttpData.request Api.lobbySummaries |> Cmd.map (SummaryUpdate >> lift)
|
||||
Api.lobbySummaries (HttpData.Response >> SummaryUpdate >> lift) |> Http.request
|
||||
|
||||
|
||||
lift : Msg -> Global.Msg
|
||||
@@ -145,7 +148,7 @@ stateGroup shared ( state, lobbies ) =
|
||||
( state |> stateId
|
||||
, Html.li []
|
||||
[ Html.div []
|
||||
[ Html.h2 [] [ state |> stateDescription |> Lang.html shared ]
|
||||
[ Html.h3 [] [ state |> stateDescription |> Lang.html shared ]
|
||||
, HtmlK.ul [] (lobbies |> List.map (lobby shared))
|
||||
]
|
||||
]
|
||||
@@ -159,8 +162,16 @@ lobby shared data =
|
||||
[ HtmlE.onClick (Route.Start { section = Start.Join (Just data.gameCode) } |> Global.ChangePage)
|
||||
, WlA.clickable
|
||||
]
|
||||
[ Html.span [] [ Html.text data.name ]
|
||||
, Html.span [] [ Strings.GameCode { code = data.gameCode } |> Lang.html shared ]
|
||||
[ Html.span [ HtmlA.class "lobby-name", Strings.LobbyRequiresPassword |> Lang.title shared ]
|
||||
[ Html.text data.name
|
||||
, Html.text " "
|
||||
, Icon.lock
|
||||
|> Icon.viewStyled []
|
||||
|> Maybe.justIf data.password
|
||||
|> Maybe.withDefault Html.nothing
|
||||
]
|
||||
, Html.span [ HtmlA.class "lobby-game-code" ]
|
||||
[ Strings.GameCode { code = GameCode.toString data.gameCode } |> Lang.html shared ]
|
||||
, Icon.viewStyled
|
||||
[ HtmlA.title "Join Game"
|
||||
, WlA.listItemSlot WlA.AfterItem
|
||||
|
||||
@@ -5,4 +5,4 @@ import MassiveDecks.Requests.HttpData.Messages as HttpData
|
||||
|
||||
|
||||
type Msg
|
||||
= SummaryUpdate (HttpData.Msg (List Summary))
|
||||
= SummaryUpdate (HttpData.Msg () (List Summary))
|
||||
|
||||
@@ -4,6 +4,7 @@ module MassiveDecks.Pages.Start.LobbyBrowser.Model exposing
|
||||
, UserSummary
|
||||
)
|
||||
|
||||
import MassiveDecks.Error.Model exposing (Error)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Requests.HttpData.Model exposing (HttpData)
|
||||
@@ -12,7 +13,7 @@ import MassiveDecks.Requests.HttpData.Model exposing (HttpData)
|
||||
{-| The model for the lobby browser.
|
||||
-}
|
||||
type alias Model =
|
||||
HttpData (List Summary)
|
||||
HttpData () (List Summary)
|
||||
|
||||
|
||||
{-| An external summary of a lobby.
|
||||
@@ -22,6 +23,7 @@ type alias Summary =
|
||||
, gameCode : GameCode
|
||||
, state : Lobby.State
|
||||
, users : UserSummary
|
||||
, password : Bool
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module MassiveDecks.Pages.Start.Messages exposing (Msg(..))
|
||||
|
||||
import MassiveDecks.Models.MdError exposing (MdError)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Pages.Start.LobbyBrowser.Messages as LobbyBrowser
|
||||
import MassiveDecks.Requests.HttpData.Messages as HttpData
|
||||
@@ -8,6 +9,8 @@ import MassiveDecks.Requests.HttpData.Messages as HttpData
|
||||
type Msg
|
||||
= GameCodeChanged String
|
||||
| NameChanged String
|
||||
| StartGame (HttpData.Msg Lobby.Auth)
|
||||
| JoinGame (HttpData.Msg Lobby.Auth)
|
||||
| StartGame (HttpData.Msg () Lobby.Auth)
|
||||
| JoinGame (HttpData.Msg MdError Lobby.Auth)
|
||||
| LobbyBrowserMsg LobbyBrowser.Msg
|
||||
| PasswordChanged String
|
||||
| PasswordWrong
|
||||
|
||||
@@ -3,6 +3,7 @@ module MassiveDecks.Pages.Start.Model exposing
|
||||
, Model
|
||||
)
|
||||
|
||||
import MassiveDecks.Models.MdError exposing (LobbyNotFoundError, MdError)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Pages.Start.LobbyBrowser.Model as LobbyBrowser
|
||||
@@ -18,8 +19,9 @@ type alias Model =
|
||||
, lobbies : LobbyBrowser.Model
|
||||
, name : String
|
||||
, gameCode : Maybe GameCode
|
||||
, newLobbyRequest : HttpData Lobby.Auth
|
||||
, joinLobbyRequest : HttpData Lobby.Auth
|
||||
, newLobbyRequest : HttpData () Lobby.Auth
|
||||
, joinLobbyRequest : HttpData MdError Lobby.Auth
|
||||
, password : Maybe String
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,27 +7,31 @@ module MassiveDecks.Requests.Api exposing
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Http
|
||||
import Json.Decode
|
||||
import Json.Decode as Json
|
||||
import MassiveDecks.Error.Model as Error
|
||||
import MassiveDecks.Models.Decoders as Decoders
|
||||
import MassiveDecks.Models.Encoders as Encoders
|
||||
import MassiveDecks.Models.MdError exposing (MdError)
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Pages.Lobby.Token as Token
|
||||
import MassiveDecks.Pages.Start.LobbyBrowser.Model as LobbyBrowser
|
||||
import MassiveDecks.Pages.Start.Model as Start
|
||||
import MassiveDecks.Requests.Request exposing (Request)
|
||||
import MassiveDecks.Requests.Request as Request exposing (Request)
|
||||
import MassiveDecks.User as User
|
||||
import MassiveDecks.Util.Result as Result
|
||||
import Url.Builder
|
||||
|
||||
|
||||
{-| List the public lobbies.
|
||||
-}
|
||||
lobbySummaries : Request (List LobbyBrowser.Summary)
|
||||
lobbySummaries =
|
||||
lobbySummaries : (Request.Response () (List LobbyBrowser.Summary) -> msg) -> Request msg
|
||||
lobbySummaries msg =
|
||||
{ method = "GET"
|
||||
, headers = []
|
||||
, url = url [ "games" ]
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectJson identity (Json.Decode.list Decoders.lobbySummary)
|
||||
, expect = Request.expectResponse msg noError (Json.list Decoders.lobbySummary)
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
@@ -35,37 +39,37 @@ lobbySummaries =
|
||||
|
||||
{-| Create a new lobby.
|
||||
-}
|
||||
newLobby : Start.LobbyCreation -> Request Lobby.Token
|
||||
newLobby creation =
|
||||
newLobby : (Request.Response () Lobby.Auth -> msg) -> Start.LobbyCreation -> Request msg
|
||||
newLobby msg creation =
|
||||
{ method = "POST"
|
||||
, headers = []
|
||||
, url = url [ "games" ]
|
||||
, body = creation |> Encoders.lobbyCreation |> Http.jsonBody
|
||||
, expect = Http.expectJson identity Decoders.lobbyToken
|
||||
, expect = Request.expectResponse (decodeToken >> msg) noError Decoders.lobbyToken
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
joinLobby : GameCode -> User.Registration -> Request Lobby.Token
|
||||
joinLobby gameCode registration =
|
||||
joinLobby : (Request.Response MdError Lobby.Auth -> msg) -> GameCode -> User.Registration -> Request msg
|
||||
joinLobby msg gameCode registration =
|
||||
{ method = "POST"
|
||||
, headers = []
|
||||
, url = url [ "games", gameCode |> GameCode.toString ]
|
||||
, body = registration |> Encoders.userRegistration |> Http.jsonBody
|
||||
, expect = Http.expectJson identity Decoders.lobbyToken
|
||||
, expect = Request.expectResponse (decodeToken >> msg) Decoders.mdError Decoders.lobbyToken
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
checkAlive : List Lobby.Token -> Request (Dict Lobby.Token Bool)
|
||||
checkAlive tokens =
|
||||
checkAlive : (Request.Response () (Dict Lobby.Token Bool) -> msg) -> List Lobby.Token -> Request msg
|
||||
checkAlive msg tokens =
|
||||
{ method = "POST"
|
||||
, headers = []
|
||||
, url = url [ "games", "alive" ]
|
||||
, body = tokens |> Encoders.checkAlive |> Http.jsonBody
|
||||
, expect = Http.expectJson identity Decoders.tokenValidity
|
||||
, expect = Request.expectResponse msg noError Decoders.tokenValidity
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
@@ -75,6 +79,19 @@ checkAlive tokens =
|
||||
{- Private -}
|
||||
|
||||
|
||||
decodeToken : Request.Response error Lobby.Token -> Request.Response error Lobby.Auth
|
||||
decodeToken =
|
||||
Request.map
|
||||
Request.GeneralError
|
||||
Request.SpecificError
|
||||
(Token.decode >> Result.unifiedMap (Error.Token >> Request.GeneralError) Request.Value)
|
||||
|
||||
|
||||
url : List String -> String
|
||||
url path =
|
||||
Url.Builder.absolute ([ "api" ] ++ path) []
|
||||
|
||||
|
||||
noError : Json.Decoder ()
|
||||
noError =
|
||||
Json.succeed ()
|
||||
|
||||
@@ -2,11 +2,8 @@ module MassiveDecks.Requests.HttpData exposing
|
||||
( autoRefresh
|
||||
, init
|
||||
, initLazy
|
||||
, interceptedRequest
|
||||
, loadingOrLoaded
|
||||
, mappedRequest
|
||||
, refreshButton
|
||||
, request
|
||||
, update
|
||||
, view
|
||||
)
|
||||
@@ -17,17 +14,14 @@ import FontAwesome.Solid as Icon
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Http
|
||||
import MassiveDecks.Error as Error
|
||||
import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Route exposing (Route)
|
||||
import MassiveDecks.Requests.HttpData.Messages exposing (..)
|
||||
import MassiveDecks.Requests.HttpData.Model exposing (..)
|
||||
import MassiveDecks.Requests.Request exposing (Request)
|
||||
import MassiveDecks.Requests.Request as Request exposing (Request)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Result as Result
|
||||
import Time
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
@@ -35,35 +29,35 @@ import Weightless.Attributes as WlA
|
||||
|
||||
{-| Set up the empty HttpData and send a request to load the data.
|
||||
-}
|
||||
init : Pull msg -> ( HttpData result, Cmd msg )
|
||||
init : Pull msg -> ( HttpData error result, Cmd msg )
|
||||
init req =
|
||||
( initLazy, req )
|
||||
|
||||
|
||||
{-| Set up the empty HttpData with no initial request, the request can be made later.
|
||||
-}
|
||||
initLazy : HttpData result
|
||||
initLazy : HttpData error result
|
||||
initLazy =
|
||||
{ loading = False, data = Nothing, error = Nothing }
|
||||
{ loading = False, data = Nothing, error = Nothing, generalError = Nothing }
|
||||
|
||||
|
||||
{-| Tries to refresh the data every X milliseconds.
|
||||
-}
|
||||
autoRefresh : Float -> Sub (Msg result)
|
||||
autoRefresh : Float -> Sub (Msg error result)
|
||||
autoRefresh every =
|
||||
Time.every every (\_ -> Pull)
|
||||
|
||||
|
||||
{-| If the data has been loaded, or is currently loading.
|
||||
-}
|
||||
loadingOrLoaded : HttpData result -> Bool
|
||||
loadingOrLoaded : HttpData error result -> Bool
|
||||
loadingOrLoaded model =
|
||||
model.loading || model.data /= Nothing
|
||||
|
||||
|
||||
{-| Update the data with the response from the request.
|
||||
-}
|
||||
update : Pull msg -> Msg result -> HttpData result -> ( HttpData result, Cmd msg )
|
||||
update : Pull msg -> Msg error result -> HttpData error result -> ( HttpData error result, Cmd msg )
|
||||
update req msg model =
|
||||
case msg of
|
||||
Pull ->
|
||||
@@ -79,32 +73,30 @@ update req msg model =
|
||||
{ model | loading = False }
|
||||
in
|
||||
case result of
|
||||
Ok response ->
|
||||
( { loadingStoppedModel
|
||||
| data = Just response
|
||||
, error = Nothing
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
Request.Value response ->
|
||||
( { loadingStoppedModel | data = Just response, generalError = Nothing }, Cmd.none )
|
||||
|
||||
Err error ->
|
||||
Request.SpecificError error ->
|
||||
( { loadingStoppedModel | error = Just error }, Cmd.none )
|
||||
|
||||
Request.GeneralError generalError ->
|
||||
( { loadingStoppedModel | generalError = Just generalError }, Cmd.none )
|
||||
|
||||
|
||||
{-| A view over the data with any error received trying to load (or refresh) if it isn't there (or prefixed if during a
|
||||
refresh).
|
||||
-}
|
||||
view : Shared -> Route -> (Msg result -> msg) -> (result -> Html msg) -> HttpData result -> Html msg
|
||||
view : Shared -> Route -> (Msg error result -> msg) -> (result -> Html msg) -> HttpData error result -> Html msg
|
||||
view shared route wrap viewResult model =
|
||||
let
|
||||
error =
|
||||
model.error |> Maybe.map (Error.view shared route)
|
||||
generalError =
|
||||
model.generalError |> Maybe.map (Error.view shared route)
|
||||
|
||||
result =
|
||||
model.data |> Maybe.map viewResult
|
||||
|
||||
content =
|
||||
List.filterMap identity [ error, result ]
|
||||
List.filterMap identity [ generalError, result ]
|
||||
|
||||
contentOrSpinner =
|
||||
if List.isEmpty content then
|
||||
@@ -118,7 +110,7 @@ view shared route wrap viewResult model =
|
||||
|
||||
{-| Show a refresh button for the data.
|
||||
-}
|
||||
refreshButton : Shared -> HttpData result -> Html (Msg result)
|
||||
refreshButton : Shared -> HttpData error result -> Html (Msg error result)
|
||||
refreshButton shared model =
|
||||
let
|
||||
title =
|
||||
@@ -147,49 +139,3 @@ refreshButton shared model =
|
||||
Wl.button
|
||||
(List.concat [ style, title, onClick ])
|
||||
[ Icon.viewStyled spin Icon.sync ]
|
||||
|
||||
|
||||
{-| A request that just stores the result in the `HttpData`.
|
||||
-}
|
||||
request : Request result -> Cmd (Msg result)
|
||||
request req =
|
||||
mappedRequest req Ok
|
||||
|
||||
|
||||
{-| A request that just maps the result before storage in the `HttpData`.
|
||||
-}
|
||||
mappedRequest : Request response -> (response -> Result Error result) -> Pull (Msg result)
|
||||
mappedRequest req f =
|
||||
interceptedRequest req f identity (Ok >> Response)
|
||||
|
||||
|
||||
{-| A request that just maps the result before storage in the `HttpData`, as well as intercepting successes to perform
|
||||
an action (note that this means the value will never get filled!
|
||||
-}
|
||||
interceptedRequest :
|
||||
Request response
|
||||
-> (response -> Result Error result)
|
||||
-> (Msg result -> msg)
|
||||
-> (result -> msg)
|
||||
-> Pull msg
|
||||
interceptedRequest req f wrap intercept =
|
||||
req |> Http.request |> Cmd.map (mapResponse f wrap intercept)
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
mapResponse :
|
||||
(response -> Result Error result)
|
||||
-> (Msg result -> msg)
|
||||
-> (result -> msg)
|
||||
-> Result Http.Error response
|
||||
-> msg
|
||||
mapResponse f wrap intercept result =
|
||||
result
|
||||
|> Result.mapError Error.Http
|
||||
|> Result.andThen f
|
||||
|> Result.map intercept
|
||||
|> Result.mapError (Err >> Response >> wrap)
|
||||
|> Result.unify
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module MassiveDecks.Requests.HttpData.Messages exposing (Msg(..))
|
||||
|
||||
import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
import MassiveDecks.Requests.Request as Request
|
||||
|
||||
|
||||
{-| A message for HttpData.
|
||||
-}
|
||||
type Msg result
|
||||
type Msg error result
|
||||
= Pull
|
||||
| Response (Result Error result)
|
||||
| Response (Request.Response error result)
|
||||
|
||||
@@ -8,10 +8,11 @@ import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
|
||||
{-| Some data that is requested and received via an HTTP request.
|
||||
-}
|
||||
type alias HttpData result =
|
||||
type alias HttpData error result =
|
||||
{ loading : Bool
|
||||
, data : Maybe result
|
||||
, error : Maybe Error
|
||||
, error : Maybe error
|
||||
, generalError : Maybe Error
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
module MassiveDecks.Requests.Request exposing (Request)
|
||||
module MassiveDecks.Requests.Request exposing
|
||||
( Interception
|
||||
, Request
|
||||
, Response(..)
|
||||
, expectResponse
|
||||
, intercept
|
||||
, map
|
||||
, maybeReplace
|
||||
, passthrough
|
||||
, replace
|
||||
)
|
||||
|
||||
import Http
|
||||
import Json.Decode as Json
|
||||
import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
import MassiveDecks.Util.Result as Result
|
||||
|
||||
|
||||
type alias Request msg =
|
||||
@@ -8,7 +21,136 @@ type alias Request msg =
|
||||
, headers : List Http.Header
|
||||
, url : String
|
||||
, body : Http.Body
|
||||
, expect : Http.Expect (Result Http.Error msg)
|
||||
, expect : Http.Expect msg
|
||||
, timeout : Maybe Float
|
||||
, tracker : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type Response error value
|
||||
= GeneralError Error
|
||||
| SpecificError error
|
||||
| Value value
|
||||
|
||||
|
||||
type Interception intercepted msg
|
||||
= Intercept (intercepted -> Maybe msg)
|
||||
| Continue
|
||||
|
||||
|
||||
replace : (intercepted -> msg) -> Interception intercepted msg
|
||||
replace originally =
|
||||
Intercept (originally >> Just)
|
||||
|
||||
|
||||
maybeReplace : (intercepted -> Maybe msg) -> Interception intercepted msg
|
||||
maybeReplace =
|
||||
Intercept
|
||||
|
||||
|
||||
passthrough : Interception intercepted msg
|
||||
passthrough =
|
||||
Continue
|
||||
|
||||
|
||||
expectResponse : (Response error value -> msg) -> Json.Decoder error -> Json.Decoder value -> Http.Expect msg
|
||||
expectResponse mapMsg errorDecoder resultDecoder =
|
||||
expectJsonOrError mapMsg errorDecoder resultDecoder
|
||||
|
||||
|
||||
intercept :
|
||||
Interception Error msg
|
||||
-> Interception error msg
|
||||
-> Interception value msg
|
||||
-> (Response error value -> msg)
|
||||
-> Response error value
|
||||
-> msg
|
||||
intercept generalErrorInterception errorInterception resultInterception otherwise response =
|
||||
case response of
|
||||
GeneralError error ->
|
||||
case generalErrorInterception of
|
||||
Intercept f ->
|
||||
f error |> Maybe.withDefault (otherwise response)
|
||||
|
||||
Continue ->
|
||||
otherwise response
|
||||
|
||||
SpecificError error ->
|
||||
case errorInterception of
|
||||
Intercept f ->
|
||||
f error |> Maybe.withDefault (otherwise response)
|
||||
|
||||
Continue ->
|
||||
otherwise response
|
||||
|
||||
Value result ->
|
||||
case resultInterception of
|
||||
Intercept f ->
|
||||
f result |> Maybe.withDefault (otherwise response)
|
||||
|
||||
Continue ->
|
||||
otherwise response
|
||||
|
||||
|
||||
map : (Error -> msg) -> (error -> msg) -> (value -> msg) -> Response error value -> msg
|
||||
map mapNetworkError mapError mapResult response =
|
||||
case response of
|
||||
GeneralError error ->
|
||||
mapNetworkError error
|
||||
|
||||
SpecificError error ->
|
||||
mapError error
|
||||
|
||||
Value result ->
|
||||
mapResult result
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
responseMap : (Response error value -> msg) -> Result Never (Response error value) -> msg
|
||||
responseMap mapMsg value =
|
||||
value |> Result.byDefinition |> mapMsg
|
||||
|
||||
|
||||
expectJsonOrError : (Response error value -> msg) -> Json.Decoder error -> Json.Decoder value -> Http.Expect msg
|
||||
expectJsonOrError toMsg errorDecoder valueDecoder =
|
||||
Http.expectStringResponse (responseMap toMsg) (manageResponse errorDecoder valueDecoder)
|
||||
|
||||
|
||||
manageResponse : Json.Decoder error -> Json.Decoder value -> Http.Response String -> Result Never (Response error value)
|
||||
manageResponse errorDecoder valueDecoder response =
|
||||
let
|
||||
r =
|
||||
case response of
|
||||
Http.BadUrl_ url ->
|
||||
url |> Error.BadUrl |> Error.Http |> GeneralError
|
||||
|
||||
Http.Timeout_ ->
|
||||
Error.Timeout |> Error.Http |> GeneralError
|
||||
|
||||
Http.NetworkError_ ->
|
||||
Error.NetworkError |> Error.Http |> GeneralError
|
||||
|
||||
Http.BadStatus_ metadata body ->
|
||||
if metadata.statusCode >= 400 && metadata.statusCode < 500 then
|
||||
case Json.decodeString errorDecoder body of
|
||||
Ok error ->
|
||||
error |> SpecificError
|
||||
|
||||
Err err ->
|
||||
err |> Error.Json |> GeneralError
|
||||
|
||||
else
|
||||
metadata.statusCode |> Error.BadStatus |> Error.Http |> GeneralError
|
||||
|
||||
Http.GoodStatus_ metadata body ->
|
||||
case Json.decodeString valueDecoder body of
|
||||
Ok value ->
|
||||
value |> Value
|
||||
|
||||
Err err ->
|
||||
err |> Error.Json |> GeneralError
|
||||
in
|
||||
Ok r
|
||||
|
||||
@@ -17,6 +17,8 @@ import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Http
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form as Form
|
||||
import MassiveDecks.Components.Form.Message as Message
|
||||
import MassiveDecks.LocalStorage as LocalStorage
|
||||
import MassiveDecks.Messages as Global
|
||||
import MassiveDecks.Model exposing (..)
|
||||
@@ -24,12 +26,12 @@ import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Pages.Lobby.Token as Token
|
||||
import MassiveDecks.Requests.Api as Api
|
||||
import MassiveDecks.Requests.Request as Request
|
||||
import MassiveDecks.Settings.Messages exposing (..)
|
||||
import MassiveDecks.Settings.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Strings.Languages.Model as Lang exposing (Language)
|
||||
import MassiveDecks.Util.Result as Result
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
@@ -44,13 +46,8 @@ init settings =
|
||||
else
|
||||
settings.tokens
|
||||
|> Dict.values
|
||||
|> Api.checkAlive
|
||||
|> Api.checkAlive (Request.map ignore ignore (RemoveInvalid >> Global.SettingsMsg))
|
||||
|> Http.request
|
||||
|> Cmd.map
|
||||
(Result.map (RemoveInvalid >> Global.SettingsMsg)
|
||||
>> Result.mapError (always Global.NoOp)
|
||||
>> Result.unify
|
||||
)
|
||||
in
|
||||
( { settings = settings
|
||||
, open = False
|
||||
@@ -160,6 +157,11 @@ auths settings =
|
||||
{- Private -}
|
||||
|
||||
|
||||
ignore : anything -> Global.Msg
|
||||
ignore =
|
||||
always Global.NoOp
|
||||
|
||||
|
||||
changeSettings : (Settings -> Settings) -> Model -> ( Model, Cmd msg )
|
||||
changeSettings f model =
|
||||
let
|
||||
@@ -178,7 +180,7 @@ compactSwitch shared =
|
||||
settings =
|
||||
model.settings
|
||||
in
|
||||
Components.formSection shared
|
||||
Form.section shared
|
||||
"compact-cards"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
@@ -193,7 +195,7 @@ compactSwitch shared =
|
||||
]
|
||||
]
|
||||
)
|
||||
[ Components.info Strings.CompactCardsExplanation ]
|
||||
[ Message.info Strings.CompactCardsExplanation ]
|
||||
|
||||
|
||||
|
||||
@@ -202,7 +204,7 @@ compactSwitch shared =
|
||||
|
||||
speechSwitch : Shared -> Html Global.Msg
|
||||
speechSwitch shared =
|
||||
Components.formSection shared
|
||||
Form.section shared
|
||||
"speech"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
@@ -214,7 +216,7 @@ speechSwitch shared =
|
||||
]
|
||||
]
|
||||
)
|
||||
[ Components.info Strings.SpeechExplanation ]
|
||||
[ Message.info Strings.SpeechExplanation ]
|
||||
|
||||
|
||||
|
||||
@@ -223,7 +225,7 @@ speechSwitch shared =
|
||||
|
||||
notificationsSwitch : Shared -> Html Global.Msg
|
||||
notificationsSwitch shared =
|
||||
Components.formSection shared
|
||||
Form.section shared
|
||||
"notifications"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
@@ -235,8 +237,8 @@ notificationsSwitch shared =
|
||||
]
|
||||
]
|
||||
)
|
||||
[ Components.info Strings.NotificationsExplanation
|
||||
, Components.info Strings.NotificationsBrowserPermissions
|
||||
[ Message.info Strings.NotificationsExplanation
|
||||
, Message.info Strings.NotificationsBrowserPermissions
|
||||
]
|
||||
|
||||
|
||||
@@ -246,7 +248,7 @@ languageSelector shared =
|
||||
selected =
|
||||
Lang.currentLanguage shared
|
||||
in
|
||||
Components.formSection
|
||||
Form.section
|
||||
shared
|
||||
"language"
|
||||
(Wl.select
|
||||
@@ -256,7 +258,7 @@ languageSelector shared =
|
||||
]
|
||||
(Lang.languages |> List.map (languageOption selected))
|
||||
)
|
||||
[ Components.info Strings.MissingLanguage ]
|
||||
[ Message.info Strings.MissingLanguage ]
|
||||
|
||||
|
||||
onChangeLang : String -> Global.Msg
|
||||
|
||||
@@ -3,8 +3,6 @@ module MassiveDecks.Strings exposing (MdString(..))
|
||||
{-| This module deals with text that is shown to the user in the application - strings.
|
||||
-}
|
||||
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
|
||||
|
||||
{-| Each type represents a message that may be shown to the user. Some have arguments that are variable but should be
|
||||
included in some form in the message.
|
||||
@@ -36,7 +34,8 @@ type MdString
|
||||
| NameLabel -- A label for a user name text field.
|
||||
| NameInUse -- An error indicating the name the user asked for is already in use and they should try another.
|
||||
| RejoinTitle -- A title for a list of games the user was previously in and might be able to rejoin.
|
||||
| RejoinGame { code : GameCode } -- A description of the action of attempting to rejoin a game the user was previously in.
|
||||
| RejoinGame { code : String } -- A description of the action of attempting to rejoin a game the user was previously in.
|
||||
| LobbyRequiresPassword -- An explanation that the given lobby requires a password to join.
|
||||
-- Rules
|
||||
| CardsAgainstHumanity -- The name of "Cards Against Humanity" (https://cardsagainsthumanity.com/).
|
||||
| Rules -- The title for a DESCRIPTION of the rules.
|
||||
@@ -79,6 +78,8 @@ type MdString
|
||||
-- Terms
|
||||
| Czar -- The name for the "Card Czar" (the player that judges the round).
|
||||
| CzarDescription -- A short description of what the czar does.
|
||||
| Player -- A term for a player in the game with no special role.
|
||||
| Spectator -- A term for a user who watches the game, but doesn't play in it.
|
||||
| Call -- The name for a call card (a black card).
|
||||
| CallDescription -- A short description of what a call is.
|
||||
| Response -- The name for a response card (a white card).
|
||||
@@ -87,11 +88,11 @@ type MdString
|
||||
| PointDescription -- A short description of what a point is.
|
||||
| GameCodeTerm -- The term for a unique code for a game that allows a user to find the game easily.
|
||||
| GameCodeDescription -- A short description of what a game code is.
|
||||
| GameCode { code : GameCode } -- Render a game code.
|
||||
| GameCode { code : String } -- Render a game code.
|
||||
| GameCodeSpecificDescription -- A short description of what a specific game code and how to use it.
|
||||
| GameCodeHowToAcquire -- A short description of how to get a game code.
|
||||
| Deck -- The name for a deck of cards.
|
||||
| Playing -- A term for a person who is in a round, but has not yet submitted a play.
|
||||
| StillPlaying -- A term for a person who is in a round, but has not yet submitted a play.
|
||||
| PlayingDescription -- A description of a person who is in a round, but has not yet submitted a play.
|
||||
| Played -- A term for a person who is in a round, and has submitted a play.
|
||||
| PlayedDescription -- A description of a person who is in a round, and has submitted a play.
|
||||
@@ -111,7 +112,7 @@ type MdString
|
||||
| NumberOfCards { numberOfCards : Int } -- A number of cards as a single-digit number. This will be enhanced to render specially as a circle with the number in.
|
||||
-- Lobby
|
||||
| Invite -- A description of the action of inviting players to the game.
|
||||
| InviteExplanation { gameCode : GameCode, password : Maybe String } -- An explanation of how players can join the game using the given game code and, potentially, password.
|
||||
| InviteExplanation { gameCode : String, password : Maybe String } -- An explanation of how players can join the game using the given game code and, potentially, password.
|
||||
| InviteLinkHelp -- An explanation that the users can send the link to people to invite them to the game.
|
||||
| Cast -- A description of the action of casting a view of the game to another device (e.g: a TV).
|
||||
| CastConnecting -- A description of trying to connect to the casting device.
|
||||
@@ -153,22 +154,29 @@ type MdString
|
||||
| DeckAlreadyAdded -- A description of the problem of the deck already being added to the game configuration.
|
||||
| ConfigureDecks -- A name for the section of the configuration screen for changing the decks for the game.
|
||||
| ConfigureRules -- A name for the section of the configuration screen for changing the rules for the game.
|
||||
| ConfigureGame -- A name for the section of the configuration screen for changing the settings for the game.
|
||||
| ConfigurePrivacy -- A name for the section of the configuration screen for changing the settings for the game.
|
||||
| HandSize -- The name of the rule defining how many cards a player can hold in their hand.
|
||||
| HandSizeDescription -- The description of the above rule.
|
||||
| ScoreLimit -- The name of the rule defining how many points a player has to accumulate to win the game.
|
||||
| ScoreLimitDescription -- The description of the above rule.
|
||||
| NeedAtLeastOneDeck -- A description of the problem that the game needs at least one deck to start.
|
||||
| NeedAtLeastThreePlayers -- A description of the problem that the game needs at least three players to start.
|
||||
| PasswordShared -- A warning that game passwords are visible to anyone else in the game.
|
||||
| PasswordNotSecured -- A warning that game passwords are not stored securely and should not be used elsewhere.
|
||||
| GamePassword -- A short label for the game password.
|
||||
| GamePasswordDescription -- A description of a password to stop random people entering your game.
|
||||
| LobbyPassword -- A short label for the lobby password.
|
||||
| LobbyPasswordDescription -- A description of a password to stop random people entering your lobby.
|
||||
| StartGame -- A short description of the action of starting the game.
|
||||
| Public -- The name of the setting for making the lobby public.
|
||||
| PublicDescription -- A description of what the public setting does (makes the game visible in the lobby browser).
|
||||
-- Game
|
||||
| SubmitPlay -- A description of the action of submitting the play for the czar to judge.
|
||||
| TakeBackPlay -- A description of the action of taking back a previously submitted play.
|
||||
| JudgePlay -- A description of the action of choosing a play to win the round.
|
||||
| LikePlay -- A description of the action of liking a play.
|
||||
| Playing -- A description of the stage of the round where players are playing responses into the round.
|
||||
| Revealing -- A description of the stage of the round where the czar is revealing the plays.
|
||||
| Judging -- A description of the stage of the round where the czar is picking a winner.
|
||||
| Complete -- A description of the stage of the round where it is finished.
|
||||
-- Instructions
|
||||
| PlayInstruction { numberOfCards : Int } -- Instruction to the player on how to play cards.
|
||||
| SubmitInstruction -- Instruction to the player on how to submit their play.
|
||||
@@ -194,6 +202,18 @@ type MdString
|
||||
| BadStatusError -- An error where the server gave a response we didn't expect.
|
||||
| BadPayloadError -- An error where the server gave a response we didn't understand.
|
||||
| CastError -- An error where we the cast device couldn't connect to the game.
|
||||
| IncorrectPlayerRoleError { role : MdString, expected : MdString } -- An error where the player tries to do something when they don't have the right role (czar/player).
|
||||
| IncorrectUserRoleError { role : MdString, expected : MdString } -- An error where the user tries to do something when they don't have the right role (player/spectator).
|
||||
| IncorrectRoundStageError { stage : MdString, expected : MdString } -- An error where the user tries to do something when it doesn't make sense given the stage of the game.
|
||||
| ConfigEditConflictError -- An error where the user tries to make a change to the configuration, but someone else changed it first.
|
||||
| UnprivilegedError -- An error where the user doesn't have the privileges to perform the action they are trying to do.
|
||||
| GameNotStartedError -- An error where the game hasn't started and the user tries to do something that needs to be done in a game.
|
||||
| IncorrectIssuerError -- An error where the user tries to authenticate using credentials that are out of date.
|
||||
| InvalidAuthenticationError -- An error where the user tries to authenticate using credentials that are corrupted.
|
||||
| InvalidLobbyPasswordError -- An error where the user tries to join a game with the wrong lobby password.
|
||||
| LobbyClosedError { gameCode : String } -- An error where the user tries to join a game that has finished.
|
||||
| LobbyDoesNotExistError { gameCode : String } -- An error where the user tries to join a game that never existed.
|
||||
| OutOfCardsError -- An error where there weren't enough cards in the deck to deal cards that were needed, even after shuffling discards.
|
||||
-- Language Names
|
||||
| English -- The name of the English language (no specific dialect).
|
||||
| BritishEnglish -- The name of the British dialect of the English language.
|
||||
|
||||
@@ -4,7 +4,6 @@ module MassiveDecks.Strings.Languages.En exposing (pack)
|
||||
This is the primary language, strings here are the canonical representation, and are suitable to translate from.
|
||||
-}
|
||||
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode
|
||||
import MassiveDecks.Strings exposing (MdString(..))
|
||||
import MassiveDecks.Strings.Translation as Translation exposing (Result(..))
|
||||
|
||||
@@ -109,6 +108,9 @@ translate mdString =
|
||||
RejoinGame { code } ->
|
||||
[ Text "Rejoin “", GameCode { code = code } |> Ref, Text "”." ]
|
||||
|
||||
LobbyRequiresPassword ->
|
||||
[ Text "You need a password to join this game. Try asking the person that invited you." ]
|
||||
|
||||
-- Rules
|
||||
CardsAgainstHumanity ->
|
||||
[ Text "Cards Against Humanity" ]
|
||||
@@ -281,6 +283,12 @@ translate mdString =
|
||||
CzarDescription ->
|
||||
[ Text "The player judging the round." ]
|
||||
|
||||
Player ->
|
||||
[ Text "Player" ]
|
||||
|
||||
Spectator ->
|
||||
[ Text "Spectator" ]
|
||||
|
||||
Call ->
|
||||
[ Text "Black Card" ]
|
||||
|
||||
@@ -306,7 +314,7 @@ translate mdString =
|
||||
[ Text "A code that lets other people find and join your game." ]
|
||||
|
||||
GameCode { code } ->
|
||||
[ Text (GameCode.toString code) ]
|
||||
[ Text code ]
|
||||
|
||||
GameCodeSpecificDescription ->
|
||||
[ Text "Give this game code to people and they can join the game." ]
|
||||
@@ -317,7 +325,7 @@ translate mdString =
|
||||
Deck ->
|
||||
[ Text "Deck" ]
|
||||
|
||||
Playing ->
|
||||
StillPlaying ->
|
||||
[ Text "Playing" ]
|
||||
|
||||
PlayingDescription ->
|
||||
@@ -417,13 +425,13 @@ translate mdString =
|
||||
[ Text "Casting to ", Text deviceName, Text "." ]
|
||||
|
||||
Players ->
|
||||
[ Text "Players" ]
|
||||
[ Ref (Plural { singular = Player, amount = Nothing }) ]
|
||||
|
||||
PlayersDescription ->
|
||||
[ Text "Users playing the game." ]
|
||||
|
||||
Spectators ->
|
||||
[ Text "Audience" ]
|
||||
[ Ref (Plural { singular = Spectator, amount = Nothing }) ]
|
||||
|
||||
SpectatorsDescription ->
|
||||
[ Text "Users watching the game without playing." ]
|
||||
@@ -532,8 +540,8 @@ translate mdString =
|
||||
ConfigureRules ->
|
||||
[ Text "Rules" ]
|
||||
|
||||
ConfigureGame ->
|
||||
[ Text "Game" ]
|
||||
ConfigurePrivacy ->
|
||||
[ Text "Privacy" ]
|
||||
|
||||
HandSize ->
|
||||
[ Text "Hand Size" ]
|
||||
@@ -560,24 +568,34 @@ translate mdString =
|
||||
NeedAtLeastThreePlayers ->
|
||||
[ Text "You need at least three players to start the game." ]
|
||||
|
||||
PasswordNotSecured ->
|
||||
[ Text "Please note that game passwords are "
|
||||
, Em [ Text "not" ]
|
||||
, Text " stored securely and are shared with everyone in the lobby"
|
||||
, Text "—given this, please "
|
||||
, Em [ Text "do not" ]
|
||||
, Text " use passwords you use elsewhere."
|
||||
PasswordShared ->
|
||||
[ Text "Anyone in the game can see the password! "
|
||||
, Text "Hiding it above only affects you (useful if streaming, etc…)."
|
||||
]
|
||||
|
||||
GamePassword ->
|
||||
PasswordNotSecured ->
|
||||
[ Text "Game passwords are "
|
||||
, Em [ Text "not" ]
|
||||
, Text " stored securely—given this, please "
|
||||
, Em [ Text "do not" ]
|
||||
, Text " use serious passwords you use elsewhere!"
|
||||
]
|
||||
|
||||
LobbyPassword ->
|
||||
[ Text "Game Password" ]
|
||||
|
||||
GamePasswordDescription ->
|
||||
LobbyPasswordDescription ->
|
||||
[ Text "A password to users must enter before they can join the game." ]
|
||||
|
||||
StartGame ->
|
||||
[ Text "Start Game" ]
|
||||
|
||||
Public ->
|
||||
[ Text "Public Game" ]
|
||||
|
||||
PublicDescription ->
|
||||
[ Text "If enabled, the game will show up in the public game list for anyone to find." ]
|
||||
|
||||
-- Game
|
||||
SubmitPlay ->
|
||||
[ Text "Give these cards to the ", Ref Czar, Text " as your play for the round." ]
|
||||
@@ -591,6 +609,18 @@ translate mdString =
|
||||
LikePlay ->
|
||||
[ Text "Add a like to this play." ]
|
||||
|
||||
Playing ->
|
||||
[ Text "Playing" ]
|
||||
|
||||
Revealing ->
|
||||
[ Text "Revealing" ]
|
||||
|
||||
Judging ->
|
||||
[ Text "Judging" ]
|
||||
|
||||
Complete ->
|
||||
[ Text "Finished" ]
|
||||
|
||||
-- Instructions
|
||||
PlayInstruction { numberOfCards } ->
|
||||
[ Text "You need to choose "
|
||||
@@ -670,6 +700,46 @@ translate mdString =
|
||||
CastError ->
|
||||
[ Text "Sorry, something went wrong trying to connect to the game." ]
|
||||
|
||||
IncorrectPlayerRoleError { role, expected } ->
|
||||
[ Text "You need to be a ", Ref expected, Text " to do that, but you are a ", Ref role, Text "." ]
|
||||
|
||||
IncorrectUserRoleError { role, expected } ->
|
||||
[ Text "You need to be a ", Ref expected, Text " to do that, but you are a ", Ref role, Text "." ]
|
||||
|
||||
IncorrectRoundStageError { stage, expected } ->
|
||||
[ Text "The round needs to be at the ", Ref expected, Text " stage to do that, but it is at the ", Ref stage, Text " stage." ]
|
||||
|
||||
ConfigEditConflictError ->
|
||||
[ Text "Someone else changed the configuration before you, so their changes took priority." ]
|
||||
|
||||
UnprivilegedError ->
|
||||
[ Text "You don't have the privileges to do that." ]
|
||||
|
||||
GameNotStartedError ->
|
||||
[ Text "The game needs to started to do that." ]
|
||||
|
||||
IncorrectIssuerError ->
|
||||
[ Text "Your credentials to join this game are out of date, the game no longer exists." ]
|
||||
|
||||
InvalidAuthenticationError ->
|
||||
[ Text "Your credentials to join this game are corrupt." ]
|
||||
|
||||
InvalidLobbyPasswordError ->
|
||||
[ Text "The game password you gave was wrong. Try typing it again and if it still doesn't work, ask the person who invited you again." ]
|
||||
|
||||
LobbyClosedError { gameCode } ->
|
||||
[ Text "The game you wish to join (", Ref (GameCode { code = gameCode }), Text ") has ended." ]
|
||||
|
||||
LobbyDoesNotExistError { gameCode } ->
|
||||
[ Text "The game code you entered ("
|
||||
, Ref (GameCode { code = gameCode })
|
||||
, Text ") doesn't exist. "
|
||||
, Text "Try typing it again and if it still doesn't work, ask the person who invited you again."
|
||||
]
|
||||
|
||||
OutOfCardsError ->
|
||||
[ Text "There were not enough cards in the deck to deal everyone a hand! Try adding more decks in the game configuration." ]
|
||||
|
||||
-- Language Names
|
||||
English ->
|
||||
[ Text "English" ]
|
||||
|
||||
@@ -186,7 +186,7 @@ enhanceHtml context mdString unenhanced =
|
||||
CardcastPlayCode ->
|
||||
[ Html.blankA [ HtmlA.href "https://www.cardcastgame.com/browse" ] unenhanced ]
|
||||
|
||||
Playing ->
|
||||
StillPlaying ->
|
||||
term context PlayingDescription Icon.clock unenhanced
|
||||
|
||||
Played ->
|
||||
|
||||
@@ -6,11 +6,14 @@ module MassiveDecks.User exposing
|
||||
, Registration
|
||||
, Role(..)
|
||||
, User
|
||||
, roleDescription
|
||||
)
|
||||
|
||||
{-| Operations and models for a user in the game.
|
||||
-}
|
||||
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
|
||||
|
||||
{-| A unique Id for a user.
|
||||
-}
|
||||
@@ -32,6 +35,16 @@ type Role
|
||||
| Spectator
|
||||
|
||||
|
||||
roleDescription : Role -> MdString
|
||||
roleDescription toDescribe =
|
||||
case toDescribe of
|
||||
Player ->
|
||||
Strings.Player
|
||||
|
||||
Spectator ->
|
||||
Strings.Spectator
|
||||
|
||||
|
||||
{-| If the user is actively a part of the lobby.
|
||||
-}
|
||||
type Presence
|
||||
@@ -50,6 +63,7 @@ type Connection
|
||||
-}
|
||||
type alias Registration =
|
||||
{ name : String
|
||||
, password : Maybe String
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
module MassiveDecks.Util.Result exposing
|
||||
( error
|
||||
( byDefinition
|
||||
, error
|
||||
, isError
|
||||
, isOk
|
||||
, unifiedMap
|
||||
, unify
|
||||
)
|
||||
|
||||
@@ -35,6 +37,18 @@ isOk result =
|
||||
Result.toMaybe result /= Nothing
|
||||
|
||||
|
||||
{-| A result with a never error can be simplified to just a value.
|
||||
-}
|
||||
byDefinition : Result Never a -> a
|
||||
byDefinition result =
|
||||
case result of
|
||||
Ok value ->
|
||||
value
|
||||
|
||||
Err n ->
|
||||
never n
|
||||
|
||||
|
||||
{-| Turn a result that gives the same type in both cases into that result.
|
||||
-}
|
||||
unify : Result a a -> a
|
||||
@@ -45,3 +59,10 @@ unify result =
|
||||
|
||||
Err value ->
|
||||
value
|
||||
|
||||
|
||||
{-| Map both sides of a result at the same time to the same type, and give the unified result.
|
||||
-}
|
||||
unifiedMap : (err -> a) -> (ok -> a) -> Result err ok -> a
|
||||
unifiedMap errMap okMap =
|
||||
Result.mapError errMap >> Result.map okMap >> unify
|
||||
|
||||
@@ -5,13 +5,18 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a {
|
||||
.svg-inline--fa {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
h2,
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.subheader {
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.lobby-game-code {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@ import * as setHandSize from "./configure/set-hand-size";
|
||||
import { SetHandSize } from "./configure/set-hand-size";
|
||||
import * as setPassword from "./configure/set-password";
|
||||
import { SetPassword } from "./configure/set-password";
|
||||
import { SetPublic } from "./configure/set-public";
|
||||
import * as setScoreLimit from "./configure/set-score-limit";
|
||||
import { SetScoreLimit } from "./configure/set-score-limit";
|
||||
import * as changeHouseRule from "./configure/change-house-rule";
|
||||
import { ChangeHouseRule } from "./configure/change-house-rule";
|
||||
import * as setPublic from "./configure/set-public";
|
||||
|
||||
/**
|
||||
* An action to change the configuration of the lobby.
|
||||
@@ -21,14 +23,16 @@ export type Configure =
|
||||
| SetHandSize
|
||||
| SetScoreLimit
|
||||
| ChangeDecks
|
||||
| ChangeHouseRule;
|
||||
| ChangeHouseRule
|
||||
| SetPublic;
|
||||
|
||||
const possible = new Set([
|
||||
setPassword.is,
|
||||
setHandSize.is,
|
||||
setScoreLimit.is,
|
||||
changeDecks.is,
|
||||
changeHouseRule.is
|
||||
changeHouseRule.is,
|
||||
setPublic.is
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -65,5 +69,7 @@ export const handle: Handler<Configure> = (auth, lobby, action, config) => {
|
||||
return changeDecks.handle(auth, lobby, action, config);
|
||||
case "ChangeHouseRule":
|
||||
return changeHouseRule.handle(auth, lobby, action, config);
|
||||
case "SetPublic":
|
||||
return setPublic.handle(auth, lobby, action, config);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Action } from "../../../action";
|
||||
import * as event from "../../../event";
|
||||
import * as publicSet from "../../../events/lobby-event/configured/public-set";
|
||||
import { Handler } from "../../handler";
|
||||
import * as configure from "../configure";
|
||||
|
||||
/**
|
||||
* Set (or unset) the password for the lobby.
|
||||
*/
|
||||
export interface SetPublic extends configure.Base {
|
||||
action: NameType;
|
||||
public: boolean;
|
||||
}
|
||||
|
||||
type NameType = "SetPublic";
|
||||
const name: NameType = "SetPublic";
|
||||
|
||||
/**
|
||||
* Check if an action is an change decks action.
|
||||
* @param action The action to check.
|
||||
*/
|
||||
export const is = (action: Action): action is SetPublic =>
|
||||
action.action === name;
|
||||
|
||||
export const handle: Handler<SetPublic> = (auth, lobby, action) => {
|
||||
const config = lobby.config;
|
||||
if (action.public !== config.public) {
|
||||
const version = config.version + 1;
|
||||
const resultEvent = publicSet.of(version.toString(), action.public);
|
||||
config.public = action.public;
|
||||
config.version = version;
|
||||
return { lobby, events: [event.targetAll(resultEvent)] };
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
import Ajv = require("ajv");
|
||||
import { RegisterUser, CreateLobby, Action, CheckAlive } from "./validation";
|
||||
export const ajv = new Ajv({
|
||||
allErrors: true,
|
||||
allErrors: false,
|
||||
coerceTypes: false,
|
||||
format: "fast",
|
||||
nullable: true,
|
||||
@@ -48,6 +48,9 @@ export const Schema = {
|
||||
{
|
||||
$ref: "#/definitions/SetPassword"
|
||||
},
|
||||
{
|
||||
$ref: "#/definitions/SetPublic"
|
||||
},
|
||||
{
|
||||
$ref: "#/definitions/SetScoreLimit"
|
||||
},
|
||||
@@ -188,7 +191,7 @@ export const Schema = {
|
||||
description: "Set the hand size for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_7"
|
||||
$ref: "#/definitions/NameType_8"
|
||||
},
|
||||
change: {
|
||||
$ref: "#/definitions/Change"
|
||||
@@ -287,14 +290,18 @@ export const Schema = {
|
||||
type: "string"
|
||||
},
|
||||
NameType_6: {
|
||||
enum: ["SetScoreLimit"],
|
||||
enum: ["SetPublic"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_7: {
|
||||
enum: ["ChangeHouseRule"],
|
||||
enum: ["SetScoreLimit"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_8: {
|
||||
enum: ["ChangeHouseRule"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_9: {
|
||||
enum: ["StartGame"],
|
||||
type: "string"
|
||||
},
|
||||
@@ -424,12 +431,31 @@ export const Schema = {
|
||||
required: ["action", "if"],
|
||||
type: "object"
|
||||
},
|
||||
SetPublic: {
|
||||
defaultProperties: [],
|
||||
description: "Set (or unset) the password for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_6"
|
||||
},
|
||||
if: {
|
||||
description:
|
||||
"If the config version doesn't match this, the operation will be rejected.\nThis avoids users accidentally overwriting each other's changes.",
|
||||
type: "string"
|
||||
},
|
||||
public: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
required: ["action", "if", "public"],
|
||||
type: "object"
|
||||
},
|
||||
SetScoreLimit: {
|
||||
defaultProperties: [],
|
||||
description: "(Un)Set the score limit for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_6"
|
||||
$ref: "#/definitions/NameType_7"
|
||||
},
|
||||
if: {
|
||||
description:
|
||||
@@ -451,7 +477,7 @@ export const Schema = {
|
||||
description: "Start a game in the lobby if possible.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_8"
|
||||
$ref: "#/definitions/NameType_9"
|
||||
}
|
||||
},
|
||||
required: ["action"],
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DecksChanged } from "./configured/decks-changed";
|
||||
import { HandSizeSet } from "./configured/hand-size-set";
|
||||
import { HouseRuleChanged } from "./configured/house-rule-changed";
|
||||
import { PasswordSet } from "./configured/password-set";
|
||||
import { PublicSet } from "./configured/public-set";
|
||||
import { ScoreLimitSet } from "./configured/score-limit-set";
|
||||
|
||||
/**
|
||||
@@ -12,7 +13,8 @@ export type Configured =
|
||||
| HandSizeSet
|
||||
| ScoreLimitSet
|
||||
| DecksChanged
|
||||
| HouseRuleChanged;
|
||||
| HouseRuleChanged
|
||||
| PublicSet;
|
||||
|
||||
export interface Base {
|
||||
event: string;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as configured from "../configured";
|
||||
|
||||
/**
|
||||
* The lobby password is (un)set.
|
||||
*/
|
||||
export interface PublicSet extends configured.Base {
|
||||
event: "PublicSet";
|
||||
public: boolean;
|
||||
}
|
||||
|
||||
export const of = (version: string, isPublic: boolean): PublicSet => ({
|
||||
event: "PublicSet",
|
||||
version,
|
||||
public: isPublic
|
||||
});
|
||||
@@ -94,7 +94,7 @@ async function main(): Promise<void> {
|
||||
res.status(HttpStatus.CREATED).json(token);
|
||||
});
|
||||
|
||||
app.post("/api/games/alive", async (req, res) => {
|
||||
app.post("/api/alive", async (req, res) => {
|
||||
const result: { [key: string]: boolean } = {};
|
||||
for (const current of checkAlive.validate(req.body).tokens) {
|
||||
try {
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface Summary {
|
||||
gameCode: GameCode;
|
||||
state: State;
|
||||
users: { players: number; spectators: number };
|
||||
password?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +68,8 @@ export interface Summary {
|
||||
export const defaultConfig = (): Config => ({
|
||||
version: 0,
|
||||
rules: rules.create(),
|
||||
decks: []
|
||||
decks: [],
|
||||
public: false
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -104,7 +106,8 @@ export const summary = (gameCode: GameCode, lobby: Lobby): Summary => ({
|
||||
users: util.counts(Object.values(lobby.users), {
|
||||
players: user.isPlaying,
|
||||
spectators: user.isSpectating
|
||||
})
|
||||
}),
|
||||
...(lobby.config.password !== undefined ? { password: true } : {})
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -121,11 +124,11 @@ function usersObj(lobby: Lobby): { [id: string]: user.Public } {
|
||||
return obj;
|
||||
}
|
||||
|
||||
export const censor = (lobby: Lobby, auth: token.Claims): Public => ({
|
||||
export const censor = (lobby: Lobby): Public => ({
|
||||
name: lobby.name,
|
||||
public: lobby.public,
|
||||
users: usersObj(lobby),
|
||||
owner: lobby.owner,
|
||||
config: config.censor(lobby.config, auth),
|
||||
config: config.censor(lobby.config),
|
||||
...(lobby.game === undefined ? {} : { game: game.censor(lobby.game) })
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as source from "../games/cards/source";
|
||||
import { Rules } from "../games/rules";
|
||||
import * as rules from "../games/rules";
|
||||
import * as token from "../user/token";
|
||||
|
||||
/**
|
||||
* Configuration for a lobby.
|
||||
@@ -9,6 +8,7 @@ import * as token from "../user/token";
|
||||
export interface Config {
|
||||
version: Version;
|
||||
rules: Rules;
|
||||
public: boolean;
|
||||
password?: string;
|
||||
decks: SummarisedSource[];
|
||||
}
|
||||
@@ -18,11 +18,8 @@ export type Version = number;
|
||||
export interface Public {
|
||||
version: string;
|
||||
rules: rules.Public;
|
||||
/**
|
||||
* @maxLength 100
|
||||
* @minLength 1
|
||||
*/
|
||||
password?: boolean | string;
|
||||
public?: boolean;
|
||||
password?: string;
|
||||
decks: SummarisedSource[];
|
||||
}
|
||||
|
||||
@@ -31,20 +28,13 @@ export interface SummarisedSource {
|
||||
summary?: source.Summary;
|
||||
}
|
||||
|
||||
export function censor(config: Config, auth: token.Claims): Public {
|
||||
const privilegedPart: {} =
|
||||
auth !== undefined && auth.pvg === "Privileged"
|
||||
? config.password === undefined
|
||||
? {}
|
||||
: { password: config.password }
|
||||
: { password: config.password !== undefined };
|
||||
return {
|
||||
version: config.version.toString(),
|
||||
rules: rules.censor(config.rules),
|
||||
decks: config.decks,
|
||||
...privilegedPart
|
||||
};
|
||||
}
|
||||
export const censor = (config: Config): Public => ({
|
||||
version: config.version.toString(),
|
||||
rules: rules.censor(config.rules),
|
||||
decks: config.decks,
|
||||
...(config.public ? { public: true } : {}),
|
||||
...(config.password !== undefined ? { password: config.password } : {})
|
||||
});
|
||||
|
||||
/**
|
||||
* The way in which the decks configuration is changed.
|
||||
|
||||
@@ -103,7 +103,7 @@ export class SocketManager {
|
||||
lobby,
|
||||
events: [
|
||||
event.targetOnly(
|
||||
sync.of(gameLobby.censor(lobby, knownAuth), hand, play),
|
||||
sync.of(gameLobby.censor(lobby), hand, play),
|
||||
uid
|
||||
)
|
||||
]
|
||||
@@ -166,7 +166,7 @@ export class Sockets {
|
||||
const users = this.users(gameCode);
|
||||
const didDelete = users.delete(id);
|
||||
if (users.size < 1) {
|
||||
this.sockets.delete(gameCode);
|
||||
this.sockets.delete(gameCode);
|
||||
}
|
||||
return didDelete;
|
||||
}
|
||||
|
||||
@@ -53,9 +53,10 @@ export class InMemoryStore extends Store {
|
||||
}
|
||||
|
||||
public async *lobbySummaries(): AsyncIterableIterator<gameLobby.Summary> {
|
||||
for (const summary of wu(this.lobbies.entries()).spreadMap(
|
||||
gameLobby.summary
|
||||
)) {
|
||||
const publicSummaries = wu(this.lobbies.entries())
|
||||
.filter(([_, l]) => l.config.public)
|
||||
.spreadMap(gameLobby.summary);
|
||||
for (const summary of publicSummaries) {
|
||||
yield summary;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user