Resolve #117: Move from Weightless to MWC.
Resolve #132: Fix some issues with the Postgres backend.
This commit is contained in:
+1
-1
@@ -18,6 +18,7 @@
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm-community/dict-extra": "2.4.0",
|
||||
"elm-community/list-extra": "8.2.3",
|
||||
"elm-community/random-extra": "3.1.0",
|
||||
"fapian/elm-html-aria": "1.4.0",
|
||||
"lattyware/elm-fontawesome": "4.0.0",
|
||||
@@ -31,7 +32,6 @@
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/virtual-dom": "1.0.2",
|
||||
"elm-community/list-extra": "8.2.3",
|
||||
"owanturist/elm-union-find": "1.0.0"
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+3003
-1628
File diff suppressed because it is too large
Load Diff
+25
-8
@@ -23,16 +23,32 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.12.1",
|
||||
"@material/card": "^6.0.0",
|
||||
"@material/mwc-button": "^0.15.0",
|
||||
"@material/mwc-fab": "^0.15.0",
|
||||
"@material/mwc-icon-button": "^0.15.0",
|
||||
"@material/mwc-linear-progress": "^0.15.0",
|
||||
"@material/mwc-menu": "^0.15.0",
|
||||
"@material/mwc-select": "^0.15.0",
|
||||
"@material/mwc-slider": "^0.15.0",
|
||||
"@material/mwc-switch": "^0.15.0",
|
||||
"@material/mwc-tab": "^0.15.0",
|
||||
"@material/mwc-tab-bar": "^0.15.0",
|
||||
"@material/mwc-textarea": "^0.15.0",
|
||||
"@material/mwc-textfield": "^0.15.0",
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@webcomponents/webcomponentsjs": "^2.4.3",
|
||||
"weightless": "0.0.37"
|
||||
"canvas-confetti": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.106",
|
||||
"@types/canvas-confetti": "^1.0.0",
|
||||
"@types/chrome": "^0.0.107",
|
||||
"@types/chromecast-caf-receiver": "^5.0.4",
|
||||
"@types/chromecast-caf-sender": "^1.0.3",
|
||||
"app-manifest-loader": "^2.4.1",
|
||||
"chromecast-device-emulator": "^1.2.7",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"closure-webpack-plugin": "^2.3.0",
|
||||
"compression-webpack-plugin": "^3.1.0",
|
||||
"css-hot-loader": "^1.4.4",
|
||||
"css-loader": "^3.5.3",
|
||||
@@ -41,24 +57,25 @@
|
||||
"elm-hot-webpack-loader": "^1.1.6",
|
||||
"elm-webpack-loader": "^6.0.1",
|
||||
"extract-loader": "^5.0.1",
|
||||
"fibers": "^5.0.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"google-closure-compiler": "^20200504.0.0",
|
||||
"html-loader": "^1.1.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"node-sass": "^4.14.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "2.0.5",
|
||||
"prettier-plugin-elm": "^0.7.0",
|
||||
"sass-inline-svg": "^1.2.3",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^3.0.0",
|
||||
"ts-loader": "^7.0.2",
|
||||
"terser-webpack-plugin": "^3.0.1",
|
||||
"ts-loader": "^7.0.3",
|
||||
"typescript": "^3.8.3",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-import": {},
|
||||
"postcss-preset-env": {},
|
||||
cssnano: {},
|
||||
},
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import MassiveDecks.Error.Overlay as Overlay
|
||||
import MassiveDecks.Messages exposing (..)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Models.Decoders as Decoders
|
||||
import MassiveDecks.Models.MdError as MdError
|
||||
import MassiveDecks.Notifications as Notifications
|
||||
import MassiveDecks.Pages as Pages
|
||||
import MassiveDecks.Pages.Loading as Loading
|
||||
@@ -377,10 +378,10 @@ handleLobbyMsg baseModel lobbyMsg lobbyModel =
|
||||
Lobby.Stay newLobbyModel ->
|
||||
( { model | page = Pages.Lobby newLobbyModel }, lobbyCmd )
|
||||
|
||||
Lobby.AuthError gc authenticationError ->
|
||||
Lobby.JoinError gc error ->
|
||||
let
|
||||
( page, changePageCmd ) =
|
||||
Start.initWithAuthError shared gc authenticationError
|
||||
Start.initWithError shared gc error
|
||||
|
||||
urlCommand =
|
||||
page |> .route |> Route.Start |> Route.url |> Navigation.pushUrl shared.key
|
||||
|
||||
@@ -16,7 +16,6 @@ module MassiveDecks.Card.Source exposing
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import MassiveDecks.Card.Source.BuiltIn as BuiltIn
|
||||
import MassiveDecks.Card.Source.Cardcast as Cardcast
|
||||
import MassiveDecks.Card.Source.Custom as Player
|
||||
@@ -29,8 +28,8 @@ import MassiveDecks.Pages.Lobby.Configure.Decks.Model exposing (DeckOrError)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import Material.Select as Select
|
||||
import Paper.Tooltip as Tooltip
|
||||
|
||||
|
||||
{-| The default source for an editor.
|
||||
@@ -61,24 +60,28 @@ externalAndEquals a b =
|
||||
|
||||
{-| Get an general methods of the given type.
|
||||
-}
|
||||
generalMethods : String -> Maybe (ExternalGeneralMethods msg)
|
||||
generalMethods n =
|
||||
case n of
|
||||
"BuiltIn" ->
|
||||
BuiltIn.generalMethods |> Just
|
||||
generalMethods : General -> ExternalGeneralMethods msg
|
||||
generalMethods source =
|
||||
case source of
|
||||
GBuiltIn ->
|
||||
BuiltIn.generalMethods
|
||||
|
||||
"Cardcast" ->
|
||||
Cardcast.generalMethods |> Just
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
GCardcast ->
|
||||
Cardcast.generalMethods
|
||||
|
||||
|
||||
{-| Get an empty source of the given type.
|
||||
-}
|
||||
empty : Shared -> String -> Maybe External
|
||||
empty shared n =
|
||||
generalMethods n |> Maybe.map (\m -> m.empty shared)
|
||||
empty : Shared -> General -> External
|
||||
empty shared =
|
||||
generalMethods >> (\m -> m.empty shared)
|
||||
|
||||
|
||||
{-| Get the general source for the given specific source.
|
||||
-}
|
||||
generalMatching : External -> General
|
||||
generalMatching source =
|
||||
(externalMethods source |> .id) ()
|
||||
|
||||
|
||||
{-| An empty source of the same general type as the given one.
|
||||
@@ -111,28 +114,46 @@ defaultDetails shared source =
|
||||
|
||||
{-| A tooltip for a source.
|
||||
-}
|
||||
tooltip : Shared -> Source -> Maybe ( String, Html msg )
|
||||
tooltip shared source =
|
||||
case shared |> (methods source |> .tooltip) of
|
||||
Just ( id, rendered ) ->
|
||||
Just
|
||||
( id
|
||||
, Wl.tooltip
|
||||
(List.concat
|
||||
[ [ WlA.anchor id
|
||||
, WlA.fixed
|
||||
, WlA.anchorOpenEvents [ "mouseover" ]
|
||||
, WlA.anchorCloseEvents [ "mouseout" ]
|
||||
]
|
||||
, WlA.anchorOrigin WlA.XRight WlA.YCenter
|
||||
, WlA.transformOrigin WlA.XLeft WlA.YCenter
|
||||
]
|
||||
)
|
||||
[ rendered ]
|
||||
)
|
||||
tooltip : Shared -> Tooltip.Position -> Details -> Source -> Maybe ( String, Html msg )
|
||||
tooltip shared position details source =
|
||||
let
|
||||
ms =
|
||||
methods source
|
||||
in
|
||||
ms.tooltip (generalTooltip shared position details)
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
|
||||
{-| A general tooltip for any source.
|
||||
-}
|
||||
generalTooltip : Shared -> Tooltip.Position -> Details -> String -> List (Html msg) -> Html msg
|
||||
generalTooltip shared position details forId sourceSpecificContent =
|
||||
let
|
||||
lang l =
|
||||
Html.p [ HtmlA.class "language" ]
|
||||
[ Strings.DeckLanguage { language = l |> Lang.languageName } |> Lang.html shared
|
||||
]
|
||||
|
||||
author a =
|
||||
Html.p [ HtmlA.class "author" ] [ Strings.DeckAuthor { author = a } |> Lang.html shared ]
|
||||
|
||||
translator t =
|
||||
Html.p [ HtmlA.class "translator" ] [ Strings.DeckTranslator { translator = t } |> Lang.html shared ]
|
||||
|
||||
generalContent =
|
||||
[ details.language |> Maybe.andThen (\l -> Maybe.justIf (l /= Lang.currentLanguage shared) (lang l))
|
||||
, details.author |> Maybe.map author
|
||||
, details.translator |> Maybe.map translator
|
||||
]
|
||||
|> List.filterMap identity
|
||||
in
|
||||
[ Html.div [ HtmlA.class "source-tooltip" ]
|
||||
([ sourceSpecificContent
|
||||
, generalContent
|
||||
]
|
||||
|> List.concat
|
||||
)
|
||||
]
|
||||
|> Tooltip.view position forId
|
||||
|
||||
|
||||
{-| The logo for a source.
|
||||
@@ -144,32 +165,38 @@ logo source =
|
||||
|
||||
{-| An editor for any supported external source.
|
||||
-}
|
||||
generalEditor : Shared -> List DeckOrError -> External -> (External -> msg) -> List (Html msg)
|
||||
generalEditor shared existing currentValue update =
|
||||
generalEditor : Shared -> List DeckOrError -> External -> (External -> msg) -> Maybe msg -> msg -> List (Html msg)
|
||||
generalEditor shared existing currentValue update submit noOp =
|
||||
let
|
||||
enabledSources =
|
||||
[ shared.sources.builtIn |> Maybe.map (\_ -> BuiltIn.generalMethods)
|
||||
, Cardcast.generalMethods |> Maybe.justIf shared.sources.cardcast
|
||||
]
|
||||
|
||||
toOption source =
|
||||
Html.option [ HtmlA.value (source.id ()) ]
|
||||
[ () |> source.name |> Lang.html shared
|
||||
]
|
||||
toItem source =
|
||||
{ id = source.id ()
|
||||
, icon = source.logo ()
|
||||
, primary = [ () |> source.name |> Lang.html shared ]
|
||||
, secondary = Nothing
|
||||
, meta = Nothing
|
||||
}
|
||||
in
|
||||
[ Wl.select
|
||||
[ HtmlA.id "source-selector"
|
||||
, WlA.outlined
|
||||
, HtmlE.onInput (empty shared >> Maybe.withDefault (default shared) >> update)
|
||||
]
|
||||
(enabledSources |> List.filterMap (Maybe.map toOption))
|
||||
, editor shared existing currentValue update
|
||||
[ Select.view shared
|
||||
{ label = Strings.DeckSource
|
||||
, idToString = generalToString
|
||||
, idFromString = generalFromString
|
||||
, selected = currentValue |> generalMatching |> Just
|
||||
, wrap = Maybe.map (empty shared) >> Maybe.withDefault (default shared) >> update
|
||||
}
|
||||
[ HtmlA.id "source-selector" ]
|
||||
(enabledSources |> List.filterMap (Maybe.map toItem))
|
||||
, editor shared existing currentValue update submit noOp
|
||||
]
|
||||
|
||||
|
||||
{-| An editor for the given source value.
|
||||
-}
|
||||
editor : Shared -> List DeckOrError -> External -> (External -> msg) -> Html msg
|
||||
editor : Shared -> List DeckOrError -> External -> (External -> msg) -> Maybe msg -> msg -> Html msg
|
||||
editor shared existing source =
|
||||
(externalMethods source |> .editor) shared existing
|
||||
|
||||
|
||||
@@ -3,27 +3,32 @@ module MassiveDecks.Card.Source.BuiltIn exposing
|
||||
, methods
|
||||
)
|
||||
|
||||
import FontAwesome.Icon as Icon
|
||||
import FontAwesome.Solid as Icon
|
||||
import Html as Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import MassiveDecks.Card.Source.BuiltIn.Model exposing (..)
|
||||
import MassiveDecks.Card.Source.Methods as Source
|
||||
import MassiveDecks.Card.Source.Model as Source exposing (Source)
|
||||
import MassiveDecks.Components.Form.Message exposing (Message)
|
||||
import MassiveDecks.Icon as Icon
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Decks.Model as Decks exposing (DeckOrError)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Decks.Model exposing (DeckOrError)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang exposing (sortClosestFirst)
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import MassiveDecks.Util.NeList as NonEmptyList
|
||||
import MassiveDecks.Util.Order as Order
|
||||
import Material.Select as Select
|
||||
|
||||
|
||||
methods : Id -> Source.ExternalMethods msg
|
||||
methods given =
|
||||
{ name = name
|
||||
{ name = sourceName
|
||||
, logo = logo
|
||||
, empty = empty
|
||||
, id = id
|
||||
, id = sourceId
|
||||
, problems = problems given
|
||||
, defaultDetails = details given
|
||||
, tooltip = tooltip given
|
||||
@@ -34,10 +39,10 @@ methods given =
|
||||
|
||||
generalMethods : Source.ExternalGeneralMethods msg
|
||||
generalMethods =
|
||||
{ name = name
|
||||
{ name = sourceName
|
||||
, logo = logo
|
||||
, empty = empty
|
||||
, id = id
|
||||
, id = sourceId
|
||||
}
|
||||
|
||||
|
||||
@@ -45,29 +50,31 @@ generalMethods =
|
||||
{- Private -}
|
||||
|
||||
|
||||
id : () -> String
|
||||
id _ =
|
||||
"BuiltIn"
|
||||
sourceId : () -> Source.General
|
||||
sourceId _ =
|
||||
Source.GBuiltIn
|
||||
|
||||
|
||||
name : () -> MdString
|
||||
name () =
|
||||
sourceName : () -> MdString
|
||||
sourceName () =
|
||||
Strings.BuiltIn
|
||||
|
||||
|
||||
empty : Shared -> Source.External
|
||||
empty shared =
|
||||
shared.sources.builtIn
|
||||
|> Maybe.andThen (.decks >> List.head)
|
||||
|> Maybe.map (.decks >> NonEmptyList.head)
|
||||
|> Maybe.map .id
|
||||
|> Maybe.withDefault (Id "")
|
||||
|> Maybe.withDefault (hardcoded "")
|
||||
|> Source.BuiltIn
|
||||
|
||||
|
||||
{-| See if the id is equal to the given source.
|
||||
-}
|
||||
equals : Id -> Source.External -> Bool
|
||||
equals (Id given) source =
|
||||
equals given source =
|
||||
case source of
|
||||
Source.BuiltIn (Id other) ->
|
||||
Source.BuiltIn other ->
|
||||
given == other
|
||||
|
||||
_ ->
|
||||
@@ -79,27 +86,57 @@ problems _ () =
|
||||
[]
|
||||
|
||||
|
||||
editor : Id -> Shared -> List DeckOrError -> (Source.External -> msg) -> Html msg
|
||||
editor (Id selectedId) shared _ update =
|
||||
editor : Id -> Shared -> List DeckOrError -> (Source.External -> msg) -> Maybe msg -> msg -> Html msg
|
||||
editor selected shared existing update _ _ =
|
||||
case shared.sources.builtIn of
|
||||
Just { decks } ->
|
||||
let
|
||||
deck deckInfo =
|
||||
case deckInfo.id of
|
||||
Id other ->
|
||||
Html.option
|
||||
[ HtmlA.selected (selectedId == other)
|
||||
, HtmlA.value other
|
||||
]
|
||||
[ Html.text deckInfo.name ]
|
||||
deck { id, name, language, author, translator } =
|
||||
let
|
||||
lang =
|
||||
if language /= Lang.currentLanguage shared then
|
||||
language |> Lang.languageName |> Just
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
matches d =
|
||||
case d.source of
|
||||
Source.BuiltIn other ->
|
||||
other == id
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
secondary =
|
||||
[ lang |> Maybe.map (\l -> Strings.DeckLanguage { language = l } |> Lang.html shared)
|
||||
, Strings.DeckAuthor { author = author } |> Lang.html shared |> Just
|
||||
, translator |> Maybe.map (\t -> Strings.DeckTranslator { translator = t } |> Lang.html shared)
|
||||
]
|
||||
|> List.filterMap identity
|
||||
|> List.intersperse (Html.text ", ")
|
||||
in
|
||||
{ id = id
|
||||
, icon = Nothing
|
||||
, primary = [ Html.text name ]
|
||||
, secondary = Just secondary
|
||||
, meta = Icon.check |> Icon.viewIcon |> Maybe.justIf (existing |> List.any matches)
|
||||
}
|
||||
in
|
||||
Html.div [ HtmlA.class "primary" ]
|
||||
[ Wl.select
|
||||
[ HtmlA.id "built-in-selector"
|
||||
, WlA.outlined
|
||||
, Id >> Source.BuiltIn >> update |> HtmlE.onInput
|
||||
]
|
||||
(decks |> List.map deck)
|
||||
[ Select.view shared
|
||||
{ label = Strings.Deck
|
||||
, idToString = toString
|
||||
, idFromString = fromString shared.sources.builtIn
|
||||
, selected = Just selected
|
||||
, wrap = Maybe.withDefault (hardcoded "") >> Source.BuiltIn >> update
|
||||
}
|
||||
[ HtmlA.id "built-in-selector" ]
|
||||
(decks
|
||||
|> NonEmptyList.toList
|
||||
|> List.sortWith (sortClosestFirst (Lang.currentLanguage shared) |> Order.map (.language >> Just))
|
||||
|> List.map deck
|
||||
)
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
@@ -107,26 +144,32 @@ editor (Id selectedId) shared _ update =
|
||||
|
||||
|
||||
details : Id -> Shared -> Source.Details
|
||||
details (Id given) shared =
|
||||
details given shared =
|
||||
let
|
||||
isSame deckInfo =
|
||||
case deckInfo.id of
|
||||
Id other ->
|
||||
given == other
|
||||
isSame { id } =
|
||||
given == id
|
||||
|
||||
info =
|
||||
shared.sources.builtIn
|
||||
|> Maybe.andThen (.decks >> NonEmptyList.toList >> List.filter isSame >> List.head)
|
||||
in
|
||||
{ name =
|
||||
shared.sources.builtIn
|
||||
|> Maybe.andThen (.decks >> List.filter isSame >> List.head >> Maybe.map .name)
|
||||
|> Maybe.withDefault ""
|
||||
{ name = info |> Maybe.map .name |> Maybe.withDefault ""
|
||||
, url = Nothing
|
||||
, author = info |> Maybe.map .author
|
||||
, translator = info |> Maybe.andThen .translator
|
||||
, language = info |> Maybe.map .language
|
||||
}
|
||||
|
||||
|
||||
tooltip : Id -> Shared -> Maybe ( String, Html msg )
|
||||
tooltip (Id given) shared =
|
||||
( "builtin-" ++ given, Html.span [] [ details (Id given) shared |> .name |> Html.text ] ) |> Just
|
||||
tooltip : Id -> (String -> List (Html msg) -> Html msg) -> Maybe ( String, Html msg )
|
||||
tooltip id renderTooltip =
|
||||
let
|
||||
forId =
|
||||
"builtin-" ++ (id |> toString)
|
||||
in
|
||||
( forId, renderTooltip forId [] ) |> Just
|
||||
|
||||
|
||||
logo : () -> Maybe (Html msg)
|
||||
logo () =
|
||||
Nothing
|
||||
Icon.massiveDecks |> Icon.viewIcon |> Just
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
module MassiveDecks.Card.Source.BuiltIn.Model exposing (Id(..))
|
||||
module MassiveDecks.Card.Source.BuiltIn.Model exposing
|
||||
( Deck
|
||||
, Id
|
||||
, Info
|
||||
, fromString
|
||||
, hardcoded
|
||||
, idDecoder
|
||||
, toString
|
||||
)
|
||||
|
||||
{-| Models for the built-in source.
|
||||
-}
|
||||
|
||||
import Json.Decode as Json
|
||||
import List.Extra as List
|
||||
import MassiveDecks.Strings.Languages.Model exposing (Language)
|
||||
import MassiveDecks.Util.NeList as NonEmptyList exposing (NeList)
|
||||
|
||||
|
||||
{-| The id for a built-in deck.
|
||||
-}
|
||||
@@ -10,8 +23,56 @@ type Id
|
||||
= Id String
|
||||
|
||||
|
||||
{-| Create an id from a string.
|
||||
{-| Information about the source from the server.
|
||||
-}
|
||||
playCode : String -> Id
|
||||
playCode string =
|
||||
string |> Id
|
||||
type alias Info =
|
||||
{ decks : NeList Deck }
|
||||
|
||||
|
||||
{-| Information about a deck.
|
||||
-}
|
||||
type alias Deck =
|
||||
{ name : String
|
||||
, id : Id
|
||||
, language : Language
|
||||
, author : String
|
||||
, translator : Maybe String
|
||||
}
|
||||
|
||||
|
||||
{-| Get an id from a string.
|
||||
-}
|
||||
fromString : Maybe Info -> String -> Maybe Id
|
||||
fromString builtInInfo stringId =
|
||||
let
|
||||
matching got deck =
|
||||
case deck.id of
|
||||
Id str ->
|
||||
str == got
|
||||
|
||||
internal { decks } =
|
||||
decks |> NonEmptyList.toList |> List.find (matching stringId) |> Maybe.map .id
|
||||
in
|
||||
builtInInfo |> Maybe.andThen internal
|
||||
|
||||
|
||||
{-| Allows you to hard-code an Id for a deck. This means that the client and server could end up out of sync if the id
|
||||
doesn't actually exist.
|
||||
-}
|
||||
hardcoded : String -> Id
|
||||
hardcoded =
|
||||
Id
|
||||
|
||||
|
||||
{-| A decoder for ids.
|
||||
-}
|
||||
idDecoder : Json.Decoder Id
|
||||
idDecoder =
|
||||
Json.string |> Json.map Id
|
||||
|
||||
|
||||
{-| Convert an Id to it's string representation.
|
||||
-}
|
||||
toString : Id -> String
|
||||
toString (Id str) =
|
||||
str
|
||||
|
||||
@@ -6,18 +6,18 @@ module MassiveDecks.Card.Source.Cardcast exposing
|
||||
import Html as Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Json.Decode as Json
|
||||
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.Form.Message as Message exposing (Message)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Decks.Model as Decks exposing (DeckOrError)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Decks.Model exposing (DeckOrError)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Material.TextField as TextField
|
||||
import Url.Builder as Url
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
methods : PlayCode -> Source.ExternalMethods msg
|
||||
@@ -47,9 +47,9 @@ generalMethods =
|
||||
{- Private -}
|
||||
|
||||
|
||||
id : () -> String
|
||||
id : () -> Source.General
|
||||
id () =
|
||||
"Cardcast"
|
||||
Source.GCardcast
|
||||
|
||||
|
||||
name : () -> MdString
|
||||
@@ -58,7 +58,7 @@ name () =
|
||||
|
||||
|
||||
empty : Shared -> Source.External
|
||||
empty shared =
|
||||
empty _ =
|
||||
"" |> playCode |> Source.Cardcast
|
||||
|
||||
|
||||
@@ -81,23 +81,14 @@ problems (PlayCode pc) () =
|
||||
[]
|
||||
|
||||
|
||||
editor : PlayCode -> Shared -> List DeckOrError -> (Source.External -> msg) -> Html msg
|
||||
editor (PlayCode pc) shared existing update =
|
||||
editor : PlayCode -> Shared -> List DeckOrError -> (Source.External -> msg) -> Maybe msg -> msg -> Html msg
|
||||
editor (PlayCode pc) shared existing update submit noOp =
|
||||
let
|
||||
notAlreadyInGame potential =
|
||||
case potential of
|
||||
Source.Cardcast playCode ->
|
||||
let
|
||||
notSameDeck deckOrError =
|
||||
let
|
||||
source =
|
||||
case deckOrError of
|
||||
Decks.D d ->
|
||||
d.source
|
||||
|
||||
Decks.E e ->
|
||||
e.source
|
||||
in
|
||||
notSameDeck { source } =
|
||||
equals playCode source |> not
|
||||
in
|
||||
playCode |> Maybe.justIf (existing |> List.all notSameDeck)
|
||||
@@ -111,14 +102,16 @@ editor (PlayCode pc) shared existing update =
|
||||
Html.div [ HtmlA.class "primary" ]
|
||||
[ Html.datalist [ HtmlA.id "cardcast-recent-decks" ]
|
||||
(shared.settings.settings.recentDecks |> List.filterMap notAlreadyInGame |> List.map recentDeck)
|
||||
, Wl.textField
|
||||
[ HtmlA.value pc
|
||||
, WlA.outlined
|
||||
, WlA.list "cardcast-recent-decks"
|
||||
, TextField.view shared
|
||||
Strings.CardcastPlayCode
|
||||
TextField.Text
|
||||
pc
|
||||
[ HtmlA.list "cardcast-recent-decks"
|
||||
, HtmlE.onInput (playCode >> Source.Cardcast >> update)
|
||||
, Strings.CardcastPlayCode |> Lang.label shared
|
||||
, HtmlE.keyCode
|
||||
|> Json.map (\k -> submit |> Maybe.andThen (Maybe.justIf (k == 13)) |> Maybe.withDefault noOp)
|
||||
|> HtmlE.on "keydown"
|
||||
]
|
||||
[ Html.span [ WlA.textFieldSlot WlA.BeforeText ] [ logoInternal ] ]
|
||||
]
|
||||
|
||||
|
||||
@@ -126,12 +119,19 @@ details : PlayCode -> Shared -> Source.Details
|
||||
details (PlayCode pc) shared =
|
||||
{ name = (() |> name |> Lang.string shared) ++ " " ++ pc
|
||||
, url = Just (Url.crossOrigin "https://www.cardcastgame.com" [ "browse", "deck", pc ] [])
|
||||
, author = Nothing
|
||||
, translator = Nothing
|
||||
, language = Nothing
|
||||
}
|
||||
|
||||
|
||||
tooltip : PlayCode -> Shared -> Maybe ( String, Html msg )
|
||||
tooltip (PlayCode pc) _ =
|
||||
( "cardcast-" ++ pc, Html.span [] [ logoInternal, Html.text pc ] ) |> Just
|
||||
tooltip : PlayCode -> (String -> List (Html msg) -> Html msg) -> Maybe ( String, Html msg )
|
||||
tooltip (PlayCode pc) tooltipRender =
|
||||
let
|
||||
forId =
|
||||
"cardcast-" ++ pc
|
||||
in
|
||||
( forId, [ Html.p [ HtmlA.class "play-code" ] [ logoInternal, Html.text pc ] ] |> tooltipRender forId ) |> Just
|
||||
|
||||
|
||||
logo : () -> Maybe (Html msg)
|
||||
|
||||
@@ -14,6 +14,9 @@ methods =
|
||||
\shared ->
|
||||
{ name = name shared
|
||||
, url = Nothing
|
||||
, translator = Nothing
|
||||
, author = Nothing
|
||||
, language = Nothing
|
||||
}
|
||||
, tooltip = \_ -> Nothing
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ methods name =
|
||||
\_ ->
|
||||
{ name = name |> Maybe.withDefault ""
|
||||
, url = Nothing
|
||||
, author = Nothing
|
||||
, translator = Nothing
|
||||
, language = Nothing
|
||||
}
|
||||
, tooltip = \_ -> Nothing
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ type alias ExternalGeneralMethods msg =
|
||||
-}
|
||||
type alias IsSpecific general msg =
|
||||
{ general
|
||||
| tooltip : Shared -> Maybe ( String, Html msg )
|
||||
| tooltip : (String -> List (Html msg) -> Html msg) -> Maybe ( String, Html msg )
|
||||
, defaultDetails : Shared -> Details
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ type alias IsSpecific general msg =
|
||||
-}
|
||||
type alias IsSpecificExternal general msg =
|
||||
{ general
|
||||
| editor : Shared -> List DeckOrError -> (External -> msg) -> Html msg
|
||||
| editor : Shared -> List DeckOrError -> (External -> msg) -> Maybe msg -> msg -> Html msg
|
||||
, equals : External -> Bool
|
||||
, problems : () -> List (Message msg)
|
||||
}
|
||||
@@ -94,5 +94,5 @@ type alias IsSpecificExternal general msg =
|
||||
type alias IsGeneralExternal rest =
|
||||
{ rest
|
||||
| empty : Shared -> External
|
||||
, id : () -> String
|
||||
, id : () -> General
|
||||
}
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
module MassiveDecks.Card.Source.Model exposing
|
||||
( BuiltInDeck
|
||||
, BuiltInInfo
|
||||
, Details
|
||||
( Details
|
||||
, External(..)
|
||||
, General(..)
|
||||
, Info
|
||||
, LoadFailureReason(..)
|
||||
, Source(..)
|
||||
, Summary
|
||||
, generalDecoder
|
||||
, generalFromString
|
||||
, generalToString
|
||||
)
|
||||
|
||||
import Json.Decode as Json
|
||||
import MassiveDecks.Card.Source.BuiltIn.Model as BuiltIn
|
||||
import MassiveDecks.Card.Source.Cardcast.Model as Cardcast
|
||||
import MassiveDecks.Strings.Languages.Model exposing (Language)
|
||||
|
||||
|
||||
{-| A representation of a source in general terms, not a specific deck.
|
||||
-}
|
||||
type General
|
||||
= GBuiltIn
|
||||
| GCardcast
|
||||
|
||||
|
||||
{-| Details on where game data came from.
|
||||
@@ -53,6 +64,9 @@ type alias Summary =
|
||||
type alias Details =
|
||||
{ name : String
|
||||
, url : Maybe String
|
||||
, author : Maybe String
|
||||
, language : Maybe Language
|
||||
, translator : Maybe String
|
||||
}
|
||||
|
||||
|
||||
@@ -66,14 +80,49 @@ type LoadFailureReason
|
||||
{-| Information about what sources are available from the server.
|
||||
-}
|
||||
type alias Info =
|
||||
{ builtIn : Maybe BuiltInInfo
|
||||
{ builtIn : Maybe BuiltIn.Info
|
||||
, cardcast : Bool
|
||||
}
|
||||
|
||||
|
||||
type alias BuiltInInfo =
|
||||
{ decks : List BuiltInDeck }
|
||||
{-| Get a string name from a general source.
|
||||
-}
|
||||
generalToString : General -> String
|
||||
generalToString source =
|
||||
case source of
|
||||
GBuiltIn ->
|
||||
"BuiltIn"
|
||||
|
||||
GCardcast ->
|
||||
"Cardcast"
|
||||
|
||||
|
||||
type alias BuiltInDeck =
|
||||
{ name : String, id : BuiltIn.Id }
|
||||
{-| Get a general source by a string name.
|
||||
-}
|
||||
generalFromString : String -> Maybe General
|
||||
generalFromString sourceName =
|
||||
case sourceName of
|
||||
"BuiltIn" ->
|
||||
Just GBuiltIn
|
||||
|
||||
"Cardcast" ->
|
||||
Just GCardcast
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
{-| A Json decoder for general sources.
|
||||
-}
|
||||
generalDecoder : Json.Decoder General
|
||||
generalDecoder =
|
||||
let
|
||||
internal name =
|
||||
case generalFromString name of
|
||||
Just general ->
|
||||
Json.succeed general
|
||||
|
||||
Nothing ->
|
||||
Json.fail ("Unknown source '" ++ name ++ "'.")
|
||||
in
|
||||
Json.string |> Json.andThen internal
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
module MassiveDecks.Components exposing
|
||||
( floatingActionButton
|
||||
, iconButton
|
||||
, iconButtonStyled
|
||||
, linkButton
|
||||
)
|
||||
module MassiveDecks.Components exposing (linkButton)
|
||||
|
||||
{-| Reusable interface elements.
|
||||
-}
|
||||
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Attributes.Aria as Aria
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
{-| Something that looks like a link but is actually a button suitable for handling events on click.
|
||||
@@ -21,25 +13,3 @@ import Weightless.Attributes as WlA
|
||||
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
|
||||
|
||||
|
||||
{-| A button that is just an icon.
|
||||
-}
|
||||
iconButton : List (Html.Attribute msg) -> Icon -> Html msg
|
||||
iconButton attrs icon =
|
||||
iconButtonStyled attrs ( [], icon )
|
||||
|
||||
|
||||
{-| A button that is just an icon with styles on the icon.
|
||||
-}
|
||||
iconButtonStyled : List (Html.Attribute msg) -> ( List (Html.Attribute msg), Icon ) -> Html msg
|
||||
iconButtonStyled attrs ( styles, icon ) =
|
||||
Wl.button (List.concat [ [ WlA.fab, WlA.inverted, WlA.flat ], attrs ]) [ Icon.viewStyled styles icon ]
|
||||
|
||||
|
||||
{-| A circular button designed to be the primary action on a page.
|
||||
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.viewIcon icon ]
|
||||
|
||||
@@ -5,6 +5,7 @@ module MassiveDecks.Components.Form.Message exposing
|
||||
, error
|
||||
, errorWithFix
|
||||
, info
|
||||
, infoWithFix
|
||||
, mdError
|
||||
, none
|
||||
, view
|
||||
@@ -16,12 +17,12 @@ import FontAwesome.Icon as Icon exposing (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.Models.MdError as MdError exposing (MdError)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.NeList as NeList exposing (NeList(..))
|
||||
import Material.IconButton as IconButton
|
||||
import Svg.Attributes as Svg
|
||||
|
||||
|
||||
@@ -56,6 +57,15 @@ info mdString =
|
||||
}
|
||||
|
||||
|
||||
infoWithFix : MdString -> List (Fix msg) -> Message msg
|
||||
infoWithFix mdString fixes =
|
||||
Just
|
||||
{ severity = Info
|
||||
, description = mdString
|
||||
, fixes = fixes
|
||||
}
|
||||
|
||||
|
||||
warning : MdString -> Message msg
|
||||
warning mdString =
|
||||
Just
|
||||
@@ -127,10 +137,4 @@ internalMessage shared { severity, description, fixes } =
|
||||
|
||||
viewFix : Shared -> Fix msg -> Html msg
|
||||
viewFix shared { icon, description, action } =
|
||||
Html.li []
|
||||
[ Components.iconButton
|
||||
[ action |> HtmlE.onClick
|
||||
, description |> Lang.title shared
|
||||
]
|
||||
icon
|
||||
]
|
||||
Html.li [] [ IconButton.view shared description (icon |> Icon.present |> NeList.just) (Just action) ]
|
||||
|
||||
@@ -1,49 +1,46 @@
|
||||
module MassiveDecks.Components.Menu exposing
|
||||
( Item
|
||||
, Menu
|
||||
, Part(..)
|
||||
, button
|
||||
( button
|
||||
, link
|
||||
, open
|
||||
, toggle
|
||||
, view
|
||||
)
|
||||
|
||||
import FontAwesome.Attributes as Icon
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import FontAwesome.Icon exposing (Icon)
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Html.Attributes.Aria as HtmlA
|
||||
import MassiveDecks.Components.Menu.Model exposing (..)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import Material.ListView as ListView
|
||||
import Material.Menu as Menu
|
||||
|
||||
|
||||
{-| A menu.
|
||||
{-| Toggle the state of a menu.
|
||||
-}
|
||||
type alias Menu msg =
|
||||
List (Part msg)
|
||||
toggle : State -> State
|
||||
toggle state =
|
||||
case state of
|
||||
Open ->
|
||||
Closed
|
||||
|
||||
Closed ->
|
||||
Open
|
||||
|
||||
|
||||
{-| A part of a menu.
|
||||
{-| Get an open state from a boolean indicating if the menu is open.
|
||||
-}
|
||||
type Part msg
|
||||
= Button Item (Maybe msg)
|
||||
| Link Item (Maybe String)
|
||||
| Separator
|
||||
| Nothing
|
||||
open : Bool -> State
|
||||
open isOpen =
|
||||
if isOpen then
|
||||
Open
|
||||
|
||||
|
||||
{-| A menu item
|
||||
-}
|
||||
type alias Item =
|
||||
{ icon : Icon
|
||||
, text : MdString
|
||||
, description : MdString
|
||||
}
|
||||
else
|
||||
Closed
|
||||
|
||||
|
||||
{-| Convenience function for a menu button.
|
||||
@@ -62,28 +59,9 @@ link icon text description href =
|
||||
|
||||
{-| Render a menu to Html.
|
||||
-}
|
||||
view :
|
||||
Shared
|
||||
-> String
|
||||
-> ( WlA.XOrigin, WlA.YOrigin )
|
||||
-> ( WlA.XOrigin, WlA.YOrigin )
|
||||
-> Menu msg
|
||||
-> Html msg
|
||||
view shared anchorId ( xAnchor, yAnchor ) ( xTransform, yTransform ) menu =
|
||||
Wl.popover
|
||||
(List.concat
|
||||
[ [ WlA.anchor anchorId
|
||||
, WlA.fixed
|
||||
, WlA.anchorOpenEvents [ "click" ]
|
||||
, HtmlA.id "game-menu"
|
||||
, HtmlA.class "menu"
|
||||
, WlA.disableFocusTrap
|
||||
]
|
||||
, WlA.anchorOrigin xAnchor yAnchor
|
||||
, WlA.transformOrigin xTransform yTransform
|
||||
]
|
||||
)
|
||||
[ Wl.popoverCard [] [ Html.ul [] (menu |> List.map (menuItem shared)) ] ]
|
||||
view : Shared -> msg -> State -> Corner -> Html msg -> Menu msg -> Html msg
|
||||
view shared onClose state corner anchor menu =
|
||||
Menu.view onClose state corner anchor (menu |> List.map (menuItem shared))
|
||||
|
||||
|
||||
|
||||
@@ -93,33 +71,26 @@ view shared anchorId ( xAnchor, yAnchor ) ( xTransform, yTransform ) menu =
|
||||
menuItem : Shared -> Part msg -> Html msg
|
||||
menuItem shared mi =
|
||||
case mi of
|
||||
Button item action ->
|
||||
Html.li []
|
||||
[ listItem shared item (Maybe.isJust action) (action |> Maybe.map HtmlE.onClick |> Maybe.withDefault HtmlA.nothing)
|
||||
]
|
||||
Button { icon, text, description } action ->
|
||||
ListView.viewItem (ListView.action action)
|
||||
(Just icon)
|
||||
Nothing
|
||||
Nothing
|
||||
[ text |> Lang.string shared |> Html.text ]
|
||||
|
||||
Link item href ->
|
||||
Html.li []
|
||||
[ Html.blankA [ href |> Maybe.map HtmlA.href |> Maybe.withDefault HtmlA.nothing ]
|
||||
[ listItem shared item (Maybe.isJust href) HtmlA.nothing ]
|
||||
Link { icon, text, description } href ->
|
||||
-- TODO: Deal with being disabled in a decent way.
|
||||
Html.blankA [ href |> Maybe.map HtmlA.href |> Maybe.withDefault HtmlA.nothing ]
|
||||
[ ListView.viewItem
|
||||
ListView.Link
|
||||
(Just icon)
|
||||
Nothing
|
||||
Nothing
|
||||
[ text |> Lang.string shared |> Html.text ]
|
||||
]
|
||||
|
||||
Separator ->
|
||||
Html.li [] [ Html.hr [] [] ]
|
||||
Html.li [ HtmlA.attribute "divider" "divider", HtmlA.role "separator" ] []
|
||||
|
||||
Nothing ->
|
||||
Ignore ->
|
||||
Html.nothing
|
||||
|
||||
|
||||
listItem : Shared -> Item -> Bool -> Html.Attribute msg -> Html msg
|
||||
listItem shared { icon, text, description } enabled attr =
|
||||
Wl.listItem
|
||||
[ description |> Lang.title shared
|
||||
, WlA.clickable |> Maybe.justIf enabled |> Maybe.withDefault WlA.disabled
|
||||
, attr
|
||||
]
|
||||
[ Icon.viewStyled [ WlA.listItemSlot WlA.BeforeItem, Icon.fw ] icon
|
||||
|
||||
-- We have an icon already, so we don't want to enhance this.
|
||||
, text |> Lang.string shared |> Html.text
|
||||
]
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
module MassiveDecks.Components.Menu.Model exposing
|
||||
( Corner(..)
|
||||
, Item
|
||||
, Menu
|
||||
, Part(..)
|
||||
, State(..)
|
||||
)
|
||||
|
||||
import FontAwesome.Icon exposing (Icon)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
|
||||
|
||||
{-| A menu.
|
||||
-}
|
||||
type alias Menu msg =
|
||||
List (Part msg)
|
||||
|
||||
|
||||
{-| A part of a menu.
|
||||
-}
|
||||
type Part msg
|
||||
= Button Item (Maybe msg)
|
||||
| Link Item (Maybe String)
|
||||
| Separator
|
||||
| Ignore
|
||||
|
||||
|
||||
{-| A menu item
|
||||
-}
|
||||
type alias Item =
|
||||
{ icon : Icon
|
||||
, text : MdString
|
||||
, description : MdString
|
||||
}
|
||||
|
||||
|
||||
{-| The corner of the anchor element the menu should position itself at.
|
||||
-}
|
||||
type Corner
|
||||
= TopLeft
|
||||
| TopRight
|
||||
| BottomLeft
|
||||
| BottomRight
|
||||
| TopStart
|
||||
| TopEnd
|
||||
| BottomStart
|
||||
| BottomEnd
|
||||
|
||||
|
||||
{-| If the menu is visible or not.
|
||||
-}
|
||||
type State
|
||||
= Open
|
||||
| Closed
|
||||
@@ -16,8 +16,6 @@ import MassiveDecks.Strings.Languages.Model as Lang exposing (Language)
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Version exposing (version)
|
||||
import Url.Builder
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
{-| A view of an error.
|
||||
@@ -28,32 +26,31 @@ view shared route error =
|
||||
model =
|
||||
render error
|
||||
|
||||
report =
|
||||
rawReport =
|
||||
model.details |> Maybe.map (body shared route model.description)
|
||||
|
||||
reportView =
|
||||
report |> Maybe.map (viewReport shared) |> Maybe.withDefault []
|
||||
( report, link ) =
|
||||
rawReport |> Maybe.map (viewReport shared) |> Maybe.withDefault ( Html.nothing, Html.nothing )
|
||||
in
|
||||
Wl.expansion
|
||||
[ WlA.name "errors"
|
||||
, HtmlA.class "error"
|
||||
, HtmlA.disabled (report == Nothing)
|
||||
, WlA.noRipple (report == Nothing)
|
||||
Html.div
|
||||
[ HtmlA.class "error" ]
|
||||
[ Html.div [ HtmlA.class "header" ]
|
||||
[ Html.span [ HtmlA.class "title" ] [ Strings.Error |> Lang.html shared ]
|
||||
, Html.span [ HtmlA.class "description" ] [ model.description |> Lang.html shared ]
|
||||
, link
|
||||
]
|
||||
, report
|
||||
]
|
||||
(Html.span [ HtmlA.class "title", WlA.expansionSlot WlA.ETitle ] [ Strings.Error |> Lang.html shared ]
|
||||
:: Html.span [ HtmlA.class "description", WlA.expansionSlot WlA.EDescription ] [ model.description |> Lang.html shared ]
|
||||
:: reportView
|
||||
)
|
||||
|
||||
|
||||
{-| A view of an MdError.
|
||||
-}
|
||||
viewSpecific : Shared -> MdError -> Html msg
|
||||
viewSpecific shared error =
|
||||
Wl.expansion
|
||||
[ WlA.name "errors", HtmlA.class "error", HtmlA.attribute "open" "open" ]
|
||||
[ Html.span [ HtmlA.class "title", WlA.expansionSlot WlA.ETitle ] [ Strings.Error |> Lang.html shared ]
|
||||
, Html.span [ WlA.expansionSlot WlA.EDescription ] [ error |> MdError.shortDescribe |> Lang.html shared ]
|
||||
Html.div
|
||||
[ HtmlA.class "error" ]
|
||||
[ Html.span [ HtmlA.class "title" ] [ Strings.Error |> Lang.html shared ]
|
||||
, Html.span [] [ error |> MdError.shortDescribe |> Lang.html shared ]
|
||||
, Html.p [] [ error |> MdError.describe |> Lang.html shared ]
|
||||
]
|
||||
|
||||
@@ -62,7 +59,7 @@ viewSpecific shared error =
|
||||
{- Private -}
|
||||
|
||||
|
||||
viewReport : Shared -> String -> List (Html msg)
|
||||
viewReport : Shared -> String -> ( Html msg, Html msg )
|
||||
viewReport shared report =
|
||||
let
|
||||
github =
|
||||
@@ -77,15 +74,13 @@ viewReport shared report =
|
||||
++ report
|
||||
|
||||
url =
|
||||
Url.Builder.crossOrigin github path [ Url.Builder.string "body" reportBody ]
|
||||
Url.Builder.crossOrigin github path [ reportBody |> String.left 1950 |> Url.Builder.string "body" ]
|
||||
in
|
||||
[ Html.div [ HtmlA.class "report" ]
|
||||
[ Wl.textArea [ HtmlA.class "body", WlA.readonly, WlA.value report ] []
|
||||
, Html.span [ HtmlA.class "link" ]
|
||||
[ Html.blankA [ HtmlA.href url ] [ Strings.ReportError |> Lang.html shared ]
|
||||
]
|
||||
( Html.textarea [ HtmlA.class "report", HtmlA.readonly True, HtmlA.value report ] []
|
||||
, Html.span [ HtmlA.class "link" ]
|
||||
[ Html.blankA [ HtmlA.href url ] [ Strings.ReportError |> Lang.html shared ]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
body : Shared -> Route -> MdString -> String -> String
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
module MassiveDecks.Error.Overlay exposing (init, update, view)
|
||||
|
||||
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.Error as Error
|
||||
import MassiveDecks.Error.Messages exposing (..)
|
||||
import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
@@ -13,7 +12,9 @@ import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Route exposing (Route)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import Weightless as Wl
|
||||
import MassiveDecks.Util.NeList as NeList exposing (NeList(..))
|
||||
import Material.Card as Card
|
||||
import Material.IconButton as IconButton
|
||||
|
||||
|
||||
init : Error.Overlay
|
||||
@@ -29,20 +30,16 @@ view shared route model =
|
||||
else
|
||||
[ Html.div [ HtmlA.class "error-overlay" ]
|
||||
[ Html.div [ HtmlA.class "actions" ]
|
||||
[ Components.iconButton
|
||||
[ HtmlA.class "close"
|
||||
, Clear |> Global.ErrorMsg |> HtmlE.onClick
|
||||
, Strings.Close |> Lang.title shared
|
||||
]
|
||||
Icon.times
|
||||
, Components.iconButton
|
||||
[ HtmlA.class "refresh"
|
||||
, Global.Refresh |> HtmlE.onClick
|
||||
, Strings.Refresh |> Lang.title shared
|
||||
]
|
||||
Icon.redo
|
||||
[ IconButton.view shared
|
||||
Strings.Close
|
||||
(Icon.times |> Icon.present |> NeList.just)
|
||||
(Clear |> Global.ErrorMsg |> Just)
|
||||
, IconButton.view shared
|
||||
Strings.Refresh
|
||||
(Icon.redo |> Icon.present |> NeList.just)
|
||||
(Just Global.Refresh)
|
||||
]
|
||||
, Wl.card [ HtmlA.class "help" ]
|
||||
, Card.view [ HtmlA.class "help" ]
|
||||
[ Html.h3 [] [ Lang.html shared Strings.ErrorHelpTitle ]
|
||||
, Html.p [] [ Lang.html shared Strings.ErrorHelp ]
|
||||
]
|
||||
|
||||
@@ -9,7 +9,9 @@ module MassiveDecks.Game exposing
|
||||
)
|
||||
|
||||
import Browser.Dom as Dom
|
||||
import Browser.Events as Browser
|
||||
import Dict exposing (Dict)
|
||||
import FontAwesome.Attributes as Icon
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import FontAwesome.Solid as Icon
|
||||
import Html exposing (Html)
|
||||
@@ -39,18 +41,23 @@ import MassiveDecks.Notifications as Notifications
|
||||
import MassiveDecks.Pages.Lobby.Actions as Actions
|
||||
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
|
||||
import MassiveDecks.Pages.Lobby.Events as Events exposing (Event)
|
||||
import MassiveDecks.Pages.Lobby.Messages as Lobby
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby exposing (Lobby)
|
||||
import MassiveDecks.Pages.Lobby.Route as Lobby
|
||||
import MassiveDecks.Ports as Ports
|
||||
import MassiveDecks.Speech as Speech
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.User as User exposing (User)
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import MassiveDecks.Util.NeList as NeList
|
||||
import Material.Button as Button
|
||||
import Material.Card as Card
|
||||
import Material.IconButton as IconButton
|
||||
import Material.LinearProgress as LinearProgress
|
||||
import Set exposing (Set)
|
||||
import Task
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import Weightless.ProgressBar as ProgressBar
|
||||
|
||||
|
||||
init : (Msg -> msg) -> Game -> List Card.Response -> Round.Pick -> ( Model, Cmd msg )
|
||||
@@ -299,6 +306,12 @@ update wrap shared msg model =
|
||||
EnforceTimeLimit ->
|
||||
( model, Actions.enforceTimeLimit (model.game.round |> Round.data |> .id) (model.game.round |> Round.stage) )
|
||||
|
||||
ToggleHelp ->
|
||||
( { model | helpVisible = not model.helpVisible }, Cmd.none )
|
||||
|
||||
Confetti ->
|
||||
( { model | confetti = True }, Ports.startConfetti confettiId )
|
||||
|
||||
NoOp ->
|
||||
( model, Cmd.none )
|
||||
|
||||
@@ -308,37 +321,47 @@ subscriptions wrap anchor model =
|
||||
let
|
||||
( startedAt, maybeLimitSeconds, timedOut ) =
|
||||
roundTimeDetails model.game
|
||||
in
|
||||
if timedOut || model.game.winner /= Nothing then
|
||||
Sub.none
|
||||
|
||||
else
|
||||
case maybeLimitSeconds of
|
||||
Nothing ->
|
||||
timerSub =
|
||||
if timedOut || model.game.winner /= Nothing then
|
||||
Sub.none
|
||||
|
||||
Just limitSeconds ->
|
||||
case model.time of
|
||||
Just time ->
|
||||
let
|
||||
left =
|
||||
timeLeft anchor startedAt time limitSeconds
|
||||
in
|
||||
if left < 0 then
|
||||
Sub.none
|
||||
|
||||
else if left > showProgressBarForLast then
|
||||
Time.every 500 (UpdateTimer >> wrap)
|
||||
|
||||
else
|
||||
Time.animate (UpdateTimer >> wrap)
|
||||
|
||||
else
|
||||
case maybeLimitSeconds of
|
||||
Nothing ->
|
||||
Time.animate (UpdateTimer >> wrap)
|
||||
Sub.none
|
||||
|
||||
Just limitSeconds ->
|
||||
case model.time of
|
||||
Just time ->
|
||||
let
|
||||
left =
|
||||
timeLeft anchor startedAt time limitSeconds
|
||||
in
|
||||
if left < 0 then
|
||||
Sub.none
|
||||
|
||||
else if left > showProgressBarForLast then
|
||||
Time.every 500 (UpdateTimer >> wrap)
|
||||
|
||||
else
|
||||
Time.animate (UpdateTimer >> wrap)
|
||||
|
||||
Nothing ->
|
||||
Time.animate (UpdateTimer >> wrap)
|
||||
|
||||
confettiSub =
|
||||
if model.game.winner /= Nothing && not model.confetti then
|
||||
Browser.onAnimationFrame (Confetti |> wrap |> always)
|
||||
|
||||
else
|
||||
Sub.none
|
||||
in
|
||||
Sub.batch [ timerSub, confettiSub ]
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Shared -> Lobby.Auth -> Time.Anchor -> String -> Config -> Dict User.Id User -> Model -> Html msg
|
||||
view wrap shared auth timeAnchor name config users model =
|
||||
view : (Lobby.Msg -> msg) -> (Msg -> msg) -> Shared -> Lobby.Auth -> Time.Anchor -> String -> Config -> Dict User.Id User -> Model -> Html msg
|
||||
view wrapLobby wrap shared auth timeAnchor name config users model =
|
||||
let
|
||||
overlay =
|
||||
if model.game.players |> Dict.get auth.claims.uid |> Maybe.map (\p -> p.presence == Player.Away) |> Maybe.withDefault False then
|
||||
@@ -357,7 +380,7 @@ view wrap shared auth timeAnchor name config users model =
|
||||
else
|
||||
case model.game.winner of
|
||||
Just winners ->
|
||||
viewWinner shared users winners
|
||||
viewWinner wrapLobby shared users auth.claims.uid winners
|
||||
|
||||
Nothing ->
|
||||
viewRound wrap shared auth timeAnchor config users model
|
||||
@@ -687,7 +710,7 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
|
||||
game =
|
||||
model.game
|
||||
in
|
||||
( { model | game = { game | winner = Just winner } }, Cmd.none )
|
||||
( { model | game = { game | winner = Just winner }, confetti = False }, Cmd.none )
|
||||
|
||||
|
||||
applyGameStarted : (Msg -> msg) -> Lobby -> Round.Playing -> List Card.Response -> ( Model, Cmd msg )
|
||||
@@ -748,12 +771,34 @@ updateLikes plays uid player =
|
||||
|> Maybe.withDefault player
|
||||
|
||||
|
||||
viewWinner : Shared -> Dict User.Id User -> Set User.Id -> List (Html msg)
|
||||
viewWinner shared users winners =
|
||||
[ Wl.card [ HtmlA.id "game-winner" ]
|
||||
confettiId : String
|
||||
confettiId =
|
||||
"win-confetti-overlay"
|
||||
|
||||
|
||||
viewWinner : (Lobby.Msg -> msg) -> Shared -> Dict User.Id User -> User.Id -> Set User.Id -> List (Html msg)
|
||||
viewWinner wrapLobby shared users localUser winners =
|
||||
let
|
||||
configureNextGame =
|
||||
if users |> Dict.get localUser |> Maybe.map (.privilege >> (==) User.Privileged) |> Maybe.withDefault False then
|
||||
Button.view shared
|
||||
Button.Raised
|
||||
Strings.ConfigureNextGame
|
||||
Strings.ConfigureNextGame
|
||||
(Icon.cog |> Icon.viewIcon)
|
||||
[ HtmlA.id "new-game-config"
|
||||
, Lobby.Configure |> Just |> Lobby.ChangeSection |> wrapLobby |> HtmlE.onClick
|
||||
]
|
||||
|
||||
else
|
||||
Html.nothing
|
||||
in
|
||||
[ Card.view [ HtmlA.id "game-winner" ]
|
||||
[ Html.span [] [ Icon.trophy |> Icon.viewIcon ]
|
||||
, Html.ul [] (winners |> Set.toList |> List.map (viewWinnerListItem shared users))
|
||||
]
|
||||
, configureNextGame
|
||||
, Html.canvas [ HtmlA.id confettiId ] []
|
||||
]
|
||||
|
||||
|
||||
@@ -798,16 +843,16 @@ viewRound wrap shared auth timeAnchor config users model =
|
||||
|
||||
renderedCall =
|
||||
call |> Call.viewFilled shared config Card.Front [] parts
|
||||
|
||||
{ bar, countdown } =
|
||||
timer timeAnchor model
|
||||
in
|
||||
[ Html.div [ HtmlA.id "top-content" ]
|
||||
[ bar |> Maybe.withDefault Html.nothing
|
||||
, Html.div [ HtmlA.class "top-row" ]
|
||||
[ minorActions wrap shared auth game instruction
|
||||
, countdown |> Maybe.withDefault Html.nothing
|
||||
]
|
||||
[ case instruction |> Maybe.andThen (Maybe.justIf model.helpVisible) of
|
||||
Just i ->
|
||||
Card.view [ HtmlA.class "help" ] [ Icon.questionCircle |> Icon.viewIcon, i |> Lang.html shared ]
|
||||
|
||||
Nothing ->
|
||||
Html.nothing
|
||||
, timer timeAnchor model
|
||||
, Html.div [ HtmlA.class "top-row" ] [ minorActions wrap shared auth game model.helpVisible ]
|
||||
]
|
||||
, Html.div [ HtmlA.class "round" ] [ renderedCall, viewAction wrap shared action ]
|
||||
, content
|
||||
@@ -815,41 +860,27 @@ viewRound wrap shared auth timeAnchor config users model =
|
||||
|
||||
-- TODO: Hide this when at top. Waiting on native elm scroll events, as currently we'd have to ping constantly.
|
||||
, Html.div [ HtmlA.class "scroll-top" ]
|
||||
[ Wl.button
|
||||
[ WlA.flat
|
||||
, WlA.fab
|
||||
, WlA.inverted
|
||||
, ScrollToTop |> wrap |> HtmlE.onClick
|
||||
]
|
||||
[ Icon.viewIcon Icon.arrowUp ]
|
||||
]
|
||||
[ IconButton.view shared Strings.ScrollToTop (Icon.arrowUp |> Icon.present |> NeList.just) (ScrollToTop |> wrap |> Just) ]
|
||||
]
|
||||
|
||||
|
||||
help : Shared -> MdString -> Html msg
|
||||
help shared instruction =
|
||||
toggleHelp : (Msg -> msg) -> Shared -> Bool -> Html msg
|
||||
toggleHelp wrap shared enabled =
|
||||
let
|
||||
id =
|
||||
"context-help-button"
|
||||
extra =
|
||||
if enabled then
|
||||
[ Icon.slash |> Icon.present |> Icon.styled [ Icon.fw ] ]
|
||||
|
||||
helpContent =
|
||||
[ Components.iconButton
|
||||
[ HtmlA.id id, Strings.ViewHelpAction |> Lang.title shared ]
|
||||
Icon.question
|
||||
, Wl.popover
|
||||
(List.concat
|
||||
[ WlA.anchorOrigin WlA.XCenter WlA.YBottom
|
||||
, WlA.transformOrigin WlA.XLeft WlA.YTop
|
||||
, [ WlA.anchor id, WlA.fixed, WlA.anchorOpenEvents [ "click" ] ]
|
||||
]
|
||||
)
|
||||
[ Wl.popoverCard [] [ instruction |> Lang.html shared ] ]
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
icon =
|
||||
Icon.question |> Icon.present |> Icon.styled [ Icon.fw ] |> NeList.just |> NeList.extend extra
|
||||
in
|
||||
Html.div [ HtmlA.id "context-help" ] helpContent
|
||||
IconButton.view shared Strings.ViewHelpAction icon (ToggleHelp |> wrap |> Just)
|
||||
|
||||
|
||||
timer : Time.Anchor -> Model -> { bar : Maybe (Html msg), countdown : Maybe (Html msg) }
|
||||
timer : Time.Anchor -> Model -> Html msg
|
||||
timer timeAnchor model =
|
||||
let
|
||||
( startedAt, limit, timedOut ) =
|
||||
@@ -862,10 +893,10 @@ timer timeAnchor model =
|
||||
else
|
||||
Maybe.map2 (timeLeft timeAnchor startedAt) model.time limit
|
||||
in
|
||||
left |> Maybe.map timerInternal |> Maybe.withDefault { bar = Nothing, countdown = Nothing }
|
||||
left |> Maybe.andThen timerInternal |> Maybe.withDefault (Html.div [ HtmlA.id "timer" ] [])
|
||||
|
||||
|
||||
timerInternal : Int -> { bar : Maybe (Html msg), countdown : Maybe (Html msg) }
|
||||
timerInternal : Int -> Maybe (Html msg)
|
||||
timerInternal leftInt =
|
||||
let
|
||||
last =
|
||||
@@ -873,20 +904,18 @@ timerInternal leftInt =
|
||||
|
||||
left =
|
||||
leftInt |> toFloat
|
||||
|
||||
progressBar =
|
||||
(1 - (last - left) / last)
|
||||
|> timerProgressBar
|
||||
in
|
||||
{ bar = progressBar
|
||||
, countdown = Just (Html.div [ HtmlA.id "time-left" ] [ max 0 left / 1000 |> ceiling |> String.fromInt |> Html.text ])
|
||||
}
|
||||
(1 - (last - left) / last) |> timerProgressBar
|
||||
|
||||
|
||||
timerProgressBar : Float -> Maybe (Html msg)
|
||||
timerProgressBar proportion =
|
||||
if proportion < 1 then
|
||||
ProgressBar.determinate (min 1 (max 0 proportion)) [ HtmlA.id "timer" ] |> Just
|
||||
let
|
||||
progress =
|
||||
proportion |> max 0 |> LinearProgress.Progress
|
||||
in
|
||||
LinearProgress.view progress [ HtmlA.id "timer" ] |> Just
|
||||
|
||||
else
|
||||
Nothing
|
||||
@@ -894,7 +923,7 @@ timerProgressBar proportion =
|
||||
|
||||
showProgressBarForLast : Int
|
||||
showProgressBarForLast =
|
||||
20000
|
||||
15000
|
||||
|
||||
|
||||
timeLeft : Time.Anchor -> Time -> Time -> Int -> Int
|
||||
@@ -929,8 +958,8 @@ roundTimeDetails game =
|
||||
( complete.startedAt, Nothing, False )
|
||||
|
||||
|
||||
minorActions : (Msg -> msg) -> Shared -> Lobby.Auth -> Game -> Maybe MdString -> Html msg
|
||||
minorActions wrap shared auth game instruction =
|
||||
minorActions : (Msg -> msg) -> Shared -> Lobby.Auth -> Game -> Bool -> Html msg
|
||||
minorActions wrap shared auth game helpEnabled =
|
||||
let
|
||||
localPlayer =
|
||||
game.players |> Dict.get auth.claims.uid
|
||||
@@ -940,11 +969,10 @@ minorActions wrap shared auth game instruction =
|
||||
|
||||
enforceTimeLimit =
|
||||
if timedOut && game.rules.timeLimits.mode == Rules.Soft then
|
||||
Components.iconButton
|
||||
[ Strings.EnforceTimeLimitAction |> Lang.title shared
|
||||
, EnforceTimeLimit |> wrap |> HtmlE.onClick
|
||||
]
|
||||
Icon.forward
|
||||
IconButton.view shared
|
||||
Strings.EnforceTimeLimitAction
|
||||
(Icon.forward |> Icon.present |> NeList.just)
|
||||
(EnforceTimeLimit |> wrap |> Just)
|
||||
|> Just
|
||||
|
||||
else
|
||||
@@ -953,7 +981,7 @@ minorActions wrap shared auth game instruction =
|
||||
Html.div [ HtmlA.id "minor-actions" ]
|
||||
(List.filterMap identity
|
||||
[ Just (historyButton wrap shared)
|
||||
, instruction |> Maybe.map (help shared)
|
||||
, Just (toggleHelp wrap shared helpEnabled)
|
||||
, Maybe.map2 (\score -> \reboot -> rebootButton wrap shared score reboot)
|
||||
(localPlayer |> Maybe.map .score)
|
||||
game.rules.houseRules.reboot
|
||||
@@ -964,24 +992,26 @@ minorActions wrap shared auth game instruction =
|
||||
|
||||
historyButton : (Msg -> msg) -> Shared -> Html msg
|
||||
historyButton wrap shared =
|
||||
Components.iconButton
|
||||
[ HtmlA.id "history-button"
|
||||
, Strings.ViewGameHistoryAction |> Lang.title shared
|
||||
, ToggleHistoryView |> wrap |> HtmlE.onClick
|
||||
]
|
||||
Icon.history
|
||||
IconButton.view shared
|
||||
Strings.ViewGameHistoryAction
|
||||
(Icon.history |> Icon.present |> NeList.just)
|
||||
(ToggleHistoryView |> wrap |> Just)
|
||||
|
||||
|
||||
rebootButton : (Msg -> msg) -> Shared -> Int -> Rules.Reboot -> Html msg
|
||||
rebootButton wrap shared score reboot =
|
||||
Components.iconButton
|
||||
[ HtmlA.id "redraw"
|
||||
, { cost = reboot.cost } |> Strings.HouseRuleRebootAction |> Lang.title shared
|
||||
, WlA.disabled
|
||||
|> Maybe.justIf (score < reboot.cost)
|
||||
|> Maybe.withDefault (Redraw |> wrap |> HtmlE.onClick)
|
||||
]
|
||||
Icon.random
|
||||
let
|
||||
action =
|
||||
if score < reboot.cost then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Redraw |> wrap |> Just
|
||||
in
|
||||
IconButton.view shared
|
||||
({ cost = reboot.cost } |> Strings.HouseRuleRebootAction)
|
||||
(Icon.random |> Icon.present |> NeList.just)
|
||||
action
|
||||
|
||||
|
||||
viewAction : (Msg -> msg) -> Shared -> Maybe Action -> Html msg
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
module MassiveDecks.Game.Action exposing (actions, view)
|
||||
|
||||
import FontAwesome.Icon exposing (Icon)
|
||||
import FontAwesome.Icon as Icon exposing (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.Game.Action.Model exposing (..)
|
||||
import MassiveDecks.Game.Messages as Game exposing (Msg)
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import Weightless.Attributes as WlA
|
||||
import Material.Fab as Fab
|
||||
|
||||
|
||||
actions : List Action
|
||||
@@ -23,14 +20,14 @@ actions =
|
||||
-}
|
||||
blocking : List (Html.Attribute msg)
|
||||
blocking =
|
||||
[ HtmlA.class "important" ]
|
||||
[ HtmlA.class "blocking" ]
|
||||
|
||||
|
||||
{-| Style for an action that doesn't block the game.
|
||||
-}
|
||||
normal : List (Html.Attribute msg)
|
||||
normal =
|
||||
[ WlA.inverted, WlA.outlined ]
|
||||
[ HtmlA.class "normal" ]
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Shared -> Maybe Action -> Action -> Html msg
|
||||
@@ -53,16 +50,12 @@ view wrap shared visible action =
|
||||
Advance ->
|
||||
IconView Icon.forward blocking Strings.AdvanceRound Game.AdvanceRound
|
||||
in
|
||||
Components.floatingActionButton
|
||||
(List.concat
|
||||
[ [ title |> Lang.title shared
|
||||
, onClick |> wrap |> HtmlE.onClick
|
||||
, HtmlA.classList [ ( "action", True ), ( "exited", visible /= Just action ) ]
|
||||
]
|
||||
, attrs
|
||||
]
|
||||
)
|
||||
icon
|
||||
Fab.view shared
|
||||
Fab.Normal
|
||||
title
|
||||
(icon |> Icon.present)
|
||||
(onClick |> wrap |> Just)
|
||||
(HtmlA.classList [ ( "action", True ), ( "exited", visible /= Just action ) ] :: attrs)
|
||||
|
||||
|
||||
type alias IconView msg =
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
module MassiveDecks.Game.History exposing (view)
|
||||
|
||||
import Dict exposing (Dict)
|
||||
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 Html.Keyed as HtmlK
|
||||
import MassiveDecks.Card.Call as Call
|
||||
import MassiveDecks.Card.Model as Card
|
||||
import MassiveDecks.Card.Play as Play
|
||||
import MassiveDecks.Card.Response as Response
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Game.Messages exposing (Msg(..))
|
||||
import MassiveDecks.Game.Round as Round
|
||||
import MassiveDecks.Game.Round.Plays as Plays
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.User as User exposing (User)
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import MassiveDecks.Util.NeList as NeList
|
||||
import Material.IconButton as IconButton
|
||||
|
||||
|
||||
view : (Msg -> msg) -> Shared -> Config -> Dict User.Id User -> String -> List Round.Complete -> List (Html msg)
|
||||
view wrap shared config users name history =
|
||||
[ Html.div [ HtmlA.id "top-content" ]
|
||||
[ Html.div [ HtmlA.id "minor-actions" ]
|
||||
[ Components.iconButton
|
||||
[ HtmlA.id "return-to-game-button"
|
||||
, Strings.ViewGameHistoryAction |> Lang.title shared
|
||||
, ToggleHistoryView |> wrap |> HtmlE.onClick
|
||||
]
|
||||
Icon.arrowLeft
|
||||
[ IconButton.view shared
|
||||
Strings.ViewGameHistoryAction
|
||||
(Icon.arrowLeft |> Icon.present |> NeList.just)
|
||||
(ToggleHistoryView |> wrap |> Just)
|
||||
]
|
||||
]
|
||||
, Html.div [ HtmlA.id "history" ]
|
||||
|
||||
@@ -26,5 +26,7 @@ type Msg
|
||||
| SetPresence Player.Presence
|
||||
| SetPlayerAway User.Id
|
||||
| UpdateTimer Time
|
||||
| ToggleHelp
|
||||
| EnforceTimeLimit
|
||||
| Confetti
|
||||
| NoOp
|
||||
|
||||
@@ -31,6 +31,8 @@ type alias Model =
|
||||
, completeRound : Maybe Round.Complete
|
||||
, viewingHistory : Bool
|
||||
, time : Maybe Time
|
||||
, helpVisible : Bool
|
||||
, confetti : Bool
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +47,8 @@ emptyModel game =
|
||||
, completeRound = Nothing
|
||||
, viewingHistory = False
|
||||
, time = Nothing
|
||||
, helpVisible = False
|
||||
, confetti = False
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Html.Keyed as HtmlK
|
||||
import List.Extra as List
|
||||
import MassiveDecks.Card as Card
|
||||
import MassiveDecks.Card.Call as Call
|
||||
import MassiveDecks.Card.Model as Card
|
||||
import MassiveDecks.Card.Response as Response
|
||||
import MassiveDecks.Card.Source.Model as Source
|
||||
import MassiveDecks.Game.Action.Model as Action
|
||||
import MassiveDecks.Game.Messages as Game exposing (Msg)
|
||||
import MassiveDecks.Game.Model exposing (..)
|
||||
@@ -23,7 +23,6 @@ import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.User as User exposing (User)
|
||||
import MassiveDecks.Util.List as List
|
||||
import MassiveDecks.Util.Random as Random
|
||||
import Random
|
||||
import Set exposing (Set)
|
||||
@@ -125,7 +124,6 @@ viewHandCard wrap shared config filled picked response =
|
||||
viewBackgroundPlay : Shared -> PlayStyles -> Int -> Set User.Id -> User.Id -> Html msg
|
||||
viewBackgroundPlay shared playStyles slots played for =
|
||||
let
|
||||
-- TODO: Move to css variable --rotation when possible.
|
||||
cards =
|
||||
playStyles
|
||||
|> Dict.get for
|
||||
@@ -140,7 +138,7 @@ viewBackgroundPlay shared playStyles slots played for =
|
||||
viewBackgroundPlayCard : Shared -> CardStyle -> Html msg
|
||||
viewBackgroundPlayCard shared playStyle =
|
||||
Response.viewUnknown shared
|
||||
[ "rotate(" ++ String.fromFloat playStyle.rotation ++ "turn)" |> HtmlA.style "transform"
|
||||
[ "--rotation: " ++ String.fromFloat playStyle.rotation ++ "turn" |> HtmlA.attribute "style"
|
||||
, HtmlA.class "ignore-minimal-card-size"
|
||||
]
|
||||
|
||||
|
||||
@@ -54,27 +54,40 @@ import MassiveDecks.Speech as Speech
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Strings.Languages.Model as Lang exposing (Language)
|
||||
import MassiveDecks.User as User exposing (User)
|
||||
import MassiveDecks.Util.NeList as NonEmptyList
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
sourceInfo : Json.Decoder Source.Info
|
||||
sourceInfo =
|
||||
let
|
||||
atLeastOne info =
|
||||
if [ info.builtIn /= Nothing, info.cardcast ] |> List.any identity then
|
||||
info |> Json.succeed
|
||||
|
||||
else
|
||||
Json.fail "Must have at least one source enabled."
|
||||
in
|
||||
Json.succeed Source.Info
|
||||
|> Json.optional "builtIn" (builtInInfo |> Json.map Just) Nothing
|
||||
|> Json.optional "cardcast" Json.bool False
|
||||
|> Json.andThen atLeastOne
|
||||
|
||||
|
||||
builtInInfo : Json.Decoder Source.BuiltInInfo
|
||||
builtInInfo : Json.Decoder BuiltIn.Info
|
||||
builtInInfo =
|
||||
Json.succeed Source.BuiltInInfo
|
||||
|> Json.required "decks" (Json.list builtInDeck)
|
||||
Json.succeed BuiltIn.Info
|
||||
|> Json.required "decks" (NonEmptyList.decoder builtInDeck)
|
||||
|
||||
|
||||
builtInDeck : Json.Decoder Source.BuiltInDeck
|
||||
builtInDeck : Json.Decoder BuiltIn.Deck
|
||||
builtInDeck =
|
||||
Json.succeed Source.BuiltInDeck
|
||||
Json.succeed BuiltIn.Deck
|
||||
|> Json.required "name" Json.string
|
||||
|> Json.required "id" (Json.string |> Json.map BuiltIn.Id)
|
||||
|> Json.required "id" BuiltIn.idDecoder
|
||||
|> Json.required "language" language
|
||||
|> Json.required "author" Json.string
|
||||
|> Json.optional "translator" (Json.string |> Json.map Just) Nothing
|
||||
|
||||
|
||||
unknownValue : String -> String -> Json.Decoder a
|
||||
@@ -194,30 +207,27 @@ sourceByName name =
|
||||
Json.succeed Source.Custom
|
||||
|
||||
_ ->
|
||||
externalSourceByName name |> Json.map Source.Ex
|
||||
Json.field "source" Source.generalDecoder |> Json.andThen externalSourceByGeneral |> Json.map Source.Ex
|
||||
|
||||
|
||||
externalSource : Json.Decoder Source.External
|
||||
externalSource =
|
||||
Json.field "source" Json.string |> Json.andThen externalSourceByName
|
||||
Json.field "source" Source.generalDecoder |> Json.andThen externalSourceByGeneral
|
||||
|
||||
|
||||
externalSourceByName : String -> Json.Decoder Source.External
|
||||
externalSourceByName name =
|
||||
case name of
|
||||
"BuiltIn" ->
|
||||
Json.field "id" Json.string |> Json.map (BuiltIn.Id >> Source.BuiltIn)
|
||||
externalSourceByGeneral : Source.General -> Json.Decoder Source.External
|
||||
externalSourceByGeneral general =
|
||||
case general of
|
||||
Source.GBuiltIn ->
|
||||
Json.field "id" BuiltIn.idDecoder |> Json.map Source.BuiltIn
|
||||
|
||||
"Cardcast" ->
|
||||
Source.GCardcast ->
|
||||
Json.field "playCode" Json.string |> Json.map (Cardcast.playCode >> Source.Cardcast)
|
||||
|
||||
_ ->
|
||||
unknownValue "source" name
|
||||
|
||||
|
||||
tokenValidity : Json.Decoder (Dict Lobby.Token Bool)
|
||||
tokenValidity : Json.Decoder (List Lobby.Token)
|
||||
tokenValidity =
|
||||
Json.dict Json.bool
|
||||
Json.list lobbyToken
|
||||
|
||||
|
||||
lobbyToken : Json.Decoder Lobby.Token
|
||||
@@ -280,7 +290,7 @@ privacyConfig =
|
||||
|
||||
deckOrError : Json.Decoder DeckConfig.DeckOrError
|
||||
deckOrError =
|
||||
Json.oneOf [ Json.map DeckConfig.E deckError, Json.map DeckConfig.D deck ]
|
||||
Json.oneOf [ deckError |> Json.map DeckConfig.errorToDeckOrError, deck |> Json.map DeckConfig.deckToDeckOrError ]
|
||||
|
||||
|
||||
deck : Json.Decoder DeckConfig.Deck
|
||||
@@ -307,9 +317,12 @@ summary =
|
||||
|
||||
details : Json.Decoder Source.Details
|
||||
details =
|
||||
Json.map2 Source.Details
|
||||
(Json.field "name" Json.string)
|
||||
(Json.maybe (Json.field "url" Json.string))
|
||||
Json.succeed Source.Details
|
||||
|> Json.required "name" Json.string
|
||||
|> Json.optional "url" (Json.string |> Json.map Just) Nothing
|
||||
|> Json.optional "author" (Json.string |> Json.map Just) Nothing
|
||||
|> Json.optional "language" (language |> Json.map Just) Nothing
|
||||
|> Json.optional "translator" (Json.string |> Json.map Just) Nothing
|
||||
|
||||
|
||||
rules : Json.Decoder Rules
|
||||
|
||||
@@ -135,13 +135,13 @@ decks d =
|
||||
|
||||
|
||||
deckOrError : Decks.DeckOrError -> Json.Value
|
||||
deckOrError de =
|
||||
case de of
|
||||
Decks.D d ->
|
||||
deck d
|
||||
deckOrError d =
|
||||
case d.result of
|
||||
Ok s ->
|
||||
deck { source = d.source, summary = s }
|
||||
|
||||
Decks.E e ->
|
||||
deckError e
|
||||
Err r ->
|
||||
deckError { source = d.source, reason = r }
|
||||
|
||||
|
||||
deck : Decks.Deck -> Json.Value
|
||||
@@ -327,8 +327,8 @@ source s =
|
||||
Source.Cardcast (Cardcast.PlayCode playCode) ->
|
||||
Json.object [ ( "source", "Cardcast" |> Json.string ), ( "playCode", playCode |> Json.string ) ]
|
||||
|
||||
Source.BuiltIn (BuiltIn.Id id) ->
|
||||
Json.object [ ( "source", "BuiltIn" |> Json.string ), ( "id", id |> Json.string ) ]
|
||||
Source.BuiltIn id ->
|
||||
Json.object [ ( "source", "BuiltIn" |> Json.string ), ( "id", id |> BuiltIn.toString |> Json.string ) ]
|
||||
|
||||
|
||||
language : Language -> Json.Value
|
||||
|
||||
@@ -24,8 +24,9 @@ import MassiveDecks.Animated as Animated exposing (Animated)
|
||||
import MassiveDecks.Card.Model as Card
|
||||
import MassiveDecks.Cast.Client as Cast
|
||||
import MassiveDecks.Cast.Model as Cast
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form.Message as Message exposing (Message)
|
||||
import MassiveDecks.Components.Menu as Menu
|
||||
import MassiveDecks.Components.Menu.Model as Menu
|
||||
import MassiveDecks.Error as MdError
|
||||
import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
import MassiveDecks.Game as Game
|
||||
@@ -59,8 +60,10 @@ import MassiveDecks.User as User exposing (User)
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import MassiveDecks.Util.NeList as NeList
|
||||
import Material.Button as Button
|
||||
import Material.Card as Card
|
||||
import Material.IconButton as IconButton
|
||||
|
||||
|
||||
changeRoute : Shared -> Route -> Model -> Route.Fork ( Model, Cmd msg )
|
||||
@@ -97,6 +100,8 @@ initWithAuth shared r auth =
|
||||
, inviteDialogOpen = False
|
||||
, timeAnchor = Nothing
|
||||
, spectate = Spectate.init
|
||||
, gameMenu = Menu.Closed
|
||||
, userMenu = Nothing
|
||||
}
|
||||
, ServerConnection.connect auth.claims.gc auth.token
|
||||
)
|
||||
@@ -157,20 +162,31 @@ update wrap shared msg model =
|
||||
applySync wrap shared model state hand play partialTimeAnchor
|
||||
|
||||
Events.Configured { change } ->
|
||||
( applyConfigured change model
|
||||
, shared
|
||||
, Cmd.none
|
||||
)
|
||||
( applyConfigured change model, shared, Cmd.none )
|
||||
|
||||
Events.GameStarted { round, hand } ->
|
||||
let
|
||||
( g, cmd ) =
|
||||
( g, gameCmd ) =
|
||||
Game.applyGameStarted (GameMsg >> wrap) lobby round (hand |> Maybe.withDefault [])
|
||||
|
||||
r =
|
||||
model.route
|
||||
|
||||
changeCmd =
|
||||
if r.section == Just Configure then
|
||||
{ r | section = Nothing }
|
||||
|> Route.Lobby
|
||||
|> Route.url
|
||||
|> Navigation.replaceUrl shared.key
|
||||
|> Just
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
cmd =
|
||||
[ Just gameCmd, changeCmd ] |> List.filterMap identity |> Cmd.batch
|
||||
in
|
||||
( Stay { model | lobby = Just { lobby | game = Just g } }
|
||||
, shared
|
||||
, cmd
|
||||
)
|
||||
( Stay { model | lobby = Just { lobby | game = Just g } }, shared, cmd )
|
||||
|
||||
Events.Game gameEvent ->
|
||||
let
|
||||
@@ -294,8 +310,11 @@ update wrap shared msg model =
|
||||
|
||||
ErrorReceived error ->
|
||||
case error of
|
||||
MdError.Authentication reason ->
|
||||
( AuthError model.auth.claims.gc reason, shared, Cmd.none )
|
||||
MdError.Authentication _ ->
|
||||
( JoinError model.auth.claims.gc error, shared, Cmd.none )
|
||||
|
||||
MdError.LobbyNotFound _ ->
|
||||
( JoinError model.auth.claims.gc error, shared, Cmd.none )
|
||||
|
||||
MdError.ActionExecution (MdError.ConfigEditConflict _) ->
|
||||
let
|
||||
@@ -374,6 +393,28 @@ update wrap shared msg model =
|
||||
in
|
||||
( Stay { model | route = newRoute }, shared, newRoute |> Route.Lobby |> Route.url |> Navigation.replaceUrl shared.key )
|
||||
|
||||
SetGameMenuState newState ->
|
||||
( Stay { model | gameMenu = newState }, shared, Cmd.none )
|
||||
|
||||
SetUserMenuState id state ->
|
||||
let
|
||||
old =
|
||||
model.userMenu
|
||||
|
||||
new =
|
||||
case state of
|
||||
Menu.Open ->
|
||||
Just id
|
||||
|
||||
Menu.Closed ->
|
||||
if old /= Just id then
|
||||
old
|
||||
|
||||
else
|
||||
Nothing
|
||||
in
|
||||
( Stay { model | userMenu = new }, shared, Cmd.none )
|
||||
|
||||
NoOp ->
|
||||
( Stay model, shared, Cmd.none )
|
||||
|
||||
@@ -389,7 +430,7 @@ view wrap wrapSettings changePage shared model =
|
||||
let
|
||||
viewContent _ auth timeAnchor lobby =
|
||||
lobby.game
|
||||
|> Maybe.map (Game.view (GameMsg >> wrap) shared auth timeAnchor lobby.config.name lobby.config lobby.users)
|
||||
|> Maybe.map (Game.view wrap (GameMsg >> wrap) shared auth timeAnchor lobby.config.name lobby.config lobby.users)
|
||||
|> Maybe.withDefault (Html.div [] [ Strings.GameNotStartedError |> Lang.html shared ])
|
||||
in
|
||||
viewWithUsers wrap wrapSettings shared s viewContent model
|
||||
@@ -434,7 +475,7 @@ section r auth lobby =
|
||||
|
||||
|
||||
type alias ViewContent msg =
|
||||
Bool -> Auth -> Time.Anchor -> Lobby -> Html msg
|
||||
Message msg -> Auth -> Time.Anchor -> Lobby -> Html msg
|
||||
|
||||
|
||||
viewWithUsers : (Msg -> msg) -> (Settings.Msg -> msg) -> Shared -> Section -> ViewContent msg -> Model -> List (Html msg)
|
||||
@@ -459,7 +500,7 @@ viewWithUsers wrap wrapSettings shared s viewContent model =
|
||||
|
||||
castButton =
|
||||
castAttrs
|
||||
|> Maybe.map (viewCastButton wrap model.auth)
|
||||
|> Maybe.map (viewCastButton wrap shared model.auth)
|
||||
|> Maybe.withDefault []
|
||||
|
||||
usersIcon =
|
||||
@@ -489,12 +530,11 @@ viewWithUsers wrap wrapSettings shared s viewContent model =
|
||||
(Html.div [ HtmlA.id "top-bar" ]
|
||||
[ Html.div [ HtmlA.class "left" ]
|
||||
(List.concat
|
||||
[ [ Components.iconButtonStyled
|
||||
[ HtmlE.onClick (usersShown |> not |> Settings.ChangeOpenUserList |> wrapSettings)
|
||||
, Strings.ToggleUserList |> Lang.title shared
|
||||
]
|
||||
( [ Icon.lg ], usersIcon )
|
||||
, lobbyMenu wrap shared model.route s localUser localPlayer (maybeGame |> Maybe.map .game)
|
||||
[ [ IconButton.view shared
|
||||
Strings.ToggleUserList
|
||||
(usersIcon |> Icon.present |> Icon.styled [ Icon.lg ] |> NeList.just)
|
||||
(usersShown |> not |> Settings.ChangeOpenUserList |> wrapSettings |> Just)
|
||||
, lobbyMenu wrap shared model.gameMenu model.route s localUser localPlayer (maybeGame |> Maybe.map .game)
|
||||
]
|
||||
, castButton
|
||||
]
|
||||
@@ -503,7 +543,7 @@ viewWithUsers wrap wrapSettings shared s viewContent model =
|
||||
]
|
||||
:: HtmlK.ol [ HtmlA.class "notifications" ] notifications
|
||||
:: (model.lobby
|
||||
|> Maybe.map2 (viewLobby wrap shared model.auth viewContent) model.timeAnchor
|
||||
|> Maybe.map2 (viewLobby wrap shared model.auth model.userMenu viewContent) model.timeAnchor
|
||||
|> Maybe.withDefault
|
||||
[ Html.div [ HtmlA.class "loading" ]
|
||||
[ Icon.viewStyled [ Icon.spin, Icon.fa3x ] Icon.circleNotch ]
|
||||
@@ -519,8 +559,8 @@ viewWithUsers wrap wrapSettings shared s viewContent model =
|
||||
]
|
||||
|
||||
|
||||
viewLobby : (Msg -> msg) -> Shared -> Auth -> ViewContent msg -> Time.Anchor -> Lobby -> List (Html msg)
|
||||
viewLobby wrap shared auth viewContent timeAnchor lobby =
|
||||
viewLobby : (Msg -> msg) -> Shared -> Auth -> Maybe User.Id -> ViewContent msg -> Time.Anchor -> Lobby -> List (Html msg)
|
||||
viewLobby wrap shared auth openUserMenu viewContent timeAnchor lobby =
|
||||
let
|
||||
game =
|
||||
lobby.game |> Maybe.map .game
|
||||
@@ -528,12 +568,24 @@ viewLobby wrap shared auth viewContent timeAnchor lobby =
|
||||
privileged =
|
||||
(lobby.users |> Dict.get auth.claims.uid |> Maybe.map .privilege) == Just User.Privileged
|
||||
|
||||
canEdit =
|
||||
privileged && (game |> Maybe.map (.winner >> Maybe.isJust) |> Maybe.withDefault True)
|
||||
configDisabledReason =
|
||||
if not privileged then
|
||||
Message.info Strings.ConfigurationDisabledIfNotPrivileged
|
||||
|
||||
else if game |> Maybe.map (.winner >> Maybe.isNothing) |> Maybe.withDefault False then
|
||||
Message.infoWithFix Strings.ConfigurationDisabledWhileInGame
|
||||
[ { description = Strings.EndGame
|
||||
, icon = Icon.stopCircle
|
||||
, action = wrap EndGame
|
||||
}
|
||||
]
|
||||
|
||||
else
|
||||
Message.none
|
||||
in
|
||||
[ Html.div [ HtmlA.id "lobby-content" ]
|
||||
[ viewUsers wrap shared auth.claims.uid lobby.users game
|
||||
, Html.div [ HtmlA.id "scroll-frame" ] [ viewContent canEdit auth timeAnchor lobby ]
|
||||
[ viewUsers wrap shared auth.claims.uid lobby.users openUserMenu game
|
||||
, Html.div [ HtmlA.id "scroll-frame" ] [ viewContent configDisabledReason auth timeAnchor lobby ]
|
||||
, lobby.errors |> viewErrors shared
|
||||
]
|
||||
]
|
||||
@@ -552,12 +604,9 @@ cardSizeToAttr cardSize =
|
||||
HtmlA.nothing
|
||||
|
||||
|
||||
lobbyMenu : (Msg -> msg) -> Shared -> Route -> Section -> Maybe User -> Maybe Player -> Maybe Game -> Html msg
|
||||
lobbyMenu wrap shared r s user player game =
|
||||
lobbyMenu : (Msg -> msg) -> Shared -> Menu.State -> Route -> Section -> Maybe User -> Maybe Player -> Maybe Game -> Html msg
|
||||
lobbyMenu wrap shared menuState r s user player game =
|
||||
let
|
||||
id =
|
||||
"lobby-menu-button"
|
||||
|
||||
lobbyMenuItems =
|
||||
[ Menu.button Icon.bullhorn Strings.InvitePlayers Strings.InvitePlayersDescription (ToggleInviteDialog |> wrap |> Just) ]
|
||||
|
||||
@@ -609,11 +658,16 @@ lobbyMenu wrap shared r s user player game =
|
||||
separatedMenuItems =
|
||||
menuItems |> List.filterMap identity |> List.intersperse [ Menu.Separator ] |> List.concat
|
||||
in
|
||||
Html.div []
|
||||
[ Components.iconButtonStyled [ HtmlA.id id, Strings.GameMenu |> Lang.title shared ]
|
||||
( [ Icon.lg ], Icon.bars )
|
||||
, Menu.view shared id ( WlA.XCenter, WlA.YBottom ) ( WlA.XLeft, WlA.YTop ) separatedMenuItems
|
||||
]
|
||||
Menu.view shared
|
||||
(Menu.Closed |> SetGameMenuState |> wrap)
|
||||
menuState
|
||||
Menu.BottomEnd
|
||||
(IconButton.view shared
|
||||
Strings.GameMenu
|
||||
(Icon.bars |> Icon.present |> Icon.styled [ Icon.lg ] |> NeList.just)
|
||||
(menuState |> Menu.toggle |> SetGameMenuState |> wrap |> Just)
|
||||
)
|
||||
separatedMenuItems
|
||||
|
||||
|
||||
applyConfigured : Json.Patch -> Model -> Change
|
||||
@@ -749,18 +803,19 @@ viewNotification wrap shared users animationState notification =
|
||||
, Just "error"
|
||||
)
|
||||
in
|
||||
Wl.card
|
||||
Card.view
|
||||
[ HtmlA.class "notification", animationState, class |> Maybe.map HtmlA.class |> Maybe.withDefault HtmlA.nothing ]
|
||||
[ Html.div [ HtmlA.class "content" ]
|
||||
[ Html.span [ HtmlA.class "icon" ] [ icon ]
|
||||
, Html.span [ HtmlA.class "message" ] [ message ]
|
||||
, Wl.button
|
||||
[ WlA.flat
|
||||
, WlA.inverted
|
||||
, notification |> Animated.Exit |> NotificationMsg |> wrap |> HtmlE.onClick
|
||||
, Button.view shared
|
||||
Button.Standard
|
||||
Strings.Dismiss
|
||||
Strings.Dismiss
|
||||
Html.nothing
|
||||
[ notification |> Animated.Exit |> NotificationMsg |> wrap |> HtmlE.onClick
|
||||
, HtmlA.class "action"
|
||||
]
|
||||
[ Strings.Dismiss |> Lang.html shared ]
|
||||
]
|
||||
]
|
||||
|
||||
@@ -787,8 +842,8 @@ viewError shared error =
|
||||
error |> MdError.Game |> MdError.viewSpecific shared
|
||||
|
||||
|
||||
viewUsers : (Msg -> msg) -> Shared -> User.Id -> Dict User.Id User -> Maybe Game -> Html msg
|
||||
viewUsers wrap shared localUserId users game =
|
||||
viewUsers : (Msg -> msg) -> Shared -> User.Id -> Dict User.Id User -> Maybe User.Id -> Maybe Game -> Html msg
|
||||
viewUsers wrap shared localUserId users openUserMenu game =
|
||||
let
|
||||
localUserPrivilege =
|
||||
users |> Dict.get localUserId |> Maybe.map .privilege |> Maybe.withDefault User.Unprivileged
|
||||
@@ -797,24 +852,23 @@ viewUsers wrap shared localUserId users game =
|
||||
users |> Dict.toList |> List.partition (\( _, user ) -> user.presence == User.Joined)
|
||||
|
||||
activeGroups =
|
||||
active |> byRole |> List.map (viewRoleGroup wrap shared localUserId localUserPrivilege game)
|
||||
active |> byRole |> List.map (viewRoleGroup wrap shared localUserId localUserPrivilege openUserMenu game)
|
||||
|
||||
inactiveGroup =
|
||||
if List.isEmpty inactive then
|
||||
[]
|
||||
|
||||
else
|
||||
[ viewUserListGroup wrap shared localUserId localUserPrivilege game ( ( "left", Strings.Left ), inactive ) ]
|
||||
[ viewUserListGroup wrap shared localUserId localUserPrivilege openUserMenu game ( ( "left", Strings.Left ), inactive ) ]
|
||||
|
||||
groups =
|
||||
List.concat [ activeGroups, inactiveGroup ]
|
||||
in
|
||||
Wl.card [ HtmlA.id "users" ]
|
||||
[ Html.div [ HtmlA.class "collapsible" ] [ HtmlK.ol [] groups ] ]
|
||||
Card.view [ HtmlA.id "users" ] [ Html.div [ HtmlA.class "collapsible" ] [ HtmlK.ol [] groups ] ]
|
||||
|
||||
|
||||
viewRoleGroup : (Msg -> msg) -> Shared -> User.Id -> User.Privilege -> Maybe Game -> ( User.Role, List ( User.Id, User ) ) -> ( String, Html msg )
|
||||
viewRoleGroup wrap shared localUserId localUserPrivilege game ( role, users ) =
|
||||
viewRoleGroup : (Msg -> msg) -> Shared -> User.Id -> User.Privilege -> Maybe User.Id -> Maybe Game -> ( User.Role, List ( User.Id, User ) ) -> ( String, Html msg )
|
||||
viewRoleGroup wrap shared localUserId localUserPrivilege openUserMenu game ( role, users ) =
|
||||
let
|
||||
idAndDescription =
|
||||
case role of
|
||||
@@ -824,15 +878,15 @@ viewRoleGroup wrap shared localUserId localUserPrivilege game ( role, users ) =
|
||||
User.Spectator ->
|
||||
( "spectators", Strings.Spectators )
|
||||
in
|
||||
viewUserListGroup wrap shared localUserId localUserPrivilege game ( idAndDescription, users )
|
||||
viewUserListGroup wrap shared localUserId localUserPrivilege openUserMenu game ( idAndDescription, users )
|
||||
|
||||
|
||||
viewUserListGroup : (Msg -> msg) -> Shared -> User.Id -> User.Privilege -> Maybe Game -> ( ( String, MdString ), List ( User.Id, User ) ) -> ( String, Html msg )
|
||||
viewUserListGroup wrap shared localUserId localUserPrivilege game ( ( id, description ), users ) =
|
||||
viewUserListGroup : (Msg -> msg) -> Shared -> User.Id -> User.Privilege -> Maybe User.Id -> Maybe Game -> ( ( String, MdString ), List ( User.Id, User ) ) -> ( String, Html msg )
|
||||
viewUserListGroup wrap shared localUserId localUserPrivilege openUserMenu game ( ( id, description ), users ) =
|
||||
( id
|
||||
, Html.li [ HtmlA.class id ]
|
||||
[ Html.span [] [ description |> Lang.html shared ]
|
||||
, HtmlK.ol [] (users |> List.map (viewUser wrap shared localUserId localUserPrivilege game))
|
||||
, HtmlK.ol [] (users |> List.map (viewUser wrap shared localUserId localUserPrivilege openUserMenu game))
|
||||
]
|
||||
)
|
||||
|
||||
@@ -849,15 +903,12 @@ byRole users =
|
||||
|> List.filter (\( _, us ) -> not (List.isEmpty us))
|
||||
|
||||
|
||||
viewUser : (Msg -> msg) -> Shared -> User.Id -> User.Privilege -> Maybe Game -> ( User.Id, User ) -> ( String, Html msg )
|
||||
viewUser wrap shared localUserId localUserPrivilege game ( userId, user ) =
|
||||
viewUser : (Msg -> msg) -> Shared -> User.Id -> User.Privilege -> Maybe User.Id -> Maybe Game -> ( User.Id, User ) -> ( String, Html msg )
|
||||
viewUser wrap shared localUserId localUserPrivilege openUserMenu game ( userId, user ) =
|
||||
let
|
||||
( secondary, score ) =
|
||||
userDetails shared game userId user
|
||||
|
||||
id =
|
||||
"user-" ++ userId
|
||||
|
||||
player =
|
||||
game |> Maybe.map .players |> Maybe.andThen (Dict.get userId)
|
||||
|
||||
@@ -914,10 +965,10 @@ viewUser wrap shared localUserId localUserPrivilege game ( userId, user ) =
|
||||
in
|
||||
let
|
||||
setAway =
|
||||
if userId == localUserId then
|
||||
if userId == localUserId && game /= Nothing then
|
||||
setPresenceMenuItem wrap player |> Just
|
||||
|
||||
else if not isAway then
|
||||
else if not isAway && game /= Nothing then
|
||||
case localUserPrivilege of
|
||||
User.Privileged ->
|
||||
Menu.button Icon.userClock Strings.SetAway Strings.SetAway (userId |> SetAway |> wrap |> Just) |> Just
|
||||
@@ -946,27 +997,38 @@ viewUser wrap shared localUserId localUserPrivilege game ( userId, user ) =
|
||||
else
|
||||
[]
|
||||
|
||||
( menu, clickable ) =
|
||||
( menu, attrs ) =
|
||||
if user.presence == User.Left || List.isEmpty menuItems then
|
||||
( Html.nothing, HtmlA.nothing )
|
||||
( identity, [] )
|
||||
|
||||
else
|
||||
( menuItems |> Menu.view shared id ( WlA.XCenter, WlA.YBottom ) ( WlA.XCenter, WlA.YTop )
|
||||
, WlA.clickable
|
||||
let
|
||||
isOpen =
|
||||
Just userId == openUserMenu
|
||||
in
|
||||
( \c ->
|
||||
Menu.view shared
|
||||
(SetUserMenuState userId Menu.Closed |> wrap)
|
||||
(isOpen |> Menu.open)
|
||||
Menu.BottomRight
|
||||
c
|
||||
menuItems
|
||||
, [ not isOpen |> Menu.open |> SetUserMenuState userId |> wrap |> HtmlE.onClick
|
||||
, HtmlA.classList [ ( "active", isOpen ), ( "has-menu", True ) ]
|
||||
]
|
||||
)
|
||||
in
|
||||
( userId
|
||||
, Html.li []
|
||||
[ Wl.listItem
|
||||
[ HtmlA.classList [ ( "you", localUserId == userId ), ( "away", isAway ) ]
|
||||
, clickable
|
||||
, HtmlA.id id
|
||||
[ Html.div
|
||||
(HtmlA.classList [ ( "user", True ), ( "you", localUserId == userId ), ( "away", isAway ) ] :: attrs)
|
||||
[ Html.div [ HtmlA.class "about" ]
|
||||
[ Html.div [ HtmlA.class "name", HtmlA.title user.name ] [ Html.text user.name ]
|
||||
, Html.div [ HtmlA.class "state", HtmlA.class "compressed-terms" ] secondary
|
||||
]
|
||||
, Html.span [ HtmlA.class "scores" ] score
|
||||
]
|
||||
[ Html.span [ HtmlA.class "user", HtmlA.title user.name ] [ Html.text user.name ]
|
||||
, Html.div [ HtmlA.class "compressed-terms" ] secondary
|
||||
, Html.span [ WlA.listItemSlot WlA.AfterItem ] score
|
||||
]
|
||||
, menu
|
||||
, Html.div [] [] |> menu
|
||||
]
|
||||
)
|
||||
|
||||
@@ -994,33 +1056,33 @@ userDetails shared game userId user =
|
||||
game |> Maybe.map .round
|
||||
|
||||
details =
|
||||
[ Strings.Privileged |> Maybe.justIf (user.privilege == User.Privileged)
|
||||
, Strings.Ai |> Maybe.justIf (user.control == User.Computer)
|
||||
, round |> Maybe.andThen (\r -> Strings.Czar |> Maybe.justIf (Player.isCzar r userId))
|
||||
, Strings.Disconnected |> Maybe.justIf (user.connection == User.Disconnected && user.presence /= User.Left)
|
||||
, Strings.Away |> Maybe.justIf ((player |> Maybe.map .presence) == Just Player.Away)
|
||||
[ ( "privileged", Strings.Privileged ) |> Maybe.justIf (user.privilege == User.Privileged)
|
||||
, ( "ai", Strings.Ai ) |> Maybe.justIf (user.control == User.Computer)
|
||||
, round |> Maybe.andThen (\r -> ( "czar", Strings.Czar ) |> Maybe.justIf (Player.isCzar r userId))
|
||||
, ( "disconnected", Strings.Disconnected ) |> Maybe.justIf (user.connection == User.Disconnected && user.presence /= User.Left)
|
||||
, ( "away", Strings.Away ) |> Maybe.justIf ((player |> Maybe.map .presence) == Just Player.Away)
|
||||
, playStateDetail round userId
|
||||
]
|
||||
|
||||
score =
|
||||
player |> Maybe.map (\p -> Strings.Score { total = p.score })
|
||||
player |> Maybe.map (\p -> ( "score", Strings.Score { total = p.score } ))
|
||||
|
||||
likes =
|
||||
player |> Maybe.map (\p -> Strings.Likes { total = p.likes })
|
||||
player |> Maybe.map (\p -> ( "likes", Strings.Likes { total = p.likes } ))
|
||||
in
|
||||
( viewDetails shared details, viewDetails shared [ score, likes ] )
|
||||
|
||||
|
||||
playStateDetail : Maybe Round -> User.Id -> Maybe MdString
|
||||
playStateDetail : Maybe Round -> User.Id -> Maybe ( String, MdString )
|
||||
playStateDetail round userId =
|
||||
case round of
|
||||
Just (Round.P p) ->
|
||||
case Player.playState p userId of
|
||||
Player.Playing ->
|
||||
Just Strings.StillPlaying
|
||||
Just ( "playing", Strings.StillPlaying )
|
||||
|
||||
Player.Played ->
|
||||
Just Strings.Played
|
||||
Just ( "played", Strings.Played )
|
||||
|
||||
Player.NotInRound ->
|
||||
Nothing
|
||||
@@ -1029,20 +1091,18 @@ playStateDetail round userId =
|
||||
Nothing
|
||||
|
||||
|
||||
viewDetails : Shared -> List (Maybe MdString) -> List (Html msg)
|
||||
viewDetails shared details =
|
||||
details |> List.filterMap identity |> List.map (Lang.html shared) |> List.intersperse (Html.text " ")
|
||||
viewDetails : Shared -> List (Maybe ( String, MdString )) -> List (Html msg)
|
||||
viewDetails shared =
|
||||
List.filterMap identity
|
||||
>> List.map (\( cls, str ) -> Html.span [ HtmlA.class cls ] [ str |> Lang.html shared ])
|
||||
|
||||
|
||||
viewCastButton : (Msg -> msg) -> Auth -> List (Html.Attribute msg) -> List (Html msg)
|
||||
viewCastButton wrap auth attrs =
|
||||
[ Components.iconButtonStyled
|
||||
(List.concat
|
||||
[ [ HtmlA.class "cast-button"
|
||||
, auth |> TryCast |> wrap |> HtmlE.onClick
|
||||
]
|
||||
, attrs
|
||||
]
|
||||
)
|
||||
( [ Icon.lg ], Icon.chromecast )
|
||||
viewCastButton : (Msg -> msg) -> Shared -> Auth -> List (Html.Attribute msg) -> List (Html msg)
|
||||
viewCastButton wrap shared auth attrs =
|
||||
[ Html.div (HtmlA.class "cast-button" :: attrs)
|
||||
[ IconButton.view shared
|
||||
Strings.Cast
|
||||
(Icon.chromecast |> Icon.present |> Icon.styled [ Icon.lg ] |> NeList.just)
|
||||
(auth |> TryCast |> wrap |> Just)
|
||||
]
|
||||
]
|
||||
|
||||
@@ -15,9 +15,7 @@ import Json.Decode as Json
|
||||
import Json.Diff as Json
|
||||
import Json.Encode
|
||||
import Json.Patch as Json
|
||||
import MassiveDecks.Card.Source.Cardcast.Model as Cardcast
|
||||
import MassiveDecks.Card.Source.Model as Source exposing (Source)
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Card.Source.Model exposing (Source)
|
||||
import MassiveDecks.Components.Form as Form
|
||||
import MassiveDecks.Components.Form.Message as Message exposing (Message)
|
||||
import MassiveDecks.Error.Model as Error exposing (Error)
|
||||
@@ -52,8 +50,13 @@ import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.User as User exposing (User)
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import MassiveDecks.Util.NeList exposing (NeList(..))
|
||||
import Material.Attributes as Material
|
||||
import Material.Button as Button
|
||||
import Material.Card as Card
|
||||
import Material.Fab as Fab
|
||||
import Material.Tabs as Tabs
|
||||
import Material.TextField as TextField
|
||||
|
||||
|
||||
init : Shared -> Model
|
||||
@@ -177,16 +180,19 @@ update shared msg model config =
|
||||
( model, shared, Cmd.none )
|
||||
|
||||
|
||||
view : (Msg -> msg) -> (Lobby.Msg -> msg) -> Shared -> Maybe msg -> Bool -> Model -> GameCode -> Lobby -> Html msg
|
||||
view wrap wrapLobby shared return canEdit model gameCode lobby =
|
||||
view : (Msg -> msg) -> (Lobby.Msg -> msg) -> Shared -> Maybe msg -> Message msg -> Model -> GameCode -> Lobby -> Html msg
|
||||
view wrap wrapLobby shared return disabledReason model gameCode lobby =
|
||||
let
|
||||
canEdit =
|
||||
disabledReason |> Maybe.isNothing
|
||||
|
||||
conflicts =
|
||||
if List.isEmpty model.conflicts then
|
||||
Html.nothing
|
||||
|
||||
else
|
||||
Html.div [ HtmlA.id "merge-overlay" ]
|
||||
[ Wl.card []
|
||||
[ Card.view []
|
||||
[ model.conflicts |> viewMerge wrap shared model model.localConfig lobby.config (wrap NoOp) canEdit
|
||||
]
|
||||
]
|
||||
@@ -205,27 +211,37 @@ view wrap wrapLobby shared return canEdit model gameCode lobby =
|
||||
Privacy ->
|
||||
Privacy.All |> PrivacyId
|
||||
|
||||
returnButton msg =
|
||||
Wl.card []
|
||||
[ Wl.button [ HtmlA.class "game-in-progress", HtmlE.onClick msg, Strings.ReturnViewToGameDescription |> Lang.title shared ]
|
||||
[ Icon.play |> Icon.viewIcon
|
||||
, Strings.ReturnViewToGame |> Lang.html shared
|
||||
]
|
||||
]
|
||||
viewReturnButton msg =
|
||||
Button.view shared
|
||||
Button.Raised
|
||||
Strings.ReturnViewToGame
|
||||
Strings.ReturnViewToGameDescription
|
||||
(Icon.arrowLeft |> Icon.viewIcon)
|
||||
[ HtmlA.class "game-in-progress", HtmlE.onClick msg ]
|
||||
|
||||
returnButton =
|
||||
return |> Maybe.map viewReturnButton |> Maybe.withDefault (Html.div [] [])
|
||||
in
|
||||
Html.div [ HtmlA.class "configure" ]
|
||||
[ return |> Maybe.map returnButton |> Maybe.withDefault Html.nothing
|
||||
, Wl.card []
|
||||
[ Card.view []
|
||||
[ Html.div [ HtmlA.class "title" ]
|
||||
[ Component.view (componentById NameId) wrap shared model model.localConfig lobby.config (wrap NoOp) canEdit ConfigOption.Local |> Maybe.withDefault Html.nothing
|
||||
, Html.div [ HtmlA.class "joining" ]
|
||||
[ Invite.button wrapLobby shared
|
||||
, Strings.GameCode { code = GameCode.toString gameCode } |> Lang.html shared
|
||||
, Html.label [ Lobby.ToggleInviteDialog |> wrapLobby |> HtmlE.onClick ]
|
||||
[ Strings.GameCode { code = GameCode.toString gameCode } |> Lang.html shared ]
|
||||
]
|
||||
]
|
||||
]
|
||||
, Wl.card []
|
||||
[ Wl.tabGroup [ WlA.align WlA.Center ] (tabs |> List.map (tab wrap shared model.tab))
|
||||
, disabledReason
|
||||
|> Message.view shared
|
||||
|> Maybe.withDefault Html.nothing
|
||||
, Tabs.view shared
|
||||
{ selected = model.tab
|
||||
, change = ChangeTab >> wrap
|
||||
, ids = tabs
|
||||
, tab = tab
|
||||
, equals = (==)
|
||||
}
|
||||
, Component.view (componentById tabComponent)
|
||||
wrap
|
||||
shared
|
||||
@@ -236,9 +252,7 @@ view wrap wrapLobby shared return canEdit model gameCode lobby =
|
||||
canEdit
|
||||
ConfigOption.Local
|
||||
|> Maybe.withDefault Html.nothing
|
||||
]
|
||||
, Wl.card []
|
||||
[ startGameSegment wrap wrapLobby shared canEdit model lobby
|
||||
, startGameSegment wrap wrapLobby shared canEdit model lobby returnButton
|
||||
]
|
||||
, actions wrap shared (model.localConfig /= lobby.config)
|
||||
, conflicts
|
||||
@@ -270,19 +284,18 @@ applyChange change config model =
|
||||
|
||||
actions wrap shared hasChanges =
|
||||
Html.div [ HtmlA.class "actions" ]
|
||||
[ Components.floatingActionButton
|
||||
[ Strings.SaveChanges |> Lang.title shared
|
||||
, SaveChanges |> wrap |> HtmlE.onClick
|
||||
, HtmlA.classList [ ( "action", True ), ( "important", True ), ( "exited", not hasChanges ) ]
|
||||
]
|
||||
Icon.save
|
||||
, Components.floatingActionButton
|
||||
[ Strings.RevertChanges |> Lang.title shared
|
||||
, RevertChanges |> wrap |> HtmlE.onClick
|
||||
, HtmlA.classList [ ( "action", True ), ( "exited", not hasChanges ) ]
|
||||
, WlA.inverted
|
||||
]
|
||||
Icon.undo
|
||||
[ Fab.view shared
|
||||
Fab.Normal
|
||||
Strings.SaveChanges
|
||||
(Icon.save |> Icon.present)
|
||||
(SaveChanges |> wrap |> Just)
|
||||
[ HtmlA.classList [ ( "action", True ), ( "important", True ), ( "exited", not hasChanges ) ] ]
|
||||
, Fab.view shared
|
||||
Fab.Mini
|
||||
Strings.RevertChanges
|
||||
(Icon.undo |> Icon.present)
|
||||
(RevertChanges |> wrap |> Just)
|
||||
[ HtmlA.classList [ ( "action", True ), ( "exited", not hasChanges ), ( "normal", True ) ] ]
|
||||
]
|
||||
|
||||
|
||||
@@ -329,8 +342,14 @@ viewConflict wrap shared model local config noOp canEdit conflict =
|
||||
]
|
||||
|
||||
|
||||
resolveButton : (Msg -> msg) -> Shared -> Id -> Config.Source -> MdString -> Html msg
|
||||
resolveButton wrap shared conflict source description =
|
||||
Wl.button [ HtmlA.class "resolve", ResolveConflict source conflict |> wrap |> HtmlE.onClick ] [ description |> Lang.html shared ]
|
||||
Button.view shared
|
||||
Button.Raised
|
||||
description
|
||||
description
|
||||
Html.nothing
|
||||
[ HtmlA.class "resolve", ResolveConflict source conflict |> wrap |> HtmlE.onClick ]
|
||||
|
||||
|
||||
default : Config
|
||||
@@ -408,12 +427,12 @@ nameOption =
|
||||
\_ ->
|
||||
ConfigOption.TextField
|
||||
{ placeholder = Strings.LobbyNameLabel
|
||||
, inputType = Nothing
|
||||
, inputType = TextField.Text
|
||||
, toString = Just
|
||||
, fromString = Just
|
||||
, attrs = [ WlA.minLength 1, WlA.maxLength 100 ]
|
||||
, attrs = [ Material.minLength 1, Material.maxLength 100 ]
|
||||
}
|
||||
, extraEditor = \_ -> \_ -> \_ -> Nothing
|
||||
, extraEditor = \_ -> \_ -> \_ -> \_ -> Nothing
|
||||
, set = ConfigOption.wrappedSetter NameChange
|
||||
, messages = \_ -> []
|
||||
}
|
||||
@@ -436,8 +455,8 @@ mergeChange base local remote model =
|
||||
{ model | localConfig = updated, conflicts = oldConflicts ++ newConflicts }
|
||||
|
||||
|
||||
startGameSegment : (Msg -> msg) -> (Lobby.Msg -> msg) -> Shared -> Bool -> Model -> Lobby -> Html msg
|
||||
startGameSegment wrap wrapLobby shared canEdit model lobby =
|
||||
startGameSegment : (Msg -> msg) -> (Lobby.Msg -> msg) -> Shared -> Bool -> Model -> Lobby -> Html msg -> Html msg
|
||||
startGameSegment wrap wrapLobby shared canEdit model lobby returnButton =
|
||||
let
|
||||
config =
|
||||
lobby.config
|
||||
@@ -447,15 +466,27 @@ startGameSegment wrap wrapLobby shared canEdit model lobby =
|
||||
|
||||
startGameAttrs =
|
||||
if List.isEmpty startErrors && canEdit then
|
||||
[ StartGame |> wrap |> HtmlE.onClick ]
|
||||
StartGame |> wrap |> HtmlE.onClick
|
||||
|
||||
else
|
||||
[ WlA.disabled ]
|
||||
HtmlA.disabled True
|
||||
in
|
||||
Form.section shared
|
||||
"start-game"
|
||||
(Wl.button startGameAttrs [ Strings.StartGame |> Lang.html shared ])
|
||||
(startErrors |> Maybe.justIf canEdit |> Maybe.withDefault [])
|
||||
Html.div []
|
||||
[ Form.section
|
||||
shared
|
||||
"start-game"
|
||||
Html.nothing
|
||||
(startErrors |> Maybe.justIf canEdit |> Maybe.withDefault [])
|
||||
, Html.div [ HtmlA.class "button-spread" ]
|
||||
[ returnButton
|
||||
, Button.view shared
|
||||
Button.Raised
|
||||
Strings.StartGame
|
||||
Strings.StartGame
|
||||
(Icon.rocket |> Icon.viewIcon)
|
||||
[ startGameAttrs ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
startGameProblems : Shared -> (Msg -> msg) -> (Lobby.Msg -> msg) -> Dict User.Id User -> Model -> Config -> List (Message msg)
|
||||
@@ -496,7 +527,7 @@ startGameProblems shared wrap wrapLobby users model remote =
|
||||
summaries .responses
|
||||
|
||||
requiredResponses =
|
||||
(users |> Dict.values |> List.filter (\u -> u.role == User.Player) |> List.length) * 3
|
||||
(users |> Dict.values |> List.filter (\u -> u.role == User.Player) |> List.length) * 3 * 2
|
||||
|
||||
deckIssues =
|
||||
if noDecks then
|
||||
@@ -514,29 +545,37 @@ startGameProblems shared wrap wrapLobby users model remote =
|
||||
[ Strings.WaitForDecks |> Message.info |> Just ]
|
||||
|
||||
else
|
||||
let
|
||||
diff =
|
||||
requiredResponses - numberOfResponses
|
||||
|
||||
old =
|
||||
config.rules.houseRules.comedyWriter |> Maybe.map .number |> Maybe.withDefault 0
|
||||
|
||||
fixMsg =
|
||||
diff + old |> Just |> ComedyWriter.SetNumber |> HouseRule.ComedyWriterMsg |> Rules.HouseRulesMsg |> RulesMsg |> wrap
|
||||
in
|
||||
[ Strings.MissingCardType { cardType = Strings.Call }
|
||||
|> Message.error
|
||||
|> Maybe.justIf (summaries .calls < 1)
|
||||
, Strings.MissingCardType { cardType = Strings.Response }
|
||||
|> Message.error
|
||||
|> Maybe.justIf (numberOfResponses < 1)
|
||||
, Strings.NotEnoughCardsOfType { cardType = Strings.Response, needed = requiredResponses, have = numberOfResponses }
|
||||
|> Message.error
|
||||
, Message.errorWithFix
|
||||
(Strings.NotEnoughCardsOfType { cardType = Strings.Response, needed = requiredResponses, have = numberOfResponses })
|
||||
[ Message.Fix (Strings.AddBlankCards { amount = diff }) Icon.plus fixMsg ]
|
||||
|> Maybe.justIf (numberOfResponses < requiredResponses)
|
||||
]
|
||||
|
||||
playerCount =
|
||||
users
|
||||
|> Dict.values
|
||||
|> List.filter (\user -> user.role == User.Player && user.presence == User.Joined)
|
||||
|> List.length
|
||||
|
||||
humanPlayerCount =
|
||||
users
|
||||
|> Dict.values
|
||||
|> List.filter (\user -> user.role == User.Player && user.presence == User.Joined && user.control == User.Human)
|
||||
|> List.filter (\u -> u.role == User.Player && u.presence == User.Joined && u.control == User.Human)
|
||||
|> List.length
|
||||
|
||||
computerPlayers =
|
||||
config.rules.houseRules.rando |> Maybe.map .number |> Maybe.withDefault 0
|
||||
|
||||
playerCount =
|
||||
humanPlayerCount + computerPlayers
|
||||
|
||||
playerIssues =
|
||||
[ Message.errorWithFix
|
||||
Strings.NeedAtLeastThreePlayers
|
||||
@@ -614,18 +653,16 @@ startGameProblems shared wrap wrapLobby users model remote =
|
||||
[ deckIssues, playerIssues, aisNoWriteGoodIssues, configurationIssues ] |> List.concat |> List.filterMap identity
|
||||
|
||||
|
||||
tabs : List Tab
|
||||
tabs : NeList Tab
|
||||
tabs =
|
||||
[ Decks, Rules, TimeLimits, Privacy ]
|
||||
NeList Decks [ Rules, TimeLimits, Privacy ]
|
||||
|
||||
|
||||
tab : (Msg -> msg) -> Shared -> Tab -> Tab -> Html msg
|
||||
tab wrap shared currently target =
|
||||
Wl.tab
|
||||
((target |> ChangeTab |> wrap |> always |> HtmlE.onCheck)
|
||||
:: ([ WlA.checked ] |> Maybe.justIf (currently == target) |> Maybe.withDefault [])
|
||||
)
|
||||
[ target |> tabName |> Lang.html shared ]
|
||||
tab : Tab -> Tabs.TabModel
|
||||
tab target =
|
||||
{ label = target |> tabName
|
||||
, icon = Nothing
|
||||
}
|
||||
|
||||
|
||||
tabName : Tab -> MdString
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
module MassiveDecks.Pages.Lobby.Configure.ConfigOption exposing
|
||||
( ConfigOption
|
||||
, IntBounds
|
||||
, PrimaryEditor(..)
|
||||
, RenderMode(..)
|
||||
, ViewArgs
|
||||
, intEditor
|
||||
, maybeEditor
|
||||
, noExtraEditor
|
||||
, toMinMaxAttrs
|
||||
, toValidator
|
||||
, view
|
||||
, wrappedSetter
|
||||
)
|
||||
@@ -16,14 +19,13 @@ import Html.Events as HtmlE
|
||||
import MassiveDecks.Components.Form as Form
|
||||
import MassiveDecks.Components.Form.Message exposing (Message)
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Component.Validator exposing (Validator)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Component.Validator as Validator exposing (Validator)
|
||||
import MassiveDecks.Pages.Lobby.Configure.ConfigOption.Toggleable exposing (Toggleable)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import Material.Switch as Material
|
||||
import Material.TextField as TextField
|
||||
|
||||
|
||||
type alias Model model config =
|
||||
@@ -34,7 +36,7 @@ type alias ConfigOption model config iMsg msg =
|
||||
{ id : String
|
||||
, toggleable : Toggleable config
|
||||
, primaryEditor : model -> PrimaryEditor config msg
|
||||
, extraEditor : (iMsg -> msg) -> model -> config -> Maybe (Html msg)
|
||||
, extraEditor : (iMsg -> msg) -> Shared -> model -> config -> Maybe (Html msg)
|
||||
, set : (iMsg -> msg) -> config -> msg
|
||||
, messages : (iMsg -> msg) -> List (Message msg)
|
||||
}
|
||||
@@ -43,7 +45,7 @@ type alias ConfigOption model config iMsg msg =
|
||||
type PrimaryEditor value msg
|
||||
= TextField
|
||||
{ placeholder : MdString
|
||||
, inputType : Maybe WlA.InputType
|
||||
, inputType : TextField.Type
|
||||
, toString : value -> Maybe String
|
||||
, fromString : String -> Maybe value
|
||||
, attrs : List (Html.Attribute msg)
|
||||
@@ -55,7 +57,7 @@ intEditor : MdString -> List (Html.Attribute msg) -> PrimaryEditor Int msg
|
||||
intEditor placeholder attrs =
|
||||
TextField
|
||||
{ placeholder = placeholder
|
||||
, inputType = Just WlA.Number
|
||||
, inputType = TextField.Number
|
||||
, toString = String.fromInt >> Just
|
||||
, fromString = String.toInt
|
||||
, attrs = attrs
|
||||
@@ -78,9 +80,9 @@ maybeEditor given =
|
||||
Label { text = text }
|
||||
|
||||
|
||||
noExtraEditor : (iMsg -> msg) -> model -> config -> Maybe (Html msg)
|
||||
noExtraEditor =
|
||||
\_ -> \_ -> \_ -> Nothing
|
||||
noExtraEditor : (iMsg -> msg) -> Shared -> model -> config -> Maybe (Html msg)
|
||||
noExtraEditor _ _ _ _ =
|
||||
Nothing
|
||||
|
||||
|
||||
wrappedSetter : (config -> iMsg) -> (iMsg -> msg) -> config -> msg
|
||||
@@ -144,6 +146,30 @@ view opt validator { wrap, shared, model, local, remote, noOp, canEdit, renderMo
|
||||
(errors ++ opt.messages wrap)
|
||||
|
||||
|
||||
{-| Boundaries for an integer value.
|
||||
-}
|
||||
type alias IntBounds =
|
||||
{ min : Int
|
||||
, max : Int
|
||||
}
|
||||
|
||||
|
||||
{-| Convert bounds to min/max HTML attributes.
|
||||
-}
|
||||
toMinMaxAttrs : IntBounds -> List (Html.Attribute msg)
|
||||
toMinMaxAttrs { min, max } =
|
||||
[ min |> String.fromInt |> HtmlA.min
|
||||
, max |> String.fromInt |> HtmlA.max
|
||||
]
|
||||
|
||||
|
||||
{-| Convert bounds to a validator.
|
||||
-}
|
||||
toValidator : IntBounds -> (Int -> iMsg) -> Validator Int iMsg msg
|
||||
toValidator { min, max } =
|
||||
Validator.between min max
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
@@ -151,54 +177,73 @@ view opt validator { wrap, shared, model, local, remote, noOp, canEdit, renderMo
|
||||
viewContents : ConfigOption model config optionMsg msg -> (optionMsg -> msg) -> Shared -> model -> msg -> Bool -> Bool -> config -> List (Html msg)
|
||||
viewContents opt wrap shared model noOp canEdit readOnly config =
|
||||
let
|
||||
( switch, toggle, id ) =
|
||||
case opt.toggleable of
|
||||
Just { off, on } ->
|
||||
let
|
||||
i =
|
||||
opt.id ++ "-swtich"
|
||||
|
||||
t =
|
||||
Maybe.justIf (config == off) on |> Maybe.withDefault off |> opt.set wrap
|
||||
|
||||
s =
|
||||
Material.view
|
||||
[ HtmlA.checked (config /= off)
|
||||
, if readOnly || not canEdit then
|
||||
HtmlA.disabled True
|
||||
|
||||
else
|
||||
HtmlE.onCheck (\c -> Maybe.justIf c on |> Maybe.withDefault off |> opt.set wrap)
|
||||
, HtmlA.id i
|
||||
]
|
||||
in
|
||||
( Just s, t |> Maybe.justIf (not readOnly && canEdit), Just i )
|
||||
|
||||
Nothing ->
|
||||
( Nothing, Nothing, Nothing )
|
||||
|
||||
primaryEditor =
|
||||
case opt.primaryEditor model of
|
||||
TextField { placeholder, inputType, toString, fromString, attrs } ->
|
||||
Wl.textField
|
||||
([ HtmlA.class "primary"
|
||||
, placeholder |> Lang.label shared
|
||||
, WlA.outlined
|
||||
, inputType |> Maybe.map WlA.type_ |> Maybe.withDefault HtmlA.nothing
|
||||
, config |> toString |> Maybe.map WlA.value |> Maybe.withDefault HtmlA.nothing
|
||||
, if readOnly then
|
||||
WlA.readonly
|
||||
let
|
||||
toggleableAndOff =
|
||||
opt.toggleable |> Maybe.map (.off >> (==) config) |> Maybe.withDefault False
|
||||
in
|
||||
TextField.view shared
|
||||
placeholder
|
||||
inputType
|
||||
(config |> toString |> Maybe.withDefault "")
|
||||
(List.filterMap identity
|
||||
[ HtmlA.class "primary" |> Just
|
||||
, if readOnly then
|
||||
HtmlA.readonly True |> Just
|
||||
|
||||
else
|
||||
fromString
|
||||
>> Maybe.map (opt.set wrap)
|
||||
>> Maybe.withDefault noOp
|
||||
|> HtmlE.onInput
|
||||
, opt.toggleable
|
||||
|> Maybe.andThen (\t -> Maybe.justIf (t.off == config) WlA.disabled)
|
||||
|> Maybe.withDefault HtmlA.nothing
|
||||
, WlA.disabled |> Maybe.justIf (not canEdit) |> Maybe.withDefault HtmlA.nothing
|
||||
]
|
||||
else
|
||||
fromString
|
||||
>> Maybe.map (opt.set wrap)
|
||||
>> Maybe.withDefault noOp
|
||||
|> HtmlE.onInput
|
||||
|> Just
|
||||
, HtmlA.disabled True |> Maybe.justIf toggleableAndOff
|
||||
, toggle |> Maybe.andThen (Maybe.justIf toggleableAndOff) |> Maybe.map HtmlE.onClick
|
||||
, True |> HtmlA.disabled |> Maybe.justIf (not canEdit)
|
||||
]
|
||||
++ attrs
|
||||
)
|
||||
[]
|
||||
|
||||
Label { text } ->
|
||||
Html.label [ HtmlA.class "primary" ] [ text |> Lang.html shared ]
|
||||
|
||||
switch =
|
||||
case opt.toggleable of
|
||||
Just { off, on } ->
|
||||
Wl.switch
|
||||
[ WlA.checked |> Maybe.justIf (config /= off) |> Maybe.withDefault HtmlA.nothing
|
||||
, if readOnly then
|
||||
WlA.disabled
|
||||
|
||||
else
|
||||
HtmlE.onCheck (\c -> Maybe.justIf c on |> Maybe.withDefault off |> opt.set wrap)
|
||||
, WlA.disabled |> Maybe.justIf (not canEdit) |> Maybe.withDefault HtmlA.nothing
|
||||
]
|
||||
|> Just
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
Html.label
|
||||
(List.filterMap identity
|
||||
[ HtmlA.class "primary" |> Just
|
||||
, toggle |> Maybe.map HtmlE.onClick
|
||||
, id |> Maybe.map HtmlA.for
|
||||
]
|
||||
)
|
||||
[ text |> Lang.html shared ]
|
||||
in
|
||||
[ switch
|
||||
, Just primaryEditor
|
||||
, opt.extraEditor wrap model config
|
||||
, opt.extraEditor wrap shared model config
|
||||
]
|
||||
|> List.filterMap identity
|
||||
|
||||
@@ -30,9 +30,11 @@ import MassiveDecks.Settings as Settings
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import MassiveDecks.Util.NeList exposing (NeList(..))
|
||||
import MassiveDecks.Util.Result as Result
|
||||
import Material.IconButton as IconButton
|
||||
import Paper.Tooltip as Tooltip
|
||||
import Svg.Attributes as SvgA
|
||||
|
||||
|
||||
@@ -74,6 +76,9 @@ update shared msg model =
|
||||
Remove index ->
|
||||
( model, shared, Actions.configure (removeDeck index) )
|
||||
|
||||
NoOp ->
|
||||
( model, shared, Cmd.none )
|
||||
|
||||
|
||||
{-| View the editor/viewer for decks.
|
||||
-}
|
||||
@@ -83,8 +88,7 @@ view _ { wrap, shared, model, remote, canEdit } =
|
||||
hint =
|
||||
if canEdit then
|
||||
Components.linkButton
|
||||
[ shared |> Lang.recommended |> Add |> wrap |> HtmlE.onClick
|
||||
]
|
||||
[ shared |> Lang.recommended |> Add |> wrap |> HtmlE.onClick ]
|
||||
[ Strings.NoDecksHint |> Lang.html shared ]
|
||||
|
||||
else
|
||||
@@ -138,7 +142,7 @@ view _ { wrap, shared, model, remote, canEdit } =
|
||||
|
||||
addDeck : Source.External -> Json.Patch
|
||||
addDeck source =
|
||||
[ Json.Add [ "decks", "-" ] (D (Deck source Nothing) |> Encoders.deckOrError) ]
|
||||
[ Json.Add [ "decks", "-" ] (DeckOrError source (Ok Nothing) |> Encoders.deckOrError) ]
|
||||
|
||||
|
||||
removeDeck : Int -> Json.Patch
|
||||
@@ -175,21 +179,21 @@ all =
|
||||
|
||||
getDeck : DeckOrError -> Maybe Deck
|
||||
getDeck deckOrError =
|
||||
case deckOrError of
|
||||
D deck ->
|
||||
Just deck
|
||||
case deckOrError.result of
|
||||
Ok summary ->
|
||||
Deck deckOrError.source summary |> Just
|
||||
|
||||
E _ ->
|
||||
Err _ ->
|
||||
Nothing
|
||||
|
||||
|
||||
summaryFor : DeckOrError -> Maybe ( Source.External, Source.Summary )
|
||||
summaryFor deckOrError =
|
||||
case deckOrError of
|
||||
D deck ->
|
||||
deck.summary |> Maybe.map (\s -> ( deck.source, s ))
|
||||
case deckOrError.result of
|
||||
Ok summary ->
|
||||
summary |> Maybe.map (\s -> ( deckOrError.source, s ))
|
||||
|
||||
E _ ->
|
||||
Err _ ->
|
||||
Nothing
|
||||
|
||||
|
||||
@@ -199,33 +203,28 @@ addDeckWidget wrap shared existing deckToAdd =
|
||||
submit =
|
||||
deckToAdd |> submitDeckAction wrap existing
|
||||
in
|
||||
Html.form
|
||||
[ submit |> Result.map HtmlE.onSubmit |> Result.withDefault HtmlA.nothing ]
|
||||
[ Form.section
|
||||
shared
|
||||
"add-deck"
|
||||
(Html.div [ HtmlA.class "multipart" ]
|
||||
(List.concat
|
||||
[ Source.generalEditor shared existing deckToAdd (Update >> wrap)
|
||||
, [ Components.floatingActionButton
|
||||
[ HtmlA.type_ "submit"
|
||||
, Result.isError submit |> HtmlA.disabled
|
||||
, Strings.AddDeck |> Lang.title shared
|
||||
]
|
||||
Icon.plus
|
||||
]
|
||||
]
|
||||
)
|
||||
Form.section
|
||||
shared
|
||||
"add-deck"
|
||||
(Html.div [ HtmlA.class "multipart" ]
|
||||
(List.concat
|
||||
[ Source.generalEditor shared existing deckToAdd (Update >> wrap) (submit |> Result.toMaybe) (NoOp |> wrap)
|
||||
, [ IconButton.view shared
|
||||
Strings.AddDeck
|
||||
(NeList (Icon.plus |> Icon.present) [])
|
||||
(submit |> Result.toMaybe)
|
||||
]
|
||||
]
|
||||
)
|
||||
(submit |> Result.error |> Maybe.withDefault [])
|
||||
]
|
||||
)
|
||||
(submit |> Result.error |> Maybe.withDefault [])
|
||||
|
||||
|
||||
submitDeckAction : (Msg -> msg) -> List DeckOrError -> Source.External -> Result (List (Message msg)) msg
|
||||
submitDeckAction wrap existing deckToAdd =
|
||||
let
|
||||
potentialProblems =
|
||||
if List.any (getSource >> Source.equals deckToAdd) existing then
|
||||
if List.any (.source >> Source.equals deckToAdd) existing then
|
||||
[ Strings.DeckAlreadyAdded |> Message.info ]
|
||||
|
||||
else
|
||||
@@ -238,26 +237,19 @@ submitDeckAction wrap existing deckToAdd =
|
||||
potentialProblems |> Result.Err
|
||||
|
||||
|
||||
getSource : DeckOrError -> Source.External
|
||||
getSource deckOrError =
|
||||
case deckOrError of
|
||||
D deck ->
|
||||
deck.source
|
||||
|
||||
E error ->
|
||||
error.source
|
||||
|
||||
|
||||
viewDeck : (Msg -> msg) -> Shared -> Bool -> Int -> DeckOrError -> Html msg
|
||||
viewDeck wrap shared canEdit index deckOrError =
|
||||
let
|
||||
( deckSource, deckSummary, failureReason ) =
|
||||
case deckOrError of
|
||||
D { source, summary } ->
|
||||
( source, summary, Nothing )
|
||||
deckSource =
|
||||
deckOrError.source
|
||||
|
||||
E { source, reason } ->
|
||||
( source, Nothing, Just reason )
|
||||
( deckSummary, failureReason ) =
|
||||
case deckOrError.result of
|
||||
Ok summary ->
|
||||
( summary, Nothing )
|
||||
|
||||
Err reason ->
|
||||
( Nothing, Just reason )
|
||||
|
||||
( attr, columns ) =
|
||||
case deckSummary of
|
||||
@@ -299,16 +291,15 @@ name :
|
||||
name wrap shared canEdit index source loading maybeError details =
|
||||
let
|
||||
removeButton =
|
||||
Components.iconButton
|
||||
[ index |> Remove |> wrap |> HtmlE.onClick
|
||||
, Strings.RemoveDeck |> Lang.title shared
|
||||
, HtmlA.class "remove-button"
|
||||
]
|
||||
Icon.trash
|
||||
IconButton.view
|
||||
shared
|
||||
Strings.RemoveDeck
|
||||
(NeList (Icon.trash |> Icon.present) [])
|
||||
(index |> Remove |> wrap |> Just)
|
||||
|> Maybe.justIf canEdit
|
||||
|
||||
( maybeId, tooltip ) =
|
||||
source |> Source.Ex |> Source.tooltip shared |> Maybe.decompose
|
||||
source |> Source.Ex |> Source.tooltip shared Tooltip.Right details |> Maybe.decompose
|
||||
|
||||
attrs =
|
||||
maybeId |> Maybe.map (\id -> [ HtmlA.id id ]) |> Maybe.withDefault []
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
module MassiveDecks.Pages.Lobby.Configure.Decks.Model exposing
|
||||
( Config
|
||||
, Deck
|
||||
, DeckOrError(..)
|
||||
, DeckOrError
|
||||
, Error
|
||||
, GetSummary
|
||||
, Id(..)
|
||||
, Model
|
||||
, Msg(..)
|
||||
, deckToDeckOrError
|
||||
, errorToDeckOrError
|
||||
)
|
||||
|
||||
import MassiveDecks.Card.Source.Model as Source exposing (Source)
|
||||
@@ -36,9 +38,20 @@ type alias Error =
|
||||
|
||||
{-| A deck or an error.
|
||||
-}
|
||||
type DeckOrError
|
||||
= D Deck
|
||||
| E Error
|
||||
type alias DeckOrError =
|
||||
{ source : Source.External
|
||||
, result : Result Source.LoadFailureReason (Maybe Source.Summary)
|
||||
}
|
||||
|
||||
|
||||
deckToDeckOrError : Deck -> DeckOrError
|
||||
deckToDeckOrError { source, summary } =
|
||||
{ source = source, result = Ok summary }
|
||||
|
||||
|
||||
errorToDeckOrError : Error -> DeckOrError
|
||||
errorToDeckOrError { source, reason } =
|
||||
{ source = source, result = Err reason }
|
||||
|
||||
|
||||
{-| The model for the editor for decks.
|
||||
@@ -60,3 +73,4 @@ type Msg
|
||||
= Update Source.External
|
||||
| Add Source.External
|
||||
| Remove Int
|
||||
| NoOp
|
||||
|
||||
@@ -5,9 +5,8 @@ module MassiveDecks.Pages.Lobby.Configure.Privacy exposing
|
||||
, update
|
||||
)
|
||||
|
||||
import FontAwesome.Icon as Icon
|
||||
import FontAwesome.Solid as Icon
|
||||
import Html.Events as HtmlE
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form.Message as Message
|
||||
import MassiveDecks.Pages.Lobby.Configure.Component as Component exposing (Component)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Component.Validator as Validator
|
||||
@@ -15,9 +14,9 @@ import MassiveDecks.Pages.Lobby.Configure.ConfigOption as ConfigOption exposing
|
||||
import MassiveDecks.Pages.Lobby.Configure.ConfigOption.Toggleable as Toggleable
|
||||
import MassiveDecks.Pages.Lobby.Configure.Privacy.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless.Attributes as WlA
|
||||
import MassiveDecks.Util.NeList as NeList exposing (NeList(..))
|
||||
import Material.IconButton as IconButton
|
||||
import Material.TextField as TextField
|
||||
|
||||
|
||||
init : Model
|
||||
@@ -105,28 +104,38 @@ password =
|
||||
|
||||
passwordOption : ConfigOption Model (Maybe String) Msg msg
|
||||
passwordOption =
|
||||
let
|
||||
visibility wrap shared model _ =
|
||||
let
|
||||
icon =
|
||||
if model.passwordVisible then
|
||||
Icon.eyeSlash
|
||||
|
||||
else
|
||||
Icon.eye
|
||||
|
||||
action =
|
||||
TogglePasswordVisibility |> wrap |> Just
|
||||
in
|
||||
Just (IconButton.view shared Strings.LobbyPassword (icon |> Icon.present |> NeList.just) action)
|
||||
in
|
||||
{ id = "game-password-option"
|
||||
, toggleable = Toggleable.maybe ""
|
||||
, primaryEditor =
|
||||
\model ->
|
||||
ConfigOption.TextField
|
||||
{ placeholder = Strings.LobbyPassword
|
||||
, inputType = WlA.Password |> Maybe.justIf (not model.passwordVisible)
|
||||
, inputType =
|
||||
if not model.passwordVisible then
|
||||
TextField.Password
|
||||
|
||||
else
|
||||
TextField.Text
|
||||
, toString = identity
|
||||
, fromString = Just >> Just
|
||||
, attrs = [ WlA.minLength 1 ]
|
||||
, attrs = []
|
||||
}
|
||||
, extraEditor =
|
||||
\wrap ->
|
||||
\model ->
|
||||
\value ->
|
||||
Just
|
||||
(Components.iconButton
|
||||
[ TogglePasswordVisibility |> wrap |> HtmlE.onClick
|
||||
, WlA.disabled |> Maybe.justIf (Maybe.isNothing value) |> Maybe.withDefault HtmlA.nothing
|
||||
]
|
||||
(Icon.eyeSlash |> Maybe.justIf model.passwordVisible |> Maybe.withDefault Icon.eye)
|
||||
)
|
||||
, extraEditor = visibility
|
||||
, set = ConfigOption.wrappedSetter PasswordChange
|
||||
, messages =
|
||||
\_ ->
|
||||
|
||||
@@ -13,7 +13,6 @@ import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules as HouseRules
|
||||
import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.Model as HouseRules
|
||||
import MassiveDecks.Pages.Lobby.Configure.Rules.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
init : Model
|
||||
@@ -81,13 +80,18 @@ gameRules =
|
||||
]
|
||||
|
||||
|
||||
handSizeLimits : ConfigOption.IntBounds
|
||||
handSizeLimits =
|
||||
ConfigOption.IntBounds 3 50
|
||||
|
||||
|
||||
handSize : Component Int Model Id Msg msg
|
||||
handSize =
|
||||
Component.value
|
||||
HandSize
|
||||
(ConfigOption.view handSizeOption)
|
||||
(always False)
|
||||
(Validator.between 3 50 HandSizeChange)
|
||||
(ConfigOption.toValidator handSizeLimits HandSizeChange)
|
||||
|
||||
|
||||
handSizeOption : ConfigOption Model Int Msg msg
|
||||
@@ -95,20 +99,25 @@ handSizeOption =
|
||||
{ id = "hand-size-option"
|
||||
, toggleable = Nothing
|
||||
, primaryEditor =
|
||||
\_ -> ConfigOption.intEditor Strings.HandSize [ 3 |> WlA.min, 50 |> WlA.max ]
|
||||
\_ -> ConfigOption.intEditor Strings.HandSize (ConfigOption.toMinMaxAttrs handSizeLimits)
|
||||
, extraEditor = ConfigOption.noExtraEditor
|
||||
, set = ConfigOption.wrappedSetter HandSizeChange
|
||||
, messages = \_ -> [ Message.info Strings.HandSizeDescription ]
|
||||
}
|
||||
|
||||
|
||||
scoreLimitBounds : ConfigOption.IntBounds
|
||||
scoreLimitBounds =
|
||||
ConfigOption.IntBounds 1 10000
|
||||
|
||||
|
||||
scoreLimit : Component (Maybe Int) Model Id Msg msg
|
||||
scoreLimit =
|
||||
Component.value
|
||||
ScoreLimit
|
||||
(ConfigOption.view scoreLimitOption)
|
||||
(always False)
|
||||
(Validator.optional (Validator.between 3 10000 (Just >> ScoreLimitChange)))
|
||||
(Validator.optional (ConfigOption.toValidator scoreLimitBounds (Just >> ScoreLimitChange)))
|
||||
|
||||
|
||||
scoreLimitOption : ConfigOption Model (Maybe Int) Msg msg
|
||||
@@ -116,7 +125,7 @@ scoreLimitOption =
|
||||
{ id = "score-limit-option"
|
||||
, toggleable = Toggleable.maybe 25
|
||||
, primaryEditor =
|
||||
\_ -> ConfigOption.maybeEditor (ConfigOption.intEditor Strings.ScoreLimit [ 3 |> WlA.min, 10000 |> WlA.max ])
|
||||
\_ -> ConfigOption.maybeEditor (ConfigOption.intEditor Strings.ScoreLimit (ConfigOption.toMinMaxAttrs scoreLimitBounds))
|
||||
, extraEditor = ConfigOption.noExtraEditor
|
||||
, set = ConfigOption.wrappedSetter ScoreLimitChange
|
||||
, messages = \_ -> [ Message.info Strings.ScoreLimitDescription ]
|
||||
|
||||
+5
-11
@@ -14,7 +14,6 @@ import MassiveDecks.Pages.Lobby.Configure.ConfigOption.Toggleable as Toggleable
|
||||
import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.ComedyWriter.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
init : Model
|
||||
@@ -115,14 +114,9 @@ enabledOption =
|
||||
}
|
||||
|
||||
|
||||
numberMin : Int
|
||||
numberMin =
|
||||
1
|
||||
|
||||
|
||||
numberMax : Int
|
||||
numberMax =
|
||||
99999
|
||||
numberBounds : ConfigOption.IntBounds
|
||||
numberBounds =
|
||||
ConfigOption.IntBounds 1 99999
|
||||
|
||||
|
||||
number : Component (Maybe Int) Model Id Msg msg
|
||||
@@ -131,7 +125,7 @@ number =
|
||||
Number
|
||||
(ConfigOption.view numberOption)
|
||||
Maybe.isNothing
|
||||
(Validator.optional (Validator.between numberMin numberMax (Just >> SetNumber)))
|
||||
(Validator.optional (ConfigOption.toValidator numberBounds (Just >> SetNumber)))
|
||||
|
||||
|
||||
numberOption : ConfigOption Model (Maybe Int) Msg msg
|
||||
@@ -140,7 +134,7 @@ numberOption =
|
||||
, toggleable = Nothing
|
||||
, primaryEditor =
|
||||
\_ ->
|
||||
ConfigOption.intEditor Strings.HouseRuleComedyWriterNumber [ WlA.min numberMin, WlA.max numberMax ]
|
||||
ConfigOption.intEditor Strings.HouseRuleComedyWriterNumber (ConfigOption.toMinMaxAttrs numberBounds)
|
||||
|> ConfigOption.maybeEditor
|
||||
, extraEditor = ConfigOption.noExtraEditor
|
||||
, set = ConfigOption.wrappedSetter SetNumber
|
||||
|
||||
@@ -14,7 +14,6 @@ import MassiveDecks.Pages.Lobby.Configure.ConfigOption.Toggleable as Toggleable
|
||||
import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.Rando.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
init : Model
|
||||
@@ -102,14 +101,9 @@ enabledOption =
|
||||
}
|
||||
|
||||
|
||||
numberMin : Int
|
||||
numberMin =
|
||||
1
|
||||
|
||||
|
||||
numberMax : Int
|
||||
numberMax =
|
||||
10
|
||||
numberBounds : ConfigOption.IntBounds
|
||||
numberBounds =
|
||||
ConfigOption.IntBounds 1 10
|
||||
|
||||
|
||||
number : Component (Maybe Int) Model Id Msg msg
|
||||
@@ -118,7 +112,7 @@ number =
|
||||
Number
|
||||
(ConfigOption.view numberOption)
|
||||
Maybe.isNothing
|
||||
(Validator.optional (Validator.between numberMin numberMax (Just >> SetNumber)))
|
||||
(Validator.optional (ConfigOption.toValidator numberBounds (Just >> SetNumber)))
|
||||
|
||||
|
||||
numberOption : ConfigOption Model (Maybe Int) Msg msg
|
||||
@@ -127,7 +121,7 @@ numberOption =
|
||||
, toggleable = Toggleable.none
|
||||
, primaryEditor =
|
||||
\_ ->
|
||||
ConfigOption.intEditor Strings.HouseRuleRandoCardrissianNumber [ WlA.min numberMin, WlA.max numberMax ]
|
||||
ConfigOption.intEditor Strings.HouseRuleRandoCardrissianNumber (ConfigOption.toMinMaxAttrs numberBounds)
|
||||
|> ConfigOption.maybeEditor
|
||||
, extraEditor = ConfigOption.noExtraEditor
|
||||
, set = ConfigOption.wrappedSetter SetNumber
|
||||
|
||||
@@ -14,7 +14,6 @@ import MassiveDecks.Pages.Lobby.Configure.ConfigOption.Toggleable as Toggleable
|
||||
import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.Reboot.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
init : Model
|
||||
@@ -100,14 +99,9 @@ enabledOption =
|
||||
}
|
||||
|
||||
|
||||
costMin : Int
|
||||
costMin =
|
||||
1
|
||||
|
||||
|
||||
costMax : Int
|
||||
costMax =
|
||||
50
|
||||
costBounds : ConfigOption.IntBounds
|
||||
costBounds =
|
||||
ConfigOption.IntBounds 1 50
|
||||
|
||||
|
||||
cost : Component (Maybe Int) Model Id Msg msg
|
||||
@@ -116,7 +110,7 @@ cost =
|
||||
Cost
|
||||
(ConfigOption.view costOption)
|
||||
Maybe.isNothing
|
||||
(Validator.optional (Validator.between costMin costMax (Just >> SetCost)))
|
||||
(Validator.optional (ConfigOption.toValidator costBounds (Just >> SetCost)))
|
||||
|
||||
|
||||
costOption : ConfigOption Model (Maybe Int) Msg msg
|
||||
@@ -125,7 +119,7 @@ costOption =
|
||||
, toggleable = Toggleable.none
|
||||
, primaryEditor =
|
||||
\_ ->
|
||||
ConfigOption.intEditor Strings.HouseRuleRebootCost [ WlA.min costMin, WlA.max costMax ]
|
||||
ConfigOption.intEditor Strings.HouseRuleRebootCost (ConfigOption.toMinMaxAttrs costBounds)
|
||||
|> ConfigOption.maybeEditor
|
||||
, extraEditor = ConfigOption.noExtraEditor
|
||||
, set = ConfigOption.wrappedSetter SetCost
|
||||
|
||||
@@ -14,7 +14,6 @@ import MassiveDecks.Pages.Lobby.Configure.ConfigOption as ConfigOption exposing
|
||||
import MassiveDecks.Pages.Lobby.Configure.TimeLimits.Model exposing (..)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
init : Model
|
||||
@@ -101,13 +100,18 @@ modeOption =
|
||||
}
|
||||
|
||||
|
||||
timeLimitBounds : ConfigOption.IntBounds
|
||||
timeLimitBounds =
|
||||
ConfigOption.IntBounds 0 900
|
||||
|
||||
|
||||
timeLimit : Round.Stage -> MdString -> Bool -> Component (Maybe Int) Model Id Msg msg
|
||||
timeLimit stage description toggleable =
|
||||
Component.value
|
||||
(TimeLimit stage)
|
||||
(ConfigOption.view (timeLimitOption stage description toggleable))
|
||||
(always False)
|
||||
(Validator.optional (Validator.between 0 900 (\v -> TimeLimitChange stage (Just v))))
|
||||
(Validator.optional (ConfigOption.toValidator timeLimitBounds (\v -> TimeLimitChange stage (Just v))))
|
||||
|
||||
|
||||
stageId : Round.Stage -> String
|
||||
@@ -135,7 +139,7 @@ timeLimitOption stage description toggleable =
|
||||
}
|
||||
|> Maybe.justIf toggleable
|
||||
, primaryEditor =
|
||||
\_ -> ConfigOption.maybeEditor (ConfigOption.intEditor (Strings.TimeLimit { stage = stage |> Round.stageDescription }) [ WlA.min 0, WlA.max 900 ])
|
||||
\_ -> ConfigOption.maybeEditor (ConfigOption.intEditor (Strings.TimeLimit { stage = stage |> Round.stageDescription }) (ConfigOption.toMinMaxAttrs timeLimitBounds))
|
||||
, extraEditor = ConfigOption.noExtraEditor
|
||||
, set = ConfigOption.wrappedSetter (TimeLimitChange stage)
|
||||
, messages = \_ -> [ Message.info description ]
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
module MassiveDecks.Pages.Lobby.Invite exposing (button, dialog, overlay)
|
||||
|
||||
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 Json.Decode
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Components.Form as Form
|
||||
import MassiveDecks.Components.Form.Message as Message
|
||||
import MassiveDecks.Model exposing (..)
|
||||
@@ -16,20 +15,19 @@ import MassiveDecks.Pages.Start.Route as Start
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Html.Events as HtmlE
|
||||
import MassiveDecks.Util.NeList as NeList exposing (NeList(..))
|
||||
import Material.Card as Card
|
||||
import Material.IconButton as IconButton
|
||||
import QRCode
|
||||
import Url exposing (Url)
|
||||
import Weightless as Wl
|
||||
|
||||
|
||||
{-| A button to show the dialog.
|
||||
-}
|
||||
button : (Msg -> msg) -> Shared -> Html msg
|
||||
button wrap shared =
|
||||
Components.iconButton
|
||||
[ Lang.title shared Strings.Invite
|
||||
, ToggleInviteDialog |> wrap |> HtmlE.onClick
|
||||
]
|
||||
Icon.bullhorn
|
||||
IconButton.view shared Strings.Invite (Icon.bullhorn |> Icon.present |> NeList.just) (ToggleInviteDialog |> wrap |> Just)
|
||||
|
||||
|
||||
{-| A dialog overlay that displays information on how to invite people to the game.
|
||||
@@ -45,15 +43,11 @@ dialog wrap shared gameCode password open =
|
||||
, HtmlA.classList [ ( "open", open ) ]
|
||||
, Lobby.ToggleInviteDialog |> wrap |> HtmlE.onClick
|
||||
]
|
||||
[ Components.iconButton
|
||||
[ HtmlA.class "close"
|
||||
, Lobby.ToggleInviteDialog
|
||||
|> wrap
|
||||
|> onClickNoPropegation
|
||||
, Strings.Close |> Lang.title shared
|
||||
]
|
||||
Icon.times
|
||||
, Wl.card [ onClickNoPropegation (wrap NoOp) ]
|
||||
[ IconButton.viewNoPropagation shared
|
||||
Strings.Close
|
||||
(Icon.times |> Icon.present |> NeList.just)
|
||||
(Lobby.ToggleInviteDialog |> wrap |> Just)
|
||||
, Card.view [ HtmlE.onClickNoPropagation (wrap NoOp) ]
|
||||
[ Strings.InviteExplanation { gameCode = GameCode.toString gameCode, password = password } |> Lang.html shared
|
||||
, Form.section shared
|
||||
"invite-link"
|
||||
@@ -65,7 +59,10 @@ dialog wrap shared gameCode password open =
|
||||
, HtmlA.id "invite-link-field"
|
||||
]
|
||||
[]
|
||||
, Components.iconButton [ "invite-link-field" |> Copy |> wrap |> HtmlE.onClick ] Icon.copy
|
||||
, IconButton.view shared
|
||||
Strings.Copy
|
||||
(Icon.copy |> Icon.present |> NeList.just)
|
||||
("invite-link-field" |> Copy |> wrap |> Just)
|
||||
]
|
||||
)
|
||||
[ Message.info Strings.InviteLinkHelp ]
|
||||
@@ -92,11 +89,6 @@ overlay shared gameCode =
|
||||
{- Private -}
|
||||
|
||||
|
||||
onClickNoPropegation : msg -> Html.Attribute msg
|
||||
onClickNoPropegation msg =
|
||||
HtmlE.stopPropagationOn "click" (Json.Decode.succeed ( msg, True ))
|
||||
|
||||
|
||||
qr : String -> Html msg
|
||||
qr lobbyUrl =
|
||||
lobbyUrl
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module MassiveDecks.Pages.Lobby.Messages exposing (Msg(..))
|
||||
|
||||
import MassiveDecks.Animated as Animated
|
||||
import MassiveDecks.Components.Menu.Model as Menu
|
||||
import MassiveDecks.Game.Messages as Game
|
||||
import MassiveDecks.Game.Time as Time
|
||||
import MassiveDecks.Models.MdError exposing (MdError)
|
||||
@@ -29,5 +30,7 @@ type Msg
|
||||
| TryCast Auth
|
||||
| Copy String
|
||||
| ChangeSection (Maybe Section)
|
||||
| SetGameMenuState Menu.State
|
||||
| SetUserMenuState User.Id Menu.State
|
||||
| EndGame
|
||||
| NoOp
|
||||
|
||||
@@ -13,10 +13,11 @@ module MassiveDecks.Pages.Lobby.Model exposing
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import MassiveDecks.Animated exposing (Animated)
|
||||
import MassiveDecks.Components.Menu.Model as Menu
|
||||
import MassiveDecks.Error.Model exposing (Error)
|
||||
import MassiveDecks.Game.Model as Game exposing (Game)
|
||||
import MassiveDecks.Game.Time as Time
|
||||
import MassiveDecks.Models.MdError as MdError exposing (GameStateError, MdError)
|
||||
import MassiveDecks.Models.MdError exposing (GameStateError, MdError)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Model as Configure
|
||||
import MassiveDecks.Pages.Lobby.GameCode exposing (GameCode)
|
||||
import MassiveDecks.Pages.Lobby.Route exposing (..)
|
||||
@@ -29,7 +30,7 @@ to somewhere else with some payload.
|
||||
-}
|
||||
type Change
|
||||
= Stay Model
|
||||
| AuthError GameCode MdError.AuthenticationError
|
||||
| JoinError GameCode MdError
|
||||
| LeftGame GameCode User.LeaveReason
|
||||
| ConfigError Error
|
||||
|
||||
@@ -46,6 +47,8 @@ type alias Model =
|
||||
, inviteDialogOpen : Bool
|
||||
, timeAnchor : Maybe Time.Anchor
|
||||
, spectate : Spectate.Model
|
||||
, gameMenu : Menu.State
|
||||
, userMenu : Maybe User.Id
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ 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 Component
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Lobby.Actions as Actions
|
||||
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
|
||||
@@ -27,6 +25,8 @@ import MassiveDecks.Pages.Start.Route as Start
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.User as User
|
||||
import MassiveDecks.Util.NeList as NeList
|
||||
import Material.IconButton as IconButton
|
||||
import QRCode
|
||||
import Url exposing (Url)
|
||||
|
||||
@@ -96,21 +96,18 @@ viewSettings wrap changePage shared lobby =
|
||||
( wrap BecomePlayer, Strings.BecomePlayerDescription )
|
||||
in
|
||||
[ Html.div [ HtmlA.id "spectate-actions" ]
|
||||
[ Component.iconButton
|
||||
[ backAction |> HtmlE.onClick
|
||||
, backDescription |> Lang.title shared
|
||||
]
|
||||
Icon.arrowLeft
|
||||
, Component.iconButton
|
||||
[ { route | section = Just Lobby.Configure } |> Route.Lobby |> changePage |> HtmlE.onClick
|
||||
, Strings.ViewConfgiurationDescription |> Lang.title shared
|
||||
]
|
||||
Icon.cog
|
||||
, Component.iconButton
|
||||
[ ToggleAdvert |> wrap |> HtmlE.onClick
|
||||
, Strings.ToggleAdvertDescription |> Lang.title shared
|
||||
]
|
||||
advertiseIcon
|
||||
[ IconButton.view shared
|
||||
backDescription
|
||||
(Icon.arrowLeft |> Icon.present |> NeList.just)
|
||||
(Just backAction)
|
||||
, IconButton.view shared
|
||||
Strings.ViewConfgiurationDescription
|
||||
(Icon.cog |> Icon.present |> NeList.just)
|
||||
({ route | section = Just Lobby.Configure } |> Route.Lobby |> changePage |> Just)
|
||||
, IconButton.view shared
|
||||
Strings.ToggleAdvertDescription
|
||||
(advertiseIcon |> Icon.present |> NeList.just)
|
||||
(ToggleAdvert |> wrap |> Just)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module MassiveDecks.Pages.Start exposing
|
||||
( changeRoute
|
||||
, init
|
||||
, initWithAuthError
|
||||
, initWithError
|
||||
, route
|
||||
, update
|
||||
, view
|
||||
@@ -21,7 +21,6 @@ import MassiveDecks.Card.Model as Card
|
||||
import MassiveDecks.Card.Parts as Parts
|
||||
import MassiveDecks.Card.Response as Response
|
||||
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 exposing (Message)
|
||||
import MassiveDecks.Error as Error
|
||||
@@ -48,10 +47,14 @@ import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import MassiveDecks.Util.NeList as NeList exposing (NeList(..))
|
||||
import MassiveDecks.Version as Version
|
||||
import Material.Button as Button
|
||||
import Material.Card as Card
|
||||
import Material.IconButton as IconButton
|
||||
import Material.Tabs as Tabs
|
||||
import Material.TextField as TextField
|
||||
import Svg.Attributes as SvgA
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
changeRoute : Route -> Model -> ( Model, Cmd Global.Msg )
|
||||
@@ -93,8 +96,8 @@ init shared r =
|
||||
)
|
||||
|
||||
|
||||
initWithAuthError : Shared -> GameCode -> MdError.AuthenticationError -> ( Model, Cmd Global.Msg )
|
||||
initWithAuthError shared gameCode authenticationError =
|
||||
initWithError : Shared -> GameCode -> MdError -> ( Model, Cmd Global.Msg )
|
||||
initWithError shared gameCode error =
|
||||
let
|
||||
( model, cmd ) =
|
||||
init shared { section = Join (Just gameCode) }
|
||||
@@ -102,7 +105,7 @@ initWithAuthError shared gameCode authenticationError =
|
||||
jlr =
|
||||
model.joinLobbyRequest
|
||||
in
|
||||
( { model | joinLobbyRequest = { jlr | error = authenticationError |> MdError.Authentication |> Just } }, cmd )
|
||||
( { model | joinLobbyRequest = { jlr | error = error |> Just } }, cmd )
|
||||
|
||||
|
||||
route : Model -> Route
|
||||
@@ -191,6 +194,10 @@ update shared msg model =
|
||||
|
||||
view : Shared -> Model -> List (Html Global.Msg)
|
||||
view shared model =
|
||||
let
|
||||
r =
|
||||
model.route
|
||||
in
|
||||
[ Html.div [ HtmlA.class "page start" ]
|
||||
[ overlay shared model.overlay
|
||||
, Html.header [ HtmlA.class "title-card" ]
|
||||
@@ -201,13 +208,14 @@ view shared model =
|
||||
]
|
||||
]
|
||||
]
|
||||
, Wl.card []
|
||||
[ Wl.tabGroup [ WlA.align WlA.Center ]
|
||||
[ tab shared New model Icon.plus Strings.NewGame
|
||||
, tab shared (Join model.gameCode) model Icon.signInAlt Strings.JoinPrivateGame
|
||||
, tab shared Find model Icon.search Strings.FindPublicGame
|
||||
, tab shared About model Icon.questionCircle Strings.AboutTheGame
|
||||
]
|
||||
, Card.view []
|
||||
[ Tabs.view shared
|
||||
{ selected = r.section
|
||||
, change = \s -> Route.Start { r | section = s } |> Global.ChangePage
|
||||
, ids = NeList New [ Join model.gameCode, Find, About ]
|
||||
, tab = tabFor
|
||||
, equals = sectionsMatch
|
||||
}
|
||||
, sectionContent shared model
|
||||
]
|
||||
, Html.footer [ HtmlA.class "version-info" ]
|
||||
@@ -240,18 +248,32 @@ view shared model =
|
||||
{- Private -}
|
||||
|
||||
|
||||
tabFor : Section -> Tabs.TabModel
|
||||
tabFor section =
|
||||
case section of
|
||||
New ->
|
||||
Tabs.TabModel Strings.NewGame (Just Icon.plus)
|
||||
|
||||
Join _ ->
|
||||
Tabs.TabModel Strings.JoinPrivateGame (Just Icon.signInAlt)
|
||||
|
||||
Find ->
|
||||
Tabs.TabModel Strings.FindPublicGame (Just Icon.search)
|
||||
|
||||
About ->
|
||||
Tabs.TabModel Strings.AboutTheGame (Just Icon.questionCircle)
|
||||
|
||||
|
||||
overlay : Shared -> Maybe MdString -> Html Global.Msg
|
||||
overlay shared content =
|
||||
case content of
|
||||
Just text ->
|
||||
Html.div [ HtmlA.id "overlay" ]
|
||||
[ Components.iconButton
|
||||
[ HtmlA.class "close"
|
||||
, HideOverlay |> Global.StartMsg |> HtmlE.onClick
|
||||
, Strings.Close |> Lang.title shared
|
||||
]
|
||||
Icon.times
|
||||
, Wl.card []
|
||||
[ IconButton.view shared
|
||||
Strings.Close
|
||||
(Icon.times |> Icon.present |> NeList.just)
|
||||
(HideOverlay |> Global.StartMsg |> Just)
|
||||
, Card.view []
|
||||
[ text |> Lang.html shared
|
||||
]
|
||||
]
|
||||
@@ -306,24 +328,6 @@ gameCodeForRoute r =
|
||||
Nothing
|
||||
|
||||
|
||||
tab : Shared -> Section -> Model -> Icon -> MdString -> Html Global.Msg
|
||||
tab shared targetSection model icon title =
|
||||
let
|
||||
r =
|
||||
model.route
|
||||
in
|
||||
Wl.tab
|
||||
(List.concat
|
||||
[ [ Route.Start { r | section = targetSection } |> Global.ChangePage |> HtmlE.onClick
|
||||
]
|
||||
, [ WlA.checked ] |> Maybe.justIf (sectionsMatch r.section targetSection) |> Maybe.withDefault []
|
||||
]
|
||||
)
|
||||
[ Icon.viewIcon icon
|
||||
, title |> Lang.html shared
|
||||
]
|
||||
|
||||
|
||||
sectionsMatch : Section -> Section -> Bool
|
||||
sectionsMatch first second =
|
||||
case first of
|
||||
@@ -381,17 +385,18 @@ newContent shared model =
|
||||
|> Maybe.withDefault Html.nothing
|
||||
in
|
||||
Html.div [ HtmlA.class "new-game start-tab" ]
|
||||
[ error
|
||||
, Html.div [ HtmlA.class "restrict" ]
|
||||
(List.concat
|
||||
[ nameField shared model Nothing
|
||||
, [ Wl.button
|
||||
[ buttonAttr
|
||||
]
|
||||
[ buttonIcon, Strings.PlayGame |> Lang.html shared ]
|
||||
]
|
||||
]
|
||||
)
|
||||
[ Html.div [ HtmlA.class "tab-content" ]
|
||||
[ Html.h2 [] [ Strings.NewGame |> Lang.html shared ]
|
||||
, Html.p [] [ Html.text "Start a new game of Massive Decks." ]
|
||||
, error
|
||||
, nameField shared model Nothing
|
||||
, Button.view shared
|
||||
Button.Raised
|
||||
Strings.PlayGame
|
||||
Strings.PlayGame
|
||||
buttonIcon
|
||||
[ buttonAttr ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@@ -437,31 +442,35 @@ joinContent shared model =
|
||||
|> Maybe.withDefault []
|
||||
in
|
||||
Html.div [ HtmlA.class "join-game start-tab" ]
|
||||
[ error
|
||||
, Html.div [ HtmlA.class "restrict" ]
|
||||
[ Html.div [ HtmlA.class "tab-content" ]
|
||||
(List.concat
|
||||
[ rejoinSection shared model
|
||||
, nameField shared model nameError
|
||||
, [ Form.section shared
|
||||
[ [ Html.h2 [] [ Strings.JoinPrivateGame |> Lang.html shared ]
|
||||
, Html.p [] [ Html.text "Join a game someone invited you to." ]
|
||||
, error
|
||||
]
|
||||
, rejoinSection shared model
|
||||
, [ nameField shared model Nothing
|
||||
, Form.section shared
|
||||
"game-code-input"
|
||||
(Wl.textField
|
||||
(TextField.view shared
|
||||
Strings.GameCodeTerm
|
||||
TextField.Text
|
||||
(model.gameCode |> Maybe.map GameCode.toString |> Maybe.withDefault "")
|
||||
[ HtmlA.class "game-code-input"
|
||||
, GameCodeChanged >> Global.StartMsg |> HtmlE.onInput
|
||||
, WlA.value (model.gameCode |> Maybe.map GameCode.toString |> Maybe.withDefault "")
|
||||
, WlA.outlined
|
||||
, Strings.GameCodeTerm |> Lang.label shared
|
||||
]
|
||||
[]
|
||||
)
|
||||
[ Message.info Strings.GameCodeHowToAcquire
|
||||
, gameError |> Maybe.map Message.mdError |> Maybe.withDefault Message.none
|
||||
]
|
||||
]
|
||||
, maybePasswordField
|
||||
, [ Wl.button
|
||||
[ buttonAttr
|
||||
]
|
||||
[ buttonIcon, Strings.PlayGame |> Lang.html shared ]
|
||||
, [ Button.view shared
|
||||
Button.Raised
|
||||
Strings.PlayGame
|
||||
Strings.PlayGame
|
||||
buttonIcon
|
||||
[ buttonAttr ]
|
||||
]
|
||||
]
|
||||
)
|
||||
@@ -472,13 +481,11 @@ 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
|
||||
]
|
||||
[]
|
||||
(TextField.view shared
|
||||
Strings.LobbyPassword
|
||||
TextField.Password
|
||||
password
|
||||
[ PasswordChanged >> Global.StartMsg |> HtmlE.onInput ]
|
||||
)
|
||||
[ Message.info Strings.LobbyRequiresPassword
|
||||
, error |> Maybe.map (MdError.describe >> Message.error) |> Maybe.withDefault Message.none
|
||||
@@ -518,22 +525,17 @@ rejoinLobby shared result =
|
||||
Nothing
|
||||
|
||||
|
||||
nameField : Shared -> Model -> Maybe MdError -> List (Html Global.Msg)
|
||||
nameField : Shared -> Model -> Maybe MdError -> Html Global.Msg
|
||||
nameField shared model error =
|
||||
[ Form.section shared
|
||||
Form.section shared
|
||||
"name-input"
|
||||
(Wl.textField
|
||||
[ NameChanged
|
||||
>> Global.StartMsg
|
||||
|> HtmlE.onInput
|
||||
, WlA.value model.name
|
||||
, Strings.NameLabel |> Lang.label shared
|
||||
, WlA.outlined
|
||||
]
|
||||
[]
|
||||
(TextField.view shared
|
||||
Strings.NameLabel
|
||||
TextField.Text
|
||||
model.name
|
||||
[ NameChanged >> Global.StartMsg |> HtmlE.onInput ]
|
||||
)
|
||||
[ error |> Maybe.map (MdError.describe >> Message.error) |> Maybe.withDefault Message.none ]
|
||||
]
|
||||
|
||||
|
||||
aboutContent : Shared -> Html Global.Msg
|
||||
|
||||
@@ -9,7 +9,6 @@ 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 Html.Keyed as HtmlK
|
||||
import Http
|
||||
import MassiveDecks.Messages as Global
|
||||
@@ -30,8 +29,7 @@ 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
|
||||
import Material.ListView as ListView
|
||||
|
||||
|
||||
{-| Set up the lobby browser.
|
||||
@@ -163,10 +161,15 @@ stateGroup shared ( state, lobbies ) =
|
||||
lobby : Shared -> Summary -> ( String, Html Global.Msg )
|
||||
lobby shared data =
|
||||
( data.gameCode |> GameCode.toString
|
||||
, Wl.listItem
|
||||
[ HtmlE.onClick (Route.Start { section = Start.Join (Just data.gameCode) } |> Global.ChangePage)
|
||||
, WlA.clickable
|
||||
]
|
||||
, ListView.viewItem
|
||||
(ListView.Enabled (Route.Start { section = Start.Join (Just data.gameCode) } |> Global.ChangePage))
|
||||
Nothing
|
||||
([ Html.span [ HtmlA.class "lobby-game-code" ]
|
||||
[ Strings.GameCode { code = GameCode.toString data.gameCode } |> Lang.html shared ]
|
||||
]
|
||||
|> Just
|
||||
)
|
||||
([ Icon.viewStyled [ HtmlA.title "Join Game" ] Icon.signInAlt ] |> Just)
|
||||
[ Html.span [ HtmlA.class "lobby-name", Strings.LobbyRequiresPassword |> Lang.title shared ]
|
||||
[ Html.text data.name
|
||||
, Html.text " "
|
||||
@@ -175,12 +178,5 @@ lobby shared data =
|
||||
|> 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
|
||||
]
|
||||
Icon.signInAlt
|
||||
]
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ import MassiveDecks.Pages.Unknown.Model exposing (..)
|
||||
import MassiveDecks.Pages.Unknown.Route exposing (..)
|
||||
import MassiveDecks.Strings exposing (MdString(..))
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import Weightless
|
||||
import Material.Card as Card
|
||||
|
||||
|
||||
changeRoute : Route -> Model -> ( Model, Cmd Msg )
|
||||
@@ -38,7 +38,7 @@ route model =
|
||||
view : Shared -> Model -> List (Html Msg)
|
||||
view shared _ =
|
||||
[ Html.div [ HtmlA.class "page unknown-page" ]
|
||||
[ Weightless.card []
|
||||
[ Card.view []
|
||||
[ Html.h1 [] [ Icon.viewIcon Icon.exclamationCircle, Lang.html shared UnknownPageTitle ]
|
||||
, Html.p []
|
||||
[ Html.a [ Route.url (Route.Start { section = Start.New }) |> HtmlA.href ]
|
||||
|
||||
@@ -9,6 +9,7 @@ port module MassiveDecks.Ports exposing
|
||||
, serverSend
|
||||
, speechCommands
|
||||
, speechVoices
|
||||
, startConfetti
|
||||
, storeSettings
|
||||
, tryCast
|
||||
)
|
||||
@@ -50,3 +51,6 @@ port remoteControl : (Json.Value -> msg) -> Sub msg
|
||||
|
||||
|
||||
port languageChanged : String -> Cmd msg
|
||||
|
||||
|
||||
port startConfetti : String -> Cmd msg
|
||||
|
||||
@@ -69,7 +69,7 @@ joinLobby msg gameCode registration =
|
||||
|
||||
{-| Check if previously joined lobbies are still going.
|
||||
-}
|
||||
checkAlive : (Request.Response Never (Dict Lobby.Token Bool) -> msg) -> List Lobby.Token -> Request msg
|
||||
checkAlive : (Request.Response Never (List Lobby.Token) -> msg) -> List Lobby.Token -> Request msg
|
||||
checkAlive msg tokens =
|
||||
{ method = "POST"
|
||||
, headers = []
|
||||
|
||||
@@ -13,7 +13,6 @@ 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.Error as Error
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Route exposing (Route)
|
||||
@@ -21,10 +20,10 @@ import MassiveDecks.Requests.HttpData.Messages exposing (..)
|
||||
import MassiveDecks.Requests.HttpData.Model exposing (..)
|
||||
import MassiveDecks.Requests.Request as Request exposing (Request)
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import MassiveDecks.Util.NeList exposing (NeList(..))
|
||||
import Material.IconButton as IconButton
|
||||
import Time
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
{-| Set up the empty HttpData and send a request to load the data.
|
||||
@@ -111,31 +110,16 @@ view shared route emptyContent viewResult model =
|
||||
{-| Show a refresh button for the data.
|
||||
-}
|
||||
refreshButton : Shared -> HttpData error result -> Html (Msg error result)
|
||||
refreshButton shared model =
|
||||
refreshButton shared { loading, data } =
|
||||
let
|
||||
title =
|
||||
[ Strings.Refresh |> Lang.title shared ]
|
||||
|
||||
onClick =
|
||||
if not model.loading then
|
||||
[ HtmlE.onClick Pull ]
|
||||
applyStyle =
|
||||
if loading then
|
||||
Icon.styled [ Icon.spin ]
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
style =
|
||||
[ WlA.flat
|
||||
, WlA.fab
|
||||
, WlA.inverted
|
||||
]
|
||||
|
||||
spin =
|
||||
if model.loading then
|
||||
[ Icon.spin ]
|
||||
|
||||
else
|
||||
[]
|
||||
identity
|
||||
in
|
||||
Wl.button
|
||||
(List.concat [ style, title, onClick ])
|
||||
[ Icon.viewStyled spin Icon.sync ]
|
||||
IconButton.view shared
|
||||
Strings.Refresh
|
||||
(NeList (Icon.sync |> Icon.present |> applyStyle) [])
|
||||
(Pull |> Maybe.justIf (not loading))
|
||||
|
||||
@@ -15,13 +15,12 @@ import Dict.Extra as Dict
|
||||
import FontAwesome.Attributes as Icon
|
||||
import FontAwesome.Icon as Icon
|
||||
import FontAwesome.Solid as Icon
|
||||
import Html exposing (Html)
|
||||
import Html as Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Http
|
||||
import MassiveDecks.Card.Source as Source
|
||||
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.Icon as Icon
|
||||
@@ -41,11 +40,12 @@ import MassiveDecks.Speech as Speech
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Strings.Languages.Model as Lang exposing (Language)
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Weightless as Wl
|
||||
import Weightless.Attributes as WlA
|
||||
import Weightless.Slider as Slider
|
||||
import MassiveDecks.Util.NeList exposing (NeList(..))
|
||||
import MassiveDecks.Util.Order as Order
|
||||
import Material.IconButton as IconButton
|
||||
import Material.Select as Select
|
||||
import Material.Slider as Slider
|
||||
import Material.Switch as Switch
|
||||
|
||||
|
||||
init : (Msg -> msg) -> Settings -> ( Model, Cmd msg )
|
||||
@@ -115,7 +115,7 @@ update shared msg =
|
||||
let
|
||||
newTokens =
|
||||
model.settings.tokens
|
||||
|> Dict.filter (\_ -> \t -> Dict.get t tokenValidity |> Maybe.withDefault True)
|
||||
|> Dict.filter (\_ -> \t -> List.member t tokenValidity)
|
||||
in
|
||||
changeSettings (\s -> { s | tokens = newTokens }) model
|
||||
|
||||
@@ -129,9 +129,9 @@ update shared msg =
|
||||
ChangeSpeech voice ->
|
||||
let
|
||||
newSpeechSettings =
|
||||
model.settings.speech |> Speech.selectVoice voice
|
||||
voice |> Maybe.map (\v -> model.settings.speech |> Speech.selectVoice v)
|
||||
in
|
||||
changeSettings (\s -> { s | speech = newSpeechSettings }) model
|
||||
changeSettings (\s -> { s | speech = newSpeechSettings |> Maybe.withDefault s.speech }) model
|
||||
|
||||
ToggleAutoAdvance enabled ->
|
||||
changeSettings (\s -> { s | autoAdvance = Just enabled }) model
|
||||
@@ -167,15 +167,10 @@ view wrap shared =
|
||||
Icon.cog
|
||||
|
||||
button =
|
||||
Components.iconButtonStyled
|
||||
[ HtmlA.id "settings-button"
|
||||
, ToggleOpen |> wrap |> HtmlE.onClick
|
||||
, Strings.SettingsTitle |> Lang.title shared
|
||||
]
|
||||
( [ Icon.lg ], icon )
|
||||
IconButton.view shared Strings.SettingsTitle (NeList (icon |> Icon.present |> Icon.styled [ Icon.lg ]) []) (ToggleOpen |> wrap |> Just)
|
||||
|
||||
panel =
|
||||
Wl.card [ HtmlA.classList [ ( "settings-panel", True ), ( "open", model.open ) ] ]
|
||||
Html.div [ HtmlA.classList [ ( "settings-panel", True ), ( "mdc-card", True ), ( "open", model.open ) ] ]
|
||||
[ Html.h3 [] [ Strings.SettingsTitle |> Lang.html shared ]
|
||||
, Html.div [ HtmlA.class "body" ]
|
||||
(List.intersperse (Html.hr [] [])
|
||||
@@ -296,44 +291,30 @@ cardSize wrap shared =
|
||||
"card-size"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
[ Wl.slider
|
||||
[ HtmlA.class "primary"
|
||||
, Slider.step 1
|
||||
, Slider.min 1
|
||||
, Slider.max 3
|
||||
, Strings.CardSizeSetting |> Lang.string shared |> WlA.label
|
||||
, WlA.outlined
|
||||
, Slider.thumbLabel True
|
||||
, String.toInt
|
||||
>> Maybe.andThen cardSizeFromValue
|
||||
>> Maybe.withDefault Full
|
||||
>> ChangeCardSize
|
||||
>> wrap
|
||||
|> HtmlE.onInput
|
||||
, settings.cardSize |> cardSizeToValue |> String.fromInt |> WlA.value
|
||||
]
|
||||
[ Icon.viewStyled [ Slider.slot Slider.Before ] Icon.searchMinus
|
||||
, [ settings.cardSize |> cardSizeThumb ] |> Html.span [ Slider.slot Slider.ThumbLabel ]
|
||||
, Icon.viewStyled [ Slider.slot Slider.After ] Icon.searchPlus
|
||||
[ Html.div
|
||||
[ HtmlA.class "card-size-slider" ]
|
||||
[ Icon.viewStyled [] Icon.minimalCardSize
|
||||
, Slider.view
|
||||
[ HtmlA.class "primary"
|
||||
, Slider.step 1
|
||||
, Slider.min 1
|
||||
, Slider.max 3
|
||||
, Slider.pin
|
||||
, Slider.markers
|
||||
, cardSizeFromValue
|
||||
>> Maybe.withDefault Full
|
||||
>> ChangeCardSize
|
||||
>> wrap
|
||||
|> Slider.onChange
|
||||
, settings.cardSize |> cardSizeToValue |> Slider.value
|
||||
]
|
||||
, Icon.viewStyled [] Icon.callCard
|
||||
]
|
||||
]
|
||||
)
|
||||
[ Message.info Strings.CardSizeExplanation ]
|
||||
|
||||
|
||||
cardSizeThumb : CardSize -> Html msg
|
||||
cardSizeThumb size =
|
||||
case size of
|
||||
Minimal ->
|
||||
Icon.viewIcon Icon.minimalCardSize
|
||||
|
||||
Square ->
|
||||
Icon.viewIcon Icon.squareCardSize
|
||||
|
||||
Full ->
|
||||
Icon.viewIcon Icon.callCard
|
||||
|
||||
|
||||
autoAdvanceRound : (Msg -> msg) -> Shared -> Html msg
|
||||
autoAdvanceRound wrap shared =
|
||||
let
|
||||
@@ -344,11 +325,12 @@ autoAdvanceRound wrap shared =
|
||||
"auto-advance"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
[ Wl.switch
|
||||
[ Switch.view
|
||||
[ HtmlE.onCheck (ToggleAutoAdvance >> wrap)
|
||||
, HtmlA.checked (settings.autoAdvance |> Maybe.withDefault False)
|
||||
, HtmlA.id "auto-advance-enable"
|
||||
]
|
||||
, Html.label []
|
||||
, Html.label [ HtmlA.for "auto-advance-enable" ]
|
||||
[ Icon.viewIcon Icon.commentDots
|
||||
, Html.text " "
|
||||
, Strings.AutoAdvanceSetting |> Lang.html shared
|
||||
@@ -379,6 +361,12 @@ speechVoiceSelector wrap shared =
|
||||
|
||||
else
|
||||
Message.none
|
||||
|
||||
currentLanguage =
|
||||
shared |> Lang.currentLanguage
|
||||
|
||||
voiceSortOrder =
|
||||
combinedOrder (NeList defaultFirst [ languageMatchFirst currentLanguage ])
|
||||
in
|
||||
Html.div []
|
||||
[ Form.section shared
|
||||
@@ -386,24 +374,31 @@ speechVoiceSelector wrap shared =
|
||||
(Html.div []
|
||||
[ Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
[ Wl.switch
|
||||
[ Switch.view
|
||||
[ HtmlE.onCheck (ToggleSpeech >> wrap)
|
||||
, HtmlA.disabled isDisabled
|
||||
, HtmlA.checked enabled
|
||||
, HtmlA.id "speech-enable"
|
||||
]
|
||||
, Html.label []
|
||||
, Html.label [ HtmlA.for "speech-enable" ]
|
||||
[ Icon.viewIcon Icon.commentDots
|
||||
, Html.text " "
|
||||
, Strings.SpeechSetting |> Lang.html shared
|
||||
]
|
||||
]
|
||||
, Wl.select
|
||||
[ HtmlE.onInput (ChangeSpeech >> wrap)
|
||||
, Strings.VoiceSetting |> Lang.string shared |> WlA.label
|
||||
, WlA.outlined
|
||||
, HtmlA.disabled (not enabled || isDisabled)
|
||||
, Html.div [ HtmlA.class "children" ]
|
||||
[ Select.view shared
|
||||
{ label = Strings.VoiceSetting
|
||||
, idToString = identity
|
||||
, idFromString = Just
|
||||
, selected = selectedVoice
|
||||
, wrap = ChangeSpeech >> wrap
|
||||
}
|
||||
[ HtmlA.disabled (not enabled || isDisabled)
|
||||
, HtmlA.class "secondary"
|
||||
]
|
||||
(voices |> List.sortWith voiceSortOrder |> List.map speechVoiceOption)
|
||||
]
|
||||
(voices |> List.sortWith defaultFirst |> List.map (speechVoiceOption selectedVoice))
|
||||
]
|
||||
)
|
||||
[ Message.info Strings.SpeechExplanation
|
||||
@@ -414,7 +409,7 @@ speechVoiceSelector wrap shared =
|
||||
|
||||
defaultFirst : Speech.Voice -> Speech.Voice -> Order
|
||||
defaultFirst a b =
|
||||
if a.default && b.default then
|
||||
if a.default == b.default then
|
||||
EQ
|
||||
|
||||
else if a.default then
|
||||
@@ -424,19 +419,38 @@ defaultFirst a b =
|
||||
GT
|
||||
|
||||
|
||||
speechVoiceOption : Maybe String -> Speech.Voice -> Html msg
|
||||
speechVoiceOption selectedVoice voice =
|
||||
let
|
||||
selected =
|
||||
if selectedVoice == Just voice.name then
|
||||
[ WlA.selected ]
|
||||
languageMatchFirst : Language -> Speech.Voice -> Speech.Voice -> Order
|
||||
languageMatchFirst lang =
|
||||
Lang.sortClosestFirst lang |> Order.map (.lang >> Lang.fromCode)
|
||||
|
||||
else
|
||||
[]
|
||||
in
|
||||
Html.option
|
||||
(HtmlA.value voice.name :: selected)
|
||||
[ Html.text (voice.name ++ " (" ++ voice.lang ++ ")") ]
|
||||
|
||||
combinedOrder : NeList (a -> a -> Order) -> a -> a -> Order
|
||||
combinedOrder (NeList first rest) a b =
|
||||
case rest of
|
||||
[] ->
|
||||
first a b
|
||||
|
||||
next :: remaining ->
|
||||
let
|
||||
result =
|
||||
first a b
|
||||
in
|
||||
case result of
|
||||
EQ ->
|
||||
combinedOrder (NeList next remaining) a b
|
||||
|
||||
_ ->
|
||||
result
|
||||
|
||||
|
||||
speechVoiceOption : Speech.Voice -> Select.ItemModel String msg
|
||||
speechVoiceOption voice =
|
||||
{ id = voice.name
|
||||
, icon = Nothing
|
||||
, primary = [ voice.name |> Html.text ]
|
||||
, secondary = Nothing
|
||||
, meta = Nothing
|
||||
}
|
||||
|
||||
|
||||
notificationsSwitch : (Msg -> msg) -> Shared -> Html msg
|
||||
@@ -462,12 +476,13 @@ notificationsSwitch wrap shared =
|
||||
(Html.div []
|
||||
[ Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
[ Wl.switch
|
||||
[ Switch.view
|
||||
[ HtmlE.onCheck (ToggleNotifications >> wrap)
|
||||
, HtmlA.disabled unsupported
|
||||
, HtmlA.checked enabled
|
||||
, HtmlA.id "notifications-enable"
|
||||
]
|
||||
, Html.label []
|
||||
, Html.label [ HtmlA.for "notifications-enable" ]
|
||||
[ Icon.viewIcon Icon.bell
|
||||
, Html.text " "
|
||||
, Strings.NotificationsSetting |> Lang.html shared
|
||||
@@ -479,12 +494,13 @@ notificationsSwitch wrap shared =
|
||||
"only-when-hidden"
|
||||
(Html.div
|
||||
[ HtmlA.class "multipart" ]
|
||||
[ Wl.switch
|
||||
[ Switch.view
|
||||
[ HtmlE.onCheck (ToggleOnlyWhenHidden >> wrap)
|
||||
, HtmlA.disabled visibilityDisabled
|
||||
, HtmlA.checked settings.requireNotVisible
|
||||
, HtmlA.id "only-when-hidden-toggle"
|
||||
]
|
||||
, Html.label []
|
||||
, Html.label [ HtmlA.for "only-when-hidden-toggle" ]
|
||||
[ Icon.viewIcon Icon.eyeSlash
|
||||
, Html.text " "
|
||||
, Strings.NotificationOnlyWhenHiddenSetting |> Lang.html shared
|
||||
@@ -520,41 +536,42 @@ languageSelector wrap shared =
|
||||
Form.section
|
||||
shared
|
||||
"language"
|
||||
(Wl.select
|
||||
[ HtmlE.onInput (Lang.fromCode >> ChangeLang >> wrap)
|
||||
, Strings.LanguageSetting |> Lang.string shared |> WlA.label
|
||||
, WlA.outlined
|
||||
]
|
||||
(Lang.languages |> List.map (languageOption selected))
|
||||
(Select.view shared
|
||||
{ label = Strings.LanguageSetting
|
||||
, idToString = Lang.code
|
||||
, idFromString = Lang.fromCode
|
||||
, selected = Just selected
|
||||
, wrap = ChangeLang >> wrap
|
||||
}
|
||||
[]
|
||||
(Lang.languages |> List.map (languageOption shared selected))
|
||||
)
|
||||
[ Message.info Strings.MissingLanguage ]
|
||||
|
||||
|
||||
languageOption : Language -> Language -> Html msg
|
||||
languageOption currentLanguage language =
|
||||
languageOption : Shared -> Language -> Language -> Select.ItemModel Language msg
|
||||
languageOption shared currentLanguage language =
|
||||
let
|
||||
autonym =
|
||||
language |> Lang.autonym
|
||||
|
||||
nameInCurrentLanguage =
|
||||
language |> Lang.languageName |> Lang.givenLanguageString currentLanguage
|
||||
language
|
||||
|> Lang.languageName
|
||||
|> Lang.givenLanguageString currentLanguage
|
||||
|
||||
name =
|
||||
if currentLanguage == language then
|
||||
[ autonym |> Html.text ]
|
||||
autonym =
|
||||
if language /= currentLanguage then
|
||||
language
|
||||
|> Lang.autonym
|
||||
|> (\n -> Just [ Strings.AutonymFormat { autonym = n } |> Lang.html shared ])
|
||||
|
||||
else
|
||||
[ autonym |> Html.text
|
||||
, Html.text " ("
|
||||
, nameInCurrentLanguage |> Html.text
|
||||
, Html.text ")"
|
||||
]
|
||||
Nothing
|
||||
in
|
||||
Html.option
|
||||
[ WlA.selected |> Maybe.justIf (currentLanguage == language) |> Maybe.withDefault HtmlA.nothing
|
||||
, language |> Lang.code |> HtmlA.value
|
||||
]
|
||||
name
|
||||
{ id = language
|
||||
, icon = Nothing
|
||||
, primary = [ nameInCurrentLanguage |> Html.text ]
|
||||
, secondary = autonym
|
||||
, meta = Nothing
|
||||
}
|
||||
|
||||
|
||||
ignore : (Msg -> msg) -> a -> msg
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
module MassiveDecks.Settings.Messages exposing (Msg(..))
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import MassiveDecks.Pages.Lobby.Model as Lobby
|
||||
import MassiveDecks.Settings.Model exposing (..)
|
||||
import MassiveDecks.Strings.Languages.Model exposing (Language)
|
||||
@@ -11,10 +10,10 @@ type Msg
|
||||
| ChangeCardSize CardSize
|
||||
| ChangeOpenUserList Bool
|
||||
| ToggleOpen
|
||||
| RemoveInvalid (Dict Lobby.Token Bool)
|
||||
| RemoveInvalid (List Lobby.Token)
|
||||
| ToggleSpeech Bool
|
||||
| ToggleAutoAdvance Bool
|
||||
| ChangeSpeech String
|
||||
| ChangeSpeech (Maybe String)
|
||||
| ToggleNotifications Bool
|
||||
| ToggleOnlyWhenHidden Bool
|
||||
| NoOp
|
||||
|
||||
@@ -38,6 +38,8 @@ type MdString
|
||||
| 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.
|
||||
| YouWereKicked -- An explanation that the player was kicked from the lobby they were in.
|
||||
| ScrollToTop -- A description of the action of scrolling to the top of a screen.
|
||||
| Copy -- A description of the action of copying something (i.e: copy and paste).
|
||||
-- Rules
|
||||
| CardsAgainstHumanity -- The name of "Cards Against Humanity" (https://cardsagainsthumanity.com/).
|
||||
| Rules -- The title for a DESCRIPTION of the rules.
|
||||
@@ -80,6 +82,7 @@ type MdString
|
||||
| SettingsTitle -- The title for the settings panel.
|
||||
| LanguageSetting -- The label for the "Language" setting.
|
||||
| MissingLanguage -- A question asking if the user doesn't see the language they want.
|
||||
| AutonymFormat { autonym : String } -- How to format the name for a language in that language (e.g: in brackets).
|
||||
| TranslationBeg -- A request for help translating the game.
|
||||
| CardSizeSetting -- The label for the "Card Size" setting.
|
||||
| CardSizeExplanation -- An explanation of what the card size does (changes the size of the card).
|
||||
@@ -113,6 +116,10 @@ type MdString
|
||||
| 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.
|
||||
| DeckSource -- The name for a source of decks of cards.
|
||||
| DeckLanguage { language : MdString } -- A description of what language a deck is in.
|
||||
| DeckAuthor { author : String } -- A description of who created the deck.
|
||||
| DeckTranslator { translator : String } -- A description of who translated the deck.
|
||||
| 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.
|
||||
@@ -197,6 +204,7 @@ type MdString
|
||||
| WaitForDecks -- A hint that the user has to wait for decks to load before starting the game.
|
||||
| MissingCardType { cardType : MdString } -- An error explaining that the user needs a deck with the given type of card (call/response).
|
||||
| NotEnoughCardsOfType { cardType : MdString, needed : Int, have : Int } -- An error explaining that the user needs to add more cards of the given type for the number of players.
|
||||
| AddBlankCards { amount : Int } -- A description of the action of adding the given number of blank cards to the game.
|
||||
| AddDeck -- A description of the action of adding a deck to the game configuration.
|
||||
| RemoveDeck -- A description of the action of removing a deck from the game configuration.
|
||||
| SourceNotFound { source : MdString } -- An explanation that the deck didn't load because it doesn't exist on the source service.
|
||||
@@ -246,6 +254,9 @@ type MdString
|
||||
| ConflictDescription -- An explanation of what a conflict is.
|
||||
| YourChanges -- A title for a section showing changes tot he configuration by the user.
|
||||
| TheirChanges -- A title for a section showing changes to the configuration by someone else.
|
||||
| ConfigurationDisabledWhileInGame -- A message explaining that the configuration can't be changed while the game is in-progress.
|
||||
| ConfigurationDisabledIfNotPrivileged -- A message explaining that the configuration can't be changed if the user isn't privileged.
|
||||
| ConfigureNextGame -- A description of the action of configuring the next game for the lobby after the current one finished.
|
||||
-- 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.
|
||||
|
||||
@@ -13,6 +13,7 @@ module MassiveDecks.Strings.Languages exposing
|
||||
, languages
|
||||
, placeholder
|
||||
, recommended
|
||||
, sortClosestFirst
|
||||
, string
|
||||
, title
|
||||
)
|
||||
@@ -34,7 +35,7 @@ import MassiveDecks.Strings.Render as Render
|
||||
import MassiveDecks.Strings.Translation as Translation
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import MassiveDecks.Util.String as String
|
||||
import Weightless.Attributes as WlA
|
||||
import Material.Attributes as Material
|
||||
|
||||
|
||||
{-| A list of all the languages enabled in the application, in the order they will be presented to the end-user.
|
||||
@@ -75,6 +76,24 @@ autonym language =
|
||||
languageName language |> givenLanguageString language
|
||||
|
||||
|
||||
{-| A sort that gives the closest matches first.
|
||||
Currently this just puts all exact matches first.
|
||||
-}
|
||||
sortClosestFirst : Language -> Maybe Language -> Maybe Language -> Order
|
||||
sortClosestFirst target a b =
|
||||
if a == b then
|
||||
EQ
|
||||
|
||||
else if a == Just target then
|
||||
LT
|
||||
|
||||
else if b == Just target then
|
||||
GT
|
||||
|
||||
else
|
||||
EQ
|
||||
|
||||
|
||||
{-| The language the user is currently seeing the page in.
|
||||
-}
|
||||
currentLanguage : Shared -> Language
|
||||
@@ -144,11 +163,11 @@ alt shared =
|
||||
string shared >> String.capitalise >> HtmlA.alt
|
||||
|
||||
|
||||
{-| Convenience for an Weightless `label` attribute from the given `MdString`.
|
||||
{-| Convenience for a Material `label` attribute from the given `MdString`.
|
||||
-}
|
||||
label : Shared -> MdString -> Html.Attribute msg
|
||||
label shared =
|
||||
string shared >> String.capitalise >> WlA.label
|
||||
string shared >> String.capitalise >> Material.label
|
||||
|
||||
|
||||
{-| Get a deck to recommend to the user if they haven't added any.
|
||||
|
||||
@@ -15,7 +15,7 @@ pack =
|
||||
{ code = "en"
|
||||
, name = English
|
||||
, translate = translate
|
||||
, recommended = "cah-base-en" |> BuiltIn.Id |> Source.BuiltIn
|
||||
, recommended = "cah-base-en" |> BuiltIn.hardcoded |> Source.BuiltIn
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,12 @@ translate mdString =
|
||||
YouWereKicked ->
|
||||
[ Text "You were kicked from the game." ]
|
||||
|
||||
ScrollToTop ->
|
||||
[ Text "Scroll to the top." ]
|
||||
|
||||
Copy ->
|
||||
[ Text "Copy" ]
|
||||
|
||||
-- Rules
|
||||
CardsAgainstHumanity ->
|
||||
[ Text "Cards Against Humanity" ]
|
||||
@@ -300,6 +306,9 @@ translate mdString =
|
||||
MissingLanguage ->
|
||||
[ Text "Don’t see your language? ", Ref TranslationBeg ]
|
||||
|
||||
AutonymFormat { autonym } ->
|
||||
[ Text "(", Text autonym, Text ")" ]
|
||||
|
||||
TranslationBeg ->
|
||||
[ Text "Help translate "
|
||||
, Ref MassiveDecks
|
||||
@@ -404,6 +413,18 @@ translate mdString =
|
||||
Deck ->
|
||||
[ Text "Deck" ]
|
||||
|
||||
DeckSource ->
|
||||
[ Ref Deck, Text " Source" ]
|
||||
|
||||
DeckLanguage { language } ->
|
||||
[ Text "in ", Ref language ]
|
||||
|
||||
DeckAuthor { author } ->
|
||||
[ Text "by ", Text author ]
|
||||
|
||||
DeckTranslator { translator } ->
|
||||
[ Text "translation by ", Text translator ]
|
||||
|
||||
StillPlaying ->
|
||||
[ Text "Playing" ]
|
||||
|
||||
@@ -622,7 +643,7 @@ translate mdString =
|
||||
[ Text "End the game now." ]
|
||||
|
||||
ReturnViewToGame ->
|
||||
[ Text "Return" ]
|
||||
[ Text "Return to game" ]
|
||||
|
||||
ReturnViewToGameDescription ->
|
||||
[ Text "Return to the main game view." ]
|
||||
@@ -693,6 +714,13 @@ translate mdString =
|
||||
, Text "."
|
||||
]
|
||||
|
||||
AddBlankCards { amount } ->
|
||||
[ Text "Add "
|
||||
, amount |> String.fromInt |> Text
|
||||
, Text " blank "
|
||||
, Ref (Plural { singular = Response, amount = Just amount })
|
||||
]
|
||||
|
||||
AddDeck ->
|
||||
[ Text "Add deck." ]
|
||||
|
||||
@@ -864,6 +892,15 @@ translate mdString =
|
||||
TheirChanges ->
|
||||
[ Text "Their Changes" ]
|
||||
|
||||
ConfigurationDisabledWhileInGame ->
|
||||
[ Text "While the game in progress, you can't change the configuration." ]
|
||||
|
||||
ConfigurationDisabledIfNotPrivileged ->
|
||||
[ Text "You can't change the configuration of this game." ]
|
||||
|
||||
ConfigureNextGame ->
|
||||
[ Text "Configure Next Game" ]
|
||||
|
||||
-- Game
|
||||
SubmitPlay ->
|
||||
[ Text "Give these cards to the ", Ref Czar, Text " as your play for the round." ]
|
||||
|
||||
@@ -13,7 +13,7 @@ pack =
|
||||
{ code = "it"
|
||||
, name = Italian
|
||||
, translate = translate
|
||||
, recommended = "cah-base-en" |> BuiltIn.Id |> Source.BuiltIn
|
||||
, recommended = "cah-base-en" |> BuiltIn.hardcoded |> Source.BuiltIn
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,14 @@ translate mdString =
|
||||
YouWereKicked ->
|
||||
[ Text "Sei stato espulso dal gioco." ]
|
||||
|
||||
-- TODO: Translate
|
||||
ScrollToTop ->
|
||||
[ Text "Scroll to the top." ]
|
||||
|
||||
-- TODO: Translate
|
||||
Copy ->
|
||||
[ Text "Copy" ]
|
||||
|
||||
-- Rules
|
||||
CardsAgainstHumanity ->
|
||||
[ Text "Cards Against Humanity" ]
|
||||
@@ -316,6 +324,9 @@ translate mdString =
|
||||
MissingLanguage ->
|
||||
[ Text "Non vedi la tua lingua? ", Ref TranslationBeg ]
|
||||
|
||||
AutonymFormat { autonym } ->
|
||||
[ Text "(", Text autonym, Text ")" ]
|
||||
|
||||
TranslationBeg ->
|
||||
[ Text "Aiuta a tradurre "
|
||||
, Ref MassiveDecks
|
||||
@@ -420,6 +431,22 @@ translate mdString =
|
||||
Deck ->
|
||||
[ Text "Mazzo" ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckSource ->
|
||||
[ Ref Deck, Text " Source" ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckLanguage { language } ->
|
||||
[ Text "in ", Ref language ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckAuthor { author } ->
|
||||
[ Text "by ", Text author ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckTranslator { translator } ->
|
||||
[ Text "translation by ", Text translator ]
|
||||
|
||||
StillPlaying ->
|
||||
[ Text "Sta giocando" ]
|
||||
|
||||
@@ -711,6 +738,14 @@ translate mdString =
|
||||
, Text "."
|
||||
]
|
||||
|
||||
-- TODO: Translate
|
||||
AddBlankCards { amount } ->
|
||||
[ Text "Add "
|
||||
, amount |> String.fromInt |> Text
|
||||
, Text " blank "
|
||||
, Ref (Plural { singular = Response, amount = Just amount })
|
||||
]
|
||||
|
||||
AddDeck ->
|
||||
[ Text "Aggiungi mazzo." ]
|
||||
|
||||
@@ -883,6 +918,18 @@ translate mdString =
|
||||
TheirChanges ->
|
||||
[ Text "Le sue modifiche" ]
|
||||
|
||||
-- TODO: Translate
|
||||
ConfigurationDisabledWhileInGame ->
|
||||
[ Text "While the game in progress, you can't change the configuration." ]
|
||||
|
||||
-- TODO: Translate
|
||||
ConfigurationDisabledIfNotPrivileged ->
|
||||
[ Text "You can't change the configuration of this game." ]
|
||||
|
||||
-- TODO: Translate
|
||||
ConfigureNextGame ->
|
||||
[ Text "Configure Next Game" ]
|
||||
|
||||
-- Game
|
||||
SubmitPlay ->
|
||||
[ Text "Dai queste carte al ", Ref Czar, Text " come giocata per il turno." ]
|
||||
|
||||
@@ -11,10 +11,10 @@ import MassiveDecks.Strings.Translation as Translation exposing (Result(..))
|
||||
|
||||
pack : Translation.Pack
|
||||
pack =
|
||||
{ code = "ptBR"
|
||||
{ code = "pt-BR"
|
||||
, name = BrazilianPortuguese
|
||||
, translate = translate
|
||||
, recommended = "cah-base-ptbr" |> BuiltIn.Id |> Source.BuiltIn
|
||||
, recommended = "cah-base-ptbr" |> BuiltIn.hardcoded |> Source.BuiltIn
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,14 @@ translate mdString =
|
||||
YouWereKicked ->
|
||||
[ Text "Você foi chutado do jogo." ]
|
||||
|
||||
-- TODO: Translate
|
||||
ScrollToTop ->
|
||||
[ Text "Scroll to the top." ]
|
||||
|
||||
-- TODO: Translate
|
||||
Copy ->
|
||||
[ Text "Copy" ]
|
||||
|
||||
-- Rules
|
||||
CardsAgainstHumanity ->
|
||||
[ Text "Cartas Contra a Humanidade" ]
|
||||
@@ -317,6 +325,9 @@ translate mdString =
|
||||
MissingLanguage ->
|
||||
[ Text "Não vê seu idioma? ", Ref TranslationBeg ]
|
||||
|
||||
AutonymFormat { autonym } ->
|
||||
[ Text "(", Text autonym, Text ")" ]
|
||||
|
||||
TranslationBeg ->
|
||||
[ Text "Ajudar a traduzir o "
|
||||
, Ref MassiveDecks
|
||||
@@ -420,6 +431,22 @@ translate mdString =
|
||||
Deck ->
|
||||
[ Text "Deck" ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckSource ->
|
||||
[ Ref Deck, Text " Source" ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckLanguage { language } ->
|
||||
[ Text "in ", Ref language ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckAuthor { author } ->
|
||||
[ Text "by ", Text author ]
|
||||
|
||||
-- TODO: Translate
|
||||
DeckTranslator { translator } ->
|
||||
[ Text "translation by ", Text translator ]
|
||||
|
||||
StillPlaying ->
|
||||
[ Text "Jogando" ]
|
||||
|
||||
@@ -709,6 +736,14 @@ translate mdString =
|
||||
, Text "."
|
||||
]
|
||||
|
||||
-- TODO: Translate
|
||||
AddBlankCards { amount } ->
|
||||
[ Text "Add "
|
||||
, amount |> String.fromInt |> Text
|
||||
, Text " blank "
|
||||
, Ref (Plural { singular = Response, amount = Just amount })
|
||||
]
|
||||
|
||||
AddDeck ->
|
||||
[ Text "Adicionar deck." ]
|
||||
|
||||
@@ -880,6 +915,18 @@ translate mdString =
|
||||
TheirChanges ->
|
||||
[ Text "Mudanças dele(a)" ]
|
||||
|
||||
-- TODO: Translate
|
||||
ConfigurationDisabledWhileInGame ->
|
||||
[ Text "While the game in progress, you can't change the configuration." ]
|
||||
|
||||
-- TODO: Translate
|
||||
ConfigurationDisabledIfNotPrivileged ->
|
||||
[ Text "You can't change the configuration of this game." ]
|
||||
|
||||
-- TODO: Translate
|
||||
ConfigureNextGame ->
|
||||
[ Text "Configure Next Game" ]
|
||||
|
||||
-- Game
|
||||
SubmitPlay ->
|
||||
[ Text "Dar essas cartas ao ", Ref Czar, Text " como sua jogada da partida." ]
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
module MassiveDecks.Util.Html.Attributes exposing (nothing)
|
||||
module MassiveDecks.Util.Html.Attributes exposing
|
||||
( fullWidth
|
||||
, nothing
|
||||
, slot
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
@@ -10,3 +14,17 @@ import Json.Encode as Json
|
||||
nothing : Html.Attribute msg
|
||||
nothing =
|
||||
Json.null |> HtmlA.property ""
|
||||
|
||||
|
||||
{-| The slot attribute.
|
||||
-}
|
||||
slot : String -> Html.Attribute msg
|
||||
slot =
|
||||
HtmlA.attribute "slot"
|
||||
|
||||
|
||||
{-| The full width attribute for material components.
|
||||
-}
|
||||
fullWidth : Html.Attribute msg
|
||||
fullWidth =
|
||||
True |> Json.bool |> HtmlA.property "fullwidth"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module MassiveDecks.Util.Html.Events exposing (..)
|
||||
|
||||
import Html
|
||||
import Html.Events as HtmlE
|
||||
import Json.Decode
|
||||
|
||||
|
||||
{-| Same as onClick, but without propagating to other elements.
|
||||
-}
|
||||
onClickNoPropagation : msg -> Html.Attribute msg
|
||||
onClickNoPropagation msg =
|
||||
HtmlE.stopPropagationOn "click" (Json.Decode.succeed ( msg, True ))
|
||||
@@ -1,7 +1,5 @@
|
||||
module MassiveDecks.Util.List exposing
|
||||
( any
|
||||
, find
|
||||
, map2Long
|
||||
( map2Long
|
||||
, mappedIntersperse
|
||||
, merge
|
||||
)
|
||||
@@ -9,27 +7,6 @@ module MassiveDecks.Util.List exposing
|
||||
{-| Utility functions for lists.
|
||||
-}
|
||||
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
|
||||
|
||||
{-| Returns `True` if any value in the list matches the given predicate.
|
||||
-}
|
||||
any : (a -> Bool) -> List a -> Bool
|
||||
any predicate list =
|
||||
find predicate list /= Nothing
|
||||
|
||||
|
||||
{-| Find the first element of the given list that fulfils the given predicate.
|
||||
-}
|
||||
find : (a -> Bool) -> List a -> Maybe a
|
||||
find predicate list =
|
||||
case list of
|
||||
head :: tail ->
|
||||
Maybe.first [ Maybe.validate predicate head, find predicate tail ]
|
||||
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
|
||||
{-| Map two items together, getting `Nothing`s when the shorter ends.
|
||||
Note that if longer is shorter, shorter will be cut off to match.
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
module MassiveDecks.Util.NeList exposing
|
||||
( NeList(..)
|
||||
, decoder
|
||||
, extend
|
||||
, fromList
|
||||
, head
|
||||
, just
|
||||
, toList
|
||||
)
|
||||
|
||||
{-| A module for a list that cannot be empty.
|
||||
-}
|
||||
|
||||
import Json.Decode as Json
|
||||
|
||||
|
||||
{-| A list that cannot be empty.
|
||||
-}
|
||||
type NeList item
|
||||
= NeList item (List item)
|
||||
|
||||
|
||||
{-| A list of one item.
|
||||
-}
|
||||
just : item -> NeList item
|
||||
just item =
|
||||
NeList item []
|
||||
|
||||
|
||||
{-| Add all the new elements to the end of the list.
|
||||
-}
|
||||
extend : List item -> NeList item -> NeList item
|
||||
extend new (NeList first rest) =
|
||||
NeList first (rest ++ new)
|
||||
|
||||
|
||||
{-| Get the first element of a non-empty list.
|
||||
-}
|
||||
head : NeList item -> item
|
||||
head (NeList first _) =
|
||||
first
|
||||
|
||||
|
||||
{-| Convert to a normal list.
|
||||
-}
|
||||
toList : NeList item -> List item
|
||||
toList (NeList first rest) =
|
||||
first :: rest
|
||||
|
||||
|
||||
{-| Convert from a normal list.
|
||||
-}
|
||||
fromList : List item -> Maybe (NeList item)
|
||||
fromList items =
|
||||
case items of
|
||||
first :: rest ->
|
||||
NeList first rest |> Just
|
||||
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
|
||||
{-| A JSON decoder that fails if there are no values.
|
||||
-}
|
||||
decoder : Json.Decoder item -> Json.Decoder (NeList item)
|
||||
decoder itemDecoder =
|
||||
Json.list itemDecoder
|
||||
|> Json.andThen (fromList >> Maybe.map Json.succeed >> Maybe.withDefault (Json.fail "Can't be empty."))
|
||||
@@ -0,0 +1,48 @@
|
||||
module MassiveDecks.Util.Order exposing (..)
|
||||
|
||||
{-| Utilities for dealing with ordering.
|
||||
-}
|
||||
|
||||
|
||||
{-| Map an ordering.
|
||||
-}
|
||||
map : (a -> b) -> (b -> b -> Order) -> a -> a -> Order
|
||||
map f order a b =
|
||||
order (f a) (f b)
|
||||
|
||||
|
||||
{-| Map an ordering where the value might not exist.
|
||||
-}
|
||||
filterMap : (b -> Order) -> (a -> Maybe b) -> (b -> b -> Order) -> a -> a -> Order
|
||||
filterMap againstNothing f order a b =
|
||||
case f a of
|
||||
Just x ->
|
||||
case f b of
|
||||
Just y ->
|
||||
order x y
|
||||
|
||||
Nothing ->
|
||||
againstNothing x
|
||||
|
||||
Nothing ->
|
||||
case f b of
|
||||
Just y ->
|
||||
againstNothing y |> reverse
|
||||
|
||||
Nothing ->
|
||||
EQ
|
||||
|
||||
|
||||
{-| Get the reversed order.
|
||||
-}
|
||||
reverse : Order -> Order
|
||||
reverse o =
|
||||
case o of
|
||||
LT ->
|
||||
GT
|
||||
|
||||
EQ ->
|
||||
EQ
|
||||
|
||||
GT ->
|
||||
LT
|
||||
@@ -8,4 +8,4 @@ module MassiveDecks.Version exposing (version)
|
||||
-}
|
||||
version : String
|
||||
version =
|
||||
"dev"
|
||||
"unknown-dev"
|
||||
|
||||
+3
-2
@@ -1,7 +1,7 @@
|
||||
import { Say, Voice } from "../../ts/speech";
|
||||
import {
|
||||
State as NotificationState,
|
||||
Command as NotificationCommand
|
||||
Command as NotificationCommand,
|
||||
} from "../../ts/notification-manager";
|
||||
|
||||
type Token = string;
|
||||
@@ -61,7 +61,8 @@ export namespace Elm {
|
||||
serverSend: InboundPort<ConnectionCommand>;
|
||||
copyText: InboundPort<string>;
|
||||
remoteControl: OutboundPort<RemoteControlCommand>;
|
||||
languageChanged: InboundPort<string>
|
||||
languageChanged: InboundPort<string>;
|
||||
startConfetti: InboundPort<string>;
|
||||
};
|
||||
}
|
||||
export function init(options: {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
module Material.Attributes exposing
|
||||
( label
|
||||
, maxLength
|
||||
, minLength
|
||||
)
|
||||
|
||||
import Html
|
||||
import Html.Attributes as HtmlA
|
||||
import Json.Encode as Json
|
||||
|
||||
|
||||
label : String -> Html.Attribute msg
|
||||
label =
|
||||
HtmlA.attribute "label"
|
||||
|
||||
|
||||
minLength : Int -> Html.Attribute msg
|
||||
minLength =
|
||||
Json.int >> HtmlA.property "minLength"
|
||||
|
||||
|
||||
maxLength : Int -> Html.Attribute msg
|
||||
maxLength =
|
||||
Json.int >> HtmlA.property "maxLength"
|
||||
@@ -0,0 +1,47 @@
|
||||
module Material.Button exposing
|
||||
( Type(..)
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
|
||||
|
||||
type Type
|
||||
= Standard
|
||||
| Outlined
|
||||
| Raised
|
||||
| Unelevated
|
||||
|
||||
|
||||
view : Shared -> Type -> MdString -> MdString -> Html msg -> List (Html.Attribute msg) -> Html msg
|
||||
view shared type_ label title icon attributes =
|
||||
let
|
||||
typeAttr =
|
||||
case type_ of
|
||||
Standard ->
|
||||
[]
|
||||
|
||||
Outlined ->
|
||||
[ HtmlA.attribute "outlined" "" ]
|
||||
|
||||
Raised ->
|
||||
[ HtmlA.attribute "raised" "" ]
|
||||
|
||||
Unelevated ->
|
||||
[ HtmlA.attribute "unelevated" "" ]
|
||||
|
||||
allAttrs =
|
||||
List.concat
|
||||
[ [ label |> Lang.label shared
|
||||
, title |> Lang.title shared
|
||||
]
|
||||
, typeAttr
|
||||
, attributes
|
||||
]
|
||||
in
|
||||
Html.node "mwc-button" allAttrs [ Html.span [ HtmlA.slot "icon" ] [ icon ] ]
|
||||
@@ -0,0 +1,9 @@
|
||||
module Material.Card exposing (view)
|
||||
|
||||
import Html as HtmlA
|
||||
import Html.Attributes as HtmlA
|
||||
|
||||
|
||||
view : List (HtmlA.Attribute msg) -> List (HtmlA.Html msg) -> HtmlA.Html msg
|
||||
view attributes =
|
||||
HtmlA.div (HtmlA.class "mdc-card" :: attributes)
|
||||
@@ -0,0 +1,51 @@
|
||||
module Material.Fab exposing
|
||||
( Type(..)
|
||||
, view
|
||||
)
|
||||
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
|
||||
|
||||
type Type
|
||||
= Normal
|
||||
| Extended
|
||||
| Mini
|
||||
|
||||
|
||||
view : Shared -> Type -> MdString -> Icon.Presentation id msg -> Maybe msg -> List (Html.Attribute msg) -> Html msg
|
||||
view shared type_ title icon action attrs =
|
||||
let
|
||||
content =
|
||||
case type_ of
|
||||
Extended ->
|
||||
[ title |> Lang.html shared ]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
style =
|
||||
case type_ of
|
||||
Mini ->
|
||||
[ HtmlA.attribute "mini" "" ]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
onClick =
|
||||
case action of
|
||||
Just msg ->
|
||||
msg |> HtmlE.onClick
|
||||
|
||||
Nothing ->
|
||||
HtmlA.disabled True
|
||||
in
|
||||
Html.node "mwc-fab"
|
||||
(List.concat [ [ title |> Lang.title shared, onClick ], style, attrs ])
|
||||
((icon |> Icon.styled [ HtmlA.slot "icon" ] |> Icon.view) :: content)
|
||||
@@ -0,0 +1,67 @@
|
||||
module Material.IconButton exposing
|
||||
( view
|
||||
, viewNoPropagation
|
||||
)
|
||||
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import FontAwesome.Layering as Icon
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html.Events as HtmlE
|
||||
import MassiveDecks.Util.NeList exposing (NeList(..))
|
||||
|
||||
|
||||
{-| View a button that displays as a simple icon.
|
||||
-}
|
||||
view : Shared -> MdString -> NeList (Icon.Presentation id msg) -> Maybe msg -> Html msg
|
||||
view shared title icon action =
|
||||
let
|
||||
actionAttr =
|
||||
case action of
|
||||
Just msg ->
|
||||
msg |> HtmlE.onClick
|
||||
|
||||
Nothing ->
|
||||
HtmlA.disabled True
|
||||
in
|
||||
viewInternal [ actionAttr ] shared title icon
|
||||
|
||||
|
||||
{-| View a button that displays as a simple icon, and blocks clicks propagating to other elements.
|
||||
-}
|
||||
viewNoPropagation : Shared -> MdString -> NeList (Icon.Presentation id msg) -> Maybe msg -> Html msg
|
||||
viewNoPropagation shared title icon action =
|
||||
let
|
||||
actionAttr =
|
||||
case action of
|
||||
Just msg ->
|
||||
msg |> HtmlE.onClickNoPropagation
|
||||
|
||||
Nothing ->
|
||||
HtmlA.disabled True
|
||||
in
|
||||
viewInternal [ actionAttr ] shared title icon
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
viewInternal : List (Html.Attribute msg) -> Shared -> MdString -> NeList (Icon.Presentation id msg) -> Html msg
|
||||
viewInternal actionAttr shared title (NeList first rest) =
|
||||
let
|
||||
renderedIcon =
|
||||
case rest of
|
||||
[] ->
|
||||
first |> Icon.view
|
||||
|
||||
_ ->
|
||||
(first :: rest) |> List.map Icon.view |> Icon.layers []
|
||||
in
|
||||
Html.node "mwc-icon-button"
|
||||
((title |> Lang.title shared) :: actionAttr)
|
||||
[ renderedIcon ]
|
||||
@@ -0,0 +1,44 @@
|
||||
module Material.LinearProgress exposing
|
||||
( Progress(..)
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Json.Encode as Json
|
||||
|
||||
|
||||
{-| What progress to display.
|
||||
-}
|
||||
type
|
||||
Progress
|
||||
-- An unknown amount of progress.
|
||||
= Indeterminate
|
||||
-- A float progress from 0 (no progress) to 1 (complete).
|
||||
| Progress Float
|
||||
-- Complete.
|
||||
| Closed
|
||||
|
||||
|
||||
{-| A progress bar.
|
||||
-}
|
||||
view : Progress -> List (Html.Attribute msg) -> Html msg
|
||||
view p attributes =
|
||||
Html.node "mwc-linear-progress" (progress p :: attributes) []
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
progress : Progress -> Html.Attribute msg
|
||||
progress p =
|
||||
case p of
|
||||
Indeterminate ->
|
||||
True |> Json.bool |> HtmlA.property "indeterminate"
|
||||
|
||||
Progress float ->
|
||||
float |> Json.float |> HtmlA.property "progress"
|
||||
|
||||
Closed ->
|
||||
True |> Json.bool |> HtmlA.property "closed"
|
||||
@@ -0,0 +1,89 @@
|
||||
module Material.ListView exposing
|
||||
( Action(..)
|
||||
, action
|
||||
, interactive
|
||||
, view
|
||||
, viewItem
|
||||
)
|
||||
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Json.Encode as Json
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
|
||||
|
||||
{-| A list.
|
||||
-}
|
||||
view : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
view attributes children =
|
||||
Html.node "mwc-list" attributes children
|
||||
|
||||
|
||||
{-| What happens when you interact with the item.
|
||||
-}
|
||||
type Action msg
|
||||
= None
|
||||
| Link
|
||||
| Enabled msg
|
||||
| Disabled
|
||||
|
||||
|
||||
{-| A list item that is only sometimes enabled.
|
||||
-}
|
||||
interactive : msg -> Bool -> Action msg
|
||||
interactive msg enabled =
|
||||
if enabled then
|
||||
Enabled msg
|
||||
|
||||
else
|
||||
Disabled
|
||||
|
||||
|
||||
{-| An action from a maybe, if it is enabled.
|
||||
-}
|
||||
action : Maybe msg -> Action msg
|
||||
action a =
|
||||
case a of
|
||||
Just msg ->
|
||||
Enabled msg
|
||||
|
||||
Nothing ->
|
||||
Disabled
|
||||
|
||||
|
||||
{-| An item within a list.
|
||||
-}
|
||||
viewItem : Action msg -> Maybe Icon -> Maybe (List (Html msg)) -> Maybe (List (Html msg)) -> List (Html msg) -> Html msg
|
||||
viewItem action_ icon secondary meta children =
|
||||
let
|
||||
( optionalAttrs, optionalSlots ) =
|
||||
[ icon |> Maybe.map (\i -> ( HtmlA.attribute "graphic" "large", Icon.viewStyled [ HtmlA.slot "graphic" ] i ))
|
||||
, meta |> Maybe.map (\m -> ( True |> Json.bool |> HtmlA.property "hasMeta", Html.span [ HtmlA.slot "meta" ] m ))
|
||||
, secondary |> Maybe.map (\s -> ( True |> Json.bool |> HtmlA.property "twoline", Html.span [ HtmlA.slot "secondary" ] s ))
|
||||
]
|
||||
|> List.filterMap identity
|
||||
|> List.unzip
|
||||
|
||||
actionAttr =
|
||||
case action_ of
|
||||
Enabled msg ->
|
||||
msg |> HtmlE.onClick
|
||||
|
||||
Disabled ->
|
||||
HtmlA.disabled True
|
||||
|
||||
Link ->
|
||||
HtmlA.nothing
|
||||
|
||||
None ->
|
||||
True |> Json.bool |> HtmlA.property "noninteractive"
|
||||
|
||||
attrs =
|
||||
List.concat [ optionalAttrs, [ actionAttr ] ]
|
||||
|
||||
slots =
|
||||
List.concat [ optionalSlots, [ Html.span [] children ] ]
|
||||
in
|
||||
Html.node "mwc-list-item" attrs slots
|
||||
@@ -0,0 +1,60 @@
|
||||
module Material.Menu exposing (view)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Json.Decode
|
||||
import Json.Encode as Json
|
||||
import MassiveDecks.Components.Menu.Model exposing (..)
|
||||
|
||||
|
||||
{-| View a pop-up menu.
|
||||
-}
|
||||
view : msg -> State -> Corner -> Html msg -> List (Html msg) -> Html msg
|
||||
view onClose state corner anchor menuItems =
|
||||
let
|
||||
open =
|
||||
case state of
|
||||
Open ->
|
||||
True
|
||||
|
||||
Closed ->
|
||||
False
|
||||
|
||||
stringCorner =
|
||||
case corner of
|
||||
TopLeft ->
|
||||
"TOP_LEFT"
|
||||
|
||||
TopRight ->
|
||||
"TOP_RIGHT"
|
||||
|
||||
BottomLeft ->
|
||||
"BOTTOM_LEFT"
|
||||
|
||||
BottomRight ->
|
||||
"BOTTOM_RIGHT"
|
||||
|
||||
TopStart ->
|
||||
"TOP_START"
|
||||
|
||||
TopEnd ->
|
||||
"TOP_END"
|
||||
|
||||
BottomStart ->
|
||||
"BOTTOM_START"
|
||||
|
||||
BottomEnd ->
|
||||
"BOTTOM_END"
|
||||
in
|
||||
Html.div [ HtmlA.class "menu-anchor" ]
|
||||
[ anchor
|
||||
, Html.node "mwc-menu"
|
||||
[ True |> Json.bool |> HtmlA.property "activatable"
|
||||
, open |> Json.bool |> HtmlA.property "open"
|
||||
, stringCorner |> HtmlA.attribute "corner"
|
||||
, HtmlA.class "menu"
|
||||
, onClose |> Json.Decode.succeed |> HtmlE.on "closed"
|
||||
]
|
||||
menuItems
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
module Material.Ripple exposing (view)
|
||||
|
||||
import Html as HtmlA
|
||||
|
||||
|
||||
view : List (HtmlA.Attribute msg) -> List (HtmlA.Html msg) -> HtmlA.Html msg
|
||||
view =
|
||||
HtmlA.node "mwc-ripple"
|
||||
@@ -0,0 +1,87 @@
|
||||
module Material.Select exposing
|
||||
( ItemModel
|
||||
, Model
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Json.Decode as Json
|
||||
import Json.Encode
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
|
||||
|
||||
{-| A combo box.
|
||||
-}
|
||||
view : Shared -> Model id msg -> List (Html.Attribute msg) -> List (ItemModel id msg) -> Html msg
|
||||
view shared model attrs items =
|
||||
let
|
||||
allAttrs =
|
||||
List.concat
|
||||
[ [ model.label |> Lang.label shared
|
||||
, model.idFromString >> model.wrap |> onChange
|
||||
, HtmlA.fullWidth
|
||||
]
|
||||
, attrs
|
||||
]
|
||||
in
|
||||
Html.node "mwc-select" allAttrs (items |> List.map (viewItem model))
|
||||
|
||||
|
||||
{-| The things needed to render the select.
|
||||
-}
|
||||
type alias Model id msg =
|
||||
{ label : MdString
|
||||
, idToString : id -> String
|
||||
, idFromString : String -> Maybe id
|
||||
, selected : Maybe id
|
||||
, wrap : Maybe id -> msg
|
||||
}
|
||||
|
||||
|
||||
{-| The things needed to render a specific item in the select.
|
||||
-}
|
||||
type alias ItemModel id msg =
|
||||
{ id : id
|
||||
, icon : Maybe (Html msg)
|
||||
, primary : List (Html msg)
|
||||
, secondary : Maybe (List (Html msg))
|
||||
, meta : Maybe (Html msg)
|
||||
}
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
{-| An item within a list.
|
||||
-}
|
||||
viewItem : Model id msg -> ItemModel id msg -> Html msg
|
||||
viewItem { idToString, selected } { id, icon, primary, secondary, meta } =
|
||||
let
|
||||
( optionalAttrs, optionalSlots ) =
|
||||
[ icon |> Maybe.map (\i -> ( HtmlA.attribute "graphic" "large", Html.span [ HtmlA.slot "graphic" ] [ i ] ))
|
||||
, meta |> Maybe.map (\m -> ( True |> Json.Encode.bool |> HtmlA.property "hasMeta", Html.span [ HtmlA.slot "meta" ] [ m ] ))
|
||||
, secondary |> Maybe.map (\s -> ( True |> Json.Encode.bool |> HtmlA.property "twoline", Html.span [ HtmlA.slot "secondary" ] s ))
|
||||
]
|
||||
|> List.filterMap identity
|
||||
|> List.unzip
|
||||
|
||||
attrs =
|
||||
List.concat [ optionalAttrs, [ id |> idToString |> HtmlA.value, selected == Just id |> HtmlA.selected ] ]
|
||||
|
||||
slots =
|
||||
List.concat [ primary, optionalSlots ] |> List.intersperse (Html.text " ")
|
||||
in
|
||||
Html.node "mwc-list-item" attrs slots
|
||||
|
||||
|
||||
{-| An event for when the user changes the selection.
|
||||
-}
|
||||
onChange : (String -> msg) -> Html.Attribute msg
|
||||
onChange wrap =
|
||||
Json.at [ "target", "value" ] Json.string |> Json.map wrap |> HtmlE.on "change"
|
||||
@@ -0,0 +1,56 @@
|
||||
module Material.Slider exposing
|
||||
( markers
|
||||
, max
|
||||
, min
|
||||
, onChange
|
||||
, pin
|
||||
, step
|
||||
, value
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Json.Decode
|
||||
import Json.Encode as Json
|
||||
|
||||
|
||||
view : List (Html.Attribute msg) -> Html msg
|
||||
view attributes =
|
||||
Html.node "mwc-slider" attributes []
|
||||
|
||||
|
||||
step : Int -> Html.Attribute msg
|
||||
step =
|
||||
Json.int >> HtmlA.property "step"
|
||||
|
||||
|
||||
min : Int -> Html.Attribute msg
|
||||
min =
|
||||
Json.int >> HtmlA.property "min"
|
||||
|
||||
|
||||
max : Int -> Html.Attribute msg
|
||||
max =
|
||||
Json.int >> HtmlA.property "max"
|
||||
|
||||
|
||||
markers : Html.Attribute msg
|
||||
markers =
|
||||
True |> Json.bool |> HtmlA.property "markers"
|
||||
|
||||
|
||||
pin : Html.Attribute msg
|
||||
pin =
|
||||
True |> Json.bool |> HtmlA.property "pin"
|
||||
|
||||
|
||||
value : Int -> Html.Attribute msg
|
||||
value =
|
||||
Json.int >> HtmlA.property "value"
|
||||
|
||||
|
||||
onChange : (Int -> msg) -> Html.Attribute msg
|
||||
onChange wrap =
|
||||
Json.Decode.at [ "target", "value" ] Json.Decode.int |> Json.Decode.map wrap |> HtmlE.on "change"
|
||||
@@ -0,0 +1,8 @@
|
||||
module Material.Switch exposing (view)
|
||||
|
||||
import Html exposing (Html)
|
||||
|
||||
|
||||
view : List (Html.Attribute msg) -> Html msg
|
||||
view attributes =
|
||||
Html.node "mwc-switch" attributes []
|
||||
@@ -0,0 +1,88 @@
|
||||
module Material.Tabs exposing
|
||||
( Model
|
||||
, TabModel
|
||||
, view
|
||||
)
|
||||
|
||||
import FontAwesome.Icon as Icon exposing (Icon)
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Events as HtmlE
|
||||
import Json.Decode
|
||||
import Json.Encode
|
||||
import List.Extra as List
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.Util.Html as Html
|
||||
import MassiveDecks.Util.Html.Attributes as HtmlA
|
||||
import MassiveDecks.Util.NeList as NonEmptyList exposing (NeList)
|
||||
|
||||
|
||||
{-| A model for a bar of tabs.
|
||||
-}
|
||||
type alias Model id msg =
|
||||
{ selected : id
|
||||
, change : id -> msg
|
||||
, ids : NeList id
|
||||
, tab : id -> TabModel
|
||||
, equals : id -> id -> Bool
|
||||
}
|
||||
|
||||
|
||||
{-| A model for a tab.
|
||||
-}
|
||||
type alias TabModel =
|
||||
{ label : MdString
|
||||
, icon : Maybe Icon
|
||||
}
|
||||
|
||||
|
||||
{-| View a bar of tabs.
|
||||
-}
|
||||
view : Shared -> Model id msg -> Html msg
|
||||
view shared { selected, change, ids, tab, equals } =
|
||||
let
|
||||
idsList =
|
||||
ids |> NonEmptyList.toList
|
||||
|
||||
attrs =
|
||||
[ idsList |> List.findIndex (equals selected) |> Maybe.withDefault 0 |> activeIndex
|
||||
, ((\id -> List.getAt id idsList) >> (ids |> NonEmptyList.head |> Maybe.withDefault) >> change) |> onActivated
|
||||
]
|
||||
|
||||
viewTab { label, icon } =
|
||||
let
|
||||
( iconAttr, iconNode ) =
|
||||
case icon of
|
||||
Just i ->
|
||||
( hasImageIcon, Icon.viewStyled [ HtmlA.slot "icon" ] i )
|
||||
|
||||
Nothing ->
|
||||
( HtmlA.nothing, Html.nothing )
|
||||
in
|
||||
Html.node "mwc-tab" [ label |> Lang.label shared, iconAttr ] [ iconNode ]
|
||||
|
||||
tabs =
|
||||
idsList |> List.map (tab >> viewTab)
|
||||
in
|
||||
Html.node "mwc-tab-bar" attrs tabs
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
activeIndex : Int -> Html.Attribute msg
|
||||
activeIndex =
|
||||
Json.Encode.int >> HtmlA.property "activeIndex"
|
||||
|
||||
|
||||
hasImageIcon : Html.Attribute msg
|
||||
hasImageIcon =
|
||||
HtmlA.attribute "hasImageIcon" "hasImageIcon"
|
||||
|
||||
|
||||
onActivated : (Int -> msg) -> Html.Attribute msg
|
||||
onActivated wrap =
|
||||
Json.Decode.int |> Json.Decode.map wrap |> Json.Decode.at [ "target", "activeIndex" ] |> HtmlE.on "MDCTabBar:activated"
|
||||
@@ -0,0 +1,8 @@
|
||||
module Material.TextArea exposing (view)
|
||||
|
||||
import Html
|
||||
|
||||
|
||||
view : List (Html.Attribute msg) -> List (Html.Html msg) -> Html.Html msg
|
||||
view =
|
||||
Html.node "mwc-textarea"
|
||||
@@ -0,0 +1,96 @@
|
||||
module Material.TextField exposing
|
||||
( Type(..)
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
|
||||
|
||||
{-| The type of field.
|
||||
-}
|
||||
type Type
|
||||
= Text
|
||||
| Search
|
||||
| Tel
|
||||
| Url
|
||||
| Email
|
||||
| Password
|
||||
| Date
|
||||
| Month
|
||||
| Week
|
||||
| Time
|
||||
| DateTimeLocal
|
||||
| Number
|
||||
| Color
|
||||
|
||||
|
||||
{-| A text field for the given type of data.
|
||||
-}
|
||||
view : Shared -> MdString -> Type -> String -> List (Html.Attribute msg) -> Html msg
|
||||
view shared label t value attributes =
|
||||
let
|
||||
allAttrs =
|
||||
List.concat
|
||||
[ [ label |> Lang.label shared
|
||||
, t |> type_
|
||||
, value |> HtmlA.value
|
||||
]
|
||||
, attributes
|
||||
]
|
||||
in
|
||||
Html.node "mwc-textfield" allAttrs []
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
type_ : Type -> Html.Attribute msg
|
||||
type_ t =
|
||||
let
|
||||
stringType =
|
||||
case t of
|
||||
Text ->
|
||||
"text"
|
||||
|
||||
Search ->
|
||||
"search"
|
||||
|
||||
Tel ->
|
||||
"tel"
|
||||
|
||||
Url ->
|
||||
"url"
|
||||
|
||||
Email ->
|
||||
"email"
|
||||
|
||||
Password ->
|
||||
"password"
|
||||
|
||||
Date ->
|
||||
"date"
|
||||
|
||||
Month ->
|
||||
"month"
|
||||
|
||||
Week ->
|
||||
"week"
|
||||
|
||||
Time ->
|
||||
"time"
|
||||
|
||||
DateTimeLocal ->
|
||||
"datetime-local"
|
||||
|
||||
Number ->
|
||||
"number"
|
||||
|
||||
Color ->
|
||||
"color"
|
||||
in
|
||||
stringType |> HtmlA.attribute "type"
|
||||
@@ -0,0 +1,43 @@
|
||||
module Paper.Tooltip exposing
|
||||
( Position(..)
|
||||
, view
|
||||
)
|
||||
|
||||
import Html
|
||||
import Html.Attributes as HtmlA
|
||||
|
||||
|
||||
view : Position -> String -> List (Html.Html msg) -> Html.Html msg
|
||||
view pos for =
|
||||
Html.node "paper-tooltip" [ position pos, HtmlA.attribute "for" for ]
|
||||
|
||||
|
||||
type Position
|
||||
= Top
|
||||
| Right
|
||||
| Bottom
|
||||
| Left
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
position : Position -> Html.Attribute msg
|
||||
position pos =
|
||||
let
|
||||
positionString =
|
||||
case pos of
|
||||
Top ->
|
||||
"top"
|
||||
|
||||
Right ->
|
||||
"right"
|
||||
|
||||
Bottom ->
|
||||
"bottom"
|
||||
|
||||
Left ->
|
||||
"left"
|
||||
in
|
||||
HtmlA.attribute "position" positionString
|
||||
@@ -1,95 +0,0 @@
|
||||
module Weightless exposing
|
||||
( button
|
||||
, card
|
||||
, expansion
|
||||
, listItem
|
||||
, navigationBar
|
||||
, popover
|
||||
, popoverCard
|
||||
, select
|
||||
, slider
|
||||
, switch
|
||||
, tab
|
||||
, tabGroup
|
||||
, textArea
|
||||
, textField
|
||||
, tooltip
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
|
||||
button : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
button =
|
||||
Html.node "wl-button"
|
||||
|
||||
|
||||
card : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
card =
|
||||
Html.node "wl-card"
|
||||
|
||||
|
||||
expansion : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
expansion attrs =
|
||||
Html.node "wl-expansion" (WlA.icon "" :: attrs)
|
||||
|
||||
|
||||
listItem : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
listItem =
|
||||
Html.node "wl-list-item"
|
||||
|
||||
|
||||
navigationBar : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
navigationBar =
|
||||
Html.node "wl-nav"
|
||||
|
||||
|
||||
popover : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
popover =
|
||||
Html.node "wl-popover"
|
||||
|
||||
|
||||
popoverCard : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
popoverCard =
|
||||
Html.node "wl-popover-card"
|
||||
|
||||
|
||||
select : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
select =
|
||||
Html.node "wl-select"
|
||||
|
||||
|
||||
slider : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
slider =
|
||||
Html.node "wl-slider"
|
||||
|
||||
|
||||
switch : List (Html.Attribute msg) -> Html msg
|
||||
switch attrs =
|
||||
Html.node "wl-switch" attrs []
|
||||
|
||||
|
||||
tab : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
tab =
|
||||
Html.node "wl-tab"
|
||||
|
||||
|
||||
tabGroup : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
tabGroup =
|
||||
Html.node "wl-tab-group"
|
||||
|
||||
|
||||
textArea : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
textArea =
|
||||
Html.node "wl-textarea"
|
||||
|
||||
|
||||
textField : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
textField =
|
||||
Html.node "wl-textfield"
|
||||
|
||||
|
||||
tooltip : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
tooltip =
|
||||
Html.node "wl-tooltip"
|
||||
@@ -1,432 +0,0 @@
|
||||
module Weightless.Attributes exposing
|
||||
( Alignment(..)
|
||||
, ExpansionSlot(..)
|
||||
, InputType(..)
|
||||
, ListItemSlot(..)
|
||||
, NavSlot(..)
|
||||
, TextFieldSlot(..)
|
||||
, XOrigin(..)
|
||||
, YOrigin(..)
|
||||
, active
|
||||
, align
|
||||
, anchor
|
||||
, anchorCloseEvents
|
||||
, anchorOpenEvents
|
||||
, anchorOrigin
|
||||
, checked
|
||||
, clickable
|
||||
, disableFocusTrap
|
||||
, disabled
|
||||
, expansionSlot
|
||||
, fab
|
||||
, filled
|
||||
, fixed
|
||||
, flat
|
||||
, hoverable
|
||||
, icon
|
||||
, inverted
|
||||
, label
|
||||
, list
|
||||
, listItemSlot
|
||||
, max
|
||||
, maxLength
|
||||
, min
|
||||
, minLength
|
||||
, name
|
||||
, navSlot
|
||||
, noRipple
|
||||
, outlined
|
||||
, readonly
|
||||
, selected
|
||||
, step
|
||||
, textFieldSlot
|
||||
, transformOrigin
|
||||
, type_
|
||||
, value
|
||||
, vertical
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Json.Encode as Json
|
||||
|
||||
|
||||
step : Int -> Html.Attribute msg
|
||||
step =
|
||||
numberAttr "step"
|
||||
|
||||
|
||||
min : Int -> Html.Attribute msg
|
||||
min =
|
||||
numberAttr "min"
|
||||
|
||||
|
||||
max : Int -> Html.Attribute msg
|
||||
max =
|
||||
numberAttr "max"
|
||||
|
||||
|
||||
maxLength : Int -> Html.Attribute msg
|
||||
maxLength =
|
||||
numberAttr "maxLength"
|
||||
|
||||
|
||||
minLength : Int -> Html.Attribute msg
|
||||
minLength =
|
||||
numberAttr "minLength"
|
||||
|
||||
|
||||
anchorOpenEvents : List String -> Html.Attribute msg
|
||||
anchorOpenEvents =
|
||||
stringArrayProperty "anchorOpenEvents"
|
||||
|
||||
|
||||
anchorCloseEvents : List String -> Html.Attribute msg
|
||||
anchorCloseEvents =
|
||||
stringArrayProperty "anchorCloseEvents"
|
||||
|
||||
|
||||
anchor : String -> Html.Attribute msg
|
||||
anchor id =
|
||||
stringAttr "anchor" ("#" ++ id)
|
||||
|
||||
|
||||
icon : String -> Html.Attribute msg
|
||||
icon icon_name =
|
||||
stringAttr "icon" icon_name
|
||||
|
||||
|
||||
name : String -> Html.Attribute msg
|
||||
name group =
|
||||
stringAttr "name" group
|
||||
|
||||
|
||||
type NavSlot
|
||||
= Title
|
||||
| Left
|
||||
| Right
|
||||
|
||||
|
||||
navSlot : NavSlot -> Html.Attribute msg
|
||||
navSlot slot =
|
||||
let
|
||||
textSlot =
|
||||
case slot of
|
||||
Title ->
|
||||
"title"
|
||||
|
||||
Left ->
|
||||
"left"
|
||||
|
||||
Right ->
|
||||
"right"
|
||||
in
|
||||
stringAttr "slot" textSlot
|
||||
|
||||
|
||||
type ExpansionSlot
|
||||
= ETitle
|
||||
| EDescription
|
||||
|
||||
|
||||
noRipple : Bool -> Html.Attribute msg
|
||||
noRipple enabled =
|
||||
HtmlA.property "noRipple" (Json.bool enabled)
|
||||
|
||||
|
||||
expansionSlot : ExpansionSlot -> Html.Attribute msg
|
||||
expansionSlot slot =
|
||||
let
|
||||
textSlot =
|
||||
case slot of
|
||||
ETitle ->
|
||||
"title"
|
||||
|
||||
EDescription ->
|
||||
"description"
|
||||
in
|
||||
stringAttr "slot" textSlot
|
||||
|
||||
|
||||
type ListItemSlot
|
||||
= BeforeItem
|
||||
| AfterItem
|
||||
|
||||
|
||||
listItemSlot : ListItemSlot -> Html.Attribute msg
|
||||
listItemSlot slot =
|
||||
let
|
||||
textSlot =
|
||||
case slot of
|
||||
BeforeItem ->
|
||||
"before"
|
||||
|
||||
AfterItem ->
|
||||
"after"
|
||||
in
|
||||
stringAttr "slot" textSlot
|
||||
|
||||
|
||||
type TextFieldSlot
|
||||
= BeforeText
|
||||
| AfterText
|
||||
|
||||
|
||||
textFieldSlot : TextFieldSlot -> Html.Attribute msg
|
||||
textFieldSlot slot =
|
||||
let
|
||||
textSlot =
|
||||
case slot of
|
||||
BeforeText ->
|
||||
"before"
|
||||
|
||||
AfterText ->
|
||||
"after"
|
||||
in
|
||||
stringAttr "slot" textSlot
|
||||
|
||||
|
||||
value : String -> Html.Attribute msg
|
||||
value text =
|
||||
stringAttr "value" text
|
||||
|
||||
|
||||
type Alignment
|
||||
= Start
|
||||
| Center
|
||||
| End
|
||||
|
||||
|
||||
align : Alignment -> Html.Attribute msg
|
||||
align alignment =
|
||||
let
|
||||
textAlignment =
|
||||
case alignment of
|
||||
Start ->
|
||||
"start"
|
||||
|
||||
Center ->
|
||||
"center"
|
||||
|
||||
End ->
|
||||
"end"
|
||||
in
|
||||
stringAttr "align" textAlignment
|
||||
|
||||
|
||||
list : String -> Html.Attribute msg
|
||||
list datalistSelector =
|
||||
stringAttr "list" datalistSelector
|
||||
|
||||
|
||||
type InputType
|
||||
= Password
|
||||
| Email
|
||||
| Number
|
||||
| Color
|
||||
| Date
|
||||
| Search
|
||||
| Tel
|
||||
| File
|
||||
|
||||
|
||||
type_ : InputType -> Html.Attribute msg
|
||||
type_ inputType =
|
||||
let
|
||||
textInputType =
|
||||
case inputType of
|
||||
Password ->
|
||||
"password"
|
||||
|
||||
Email ->
|
||||
"email"
|
||||
|
||||
Number ->
|
||||
"number"
|
||||
|
||||
Color ->
|
||||
"color"
|
||||
|
||||
Date ->
|
||||
"date"
|
||||
|
||||
Search ->
|
||||
"search"
|
||||
|
||||
Tel ->
|
||||
"tel"
|
||||
|
||||
File ->
|
||||
"file"
|
||||
in
|
||||
stringAttr "type" textInputType
|
||||
|
||||
|
||||
label : String -> Html.Attribute msg
|
||||
label text =
|
||||
stringAttr "label" text
|
||||
|
||||
|
||||
inverted : Html.Attribute msg
|
||||
inverted =
|
||||
presentAttribute "inverted"
|
||||
|
||||
|
||||
vertical : Html.Attribute msg
|
||||
vertical =
|
||||
presentAttribute "vertical"
|
||||
|
||||
|
||||
filled : Html.Attribute msg
|
||||
filled =
|
||||
presentAttribute "filled"
|
||||
|
||||
|
||||
outlined : Html.Attribute msg
|
||||
outlined =
|
||||
presentAttribute "outlined"
|
||||
|
||||
|
||||
flat : Html.Attribute msg
|
||||
flat =
|
||||
presentAttribute "flat"
|
||||
|
||||
|
||||
fab : Html.Attribute msg
|
||||
fab =
|
||||
presentAttribute "fab"
|
||||
|
||||
|
||||
hoverable : Html.Attribute msg
|
||||
hoverable =
|
||||
presentAttribute "hoverable"
|
||||
|
||||
|
||||
disabled : Html.Attribute msg
|
||||
disabled =
|
||||
presentAttribute "disabled"
|
||||
|
||||
|
||||
readonly : Html.Attribute msg
|
||||
readonly =
|
||||
presentAttribute "readonly"
|
||||
|
||||
|
||||
active : Html.Attribute msg
|
||||
active =
|
||||
presentAttribute "active"
|
||||
|
||||
|
||||
clickable : Html.Attribute msg
|
||||
clickable =
|
||||
presentAttribute "clickable"
|
||||
|
||||
|
||||
checked : Html.Attribute msg
|
||||
checked =
|
||||
presentAttribute "checked"
|
||||
|
||||
|
||||
fixed : Html.Attribute msg
|
||||
fixed =
|
||||
presentAttribute "fixed"
|
||||
|
||||
|
||||
selected : Html.Attribute msg
|
||||
selected =
|
||||
presentAttribute "selected"
|
||||
|
||||
|
||||
disableFocusTrap : Html.Attribute msg
|
||||
disableFocusTrap =
|
||||
presentAttribute "disablefocustrap"
|
||||
|
||||
|
||||
type XOrigin
|
||||
= XLeft
|
||||
| XCenter
|
||||
| XRight
|
||||
|
||||
|
||||
type YOrigin
|
||||
= YTop
|
||||
| YCenter
|
||||
| YBottom
|
||||
|
||||
|
||||
anchorOrigin : XOrigin -> YOrigin -> List (Html.Attribute msg)
|
||||
anchorOrigin =
|
||||
origin "anchor"
|
||||
|
||||
|
||||
transformOrigin : XOrigin -> YOrigin -> List (Html.Attribute msg)
|
||||
transformOrigin =
|
||||
origin "transform"
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
origin : String -> XOrigin -> YOrigin -> List (Html.Attribute msg)
|
||||
origin prefix xOrigin yOrigin =
|
||||
let
|
||||
( textXOrigin, textYOrigin ) =
|
||||
textOrigin xOrigin yOrigin
|
||||
in
|
||||
[ stringAttr (prefix ++ "originx") textXOrigin
|
||||
, stringAttr (prefix ++ "originy") textYOrigin
|
||||
]
|
||||
|
||||
|
||||
textOrigin : XOrigin -> YOrigin -> ( String, String )
|
||||
textOrigin xOrigin yOrigin =
|
||||
let
|
||||
textXOrigin =
|
||||
case xOrigin of
|
||||
XLeft ->
|
||||
"left"
|
||||
|
||||
XCenter ->
|
||||
"center"
|
||||
|
||||
XRight ->
|
||||
"right"
|
||||
|
||||
textYOrigin =
|
||||
case yOrigin of
|
||||
YTop ->
|
||||
"top"
|
||||
|
||||
YCenter ->
|
||||
"center"
|
||||
|
||||
YBottom ->
|
||||
"bottom"
|
||||
in
|
||||
( textXOrigin, textYOrigin )
|
||||
|
||||
|
||||
presentAttribute : String -> Html.Attribute msg
|
||||
presentAttribute attribute =
|
||||
HtmlA.attribute attribute attribute
|
||||
|
||||
|
||||
stringArrayProperty : String -> List String -> Html.Attribute msg
|
||||
stringArrayProperty property values =
|
||||
HtmlA.property property (values |> Json.list Json.string)
|
||||
|
||||
|
||||
stringAttr : String -> String -> Html.Attribute msg
|
||||
stringAttr attribute stringValue =
|
||||
HtmlA.attribute attribute stringValue
|
||||
|
||||
|
||||
numberAttr : String -> Int -> Html.Attribute msg
|
||||
numberAttr attribute intValue =
|
||||
intValue |> String.fromInt |> HtmlA.attribute attribute
|
||||
|
||||
|
||||
numberProp : String -> Int -> Html.Attribute msg
|
||||
numberProp property intValue =
|
||||
intValue |> Json.int |> HtmlA.property property
|
||||
@@ -1,54 +0,0 @@
|
||||
module Weightless.Internal exposing
|
||||
( boolProp
|
||||
, floatProp
|
||||
, numberAttr
|
||||
, numberProp
|
||||
, presentAttribute
|
||||
, stringArrayProperty
|
||||
, stringAttr
|
||||
, stringProp
|
||||
)
|
||||
|
||||
import Html
|
||||
import Html.Attributes as HtmlA
|
||||
import Json.Encode as Json
|
||||
|
||||
|
||||
presentAttribute : String -> Html.Attribute msg
|
||||
presentAttribute attribute =
|
||||
HtmlA.attribute attribute attribute
|
||||
|
||||
|
||||
stringArrayProperty : String -> List String -> Html.Attribute msg
|
||||
stringArrayProperty property values =
|
||||
HtmlA.property property (values |> Json.list Json.string)
|
||||
|
||||
|
||||
stringAttr : String -> String -> Html.Attribute msg
|
||||
stringAttr attribute stringValue =
|
||||
HtmlA.attribute attribute stringValue
|
||||
|
||||
|
||||
numberAttr : String -> Int -> Html.Attribute msg
|
||||
numberAttr attribute intValue =
|
||||
intValue |> String.fromInt |> HtmlA.attribute attribute
|
||||
|
||||
|
||||
numberProp : String -> Int -> Html.Attribute msg
|
||||
numberProp property intValue =
|
||||
intValue |> Json.int |> HtmlA.property property
|
||||
|
||||
|
||||
floatProp : String -> Float -> Html.Attribute msg
|
||||
floatProp property floatValue =
|
||||
floatValue |> Json.float |> HtmlA.property property
|
||||
|
||||
|
||||
boolProp : String -> Bool -> Html.Attribute msg
|
||||
boolProp property boolValue =
|
||||
boolValue |> Json.bool |> HtmlA.property property
|
||||
|
||||
|
||||
stringProp : String -> String -> Html.Attribute msg
|
||||
stringProp property stringValue =
|
||||
stringValue |> Json.string |> HtmlA.property property
|
||||
@@ -1,51 +0,0 @@
|
||||
module Weightless.ProgressBar exposing
|
||||
( determinate
|
||||
, indeterminate
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
import Weightless.Internal as Internal
|
||||
|
||||
|
||||
determinate : Float -> List (Html.Attribute msg) -> Html msg
|
||||
determinate completion attrs =
|
||||
progressBar ([ mode Determinate, value completion ] ++ attrs) []
|
||||
|
||||
|
||||
indeterminate : List (Html.Attribute msg) -> Html msg
|
||||
indeterminate attrs =
|
||||
progressBar attrs []
|
||||
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
type ProgressMode
|
||||
= Indeterminate
|
||||
| Determinate
|
||||
|
||||
|
||||
progressBar : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
progressBar =
|
||||
Html.node "wl-progress-bar"
|
||||
|
||||
|
||||
mode : ProgressMode -> Html.Attribute msg
|
||||
mode =
|
||||
progressModeName >> Internal.stringProp "mode"
|
||||
|
||||
|
||||
value : Float -> Html.Attribute msg
|
||||
value =
|
||||
Internal.floatProp "value"
|
||||
|
||||
|
||||
progressModeName : ProgressMode -> String
|
||||
progressModeName progressMode =
|
||||
case progressMode of
|
||||
Indeterminate ->
|
||||
"indeterminate"
|
||||
|
||||
Determinate ->
|
||||
"determinate"
|
||||
@@ -1,54 +0,0 @@
|
||||
module Weightless.Slider exposing
|
||||
( Slot(..)
|
||||
, max
|
||||
, min
|
||||
, slot
|
||||
, step
|
||||
, thumbLabel
|
||||
)
|
||||
|
||||
import Html
|
||||
import Weightless.Internal as Internal
|
||||
|
||||
|
||||
type Slot
|
||||
= Before
|
||||
| After
|
||||
| ThumbLabel
|
||||
|
||||
|
||||
slot : Slot -> Html.Attribute msg
|
||||
slot s =
|
||||
let
|
||||
textSlot =
|
||||
case s of
|
||||
Before ->
|
||||
"before"
|
||||
|
||||
After ->
|
||||
"after"
|
||||
|
||||
ThumbLabel ->
|
||||
"thumb-label"
|
||||
in
|
||||
Internal.stringAttr "slot" textSlot
|
||||
|
||||
|
||||
step : Int -> Html.Attribute msg
|
||||
step =
|
||||
Internal.numberProp "step"
|
||||
|
||||
|
||||
min : Int -> Html.Attribute msg
|
||||
min =
|
||||
Internal.numberProp "min"
|
||||
|
||||
|
||||
max : Int -> Html.Attribute msg
|
||||
max =
|
||||
Internal.numberProp "max"
|
||||
|
||||
|
||||
thumbLabel : Bool -> Html.Attribute msg
|
||||
thumbLabel =
|
||||
Internal.boolProp "thumbLabel"
|
||||
@@ -1,21 +1,25 @@
|
||||
@import "./card/variables";
|
||||
@import "./colors";
|
||||
@import "./fonts";
|
||||
@import "./card/sources";
|
||||
@import "./card/size";
|
||||
@use "_fonts";
|
||||
@use "cards/_size";
|
||||
@use "cards/_sources";
|
||||
|
||||
$call-color: #ffffff;
|
||||
$call-background: #000000;
|
||||
|
||||
$response-color: $call-background;
|
||||
$response-background: $call-color;
|
||||
|
||||
#game {
|
||||
--card-aspect-ratio: #{$card-ratio};
|
||||
@include fluid-type(20rem, 64rem, 0.5rem, 0.8rem);
|
||||
--card-aspect-ratio: #{size.$card-ratio};
|
||||
@include size.fluid-type(20rem, 64rem, 0.5rem, 0.8rem);
|
||||
}
|
||||
|
||||
.game-card {
|
||||
width: $card-width;
|
||||
margin: $card-margin;
|
||||
width: size.$card-width;
|
||||
margin: size.$card-margin;
|
||||
perspective: 100vw;
|
||||
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: "Helvetica Neue", "Nimbus Sans L", sans-serif;
|
||||
font-family: fonts.$card;
|
||||
font-weight: bold;
|
||||
|
||||
p {
|
||||
@@ -71,7 +75,7 @@
|
||||
position: relative;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
@include pad-to-aspect-ratio(var(--card-aspect-ratio));
|
||||
@include size.pad-to-aspect-ratio(var(--card-aspect-ratio));
|
||||
|
||||
& .side {
|
||||
position: absolute;
|
||||
@@ -1,3 +1,5 @@
|
||||
/// Single source of truth for colours in the application.
|
||||
|
||||
$primary: #42a5f5;
|
||||
$on-primary: #212121;
|
||||
$primary-light: #80d6ff;
|
||||
@@ -20,3 +22,5 @@ $background: #e2e2e2;
|
||||
$secondary-background: #cccccc;
|
||||
|
||||
$transparent: rgba(#ffffff, 0);
|
||||
|
||||
$card: #ffffff;
|
||||
@@ -0,0 +1,76 @@
|
||||
@use "./colors";
|
||||
|
||||
.error-overlay {
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
background-color: transparentize(colors.$secondary-background, 0.6);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
|
||||
padding: 3em;
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.help {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.errors {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #{colors.$on-error};
|
||||
background-color: #{colors.$error};
|
||||
|
||||
padding: 0.5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title > span {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
|
||||
.icon-prefix {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 0.5em;
|
||||
white-space: normal;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: block;
|
||||
justify-content: flex-end;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.report {
|
||||
margin: 0.5em;
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
height: 10em;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
$general: "Roboto", sans-serif;
|
||||
$card: "Helvetica Neue", "Nimbus Sans L", sans-serif;
|
||||
$game-code: "Source Code Pro", monospace;
|
||||
|
||||
@font-face {
|
||||
font-family: "Nimbus Sans L";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local("Nimbus Sans L"), local("NimbusSansL"),
|
||||
url("../../assets/fonts/nimbus-sans-l-regular.woff2") format("woff2");
|
||||
font-display: fallback;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -13,7 +17,7 @@
|
||||
font-weight: 700;
|
||||
src: local("Nimbus Sans L Bold"), local("NimbusSansL-Bold"),
|
||||
url("../../assets/fonts/nimbus-sans-l-700.woff2") format("woff2");
|
||||
font-display: fallback;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -1,11 +1,10 @@
|
||||
@import "./card/variables";
|
||||
@import "./colors";
|
||||
@import "~weightless/style/base/elevation";
|
||||
@import "./game/judging";
|
||||
@import "./game/playing";
|
||||
@import "./game/plays";
|
||||
@import "./game/history";
|
||||
@import "./pages/lobby/variables";
|
||||
@use "./_colors";
|
||||
@use "./cards";
|
||||
@use "cards/_size";
|
||||
@use "./game/_judging";
|
||||
@use "./game/_playing";
|
||||
@use "./game/_plays";
|
||||
@use "./game/_history";
|
||||
|
||||
// The main game area.
|
||||
#game {
|
||||
@@ -17,6 +16,8 @@
|
||||
|
||||
min-height: 100%;
|
||||
|
||||
//background: colors.$background url(../../assets/images/background.svg) repeat;
|
||||
|
||||
.action {
|
||||
position: fixed;
|
||||
bottom: 1em;
|
||||
@@ -45,57 +46,67 @@
|
||||
}
|
||||
}
|
||||
|
||||
#new-game-config {
|
||||
margin-right: 3em;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
#win-confetti-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#top-content {
|
||||
position: sticky;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
--progress-bar-bg: #{$error};
|
||||
font-size: 5em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
|
||||
#timer {
|
||||
flex-shrink: 0;
|
||||
height: 0.2em;
|
||||
}
|
||||
|
||||
#time-left {
|
||||
margin: 1rem;
|
||||
height: 4px;
|
||||
--mdc-linear-progress-buffer-color: #{colors.$error};
|
||||
}
|
||||
|
||||
.top-row {
|
||||
margin-top: -0.2em;
|
||||
height: 2.5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.help {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
|
||||
font-size: 2em;
|
||||
|
||||
> span {
|
||||
margin-left: 1em;
|
||||
flex-basis: 5em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#minor-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 1rem;
|
||||
|
||||
#context-help {
|
||||
flex-grow: 0;
|
||||
--card-padding: 0.5em;
|
||||
font-size: 1.2rem;
|
||||
|
||||
wl-popover-card {
|
||||
min-width: 10em;
|
||||
max-width: 20em;
|
||||
}
|
||||
}
|
||||
|
||||
wl-button {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
margin: 0.5em 1em 0 1em;
|
||||
}
|
||||
|
||||
// The button to scroll to the top of the game area.
|
||||
@@ -110,7 +121,7 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
wl-button {
|
||||
mwc-icon-button {
|
||||
pointer-events: all;
|
||||
position: sticky;
|
||||
bottom: 0.5em;
|
||||
@@ -137,7 +148,7 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 2em 0;
|
||||
padding: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@@ -146,7 +157,7 @@
|
||||
ol.card-set {
|
||||
&.picked {
|
||||
.side {
|
||||
border-color: $primary;
|
||||
border-color: colors.$primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +174,7 @@ ul.cards {
|
||||
margin-top: 0.5em;
|
||||
|
||||
li {
|
||||
min-width: $card-width;
|
||||
min-width: size.$card-width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +188,7 @@ ol.card-set {
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
height: $card-overlap;
|
||||
height: size.$card-overlap;
|
||||
overflow: visible;
|
||||
|
||||
&:hover {
|
||||
@@ -185,7 +196,7 @@ ol.card-set {
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
height: height($card-width, var(--card-aspect-ratio));
|
||||
height: size.height(size.$card-width, var(--card-aspect-ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
@use "_colors";
|
||||
@use "_fonts";
|
||||
|
||||
@use "~@material/card";
|
||||
@include card.core-styles;
|
||||
|
||||
/// Support styling for Material Web Components.
|
||||
|
||||
:root {
|
||||
--mdc-theme-primary: #{colors.$primary};
|
||||
--mdc-theme-on-primary: #{colors.$on-primary};
|
||||
|
||||
--mdc-theme-secondary: #{colors.$secondary};
|
||||
--mdc-theme-on-secondary: #{colors.$on-secondary};
|
||||
|
||||
--mdc-theme-surface: #{colors.$card};
|
||||
--mdc-theme-background: #{colors.$background};
|
||||
|
||||
--mdc-typography-font-family: #{fonts.$general};
|
||||
|
||||
--mdc-menu-item-height: 48px;
|
||||
|
||||
--mdc-icon-size: 0.75em;
|
||||
--mdc-icon-button-size: 1.5em;
|
||||
}
|
||||
|
||||
mwc-icon-button {
|
||||
svg,
|
||||
.fa-layers {
|
||||
display: block !important;
|
||||
width: 0.7em !important;
|
||||
height: 0.7em !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mdc-card {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
mwc-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mwc-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mwc-fab {
|
||||
line-height: 1;
|
||||
|
||||
&:focus,
|
||||
&::-moz-focus-inner {
|
||||
outline: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.svg-inline--fa {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
|
||||
transition: opacity 15ms linear 30ms,
|
||||
transform 0.27s cubic-bezier(0, 0, 0.2, 1) 0ms;
|
||||
|
||||
&.exited {
|
||||
transition: opacity 15ms linear 0.15s,
|
||||
transform 0.18s cubic-bezier(0.4, 0, 1, 1) 0ms;
|
||||
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.large {
|
||||
--button-fab-size: 3em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-anchor {
|
||||
position: relative;
|
||||
|
||||
mwc-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a > mwc-list-item {
|
||||
height: 48px;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "colors";
|
||||
@use "_colors";
|
||||
|
||||
.start-settings {
|
||||
position: fixed;
|
||||
@@ -26,6 +26,8 @@
|
||||
padding: 0 1em 1em 1em;
|
||||
border-radius: 0.5em;
|
||||
|
||||
color: colors.$on-primary;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -59,3 +61,14 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-size-slider {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
$call-color: #ffffff;
|
||||
$call-background: #000000;
|
||||
|
||||
$response-color: $call-background;
|
||||
$response-background: $call-color;
|
||||
|
||||
$card-width: 18em;
|
||||
$card-margin: 0.5em;
|
||||
$card-overlap: 4em;
|
||||
|
||||
$card-ratio: 5/7;
|
||||
$card-compact-ratio: 1;
|
||||
|
||||
@mixin pad-to-aspect-ratio($ratio) {
|
||||
padding-top: #{calc(1 / #{$ratio} * 100%)};
|
||||
}
|
||||
|
||||
@function height($width, $ratio) {
|
||||
@return calc(#{$width} * 1 / #{$ratio});
|
||||
}
|
||||
|
||||
@function strip-unit($value) {
|
||||
@return $value / ($value * 0 + 1);
|
||||
}
|
||||
|
||||
@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
|
||||
font-size: $min-font-size;
|
||||
@media screen and (min-width: $min-vw) {
|
||||
font-size: calc(
|
||||
#{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} *
|
||||
((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)})
|
||||
);
|
||||
}
|
||||
@media screen and (min-width: $max-vw) {
|
||||
font-size: $max-font-size;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,34 @@
|
||||
@import "./variables";
|
||||
$card-width: 18em;
|
||||
$card-margin: 0.5em;
|
||||
$card-overlap: 4em;
|
||||
|
||||
$card-ratio: 5/7;
|
||||
$card-compact-ratio: 1;
|
||||
|
||||
@mixin pad-to-aspect-ratio($ratio) {
|
||||
padding-top: #{calc(1 / #{$ratio} * 100%)};
|
||||
}
|
||||
|
||||
@function height($width, $ratio) {
|
||||
@return calc(#{$width} * 1 / #{$ratio});
|
||||
}
|
||||
|
||||
@function strip-unit($value) {
|
||||
@return $value / ($value * 0 + 1);
|
||||
}
|
||||
|
||||
@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
|
||||
font-size: $min-font-size;
|
||||
@media screen and (min-width: $min-vw) {
|
||||
font-size: calc(
|
||||
#{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} *
|
||||
((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)})
|
||||
);
|
||||
}
|
||||
@media screen and (min-width: $max-vw) {
|
||||
font-size: $max-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
.minimal-card-size #game {
|
||||
--card-aspect-ratio: 0;
|
||||
@@ -36,7 +66,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
.card-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
height: 1em;
|
||||
font-size: inherit;
|
||||
vertical-align: -0.125em;
|
||||
background-image: url(../../assets/images/cardcast.png);
|
||||
background-image: url(../../../assets/images/cardcast.png);
|
||||
background-size: contain;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
@import "./colors";
|
||||
|
||||
.error-overlay {
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
background-color: transparentize($secondary-background, 0.6);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
|
||||
padding: 3em;
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.help {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.errors {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
--expansion-color: #{$on-error};
|
||||
--expansion-header-description-color: #{$on-error};
|
||||
--expansion-bg: #{$error};
|
||||
|
||||
padding: 0.25em;
|
||||
|
||||
.description {
|
||||
white-space: normal;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.report {
|
||||
.body {
|
||||
margin: 0 1em;
|
||||
--input-color: #{$on-error};
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@import "../colors";
|
||||
@import "../card/size";
|
||||
@use "../_colors";
|
||||
@use "../cards/_size";
|
||||
|
||||
#history {
|
||||
h2 {
|
||||
@@ -15,10 +15,10 @@
|
||||
counter-reset: historic-round-number;
|
||||
|
||||
.historic-round:nth-child(even) {
|
||||
background-color: transparentize($primary-light, 0.6);
|
||||
background-color: transparentize(colors.$primary-light, 0.6);
|
||||
}
|
||||
.historic-round:nth-child(odd) {
|
||||
background-color: transparentize($secondary-light, 0.6);
|
||||
background-color: transparentize(colors.$secondary-light, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
}
|
||||
|
||||
.historic-call {
|
||||
flex-basis: $card-width;
|
||||
min-width: $card-width;
|
||||
flex-basis: size.$card-width;
|
||||
min-width: size.$card-width;
|
||||
|
||||
& > div {
|
||||
margin: 0.5em;
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../colors";
|
||||
@use "../_colors";
|
||||
|
||||
.liked {
|
||||
.side::after {
|
||||
@@ -12,14 +12,7 @@
|
||||
display: block;
|
||||
content: "";
|
||||
opacity: 0.3;
|
||||
//noinspection CssInvalidFunction
|
||||
background: inline-svg(
|
||||
"./assets/images/certificate-thumbs-up.svg",
|
||||
(
|
||||
path: (
|
||||
fill: $primary
|
||||
)
|
||||
)
|
||||
);
|
||||
background: colors.$primary;
|
||||
mask-image: url("../../../assets/images/certificate-thumbs-up.svg");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../card/variables";
|
||||
@use "../cards/_size";
|
||||
|
||||
// A hand of cards.
|
||||
.hand {
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
// Unknown plays for the round animated in at the top of the screen during the playing stage.
|
||||
.background-plays {
|
||||
--card-aspect-ratio: #{$card-ratio};
|
||||
--card-aspect-ratio: #{size.$card-ratio};
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -45,10 +45,11 @@
|
||||
|
||||
.play {
|
||||
position: relative;
|
||||
transform: translateY(-$card-width * 4);
|
||||
transform: translateY(size.$card-width * -4);
|
||||
transition: transform 500ms ease-in;
|
||||
width: $card-width + $card-margin * 2;
|
||||
margin-left: -0.5 * ($card-width + $card-margin * 2);
|
||||
width: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.game-card {
|
||||
transform: rotate(0);
|
||||
@@ -56,9 +57,6 @@
|
||||
transform-origin: center;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.played {
|
||||
@@ -66,7 +64,7 @@
|
||||
transition: transform 500ms ease-out;
|
||||
|
||||
.game-card {
|
||||
transform: rotate(var(--rotation));
|
||||
transform: rotate(var(--rotation, 0));
|
||||
transition: transform 500ms ease-out;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user