Add AtP ad, ad configuration.

This commit is contained in:
Gareth Latty
2022-05-20 21:11:44 +01:00
parent d0c0225cc1
commit c5078ad341
25 changed files with 413 additions and 66 deletions
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="200" viewBox="0 0 135 52.9">
<defs/>
<defs>
<linearGradient id="a">
<stop stop-color="#f69c30"/>
</linearGradient>
<linearGradient id="b">
<stop stop-color="#30b4f6"/>
</linearGradient>
</defs>
<path fill="url(#b)" d="M13.2 25v4.63l1.66-.66 1.65.66V25z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -26.7 -91.5)"/>
<path stroke-width=".13" d="M29.08 9.42l-.56.02h-1.63l-.57-.02-.55 1.82q-.24.06-.7.06-.44 0-.72-.1l-.06-.09 2.6-7.81q.43-.06.93-.06t.87.07l2.55 7.82q-.34.2-.82.2-.46 0-.67-.18-.2-.18-.34-.67zM26.7 8.2l.52-.01h1l.5.01-.36-1.18q-.4-1.33-.62-2.26h-.07q-.1.5-.51 1.97z" aria-label="A"/>
<path fill="url(#a)" d="M17.5 25v4.63l1.66-.66 1.65.66V25z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -26.7 -91.5)"/>
<path stroke-width=".13" d="M44.5 4.65l.01.87v5.8q-.34.05-.8.05-.43 0-.77-.05v-5.8l.01-.87-.7.02h-1.3q-.06-.3-.06-.66t.06-.65h5.58q.08.29.08.64 0 .34-.2.5t-.64.16h-.54l-.71-.01z" aria-label="T"/>
<path fill="url(#b)" d="M26.2 25v4.63l1.65-.66 1.66.66V25z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -26.7 -91.5)"/>
<path stroke-width=".13" d="M76.43 4.65v6.67q-.33.05-.78.05-.44 0-.78-.05v-5.8l.01-.87-.7.02h-1.3q-.06-.3-.06-.66t.06-.65h5.58q.08.29.08.64 0 .34-.2.5t-.64.16h-.54l-.71-.01z" aria-label="T"/>
<path fill="url(#a)" d="M30.6 25v4.63l1.65-.66 1.66.66V25z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -26.7 -91.5)"/>
<path stroke-width=".13" d="M93.3 6.49V3.29q.3-.06.76-.06.48 0 .8.06v7.96q-.33.06-.8.06-.45 0-.76-.06V7.8h-2.88l.02.86v2.6q-.3.05-.78.05t-.78-.06V3.3q.3-.06.78-.06t.78.06v2.34l-.02.86.63-.01h1.64z" aria-label="H"/>
<path fill="url(#b)" d="M35 25v4.63l1.65-.66 1.66.66V25z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -26.7 -91.5)"/>
<path stroke-width=".13" d="M106.52 9.42l-.01.73.81-.03h2.86q0 .38-.04.57-.13.75-1 .75h-3.27q-.42 0-.67-.25-.23-.25-.23-.68V3.54l.08-.09h4.85q.06.3.06.63t-.13.69h-3.33l.01.8v1.2l.76-.01h1.96q.1.27.1.64t-.1.64h-2.72z" aria-label="E"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="200" viewBox="0 0 67.73 52.92"><defs/><g fill="none" stroke="#000" stroke-width="1.94"><path d="M0 1.32h67.73M0 21.17h67.73"/></g></svg>

After

