Add desktop notifications to let players know when they need to do something. Also moved the overlays into Elm rather than javascript hackery.

This commit is contained in:
Gareth Latty
2016-06-02 03:35:04 +01:00
parent b20a8b3d65
commit 494154bc2a
21 changed files with 541 additions and 189 deletions
+57 -57
View File
@@ -1,63 +1,63 @@
module MassiveDecks.Components.About exposing (aboutOverlay)
module MassiveDecks.Components.About exposing (show)
import Html exposing (..)
import Html.Attributes exposing (..)
import MassiveDecks.Components.Icon exposing (..)
import MassiveDecks.Components.Overlay as Overlay
{-| The about overlay for telling players about the game.
-}
aboutOverlay : Html a
aboutOverlay =
div [ id "about" ]
[ div [ class "mui-panel" ]
[ h1 [] [ icon "info-circle", text " About" ]
, p [] [ text "Massive Decks is a web game based on the excellent "
, a [ href "https://cardsagainsthumanity.com/", target "_blank" ] [ text "Cards against Humanity" ]
, text " - a party game where you play white cards to try and produce the most amusing outcome when "
, text "combined with the given black card."
]
, p [] [ text "Massive Decks is also inspired by: "
, ul [] [ li [] [ a [ href "https://www.cardcastgame.com/", target "_blank" ] [ text "Cardcast" ]
, text " - an app that allows you to play on a ChromeCast."
]
, li [] [ a [ href "http://pretendyoure.xyz/zy/", target "_blank" ] [ text "Pretend You're Xyzzy" ]
, text " - a web game where you can jump in with people you don't know."
]
]
]
, p [] [ text "This is an open source game developed in "
, a [ href "http://elm-lang.org/", target "_blank" ] [ text "Elm" ]
, text " for the client and "
, a [ href "http://www.scala-lang.org/", target "_blank" ] [ text "Scala" ]
, text " for the server."
]
, p [] [ text "We also use: "
, ul [] [ li [] [ a [ href "https://www.cardcastgame.com/", target "_blank" ] [ text "Cardcast" ]
, text "'s APIs for getting decks of cards (you can go there to make your own!)."
]
, li [] [ text "The "
, a [ href "https://www.playframework.com/", target "_blank" ] [ text "Play framework" ]
]
, li [] [ a [ href "http://lesscss.org/", target "_blank" ] [ text "Less" ] ]
, li [] [ a [ href "https://fortawesome.github.io/Font-Awesome/", target "_blank" ] [ text "Font Awesome" ] ]
, li [] [ a [ href "https://www.muicss.com", target "_blank" ] [ text "MUI" ] ]
]
]
, p [] [ text "Bug reports and contributions are welcome on the "
, a [ href "https://github.com/Lattyware/massivedecks", target "_blank" ] [ text "GitHub repository" ]
, text ", where you can find the complete source to the game, under the GPLv3 license. The game concept "
, text "'Cards against Humanity' is used under a "
, a [ href "https://creativecommons.org/licenses/by-nc-sa/2.0/", target "_blank" ] [ text "Creative Commons BY-NC-SA 2.0 license" ]
, text " granted by "
, a [ href "https://cardsagainsthumanity.com/", target "_blank" ] [ text "Cards against Humanity" ]
]
, p [ class "close-link" ]
[ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, attribute "onClick" "closeOverlay()"
] [ icon "times", text " Close" ] ]
]
]
show : Overlay.Message a
show = Overlay.Show icon title contents
icon : String
icon = "info-circle"
title : String
title = "About"
contents : List (Html a)
contents =
[ p [] [ text "Massive Decks is a web game based on the excellent "
, a [ href "https://cardsagainsthumanity.com/", target "_blank" ] [ text "Cards against Humanity" ]
, text " - a party game where you play white cards to try and produce the most amusing outcome when "
, text "combined with the given black card."
]
, p [] [ text "Massive Decks is also inspired by: "
, ul [] [ li [] [ a [ href "https://www.cardcastgame.com/", target "_blank" ] [ text "Cardcast" ]
, text " - an app that allows you to play on a ChromeCast."
]
, li [] [ a [ href "http://pretendyoure.xyz/zy/", target "_blank" ] [ text "Pretend You're Xyzzy" ]
, text " - a web game where you can jump in with people you don't know."
]
]
]
, p [] [ text "This is an open source game developed in "
, a [ href "http://elm-lang.org/", target "_blank" ] [ text "Elm" ]
, text " for the client and "
, a [ href "http://www.scala-lang.org/", target "_blank" ] [ text "Scala" ]
, text " for the server."
]
, p [] [ text "We also use: "
, ul [] [ li [] [ a [ href "https://www.cardcastgame.com/", target "_blank" ] [ text "Cardcast" ]
, text "'s APIs for getting decks of cards (you can go there to make your own!)."
]
, li [] [ text "The "
, a [ href "https://www.playframework.com/", target "_blank" ] [ text "Play framework" ]
]
, li [] [ a [ href "http://lesscss.org/", target "_blank" ] [ text "Less" ] ]
, li [] [ a [ href "https://fortawesome.github.io/Font-Awesome/", target "_blank" ] [ text "Font Awesome" ] ]
, li [] [ a [ href "https://www.muicss.com", target "_blank" ] [ text "MUI" ] ]
]
]
, p [] [ text "Bug reports and contributions are welcome on the "
, a [ href "https://github.com/Lattyware/massivedecks", target "_blank" ] [ text "GitHub repository" ]
, text ", where you can find the complete source to the game, under the GPLv3 license. The game concept "
, text "'Cards against Humanity' is used under a "
, a [ href "https://creativecommons.org/licenses/by-nc-sa/2.0/", target "_blank" ] [ text "Creative Commons BY-NC-SA 2.0 license" ]
, text " granted by "
, a [ href "https://cardsagainsthumanity.com/", target "_blank" ] [ text "Cards against Humanity" ]
]
]
@@ -0,0 +1,102 @@
port module MassiveDecks.Components.BrowserNotifications exposing (Model, Message, ConsumerMessage(..), Permission(..), init, update, subscriptions, notify, enable, disable)
import Maybe
import MassiveDecks.Util as Util
init : Bool -> Bool -> Model
init supported enabled =
{ supported = supported
, enabled = enabled
, permission = Nothing
}
type alias Model =
{ supported : Bool
, enabled : Bool
, permission : Maybe Permission
}
type alias Notification =
{ title : String
, icon : Maybe String
}
type Permission
= Granted
| Denied
| Default
update : Message -> Model -> (Model, Cmd Message, Cmd ConsumerMessage)
update message model =
case message of
PermissionGiven permission ->
({ model | permission = Just permission }, Cmd.none, Util.cmd (PermissionChanged permission))
SendNotification notification ->
if model.supported && model.enabled then
(model, notifications notification, Cmd.none)
else
(model, Cmd.none, Cmd.none)
EnableNotifications ->
({ model | enabled = True }, requestPermission (), Cmd.none)
DisableNotifications ->
({ model | enabled = False }, Cmd.none, Cmd.none)
type ConsumerMessage
= PermissionChanged Permission
type Message
= PermissionGiven Permission
| SendNotification Notification
| EnableNotifications
| DisableNotifications
subscriptions : Model -> Sub Message
subscriptions model =
if model.supported && model.enabled && model.permission == Nothing then
permissions permission
else
Sub.none
port permissions : (String -> msg) -> Sub msg
permission : String -> Message
permission name =
let
permission = case name of
"granted" -> Granted
"denied" -> Denied
"default" -> Default
_ ->
let
_ = Debug.log "Unexpected permission for browser notifications, assuming denied" name
in
Denied
in
PermissionGiven permission
notify : Notification -> Message
notify notification = SendNotification notification
port notifications : Notification -> Cmd msg
enable : Message
enable = EnableNotifications
disable : Message
disable = DisableNotifications
port requestPermission : () -> Cmd msg
@@ -0,0 +1,95 @@
port module MassiveDecks.Components.Overlay exposing (Model, Message(..), init, view, update, map)
import Json.Decode as Json
import Html.App as Html
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import MassiveDecks.Components.Icon as Icon
type alias Model a =
{ icon : String
, title : String
, contents : List (Html a)
, wrap : (Message a) -> a
, shown : Bool
}
type Message a
= Show String String (List (Html a))
| Hide
| NoOp
init : ((Message a) -> a) -> Model a
init wrap =
{ icon = ""
, title = ""
, contents = []
, wrap = wrap
, shown = False
}
map : (a -> b) -> Message a -> Message b
map mapper message =
case message of
Show icon title contents ->
Show icon title (List.map (Html.map mapper) contents)
Hide ->
Hide
NoOp ->
NoOp
update : Message a -> Model a -> Model a
update message model =
case message of
Show icon title contents ->
{ model | icon = icon
, title = title
, contents = contents
, shown = True
}
Hide ->
{ model | shown = False }
NoOp ->
model
view : Model a -> List (Html a)
view model =
if model.shown then
[ div [ id "mui-overlay", onClickIfId "mui-overlay" (model.wrap Hide) (model.wrap NoOp) ]
[ div [ class "overlay mui-panel" ]
([ h1 [] [ Icon.icon model.icon, text " ", text model.title ] ] ++
model.contents ++
[ p [ class "close-link"]
[ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, onClick (model.wrap Hide)
] [ Icon.icon "times", text " Close" ]
]
])
]
]
else
[]
onClickIfId : String -> msg -> msg -> Attribute msg
onClickIfId targetId message noOp =
on "click" (ifIdDecoder targetId |> Json.map (\match -> if match then message else noOp))
ifIdDecoder : String -> Json.Decoder Bool
ifIdDecoder targetId = Json.at [ "target", "id" ] Json.string |> Json.map ((==) targetId)
+1
View File
@@ -10,4 +10,5 @@ type alias Init =
, gameCode : Maybe String
, existingGame : Maybe GameCodeAndSecret
, seed : String
, browserNotificationsSupported : Bool
}
+77 -5
View File
@@ -8,11 +8,15 @@ import Time
import WebSocket
import AnimationFrame
import Html exposing (Html)
import MassiveDecks.API as API
import MassiveDecks.API.Request as Request
import MassiveDecks.Components.QR as QR
import MassiveDecks.Components.BrowserNotifications as BrowserNotifications
import MassiveDecks.Components.Overlay as Overlay
import MassiveDecks.Models exposing (Init)
import MassiveDecks.Models.JSON.Decode exposing (lobbyDecoder)
import MassiveDecks.Models.JSON.Encode exposing (encodePlayerSecret)
@@ -38,11 +42,12 @@ init init lobbyAndHand secret =
, hand = lobbyAndHand.hand
, config = Config.init
, playing = Playing.init init
, browserNotifications = BrowserNotifications.init init.browserNotificationsSupported False
, secret = secret
, init = init
, notification = Nothing
} ! [ QR.encodeAndRender "invite-qr-code" (Util.lobbyUrl init.url lobbyAndHand.lobby.gameCode)
]
, qrNeedsRendering = False
} ! []
{-| Subscriptions for the lobby.
@@ -55,10 +60,15 @@ subscriptions model =
Just round -> Playing.subscriptions model.playing |> Sub.map PlayingMessage
websocket = WebSocket.listen (webSocketUrl model.init.url model.lobby.gameCode) webSocketResponseDecoder
browserNotifications = BrowserNotifications.subscriptions model.browserNotifications |> Sub.map BrowserNotificationsMessage
render = if model.qrNeedsRendering then [ AnimationFrame.diffs (\_ -> LocalMessage RenderQr) ] else []
in
Sub.batch [ delegated |> Sub.map LocalMessage
, websocket
]
Sub.batch ([ delegated |> Sub.map LocalMessage
, websocket
, browserNotifications |> Sub.map LocalMessage
] ++ render)
webSocketUrl : String -> String -> String
@@ -138,6 +148,15 @@ update message model =
in
({ model | playing = playing }, Cmd.map (LocalMessage << PlayingMessage) cmd)
BrowserNotificationsMessage notificationMessage ->
let
(browserNotifications, localCmd, cmd) = BrowserNotifications.update notificationMessage model.browserNotifications
in
{ model | browserNotifications = browserNotifications } !
[ Cmd.map (LocalMessage << BrowserNotificationsMessage) localCmd
, Cmd.map overlayAlert cmd
]
UpdateLobby lobby ->
model |> updateLobbyAndHand { lobby = lobby, hand = model.hand }
:> updateHandIfRoundStarted
@@ -148,6 +167,12 @@ update message model =
Identify ->
(model, Cmd.map LocalMessage (WebSocket.send (webSocketUrl model.init.url model.lobby.gameCode) (encodePlayerSecret model.secret |> encode 0)))
DisplayInviteOverlay ->
{ model | qrNeedsRendering = True } ! [ Util.cmd (UI.inviteOverlay model.init.url model.lobby.gameCode |> OverlayMessage) ]
RenderQr ->
{ model | qrNeedsRendering = False } ! [ QR.encodeAndRender "invite-qr-code" (Util.lobbyUrl model.init.url model.lobby.gameCode) ]
DismissNotification notification ->
let
newModel =
@@ -182,6 +207,35 @@ update message model =
Event.PlayerLeft id ->
notificationChange model (Notification.playerLeft id players)
Event.RoundJudging _ ->
case model.lobby.round of
Nothing ->
(model, Cmd.none)
Just round ->
let
cmd = if (round.czar == model.secret.id) then
Util.cmd (BrowserNotifications.notify { title = "You need to pick a winner for the round.", icon = icon model "gavel" } |> BrowserNotificationsMessage |> LocalMessage)
else
Cmd.none
in
(model, cmd)
Event.PlayerStatus id status ->
let
cmd = if (id == model.secret.id) then
case status of
Player.NotPlayed ->
Util.cmd (BrowserNotifications.notify { title = "You need to play a card for the round.", icon = icon model "hourglass" } |> BrowserNotificationsMessage |> LocalMessage)
Player.Skipping ->
Util.cmd (BrowserNotifications.notify { title = "You are being skipped due to inactivity.", icon = icon model "fast-forward"} |> BrowserNotificationsMessage |> LocalMessage)
_ ->
Cmd.none
else
Cmd.none
in
(model, cmd)
_ ->
(model, Cmd.none)
@@ -189,6 +243,24 @@ update message model =
(model, Cmd.none)
overlayAlert : BrowserNotifications.ConsumerMessage -> ConsumerMessage
overlayAlert message =
case message of
BrowserNotifications.PermissionChanged permission ->
case permission of
BrowserNotifications.Denied ->
(Overlay.Show "times-circle" "Can't enable desktop notifications."
[ Html.text "You did not give Massive Decks permission to give you desktop notifications." ]
) |> OverlayMessage
_ ->
NoOp |> LocalMessage
icon : Model -> String -> Maybe String
icon model name = Just (model.init.url ++ "assets/images/icons/" ++ name ++ ".png")
type alias Update = Model -> (Model, Cmd ConsumerMessage)
+73 -41
View File
@@ -65,31 +65,80 @@ diffPlayer oldPlayers newPlayer =
diffRound : Maybe Game.Round -> Maybe Game.Round -> List Event
diffRound oldRound newRound =
let
differentRound = Maybe.map .call oldRound /= Maybe.map .call newRound
differentOrChangedRoundEvents = if differentRound then
List.filterMap identity
[ Maybe.map roundStart newRound
, newRound `Maybe.andThen` (\newRound -> case newRound.responses of
Card.Hidden count -> Just (roundPlayed count)
Card.Revealed _ -> Nothing)
]
else
Maybe.map2 changedRound oldRound newRound |> Maybe.withDefault []
roundEvent = newRound `Maybe.andThen` (\newRound -> case newRound.responses of
Card.Hidden count -> Nothing
Card.Revealed revealed -> roundEnd newRound.call newRound.czar revealed)
in
differentOrChangedRoundEvents `Util.andMaybe` roundEvent
differentRounds = (Maybe.map .call oldRound) /= (Maybe.map .call newRound)
changedRound : Game.Round -> Game.Round -> List Event
changedRound oldRound newRound =
case oldRound.responses of
Card.Hidden oldCount ->
case newRound.responses of
Card.Hidden newCount -> if (oldCount < newCount) then [ roundPlayed (newCount - oldCount) ] else []
Card.Revealed _ -> [ roundJudging newRound ]
Card.Revealed responses ->
[]
start = case newRound of
Just round -> if differentRounds then [ roundStart round ] else []
Nothing ->
[]
played = if differentRounds then [] else case newRound of
Just round ->
let
oldCount = playedInRound oldRound |> Maybe.withDefault 0
newCount = playedInRound newRound |> Maybe.withDefault 0
in
if oldCount < newCount then [ RoundPlayed (newCount - oldCount) ] else []
Nothing ->
[]
judging = case newRound of
Just round ->
case round.responses of
Card.Hidden _ ->
[]
Card.Revealed responses ->
let
oldJudging = Maybe.map (\or -> case or.responses of
Card.Hidden _ -> False
Card.Revealed oldResponses -> Util.isNothing oldResponses.playedByAndWinner) oldRound |> Maybe.withDefault False
newJudging = Util.isNothing responses.playedByAndWinner
in
if newJudging && ((not oldJudging) || (oldJudging && differentRounds)) then
[ RoundJudging responses.cards ]
else
[]
Nothing ->
[]
ended = case newRound of
Just round ->
case round.responses of
Card.Hidden _ ->
[]
Card.Revealed responses ->
case responses.playedByAndWinner of
Nothing ->
[]
Just playedByAndWinner ->
let
oldEnded = Maybe.map (\or -> case or.responses of
Card.Hidden _ -> False
Card.Revealed oldResponses -> not (Util.isNothing oldResponses.playedByAndWinner)) oldRound |> Maybe.withDefault False
in
if (not oldEnded) || (oldEnded && differentRounds) then
[ RoundEnd round.call round.czar responses.cards playedByAndWinner ]
else
[]
Nothing ->
[]
in
List.concat
[ start
, played
, judging
, ended
]
playedInRound : Maybe Game.Round -> Maybe Int
playedInRound maybeRound =
let
calc = (\round -> case round.responses of
Card.Hidden count -> Just count
Card.Revealed _ -> Nothing)
in
maybeRound `Maybe.andThen` calc
{- Event Constructors -}
@@ -113,20 +162,3 @@ playerDisconnect player = PlayerDisconnect player.id
roundStart : Game.Round -> Event
roundStart round = RoundStart round.call round.czar
roundPlayed : Int -> Event
roundPlayed amount = RoundPlayed amount
roundJudging : Game.Round -> Event
roundJudging round =
let
responses = case round.responses of
Card.Hidden _ -> Nothing
Card.Revealed responses -> Just responses
played = Maybe.map .cards responses |> Maybe.withDefault []
in
RoundJudging played
roundEnd : Card.Call -> Player.Id -> Card.RevealedResponses -> Maybe Event
roundEnd call czar responses =
Maybe.map (RoundEnd call czar responses.cards) responses.playedByAndWinner
@@ -7,12 +7,15 @@ import MassiveDecks.Scenes.Lobby.Event as Event exposing (Event)
import MassiveDecks.Scenes.Config.Messages as Config
import MassiveDecks.Scenes.Playing.Messages as Playing
import MassiveDecks.Components.Errors as Errors
import MassiveDecks.Components.Overlay as Overlay
import MassiveDecks.Components.BrowserNotifications as BrowserNotifications
{-| This type is used for all sending of messages, allowing us to send messages handled outside this scene.
-}
type ConsumerMessage
= ErrorMessage Errors.Message
| OverlayMessage (Overlay.Message Message)
| Leave
| LocalMessage Message
@@ -24,7 +27,10 @@ type Message
| UpdateLobby Game.Lobby
| UpdateHand Card.Hand
| Identify
| DisplayInviteOverlay
| RenderQr
| NoOp
| GameEvent Event
| BrowserNotificationsMessage BrowserNotifications.Message
| ConfigMessage Config.ConsumerMessage
| PlayingMessage Playing.ConsumerMessage
@@ -7,6 +7,7 @@ import MassiveDecks.Models.Card as Card
import MassiveDecks.Models.Notification as Notification exposing (Notification)
import MassiveDecks.Scenes.Config.Models as Config
import MassiveDecks.Scenes.Playing.Models as Playing
import MassiveDecks.Components.BrowserNotifications as BrowserNotifications
{-| The state of the lobby.
@@ -16,7 +17,9 @@ type alias Model =
, hand : Card.Hand
, config : Config.Model
, playing : Playing.Model
, browserNotifications : BrowserNotifications.Model
, secret : Player.Secret
, init : Init
, notification : Maybe Notification
, qrNeedsRendering : Bool
}
+72 -50
View File
@@ -1,13 +1,15 @@
module MassiveDecks.Scenes.Lobby.UI exposing (view)
module MassiveDecks.Scenes.Lobby.UI exposing (view, inviteOverlay)
import Html exposing (..)
import Html.App as Html
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import MassiveDecks.Components.About as About
import MassiveDecks.Components.QR as QR
import MassiveDecks.Components.Icon as Icon
import MassiveDecks.Components.About as About
import MassiveDecks.Components.Overlay as Overlay
import MassiveDecks.Components.BrowserNotifications as BrowserNotifications
import MassiveDecks.Scenes.Config as Config
import MassiveDecks.Scenes.Playing as Playing
import MassiveDecks.Scenes.Lobby.Models exposing (Model)
@@ -24,7 +26,6 @@ view model =
url = model.init.url
gameCode = lobby.gameCode
players = lobby.players
notification = model.notification
(header, contents) = case lobby.round of
Nothing -> ([], [ Config.view model |> Html.map (ConfigMessage >> LocalMessage) ])
Just round ->
@@ -33,12 +34,10 @@ view model =
in
(h |> List.map (Html.map (PlayingMessage >> LocalMessage)), c |> List.map (Html.map (PlayingMessage >> LocalMessage)))
in
root [ appHeader header notification
root [ appHeader header model
, spacer
, scores players
, contentWrapper contents
, inviteOverlay url gameCode
, About.aboutOverlay
]
@@ -83,12 +82,12 @@ score player =
]
appHeader : List (Html ConsumerMessage) -> Maybe Notification -> Html ConsumerMessage
appHeader contents notification = (header [] [ div [ class "mui-appbar mui--appbar-line-height" ]
appHeader : List (Html ConsumerMessage) -> Model -> Html ConsumerMessage
appHeader contents model = (header [] [ div [ class "mui-appbar mui--appbar-line-height" ]
[ div [ class "mui--appbar-line-height" ]
[ span [ class "score-buttons" ] (List.append [ scoresButton True, scoresButton False ] (notificationPopup notification))
[ span [ class "score-buttons" ] (List.append [ scoresButton True, scoresButton False ] (notificationPopup model.notification))
, span [ id "title", class "mui--text-title mui--visible-xs-inline-block" ] contents
, gameMenu ] ] ])
, gameMenu model ] ] ])
scoresButton : Bool -> Html msg
@@ -152,56 +151,79 @@ playerIcon player =
{-| The overlay for inviting players to a lobby.
-}
inviteOverlay : String -> String -> Html msg
inviteOverlay appUrl lobbyId =
inviteOverlay : String -> String -> Overlay.Message msg
inviteOverlay appUrl gameCode =
let
url = Util.lobbyUrl appUrl lobbyId
url = Util.lobbyUrl appUrl gameCode
in
div [ id "invite" ]
[ div [ class "mui-panel" ]
[ h1 [] [ Icon.icon "bullhorn", text " Invite Players" ]
, p [] [ text "To invite other players, simply send them this link: " ]
, p [] [ a [ href url ] [ text url ] ]
, p [] [ text "Have them scan this QR code: " ]
, QR.view "invite-qr-code"
, p [] [ text "Or give them this game code to enter on the start page: " ]
, p [] [ input [ readonly True, value lobbyId ] [] ]
, p [ class "close-link" ]
[ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, attribute "onClick" "closeOverlay()"
] [ Icon.icon "times", text " Close" ] ]
]
Overlay.Show "bullhorn" "Invite Players"
[ p [] [ text "To invite other players, simply send them this link: " ]
, p [] [ a [ href url ] [ text url ] ]
, p [] [ text "Have them scan this QR code: " ]
, QR.view "invite-qr-code"
, p [] [ text "Or give them this game code to enter on the start page: " ]
, p [] [ input [ readonly True, value gameCode ] [] ]
]
notificationsMenuItem : BrowserNotifications.Model -> List (Html ConsumerMessage)
notificationsMenuItem model =
let
(notClickable, enabled) =
if not model.supported then
(Just "Your browser does not support desktop notifications.", False)
else if model.permission == Just BrowserNotifications.Denied then
(Just "You have denied Massive Decks permission to display desktop notifications.", False)
else
(Nothing, model.enabled)
classes = classList
[ ("link", True)
, ("disabled", not (Util.isNothing notClickable))
]
extraAttrs =
case notClickable of
Nothing ->
[ onClick (LocalMessage <| BrowserNotificationsMessage <| (if enabled then BrowserNotifications.disable else BrowserNotifications.enable)) ]
Just msg ->
[ title msg ]
attributes = [ classes, attribute "tabindex" "0", attribute "role" "button" ] ++ extraAttrs
description = " " ++ (if enabled then "Disable" else "Enable") ++ " Notifications"
in
[ li [] [ a attributes [ Icon.fwIcon (if enabled then "bell-slash" else "bell"), text description ] ]
]
{-| The menu for the game.
-}
gameMenu : Html ConsumerMessage
gameMenu = div [ class "menu mui-dropdown" ]
gameMenu : Model -> Html ConsumerMessage
gameMenu model = div [ class "menu mui-dropdown" ]
[ button [ class "mui-btn mui-btn--small mui-btn--primary"
, attribute "data-mui-toggle" "dropdown"
, title "Game menu."
] [ Icon.fwIcon "ellipsis-h" ]
, ul [ class "mui-dropdown__menu mui-dropdown__menu--right" ]
[ li [] [ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, attribute "onClick" "inviteOverlay()"
] [ Icon.fwIcon "bullhorn", text " Invite Players" ] ]
, li [] [ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, onClick Leave
] [ Icon.fwIcon "sign-out", text " Leave Game" ] ]
, li [ class "mui-divider" ] []
, li [] [ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, attribute "onClick" "aboutOverlay()"
] [ Icon.fwIcon "info-circle", text " About" ] ]
, li [] [ a [ href "https://github.com/Lattyware/massivedecks/issues/new", target "_blank" ]
[ Icon.fwIcon "bug", text " Report a bug" ] ]
]
([ li [] [ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, onClick (DisplayInviteOverlay |> LocalMessage)
] [ Icon.fwIcon "bullhorn", text " Invite Players" ] ]
] ++ (notificationsMenuItem model.browserNotifications) ++
[ li [] [ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, onClick Leave
] [ Icon.fwIcon "sign-out", text " Leave Game" ] ]
, li [ class "mui-divider" ] []
, li [] [ a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
, onClick (About.show |> OverlayMessage)
] [ Icon.fwIcon "info-circle", text " About" ] ]
, li [] [ a [ href "https://github.com/Lattyware/massivedecks/issues/new", target "_blank" ]
[ Icon.fwIcon "bug", text " Report a bug" ] ]
])
]
@@ -357,7 +357,12 @@ renderSkippingNotice : List (Html Message)
renderSkippingNotice =
[ div [ class "notice" ]
[ h3 [] [ Icon.icon "fast-forward" ]
, span [] [ text "You are currently being skipped because you took too long to play."
, span [] [ text "You are currently being skipped because you took too long to play. "
, a [ class "link"
, attribute "tabindex" "0"
, attribute "role" "button"
]
[ Icon.fwIcon "bell", text "Enable Notifications?" ]
]
, div [ class "actions" ]
[ button [ class "mui-btn mui-btn--small"
+11 -3
View File
@@ -8,6 +8,7 @@ import MassiveDecks.Models.Game as Game
import MassiveDecks.Components.Storage as Storage
import MassiveDecks.Components.Input as Input
import MassiveDecks.Components.Errors as Errors
import MassiveDecks.Components.Overlay as Overlay
import MassiveDecks.Scenes.Start.Messages exposing (InputId(..), Message(..))
import MassiveDecks.Scenes.Start.Models exposing (Model)
import MassiveDecks.Scenes.Start.UI as UI
@@ -36,6 +37,7 @@ init init =
, gameCodeInput = Input.init GameCode "game-code-input" [ text "The code for the game to join." ] (init.gameCode |> Maybe.withDefault "") "" InputMessage
, info = Nothing
, errors = Errors.init
, overlay = Overlay.init OverlayMessage
}
, command)
@@ -65,9 +67,9 @@ view model =
Html.map LobbyMessage (Lobby.view lobby)
in
div []
[ contents
, Errors.view model.errors |> Html.map ErrorMessage
]
([ contents
, Errors.view model.errors |> Html.map ErrorMessage
] ++ Overlay.view model.overlay)
{-| Handles messages and alters the model as appropriate.
@@ -114,11 +116,17 @@ update message model =
, gameCodeInput = gameCodeInput
}, Cmd.batch [ nameCmd, gameCodeCmd ])
OverlayMessage overlayMessage ->
({ model | overlay = Overlay.update overlayMessage model.overlay }, Cmd.none)
LobbyMessage message ->
case message of
Lobby.ErrorMessage message ->
(model, Util.cmd (ErrorMessage message))
Lobby.OverlayMessage message ->
(model, Util.cmd (OverlayMessage (Overlay.map (Lobby.LocalMessage >> LobbyMessage) message)))
Lobby.Leave ->
let
leave = case model.lobby of
@@ -2,6 +2,7 @@ module MassiveDecks.Scenes.Start.Messages exposing (..)
import MassiveDecks.Components.Input as Input
import MassiveDecks.Components.Errors as Errors
import MassiveDecks.Components.Overlay as Overlay
import MassiveDecks.Scenes.Lobby.Messages as Lobby
import MassiveDecks.Models.Game as Game
import MassiveDecks.Models.Player as Player
@@ -19,6 +20,7 @@ type Message
| InputMessage (Input.Message InputId)
| LobbyMessage Lobby.ConsumerMessage
| ErrorMessage Errors.Message
| OverlayMessage (Overlay.Message Message)
| NoOp
@@ -4,6 +4,7 @@ import MassiveDecks.Models exposing (Init)
import MassiveDecks.Scenes.Lobby.Models as Lobby
import MassiveDecks.Components.Input as Input
import MassiveDecks.Components.Errors as Errors
import MassiveDecks.Components.Overlay as Overlay
import MassiveDecks.Scenes.Start.Messages exposing (Message, InputId(..))
@@ -16,4 +17,5 @@ type alias Model =
, gameCodeInput : Input.Model InputId Message
, info : Maybe String
, errors : Errors.Model
, overlay : Overlay.Model Message
}
+1 -2
View File
@@ -42,10 +42,9 @@ view model =
, a [ class "about-link mui--divider-top link"
, attribute "tabindex" "0"
, attribute "role" "button"
, attribute "onClick" "aboutOverlay()"
, onClick (About.show |> OverlayMessage)
]
[ Icon.icon "question-circle" , text " About" ]
, About.aboutOverlay
, div [ id "forkongithub" ] [ div [] [ a [ href "https://github.com/lattyware/massivedecks", target "_blank" ]
[ Icon.icon "github", text " Fork me on GitHub" ] ] ]
])
+8
View File
@@ -131,3 +131,11 @@ apply fs value = List.map (\f -> f value) fs
after : Time -> Task x a -> Task x a
after waitFor task =
Process.sleep waitFor `Task.andThen` (\_ -> task)
{-| Check if a Maybe is Nothing.
-}
isNothing : Maybe a -> Bool
isNothing m =
case m of
Just _ -> False
Nothing -> True