diff --git a/app/assets/stylesheets/lobby.less b/app/assets/stylesheets/lobby.less index e9c5cc6..350a2d8 100644 --- a/app/assets/stylesheets/lobby.less +++ b/app/assets/stylesheets/lobby.less @@ -124,43 +124,46 @@ margin: 10px auto; } -ol.picked { - margin: 0px; - padding: 0px; - list-style-type: none; - white-space: nowrap; +.picked { + ol { + display: inline; + margin: 0px; + padding: 0px; + list-style-type: none; + white-space: nowrap; - & > li { - display: inline-block; - position: relative; - white-space: normal; - - .card { - margin: 10px 5px 10px 5px; - } - - & > .response { + & > li { display: inline-block; - position: relative; + white-space: normal; - & > .withdraw-button { - position: absolute; - left: -4px; - bottom: -9px; + .card { + margin: 10px 5px 10px 5px; } - } - &:not(:first-child) { - margin-left: -(@card-width * @card-overlap); - @media (min-width: @enhance-width) { - margin-left: -(@card-width-enhanced * @card-overlap); + & > .response { + display: inline-block; + position: relative; + + & > .withdraw-button { + position: absolute; + left: -4px; + bottom: -9px; + } + } + + &:not(:first-child) { + margin-left: -(@card-width * @card-overlap); + @media (min-width: @enhance-width) { + margin-left: -(@card-width-enhanced * @card-overlap); + } } } } -} -.play-area > .round-area > ol.picked > li.play-button { - margin-left: -45px; + .play-button { + display: inline; + margin-left: -45px; + } } ol.considering { @@ -187,7 +190,7 @@ ol.considering { } } -.play-area > .round-area > ol.considering > li.choose-button { +.choose-button { margin-left: -45px; } diff --git a/client/src/MassiveDecks/Components/About.elm b/client/src/MassiveDecks/Components/About.elm index e42b328..50148c7 100644 --- a/client/src/MassiveDecks/Components/About.elm +++ b/client/src/MassiveDecks/Components/About.elm @@ -23,43 +23,43 @@ title = "About" contents : String -> List (Html a) contents version = ([ p [] [ text "Massive Decks is a web game based on the excellent " - , a [ href "https://cardsagainsthumanity.com/", target "_blank" ] [ text "Cards against Humanity" ] + , a [ href "https://cardsagainsthumanity.com/", target "_blank", rel "noopener" ] [ 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" ] + , ul [] [ li [] [ a [ href "https://www.cardcastgame.com/", target "_blank", rel "noopener" ] [ 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" ] + , li [] [ a [ href "http://pretendyoure.xyz/zy/", target "_blank", rel "noopener" ] [ 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" ] + , a [ href "http://elm-lang.org/", target "_blank", rel "noopener" ] [ text "Elm" ] , text " for the client and " - , a [ href "http://www.scala-lang.org/", target "_blank" ] [ text "Scala" ] + , a [ href "http://www.scala-lang.org/", target "_blank", rel "noopener" ] [ text "Scala" ] , text " for the server." ] , p [] [ text "We also use: " - , ul [] [ li [] [ a [ href "https://www.cardcastgame.com/", target "_blank" ] [ text "Cardcast" ] + , ul [] [ li [] [ a [ href "https://www.cardcastgame.com/", target "_blank", rel "noopener" ] [ 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" ] + , a [ href "https://www.playframework.com/", target "_blank", rel "noopener" ] [ 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" ] ] + , li [] [ a [ href "http://lesscss.org/", target "_blank", rel "noopener" ] [ text "Less" ] ] + , li [] [ a [ href "https://fortawesome.github.io/Font-Awesome/", target "_blank", rel "noopener" ] [ text "Font Awesome" ] ] + , li [] [ a [ href "https://www.muicss.com", target "_blank", rel "noopener" ] [ text "MUI" ] ] ] ] , p [] [ text "Bug reports and contributions are welcome on the " - , a [ href "https://github.com/Lattyware/massivedecks", target "_blank" ] [ text "GitHub repository" ] + , a [ href "https://github.com/Lattyware/massivedecks", target "_blank", rel "noopener" ] [ 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" ] + , a [ href "https://creativecommons.org/licenses/by-nc-sa/2.0/", target "_blank", rel "noopener" ] [ text "Creative Commons BY-NC-SA 2.0 license" ] , text " granted by " - , a [ href "https://cardsagainsthumanity.com/", target "_blank" ] [ text "Cards against Humanity" ] + , a [ href "https://cardsagainsthumanity.com/", target "_blank", rel "noopener" ] [ text "Cards against Humanity" ] ] ]) ++ (if String.isEmpty version then [] else [ p [] [ text ("This server is running version " ++ version ++ ".") ] ]) diff --git a/client/src/MassiveDecks/Components/Errors.elm b/client/src/MassiveDecks/Components/Errors.elm index ee4b435..04eccc3 100644 --- a/client/src/MassiveDecks/Components/Errors.elm +++ b/client/src/MassiveDecks/Components/Errors.elm @@ -86,7 +86,7 @@ errorMessage applicationInfo error = url = reportUrl applicationInfo (reportText error.message) bugReportLink = if error.bugReport then - Just (p [] [ a [ href url, target "_blank" ] [ Icon.icon "bug", text " Report this as a bug." ] ]) + Just (p [] [ a [ href url, target "_blank", rel "noopener" ] [ Icon.icon "bug", text " Report this as a bug." ] ]) else Nothing in diff --git a/client/src/MassiveDecks/Scenes/Config/UI.elm b/client/src/MassiveDecks/Scenes/Config/UI.elm index 957a58d..424b7b7 100644 --- a/client/src/MassiveDecks/Scenes/Config/UI.elm +++ b/client/src/MassiveDecks/Scenes/Config/UI.elm @@ -64,7 +64,7 @@ invite appUrl lobbyId = deckIdInputLabel : List (Html msg) deckIdInputLabel = [ text " A " - , a [ href "https://www.cardcastgame.com/browse", target "_blank" ] [ text "Cardcast" ] + , a [ href "https://www.cardcastgame.com/browse", target "_blank", rel "noopener" ] [ text "Cardcast" ] , text " Play Code" ] @@ -89,31 +89,42 @@ deckList decks loadingDecks deckId = , th [ title "Responses" ] [ Icon.icon "square-o" ] ] ] - , tbody [] (List.concat + , Util.tbody [] (List.concat [ emptyDeckListInfo ((List.isEmpty decks) && List.isEmpty loadingDecks) - , List.map (\deck -> tr [] - [ td [] [ deckLink deck.id ] - , td [ title deck.name ] [ text deck.name ] - , td [] [ text (toString deck.calls) ] - , td [] [ text (toString deck.responses) ] - ]) decks - , List.map (\deck -> tr [] [ td [] [ deckLink deck ], td [ colspan 3 ] [ Icon.spinner ] ]) loadingDecks - , [ tr [] [ td [ colspan 4 ] [ Input.view deckId ] ] ] + , List.map loadedDeckEntry decks + , List.map loadingDeckEntry loadingDecks + , [ ("!!input", tr [] [ td [ colspan 4 ] [ Input.view deckId ] ]) ] ]) ] +loadedDeckEntry : Game.DeckInfo -> (String, Html msg) +loadedDeckEntry deck = + let + row = tr [] [ td [] [ deckLink deck.id ] + , td [ title deck.name ] [ text deck.name ] + , td [] [ text (toString deck.calls) ] + , td [] [ text (toString deck.responses) ] + ] + in + (deck.id, row) + +loadingDeckEntry : String -> (String, Html msg) +loadingDeckEntry deckId = + let + row = tr [] [ td [] [ deckLink deckId ], td [ colspan 3 ] [ Icon.spinner ] ] + in + (deckId, row) deckLink : String -> Html msg -deckLink id = a [ href ("https://www.cardcastgame.com/browse/deck/" ++ id), target "_blank" ] [ text id ] +deckLink id = a [ href ("https://www.cardcastgame.com/browse/deck/" ++ id), target "_blank", rel "noopener" ] [ text id ] - -emptyDeckListInfo : Bool -> List (Html Message) +emptyDeckListInfo : Bool -> List (String, Html Message) emptyDeckListInfo display = if display then - [ tr [] [ td [ colspan 4 ] + [ ("!!emptyInfo", tr [] [ td [ colspan 4 ] [ Icon.icon "info-circle" , text " You will need to add at least one " - , a [ href "https://www.cardcastgame.com/browse", target "_blank" ] [ text "Cardcast deck" ] + , a [ href "https://www.cardcastgame.com/browse", target "_blank", rel "noopener" ] [ text "Cardcast deck" ] , text " to the game." , text " Not sure? Try " , a [ class "link" @@ -123,7 +134,7 @@ emptyDeckListInfo display = ] [ text "clicking here to add the Cards Against Humanity base set" ] , text "." ] - ] + ]) ] else [] diff --git a/client/src/MassiveDecks/Scenes/History/UI.elm b/client/src/MassiveDecks/Scenes/History/UI.elm index 993b84e..6a6224f 100644 --- a/client/src/MassiveDecks/Scenes/History/UI.elm +++ b/client/src/MassiveDecks/Scenes/History/UI.elm @@ -5,6 +5,7 @@ import Dict import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) +import Html.Keyed as Keyed import MassiveDecks.Scenes.History.Models exposing (Model) import MassiveDecks.Scenes.History.Messages exposing (ConsumerMessage(..)) @@ -20,7 +21,7 @@ view model players = let content = case model.rounds of Just rounds -> - ul [] (List.map (finishedRound players) rounds) + Keyed.ul [] (List.map (\round -> (round.call.id, finishedRound players round)) rounds) Nothing -> Icon.spinner @@ -45,14 +46,13 @@ finishedRound players round = li [ class "round" ] [ div [] [ div [ class "who" ] czar, CardsUI.call round.call [] ] - , ul [ class "plays" ] (List.map (responses players round.playedByAndWinner.winner) playedCardsByPlayer) + , Keyed.ul [ class "plays" ] (List.map (\(id, cards) -> (toString id, responses players round.playedByAndWinner.winner id cards)) playedCardsByPlayer) ] -responses : List Player -> Player.Id -> (Player.Id, List Card.Response) -> Html msg -responses players winnerId idAndResponses = +responses : List Player -> Player.Id -> Player.Id -> List Card.Response -> Html msg +responses players winnerId playerId responses = let - (playerId, responses) = idAndResponses winner = playerId == winnerId winnerPrefix = if (winner) then [ Icon.icon "trophy", text " " ] else [] player = @@ -63,7 +63,7 @@ responses players winnerId idAndResponses = li [] [ div [ class "responses" ] [ div [ classList [ ("who", True), ("won", winner) ] ] player - , ul [] (List.map (\r -> li [] [ CardsUI.response False [] r ]) responses) + , Keyed.ul [] (List.map (\r -> (r.id, li [] [ CardsUI.response False [] r ])) responses) ] ] diff --git a/client/src/MassiveDecks/Scenes/Lobby/UI.elm b/client/src/MassiveDecks/Scenes/Lobby/UI.elm index 5c9a591..9b37ca3 100644 --- a/client/src/MassiveDecks/Scenes/Lobby/UI.elm +++ b/client/src/MassiveDecks/Scenes/Lobby/UI.elm @@ -71,7 +71,7 @@ scores shownAsOverlay players = else [] sidebar = - div [ id "scores", classList [ ("shownAsOverlay", shownAsOverlay) ] ] + div [ id "scores", classList [ ("shownAsOverlay", shownAsOverlay), ("mui--z1", True) ] ] [ div [ id "scores-title", class "mui--appbar-line-height mui--text-headline" ] ([ text "Players" ] ++ closeLink) , div [ class "mui-divider" ] [] , table [ class "mui-table" ] @@ -113,7 +113,7 @@ score player = appHeader : List (Html ConsumerMessage) -> Model -> Html ConsumerMessage -appHeader contents model = (header [] [ div [ class "mui-appbar mui--appbar-line-height" ] +appHeader contents model = (header [] [ div [ class "mui-appbar mui--z1 mui--appbar-line-height" ] [ div [ class "mui--appbar-line-height" ] [ span [ class "score-buttons" ] ([ scoresButton ] ++ (notificationPopup model.notification)) , span [ id "title", class "mui--text-title mui--visible-xs-inline-block" ] contents @@ -262,7 +262,7 @@ gameMenu model = , attribute "role" "button" , onClick (About.show model.init.version |> OverlayMessage) ] [ Icon.fwIcon "info-circle", text " About" ] ] - , li [] [ a [ href url, target "_blank" ] + , li [] [ a [ href url, target "_blank", rel "noopener" ] [ Icon.fwIcon "bug", text " Report a bug" ] ] ]) ] diff --git a/client/src/MassiveDecks/Scenes/Playing.elm b/client/src/MassiveDecks/Scenes/Playing.elm index 30688a8..d967a2b 100644 --- a/client/src/MassiveDecks/Scenes/Playing.elm +++ b/client/src/MassiveDecks/Scenes/Playing.elm @@ -256,9 +256,18 @@ updatePositioning shownPlayed seed = (ShownPlayedCards (shownPlayed.animated ++ newAnimated) [], newSeed) -randomPositioning : Random.Generator ShownCard -randomPositioning = Random.map4 ShownCard (Random.int -90 90) (Random.int 0 50) Random.bool (Random.int -5 1) - - initialRandomPositioning : Random.Generator ShownCard -initialRandomPositioning = Random.map3 (\r h l -> ShownCard r h l -100) (Random.int -75 75) (Random.int 0 50) Random.bool +initialRandomPositioning = + Random.map3 (\r h l -> ShownCard r h l -100) + (Random.int -75 75) + (Random.int 0 50) + Random.bool + + +randomPositioning : Random.Generator ShownCard +randomPositioning = + Random.map4 ShownCard + (Random.int -90 90) + (Random.int 25 50) + Random.bool + (Random.int -5 1) diff --git a/client/src/MassiveDecks/Scenes/Playing/UI.elm b/client/src/MassiveDecks/Scenes/Playing/UI.elm index 6196742..488d812 100644 --- a/client/src/MassiveDecks/Scenes/Playing/UI.elm +++ b/client/src/MassiveDecks/Scenes/Playing/UI.elm @@ -4,6 +4,7 @@ import Html exposing (..) import Html.App as Html import Html.Attributes exposing (..) import Html.Events exposing (..) +import Html.Keyed as Keyed import MassiveDecks.Components.Icon as Icon import MassiveDecks.Models.Game as Game @@ -146,10 +147,9 @@ getById cards id = List.filter (\card -> card.id == id) cards |> List.head consideringView : Int -> List Card.Response -> Bool -> Html Message consideringView considering consideringCards isCzar = let - extra = if isCzar then [ chooseButton considering ] else [] + extra = if isCzar then [ ("!!button", chooseButton considering) ] else [] in - ol [ class "considering" ] - (List.append (List.map (\card -> li [] [ (playedResponse card) ]) consideringCards) extra) + div [] ([ Keyed.ol [ class "considering" ] ((List.map (\card -> (card.id, li [] [ (playedResponse card) ])) consideringCards) ++ extra) ]) winnerHeaderAndContents : Game.FinishedRound -> List Player -> (List (Html Message), List (Html Message)) @@ -180,13 +180,13 @@ playArea : List (Html Message) -> Html Message playArea contents = div [ class "play-area" ] contents -response : List String -> Bool -> Card.Response -> Html Message +response : List String -> Bool -> Card.Response -> (String, Html Message) response picked disabled response = let isPicked = List.member response.id picked clickHandler = if isPicked || disabled then [] else [ onClick (Pick response.id) ] in - CardsUI.response isPicked clickHandler response + (response.id, CardsUI.response isPicked clickHandler response) blankResponse : ShownCard -> Html Message @@ -205,12 +205,12 @@ positioning shownCard = ] -handRender : Bool -> List (Html Message) -> Html Message +handRender : Bool -> List (String, Html Message) -> Html Message handRender disabled contents = let classes = "hand mui--divider-top" ++ if disabled then " disabled" else "" in - ul [ class classes ] (List.map (\item -> li [] [ item ]) contents) + Keyed.ul [ class classes ] (List.map (\(key, item) -> (key, li [] [ item ])) contents) handView : List String -> Bool -> List Card.Response -> Html Message @@ -218,15 +218,19 @@ handView picked disabled responses = handRender disabled (List.map (response picked disabled) responses) -pickedResponse : Card.Response -> Html Message +pickedResponse : Card.Response -> (String, Html Message) pickedResponse response = - li [] - [ div [ class "card response mui-panel" ] [ div [ class "response-text" ] - [ text (Util.firstLetterToUpper response.text) - , text "." - ] - , withdrawButton response.id - ] ] + let + item = li [] + [ div [ class "card response mui-panel" ] [ div [ class "response-text" ] + [ text (Util.firstLetterToUpper response.text) + , text "." + ] + , withdrawButton response.id + ] + ] + in + (response.id, item) pickedView : List Card.Response -> Int -> List (ShownCard) -> List (Html Message) @@ -234,9 +238,9 @@ pickedView picked slots shownPlayed = let numberPicked = List.length picked pb = if (numberPicked < slots) then [] else [ playButton ] - pickedResponses = List.map (pickedResponse) picked in - [ ol [ class "picked" ] (List.concat [ pickedResponses, pb ]) + [ div [ class "picked" ] + ([ Keyed.ol [] (List.map pickedResponse picked) ] ++ pb) , div [ class "others-picked" ] (List.map blankResponse shownPlayed) ] @@ -250,9 +254,9 @@ withdrawButton id = button playButton : Html Message -playButton = li [ class "play-button" ] [ button - [ class "mui-btn mui-btn--small mui-btn--accent mui-btn--fab", title "Play these responses.", onClick Play ] - [ Icon.icon "check" ] ] +playButton = button + [ class "play-button mui-btn mui-btn--small mui-btn--accent mui-btn--fab", title "Play these responses.", onClick Play ] + [ Icon.icon "check" ] playedView : Bool -> Card.RevealedResponses -> Html Message @@ -276,9 +280,9 @@ playedResponse response = chooseButton : Int -> Html Message -chooseButton playedId = li [ class "choose-button" ] [ button - [ class "mui-btn mui-btn--small mui-btn--accent mui-btn--fab", onClick (Choose playedId) ] - [ Icon.icon "trophy" ] ] +chooseButton playedId = + button [ class "choose-button mui-btn mui-btn--small mui-btn--accent mui-btn--fab", onClick (Choose playedId) ] + [ Icon.icon "trophy" ] infoBar : Game.Lobby -> Player.Secret -> List (Html Message) @@ -288,7 +292,7 @@ infoBar lobby secret = in case content of Just message -> - [ div [ id "info-bar" ] + [ div [ id "info-bar", class "mui--z1" ] [ Icon.icon "info-circle" , text " " , text message diff --git a/client/src/MassiveDecks/Scenes/Start/UI.elm b/client/src/MassiveDecks/Scenes/Start/UI.elm index 65e6fac..d14f965 100644 --- a/client/src/MassiveDecks/Scenes/Start/UI.elm +++ b/client/src/MassiveDecks/Scenes/Start/UI.elm @@ -21,10 +21,6 @@ view model = let nameEntered = not (String.isEmpty model.nameInput.value) gameCodeEntered = not (String.isEmpty model.gameCodeInput.value) - active = ([ class "mui--is-active" ], " mui--is-active") - inactive = ([], "") - ((createLiClass, createDivClass), (joinLiClass, joinDivClass)) = - if model.init.gameCode /= Nothing then (inactive, active) else (active, inactive) versionInfo = if String.isEmpty model.init.version then [] else [ text " Version ", text model.init.version ] in div [ id "start-screen" ] @@ -39,7 +35,7 @@ view model = , onClick (About.show model.init.version |> OverlayMessage) ] [ Icon.icon "question-circle" , text " About" ] - , div [ id "forkongithub" ] [ div [] [ a [ href "https://github.com/lattyware/massivedecks", target "_blank" ] + , div [ id "forkongithub" ] [ div [] [ a [ href "https://github.com/lattyware/massivedecks", target "_blank", rel "noopener" ] [ Icon.icon "github", text " Fork me on GitHub" ] ] ] ]) , footer [] diff --git a/client/src/MassiveDecks/Util.elm b/client/src/MassiveDecks/Util.elm index f7b0d92..d5a3d49 100644 --- a/client/src/MassiveDecks/Util.elm +++ b/client/src/MassiveDecks/Util.elm @@ -7,6 +7,7 @@ import Process import Time exposing (Time) import Html exposing (..) +import Html.Keyed as Keyed import Html.Events exposing (..) @@ -152,7 +153,7 @@ onKeyDown key message noOp = on "keydown" (Json.at [ "key" ] Json.string |> Json.map (\pressed -> if pressed == key then message else noOp)) -{-| Perform an action on click, only if the id of the clicked element matches (i.e: only on click for a given element +{-| Perform an action on click, only if the id of the clicked element matches (i.e: only on click for a given element in the tree). -} onClickIfId : String -> msg -> msg -> Attribute msg @@ -161,3 +162,9 @@ onClickIfId targetId message noOp = ifIdDecoder : Json.Decoder String ifIdDecoder = Json.at [ "target", "id" ] Json.string + + +{-| Create a tbody where each row is keyed. +-} +tbody : List (Attribute msg) -> List (String, Html msg) -> Html msg +tbody = Keyed.node "tbody" diff --git a/elm-package.json b/elm-package.json index 9a46750..ffbd6f6 100644 --- a/elm-package.json +++ b/elm-package.json @@ -9,11 +9,11 @@ "exposed-modules": [], "dependencies": { "elm-lang/animation-frame": "1.0.0 <= v < 2.0.0", - "elm-lang/core": "4.0.0 <= v < 5.0.0", - "elm-lang/html": "1.0.0 <= v < 2.0.0", - "elm-lang/websocket": "1.0.0 <= v < 2.0.0", + "elm-lang/core": "4.0.1 <= v < 5.0.0", + "elm-lang/html": "1.1.0 <= v < 2.0.0", + "elm-lang/websocket": "1.0.1 <= v < 2.0.0", "elm-lang/window": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0" -} \ No newline at end of file +} diff --git a/public/javascripts/util.js b/public/javascripts/util.js index 009439f..2295cd9 100644 --- a/public/javascripts/util.js +++ b/public/javascripts/util.js @@ -1,5 +1,5 @@ function toggleWarningDrawer() { - document.getElementById('#warning-drawer').classList.toggle('shut'); + document.getElementById('warning-drawer').classList.toggle('shut'); } function start(url, version) { @@ -34,7 +34,7 @@ function start(url, version) { }); game.ports.qr.subscribe(function (idAndValue) { - new QRCode(document.getElementById('#' + idAndValue.id), { + new QRCode(document.getElementById(idAndValue.id), { text: idAndValue.value, width: 200, height: 200