Width:  |  Height:  |  Size: 196 B

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="200" viewBox="0 0 135 52.9">
<defs/>
<defs>
<linearGradient id="a">
<stop stop-color="#f69c30"/>
</linearGradient>
<linearGradient id="b">
<stop stop-color="#30b4f6"/>
</linearGradient>
</defs>
<path fill="url(#a)" d="M9.31 31.4v9.26l3.31-1.32 3.3 1.32V31.4z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -31.5 -96.5)"/>
<path stroke-width=".26" d="M14.74 35.99H12.6v5.28q-.65.12-1.56.12t-1.56-.12V25.45l.16-.14 3.15-.07 1.95-.02q2.52 0 4 1.38 1.52 1.37 1.52 4 0 1.3-.4 2.3t-1.12 1.68T17 35.63q-1 .35-2.26.35zm-2.14-8.33v5.94l1.54-.02q.65 0 1.09-.07.47-.1.77-.3.33-.24.58-.68.24-.4.35-.88.12-.52.12-1.03 0-.53-.12-1.05-.11-.53-.4-.95t-.74-.68-1.16-.25h-1.2q-.48 0-.83-.03z" aria-label="P"/>
<path fill="url(#b)" d="M16.5 31.4v9.26l3.3-1.32 3.31 1.32V31.4z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -31.5 -96.5)"/>
<path stroke-width=".26" d="M43.73 37.82l-1.14.03h-3.26l-1.14-.03-1.1 3.64q-.48.12-1.4.12-.88 0-1.44-.21l-.11-.17 5.2-15.67q.87-.11 1.87-.11t1.75.14l5.1 15.67q-.68.4-1.63.4-.93 0-1.35-.36-.4-.35-.68-1.33zm-4.77-2.47l1.05-.02h2l.99.02-.7-2.38q-.8-2.65-1.26-4.51h-.14q-.2 1-1.02 3.93z" aria-label="A"/>
<path fill="url(#a)" d="M23.7 31.4v9.26l3.3-1.32 3.32 1.32V31.4z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -31.5 -96.5)"/>
<path stroke-width=".26" d="M72.73 30.11q0 .78-.24 1.47-.2.68-.6 1.24-.4.54-.93.95-.52.4-1.14.61.6.2 1 .72.42.52.72 1.33l.86 2.35q.21.59.47 1t.63.7q-.3.42-.89.68-.56.25-1.28.25-.7 0-1.12-.41t-.77-1.42l-.98-2.78q-.3-.81-.77-1.24-.47-.44-1.4-.44h-1.42v6.1q-.32.04-.72.06-.4.05-.86.05-.87 0-1.52-.12V25.4l.16-.14 3.2-.07 2.12-.02q2.52 0 4 1.26 1.49 1.26 1.49 3.66zm-7.86-2.53v5.13q1.22-.07 2.15-.07 2.49 0 2.49-2.51T67 27.6h-1.25q-.52 0-.89-.03z" aria-label="R"/>
<path fill="url(#b)" d="M30.9 31.4v9.26l3.3-1.32 3.31 1.32V31.4z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -31.5 -96.5)"/>
<path stroke-width=".26" d="M95.48 27.91l.03 1.73v11.6q-.68.11-1.59.11-.88 0-1.56-.11v-11.6l.02-1.73-1.42.03h-2.58q-.12-.59-.12-1.31t.12-1.3h11.15q.17.58.17 1.27 0 .68-.4 1t-1.28.33h-1.1l-1.42-.02z" aria-label="T"/>
<path fill="url(#a)" d="M38.1 31.4v9.26l3.3-1.32 3.32 1.32V31.4z" paint-order="stroke fill markers" transform="matrix(3.67 0 0 3.67 -31.5 -96.5)"/>
<path stroke-width=".26" d="M121.9 30.11l1.85-4.62q.53-.12 1.35-.12t1.47.21l.11.19-4.47 10.68v4.95q-.6.12-1.56.12t-1.56-.12v-5.06l-4.48-10.5q.98-.55 1.77-.55t1.17.3.65 1l1.47 3.59q.6 1.59 1 3.12h.14q.38-1.35 1.1-3.21z" aria-label="Y"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+14 -3
View File
@@ -88,7 +88,9 @@ init flags url key =
, speech = speech
, notifications = Notifications.init
, remoteMode = remoteMode
, serverVersion = Nothing
, sources = { builtIn = Nothing, manyDecks = Nothing, jsonAgainstHumanity = Nothing }
, adverts = { manyDecks = False, atTheParty = False }
}
( page, pageCmd ) =
@@ -99,7 +101,7 @@ init flags url key =
( Pages.Loading, Cmd.none )
sourceCmd =
Request.map (Error.Add >> ErrorMsg) never UpdateSources |> Api.sourceInfo |> Http.request
Request.map (Error.Add >> ErrorMsg) never UpdateServerConfig |> Api.serverConfig |> Http.request
in
( { page = page, shared = shared, errorOverlay = Overlay.init }
, Cmd.batch [ sourceCmd, pageCmd, settingsCmd, speechCmd ]
@@ -271,12 +273,21 @@ update msg model =
in
( { model | shared = { oldShared | notifications = notifications } }, notificationsCmd )
UpdateSources info ->
UpdateServerConfig { serverVersion, sourceInfo, adverts } ->
let
oldShared =
model.shared
in
( { model | shared = { oldShared | sources = info } }, Cmd.none )
( { model
| shared =
{ oldShared
| serverVersion = Just serverVersion
, sources = sourceInfo
, adverts = adverts
}
}
, Cmd.none
)
Refresh ->
( model, Navigation.reload )
+2 -2
View File
@@ -1,6 +1,5 @@
module MassiveDecks.Messages exposing (Msg(..))
import MassiveDecks.Card.Source.Model as Source
import MassiveDecks.Cast.Model as Cast
import MassiveDecks.Error.Messages as Error
import MassiveDecks.Notifications.Model as Notifications
@@ -8,6 +7,7 @@ import MassiveDecks.Pages.Lobby.Messages as Lobby
import MassiveDecks.Pages.Lobby.Model as Lobby
import MassiveDecks.Pages.Route exposing (Route)
import MassiveDecks.Pages.Start.Messages as Start
import MassiveDecks.ServerConfig exposing (ServerConfig)
import MassiveDecks.Settings.Messages as Settings
import MassiveDecks.Speech as Speech
import Url exposing (Url)
@@ -28,6 +28,6 @@ type Msg
| UpdateToken Lobby.Auth
| CastStatusUpdate Cast.Status
| RemoteCommand Cast.RemoteControlCommand
| UpdateSources Source.Info
| UpdateServerConfig ServerConfig
| Refresh
| BlockedExternalUrl
+3
View File
@@ -7,6 +7,7 @@ import Browser.Navigation as Navigation
import MassiveDecks.Card.Source.Model as Sources
import MassiveDecks.Cast.Model as Cast
import MassiveDecks.Notifications.Model as Notifications
import MassiveDecks.ServerConfig exposing (Adverts)
import MassiveDecks.Settings.Model as Settings exposing (Settings)
import MassiveDecks.Speech as Speech
import MassiveDecks.Strings.Languages.Model exposing (Language)
@@ -24,7 +25,9 @@ type alias Shared =
, speech : Speech.Model
, notifications : Notifications.Model
, remoteMode : Bool
, serverVersion : Maybe String
, sources : Sources.Info
, adverts : Adverts
}
@@ -16,6 +16,7 @@ module MassiveDecks.Models.Decoders exposing
, privilege
, remoteControlCommand
, revealingRound
, serverConfig
, settings
, sourceInfo
, specificRound
@@ -53,6 +54,7 @@ import MassiveDecks.Pages.Lobby.Events as Events exposing (Event)
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
import MassiveDecks.Pages.Lobby.Model as Lobby exposing (Lobby)
import MassiveDecks.Pages.Start.LobbyBrowser.Model as LobbyBrowser
import MassiveDecks.ServerConfig as ServerConfig exposing (ServerConfig)
import MassiveDecks.Settings.Model as Settings exposing (Settings)
import MassiveDecks.Speech as Speech
import MassiveDecks.Strings.Languages as Lang
@@ -62,6 +64,13 @@ import MassiveDecks.Util.NeList as NonEmptyList
import Set exposing (Set)
adverts : Json.Decoder ServerConfig.Adverts
adverts =
Json.succeed ServerConfig.Adverts
|> Json.optional "manyDecks" Json.bool False
|> Json.optional "atTheParty" Json.bool False
sourceInfo : Json.Decoder Source.Info
sourceInfo =
let
@@ -79,6 +88,14 @@ sourceInfo =
|> Json.andThen atLeastOne
serverConfig : Json.Decoder ServerConfig
serverConfig =
Json.succeed ServerConfig
|> Json.required "version" Json.string
|> Json.required "sources" sourceInfo
|> Json.optional "adverts" adverts { manyDecks = False, atTheParty = False }
builtInInfo : Json.Decoder BuiltIn.Info
builtInInfo =
Json.succeed BuiltIn.Info
+29 -7
View File
@@ -200,13 +200,34 @@ view shared model =
model.route
manyDecksAd { baseUrl } =
Html.blankA
[ HtmlA.href baseUrl, HtmlA.id "many-decks-ad", Strings.ManyDecksWhereToGet |> Lang.title shared ]
[ Html.div []
[ Icon.boxOpen |> Icon.viewIcon
, Html.span [] [ Strings.ManyDecks |> Lang.string shared |> Html.text ]
if shared.adverts.manyDecks then
Html.blankA
[ HtmlA.href baseUrl, HtmlA.id "many-decks-ad", Strings.ManyDecksWhereToGet |> Lang.title shared ]
[ Html.div []
[ Icon.boxOpen |> Icon.viewIcon
, Html.span [] [ Strings.ManyDecks |> Lang.string shared |> Html.text ]
]
]
]
else
Html.nothing
atThePartyAd =
if shared.adverts.atTheParty then
Html.div [ HtmlA.id "at-the-party-ad" ]
[ Html.div []
[ Html.blankA
[ HtmlA.href "https://store.steampowered.com/app/1506530/At_The_Party"
, HtmlA.title "Check out Reread Game's new game, At The Party!"
]
[ Html.div [ HtmlA.class "at-the" ] [ Html.span [] [ Html.text "At The" ] ]
, Html.div [ HtmlA.class "party" ] [ Html.span [] [ Html.text "Party" ] ]
]
]
]
else
Html.nothing
in
[ Html.div [ HtmlA.class "page start" ]
[ overlay shared model.overlay
@@ -247,11 +268,12 @@ view shared model =
[ Html.text "\""
, Strings.MassiveDecks |> Lang.html shared
, Html.text "\" "
, Strings.Version { versionNumber = Version.version } |> Lang.html shared
, Strings.Version { clientVersion = Version.version, serverVersion = shared.serverVersion } |> Lang.html shared
]
]
]
, shared.sources.manyDecks |> Maybe.map manyDecksAd |> Maybe.withDefault Html.nothing
, atThePartyAd
]
+7 -6
View File
@@ -3,7 +3,7 @@ module MassiveDecks.Requests.Api exposing
, joinLobby
, lobbySummaries
, newLobby
, sourceInfo
, serverConfig
)
import Http
@@ -19,6 +19,7 @@ import MassiveDecks.Pages.Lobby.Token as Token
import MassiveDecks.Pages.Start.LobbyBrowser.Model as LobbyBrowser
import MassiveDecks.Pages.Start.Model as Start
import MassiveDecks.Requests.Request as Request exposing (Request)
import MassiveDecks.ServerConfig exposing (ServerConfig)
import MassiveDecks.User as User
import MassiveDecks.Util.Result as Result
import Url.Builder
@@ -80,15 +81,15 @@ checkAlive msg tokens =
}
{-| Find out what sources the server offers.
{-| Get configuration from the server.
-}
sourceInfo : (Request.Response Never Source.Info -> msg) -> Request msg
sourceInfo msg =
serverConfig : (Request.Response Never ServerConfig -> msg) -> Request msg
serverConfig msg =
{ method = "GET"
, headers = []
, url = url [ "sources" ]
, url = url [ "config" ]
, body = Http.emptyBody
, expect = Request.expectResponse msg noError Decoders.sourceInfo
, expect = Request.expectResponse msg noError Decoders.serverConfig
, timeout = Nothing
, tracker = Nothing
}
@@ -0,0 +1,19 @@
module MassiveDecks.ServerConfig exposing
( Adverts
, ServerConfig
)
import MassiveDecks.Card.Source.Model as Source
type alias Adverts =
{ manyDecks : Bool
, atTheParty : Bool
}
type alias ServerConfig =
{ serverVersion : String
, sourceInfo : Source.Info
, adverts : Adverts
}
+1 -1
View File
@@ -54,7 +54,7 @@ type MdString
-- Special
| Noun { noun : Noun, quantity : Quantity } -- The given noun, described in the given quantity and context.
-- Start screen.
| Version { versionNumber : String } -- The version of the game being played.
| Version { clientVersion : String, serverVersion : Maybe String } -- The version of the game being played.
| ShortGameDescription -- A one-line description of the game.
| WhatIsThis -- A title for a section describing the game.
| GameDescription -- A long description of the game.
@@ -92,8 +92,17 @@ translate _ mdString =
[ Text "Zuschauer" ]
-- Start screen.
Version { versionNumber } ->
[ Text "Version ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Version " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "Ein lustiges Party-Spiel." ]
@@ -91,8 +91,17 @@ translate _ mdString =
[ Text "Zuschauer" ]
-- Start screen.
Version { versionNumber } ->
[ Text "Version ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Version " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "Ein lustiges Party-Spiel." ]
@@ -46,8 +46,17 @@ translate _ mdString =
List.concat [ singular, plural ]
-- Start screen.
Version { versionNumber } ->
[ Text "Version ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Version " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "A comedy party game." ]
@@ -68,8 +68,17 @@ translate _ mdString =
List.concat [ singular, plural ]
-- Start screen.
Version { versionNumber } ->
[ Text "Versión ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Versión " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "Un juego para fiestas de comedia." ]
@@ -81,8 +81,17 @@ translate _ mdString =
List.concat [ singular, plural ]
-- Start screen.
Version { versionNumber } ->
[ Text "Versi ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Versi " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "Sebuah permainan pesta komedi." ]
@@ -91,8 +91,17 @@ translate _ mdString =
[ Text "Spettatori" ]
-- Start screen.
Version { versionNumber } ->
[ Text "Versione ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Versione " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "Un divertente gioco di società." ]
@@ -67,8 +67,17 @@ translate maybeDeclCase mdString =
[ Text (decl noun quantity declCase) ]
-- Start screen.
Version { versionNumber } ->
[ Text "Wersja ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Wersja " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "Komediowa gra towarzyska." ]
@@ -91,8 +91,17 @@ translate _ mdString =
[ Text "Espectadores" ]
-- Start screen.
Version { versionNumber } ->
[ Text "Versão ", Text versionNumber, Text "" ]
Version { clientVersion, serverVersion } ->
let
quote version =
[ Text "", Text version, Text "" ]
in
List.concat
[ [ Text "Versão " ]
, clientVersion |> quote
, [ Text " / " ]
, serverVersion |> Maybe.map quote |> Maybe.withDefault []
]
ShortGameDescription ->
[ Text "Um jogo de festa de comédia." ]
+5
View File
@@ -9,6 +9,11 @@
display: flex;
justify-content: flex-end;
z-index: 10;
pointer-events: none;
> div {
pointer-events: all;
}
}
.settings-panel {
+141 -11
View File
@@ -38,7 +38,7 @@
flex-wrap: wrap;
justify-content: center;
overflow: hidden;
padding: 4em 0 0 2em;
padding: 5em 0 0 2em;
h1,
.subtitle {
@@ -177,10 +177,12 @@
}
}
#many-decks-ad div {
#many-decks-ad > div {
font-size: clamp(0.9rem, 3vw, 1.2rem);
position: fixed;
left: -0.8em;
top: -3em;
left: 0;
top: -6.5em;
z-index: 11;
@@ -191,14 +193,11 @@
justify-content: flex-start;
width: 7em;
height: 7em;
border-radius: 100%;
height: 10em;
border-radius: 7em;
transition: left 0.25s, top 0.25s;
transform: rotate(-15deg);
font-size: 1.2em;
background-color: colors.$many-decks;
color: #ffffff;
@@ -207,7 +206,138 @@
}
&:hover {
left: -0.7em;
top: -2.5em;
left: 0em;
top: -5.5em;
}
}
@keyframes sway {
0% {
transform: rotateX(0);
}
33% {
transform: rotateX(30deg);
}
66% {
transform: rotateX(-30deg);
}
100% {
transform: rotateX(0);
}
}
#at-the-party-ad {
font-size: clamp(0.25rem, 2vw, 1.25rem);
> div {
position: fixed;
right: -5em;
top: 2rem;
transform: rotate(15deg);
width: 30em;
perspective: 20em;
&::before {
content: "";
display: block;
position: absolute;
right: -0.4em;
top: -0.4em;
background-image: url(../../../assets/images/at-the-party/lines.svg);
background-size: 7em 100%;
width: 40em;
height: 6.5em;
box-sizing: border-box;
z-index: -1;
}
> a {
display: block;
width: 50%;
aspect-ratio: 135 / 53;
margin: auto;
background: none;
transition: none;
position: relative;
&::before {
transform: rotate(-15deg);
content: "";
display: block;
position: absolute;
right: -10em;
top: -2em;
width: 50em;
height: 22em;
pointer-events: none;
opacity: 0;
transition: opacity 0.8s;
background-image: url(data:image/svg+xml;base64,CiAgPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMDAlJyBoZWlnaHQ9JzEwMCUnPgogICAgPHJlY3Qgd2lkdGg9JzEwMDAlJyBoZWlnaHQ9JzEwMDAlJyBmaWxsPScjZmZmJy8+CiAgICA8cmVjdCB3aWR0aD0nMTAwJScgaGVpZ2h0PScxMDAlJyBmaWxsPScjZmYwMDAwJy8+CiAgICA8c3ZnIHg9JzUwJScgeT0nMTAlJyBvdmVyZmxvdz0ndmlzaWJsZSc+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDIwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSg0MCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoNjApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDgwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgxMDApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDEyMCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMTQwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgxNjApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDE4MCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMjAwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgyMjApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDI0MCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMjYwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgyODApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDMwMCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMzIwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgzNDApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDM2MCknLz4KCiAgICA8L3N2Zz4KICA8L3N2Zz4=);
background-size: 150% 150%;
background-position: 15% 5%;
mask-image: radial-gradient(
ellipse at top right,
#000000 0,
#000000 40%,
transparent 60%
);
z-index: -10;
}
&:hover::before {
opacity: 1;
}
&:hover {
background-position: inherit;
}
> div {
content: "";
background-size: contain;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
> span {
display: none;
}
animation: sway 4s ease-in-out 0s alternate infinite;
@media (prefers-reduced-motion) {
animation: none;
}
}
> .at-the {
transform-origin: top center;
background-image: url(../../../assets/images/at-the-party/at-the.svg);
}
> .party {
background-image: url(../../../assets/images/at-the-party/party.svg);
animation-delay: 0.5s;
}
}
}
}
+3 -3
View File
@@ -138,7 +138,7 @@ module.exports = (env, argv) => {
test: /\.(jpg|png|svg)$/,
type: "asset/resource",
generator: {
filename: "assets/images/[name].[hash].[ext]",
filename: "assets/images/[name].[hash][ext]",
},
},
// Font assets.
@@ -146,7 +146,7 @@ module.exports = (env, argv) => {
test: /\.(woff2)$/,
type: "asset/resource",
generator: {
filename: "assets/fonts/[name].[hash].[ext]",
filename: "assets/fonts/[name].[hash][ext]",
},
},
// App manifest.
@@ -155,7 +155,7 @@ module.exports = (env, argv) => {
exclude: [/elm-stuff/, /node_modules/, /dist/],
type: "asset/resource",
generator: {
filename: "assets/[name].[hash].[ext]",
filename: "assets/[name].[hash][ext]",
},
use: [
{
+10
View File
@@ -173,6 +173,16 @@
checkAfter: "PT5M",
},
// Show additional content to users of the application. These are not adverts
// you can buy, just related projects to Massive Decks.
adverts: {
// Show a link to Many Decks for users to build their own decks.
// Note this will only work if you have Many Decks set as a source.
manyDecks: true,
// Show a link to At The Party, Reread Games new game.
atTheParty: false,
},
// A file to touch when the server is started. If null, we won't touch any files.
touchOnStart: null,
// This will be overwritten by the environment variable MD_TOUCH_ON_START, if set.
+12 -6
View File
@@ -29,6 +29,7 @@ export interface Config<D extends Duration> extends EnvironmentalConfig {
storage: BaseStorage<D>;
cache: BaseCache<D>;
defaults: LobbyConfig.Defaults;
adverts?: Adverts;
}
export type Parsed = Config<ParsedDuration>;
@@ -121,8 +122,13 @@ export type PostgreSQLCache = BasePostgreSQLCache<ParsedDuration>;
const parseDuration = (unparsed: UnparsedDuration): ParsedDuration =>
Moment.duration(unparsed).asMilliseconds();
interface Adverts {
manyDecks?: boolean;
atTheParty?: boolean;
}
export const parseStorage = (
storage: BaseStorage<UnparsedDuration>
storage: BaseStorage<UnparsedDuration>,
): BaseStorage<ParsedDuration> => ({
...storage,
abandonedTime: parseDuration(storage.abandonedTime),
@@ -130,28 +136,28 @@ export const parseStorage = (
});
export const parseCache = (
cache: BaseCache<UnparsedDuration>
cache: BaseCache<UnparsedDuration>,
): BaseCache<ParsedDuration> => ({
...cache,
checkAfter: parseDuration(cache.checkAfter),
});
export const parseTimeouts = (
timeouts: Timeouts<UnparsedDuration>
timeouts: Timeouts<UnparsedDuration>,
): Timeouts<ParsedDuration> =>
Util.mapObjectValues(timeouts, (key: string, value: UnparsedDuration) =>
parseDuration(value)
parseDuration(value),
);
export const parseTasks = (
tasks: Tasks<UnparsedDuration>
tasks: Tasks<UnparsedDuration>,
): Tasks<ParsedDuration> => ({
...tasks,
processTickFrequency: parseDuration(tasks.processTickFrequency),
});
const parseManyDecks = (
manyDecks: BaseManyDecks<UnparsedDuration>
manyDecks: BaseManyDecks<UnparsedDuration>,
): ManyDecks => ({
...manyDecks,
baseUrl: manyDecks.baseUrl.endsWith("/")
+19 -11
View File
@@ -50,8 +50,8 @@ function getConfigFilePath(): string {
async function main(): Promise<void> {
const config = ServerConfig.parse(
JSON5.parse(
(await fs.readFile(getConfigFilePath())).toString()
) as ServerConfig.Unparsed
(await fs.readFile(getConfigFilePath())).toString(),
) as ServerConfig.Unparsed,
);
const { app } = ws(express());
@@ -64,7 +64,7 @@ async function main(): Promise<void> {
if (environment !== "development" && config.secret === "CHANGE ME") {
throw new Error(
"Secret not set - this should never be the case outside of " +
"development. Please set a real random secret value in 'config.json5'."
"development. Please set a real random secret value in 'config.json5'.",
);
}
@@ -75,11 +75,19 @@ async function main(): Promise<void> {
app.use(
expressWinston.logger({
winstonInstance: Logging.logger,
})
}),
);
app.get("/api/version", async (req, res) => res.json(config.version));
app.get("/api/config", async (req, res) =>
res.json({
version: config.version,
sources: state.sources.clientInfo(),
adverts: config.adverts,
}),
);
app.get("/api/games", async (req, res) => {
const result = [];
for await (const summary of state.store.lobbySummaries()) {
@@ -92,14 +100,14 @@ async function main(): Promise<void> {
const { gameCode, token, tasks } = await state.store.newLobby(
CreateLobby.validate(req.body),
config.secret,
config.defaults
config.defaults,
);
for (const task of tasks) {
state.tasks.enqueue(state, task);
}
res.append(
"Location",
`${req.protocol}://${req.hostname}/${config.basePath}api/games/${gameCode}`
`${req.protocol}://${req.hostname}/${config.basePath}api/games/${gameCode}`,
);
res.status(HttpStatus.CREATED).json(token);
});
@@ -111,7 +119,7 @@ async function main(): Promise<void> {
const claims = Token.validate(
current,
await state.store.id(),
state.config.secret
state.config.secret,
);
if (await state.store.exists(claims.gc)) {
result.push(current);
@@ -133,7 +141,7 @@ async function main(): Promise<void> {
}
const newUser = User.create(
registration,
lobby.config.audienceMode ? "Spectator" : "Player"
lobby.config.audienceMode ? "Spectator" : "Player",
);
if (
wu(Object.values(lobby.users)).find((u) => u.name === registration.name)
@@ -148,7 +156,7 @@ async function main(): Promise<void> {
game.playerOrder.push(id);
if (newUser.role === "Player") {
game.players[id] = Player.initial(
game.decks.responses.draw(game.rules.handSize)
game.decks.responses.draw(game.rules.handSize),
);
}
}
@@ -208,7 +216,7 @@ async function main(): Promise<void> {
expressWinston.errorLogger({
winstonInstance: Logging.logger,
msg: "{{err.message}}",
})
}),
);
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
@@ -253,7 +261,7 @@ async function main(): Promise<void> {
state.tasks
.loadFromStore(state)
.catch((error) =>
Logging.logException("Error running store tasks:", error)
Logging.logException("Error running store tasks:", error),
);
app.listen(config.listenOn, async () => {