diff --git a/client/src/modules/GameCreationStepManager.js b/client/src/modules/GameCreationStepManager.js index b6fdfa9..f674f45 100644 --- a/client/src/modules/GameCreationStepManager.js +++ b/client/src/modules/GameCreationStepManager.js @@ -110,7 +110,12 @@ export class GameCreationStepManager { const button = document.getElementById('create-game'); button.removeEventListener('click', this.steps['5'].forwardHandler); button.classList.add('submitted'); - button.innerText = 'Creating'; + button.innerText = 'Creating...'; + const restoreButton = () => { + button.innerText = 'Create'; + button.classList.remove('submitted'); + button.addEventListener('click', this.steps['5'].forwardHandler); + }; XHRUtility.xhr( '/api/games/create', 'POST', @@ -126,20 +131,26 @@ export class GameCreationStepManager { ) ) .then((res) => { - if (res - && typeof res === 'object' - && Object.prototype.hasOwnProperty.call(res, 'content') - && typeof res.content === 'string' - ) { - const json = JSON.parse(res.content); - UserUtility.setAnonymousUserId(json.cookie, json.environment); - window.location = ('/game/' + json.accessCode); + if (res.status === 201) { + try { + const json = JSON.parse(res.content); + UserUtility.setAnonymousUserId(json.cookie, json.environment); + window.location.replace( + window.location.protocol + '//' + window.location.host + + '/game/' + json.accessCode + ); + } catch (e) { + restoreButton(); + toast( + 'There was a problem creating the game. Please contact the developers.', + 'error', + true, + true + ); + } } }).catch((e) => { - const button = document.getElementById('create-game'); - button.innerText = 'Create'; - button.classList.remove('submitted'); - button.addEventListener('click', this.steps['5'].forwardHandler); + restoreButton(); toast(e.content, 'error', true, true, 'medium'); if (e.status === 429) { toast('You\'ve sent this request too many times.', 'error', true, true, 'medium'); diff --git a/client/src/modules/GameStateRenderer.js b/client/src/modules/GameStateRenderer.js index c010a36..02995a7 100644 --- a/client/src/modules/GameStateRenderer.js +++ b/client/src/modules/GameStateRenderer.js @@ -108,7 +108,7 @@ export class GameStateRenderer { '?playerCount=' + getGameSize(this.stateBucket.currentGameState.deck) + '&timer=' + encodeURIComponent(timeString); - QRCode.toCanvas(document.getElementById('canvas'), link, { scale: 3 }, function (error) { + QRCode.toCanvas(document.getElementById('canvas'), link, { scale: 2 }, function (error) { if (error) console.error(error); console.log('success!'); }); diff --git a/client/src/modules/HTMLFragments.js b/client/src/modules/HTMLFragments.js index d948661..bc105d2 100644 --- a/client/src/modules/HTMLFragments.js +++ b/client/src/modules/HTMLFragments.js @@ -6,16 +6,18 @@ export const HTMLFragments = {
- -
-
- clock -
+
+ +
+
+ clock +
+
+
+ person +
+
-
- person -
-
@@ -37,8 +39,9 @@ export const HTMLFragments = {
`, START_GAME_PROMPT: - `
+ `
+

All players must join to start.

`, END_GAME_PROMPT: `
@@ -106,7 +109,7 @@ export const HTMLFragments = {
-
+
@@ -152,8 +155,8 @@ export const HTMLFragments = {
- - + +
`, GAME_PLAYER: `
diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js index 995b34c..fabd6f9 100644 --- a/client/src/scripts/game.js +++ b/client/src/scripts/game.js @@ -99,12 +99,10 @@ function processGameState ( document.getElementById('game-state-container').innerHTML = HTMLFragments.LOBBY; gameStateRenderer.renderLobbyHeader(); gameStateRenderer.renderLobbyPlayers(); - if ( - currentGameState.isFull - && ( - currentGameState.client.userType === globals.USER_TYPES.MODERATOR + if (( + currentGameState.client.userType === globals.USER_TYPES.MODERATOR || currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR - ) + ) && refreshPrompt ) { displayStartGamePromptForModerators(currentGameState, gameStateRenderer); @@ -172,12 +170,10 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW stateBucket.currentGameState.people.push(player); stateBucket.currentGameState.isFull = gameIsFull; gameStateRenderer.renderLobbyPlayers(); - if ( - gameIsFull - && ( - stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR + if (( + stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR || stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR - ) + ) ) { displayStartGamePromptForModerators(stateBucket.currentGameState, gameStateRenderer); } @@ -329,10 +325,27 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW } function displayStartGamePromptForModerators (gameState, gameStateRenderer) { - const div = document.createElement('div'); - div.innerHTML = HTMLFragments.START_GAME_PROMPT; - div.querySelector('#start-game-button').addEventListener('click', gameStateRenderer.startGameHandler); - document.body.appendChild(div); + const existingPrompt = document.getElementById('start-game-prompt'); + if (existingPrompt) { + enableOrDisableStartButton(gameState, existingPrompt, gameStateRenderer.startGameHandler); + } else { + const newPrompt = document.createElement('div'); + newPrompt.setAttribute('id', 'start-game-prompt'); + newPrompt.innerHTML = HTMLFragments.START_GAME_PROMPT; + + document.body.appendChild(newPrompt); + enableOrDisableStartButton(gameState, newPrompt, gameStateRenderer.startGameHandler); + } +} + +function enableOrDisableStartButton (gameState, buttonContainer, handler) { + if (gameState.isFull) { + buttonContainer.querySelector('#start-game-button').addEventListener('click', handler); + buttonContainer.querySelector('#start-game-button').classList.remove('disabled'); + } else { + buttonContainer.querySelector('#start-game-button').removeEventListener('click', handler); + buttonContainer.querySelector('#start-game-button').classList.add('disabled'); + } } function removeStartGameFunctionalityIfPresent (gameStateRenderer) { diff --git a/client/src/styles/GLOBAL.css b/client/src/styles/GLOBAL.css index 61fedd8..0f7e1db 100644 --- a/client/src/styles/GLOBAL.css +++ b/client/src/styles/GLOBAL.css @@ -251,6 +251,12 @@ button { pointer-events: none; } +.disabled { + filter: opacity(0.5) grayscale(1); + pointer-events: none; + animation: none !important; +} + .container { padding: 5px; border-radius: 3px; @@ -340,7 +346,7 @@ input { align-items: center; padding: 5px 0; width: 100%; - background-color: #333243; + background-color: #1e1b26; height: 45px; z-index: 53000; } diff --git a/client/src/styles/create.css b/client/src/styles/create.css index 0b3acff..70ca168 100644 --- a/client/src/styles/create.css +++ b/client/src/styles/create.css @@ -419,6 +419,7 @@ input[type="number"] { color: #e7e7e7; font-size: 30px; padding: 10px 50px; + width: 100px; } #create-game:hover { @@ -538,6 +539,7 @@ input[type="number"] { margin-top: 1em; justify-content: center; width: 100%; + height: 50px; } #upload-custom-roles-modal input[type='file'] { @@ -651,6 +653,10 @@ input[type="number"] { font-size: 16px; } + #create-game { + width: 80px; + } + .role-name { font-size: 13px; font-weight: bold; diff --git a/client/src/styles/game.css b/client/src/styles/game.css index dbbe2d9..4bcdd20 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -17,7 +17,7 @@ #lobby-players { overflow-y: auto; - max-height: 44em; + max-height: 30em; overflow-x: hidden; padding: 0 10px; border-radius: 3px; @@ -46,7 +46,7 @@ flex-wrap: wrap; display: flex; width: 95%; - margin: 1em auto 100px auto; + margin: 1em auto 140px auto; animation: fade-in-slide-up 2s; } @@ -83,6 +83,13 @@ width: 100%; } +#lobby-header > div:nth-child(2) { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; +} + h1 { text-align: center; margin: 0.5em auto; @@ -434,27 +441,38 @@ label[for='moderator'] { } #start-game-prompt, #end-game-prompt { + padding: 0.5em 0; display: flex; + flex-direction: column; align-items: center; - justify-content: center; + justify-content: space-evenly; position: fixed; z-index: 3; - border-radius: 3px; font-family: 'signika-negative', sans-serif; font-weight: 100; - box-shadow: 0 -2px 6px 0 rgb(0 0 0 / 45%); + box-shadow: 0 -6px 15px rgba(0, 0, 0, 0.5); left: 0; right: 0; bottom: 0; + border-radius: 3px; /* width: fit-content; */ font-size: 20px; - height: 85px; + height: 100px; margin: 0 auto; - animation: fade-in-slide-up 10s ease; - animation-fill-mode: forwards; - animation-direction: normal; - width: 100%; - background-color: #333243; + max-width: 100%; + background-color: #1b1a24; +} + +#start-game-prompt p { + color: whitesmoke; + font-size: 15px; +} + +#start-game-prompt > div { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; } #end-game-prompt { @@ -471,10 +489,15 @@ label[for='moderator'] { transition: background-color, border 0.3s ease-out; text-shadow: 0 3px 4px rgb(0 0 0 / 85%); font-size: 25px; + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: none; } #start-game-button { background-color: #1c8a36; + margin-bottom: 10px; animation: shadow-pulse 1.5s infinite ease-out; } @@ -563,6 +586,7 @@ canvas { margin: 0.5em 0; position: relative; box-shadow: 2px 3px 6px rgb(0 0 0 / 50%); + border-radius: 3px; } .game-player-name { @@ -576,33 +600,44 @@ canvas { } .kill-player-button, .reveal-role-button { + background-color: #333243; font-family: 'signika-negative', sans-serif !important; border-radius: 3px; color: #e7e7e7; + height: 25px; font-size: 16px; cursor: pointer; border: 2px solid transparent; - transition: background-color, border 0.3s ease-out; + transition: background-color, border 0.2s ease-out; text-shadow: 0 3px 4px rgb(0 0 0 / 55%); margin: 5px 0 5px 25px; - width: 117px; + width: 65px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.kill-player-button:hover, .reveal-role-button:hover { + border: 2px solid #b1afcd; +} + +#game-players-container { + width: 27em; + max-width: 100%; +} + .placeholder-button { font-family: 'signika-negative', sans-serif !important; display: flex; justify-content: center; + align-items: center; border-radius: 3px; color: #767676; font-weight: bold; font-size: 16px; - border: 2px solid transparent; text-shadow: 0 3px 4px rgb(0 0 0 / 55%); - margin: 5px 0 5px 35px; - width: 103px; + margin: 5px 0 5px 25px; + width: 65px; } #game-link:hover { @@ -610,21 +645,12 @@ canvas { border: 2px solid #d7d7d7; } -.reveal-role-button { - background-color: #586a6e; -} - -.reveal-role-button:hover { - background-color: #4e5664; -} - -.kill-player-button:hover { - background-color: #b35c5c; -} +/*.reveal-role-button {*/ +/* background-color: #586a6e;*/ +/*}*/ .reveal-role-button img { width: 18px; - margin-left: 5px; } #game-player-list > .game-player.killed::after { @@ -660,9 +686,9 @@ canvas { cursor: pointer; } -.kill-player-button { - background-color: #9f4747; -} +/*.kill-player-button {*/ +/* background-color: #9f4747;*/ +/*}*/ .killed-card { width: 55% !important; @@ -720,7 +746,7 @@ canvas { #lobby-people-container , #game-people-container { background-color: #333243; - padding: 10px 10px 0 10px; + padding: 10px; border-radius: 3px; min-height: 25em; max-width: 35em; @@ -738,6 +764,14 @@ canvas { align-items: center; } +@media(max-width: 800px) { + #start-game-prompt, #end-game-prompt { + border-radius: 0; + width: 100%; + bottom: 0; + } +} + @media(max-width: 500px) { label { font-size: 18px; @@ -752,7 +786,7 @@ canvas { } #game-state-container { - margin: 0 auto 85px auto; + margin: 0 auto 105px auto; } button { diff --git a/server/api/GamesAPI.js b/server/api/GamesAPI.js index 4fa21bf..0ca2764 100644 --- a/server/api/GamesAPI.js +++ b/server/api/GamesAPI.js @@ -50,7 +50,7 @@ router.post('/create', function (req, res) { if (result instanceof Error) { res.status(500).send(); } else { - res.send(result); // game was created successfully, and access code was returned + res.status(201).send(result); // game was created successfully, and access code was returned } }).catch((e) => { if (e === globals.ERROR_MESSAGE.BAD_CREATE_REQUEST) { diff --git a/server/config/globals.js b/server/config/globals.js index b686fcb..75a3e84 100644 --- a/server/config/globals.js +++ b/server/config/globals.js @@ -20,7 +20,7 @@ const globals = { res.status(400).send('Request has invalid content type.'); } }, - STALE_GAME_HOURS: 12, + STALE_GAME_HOURS: 24, CLIENT_COMMANDS: { FETCH_GAME_STATE: 'fetchGameState', START_GAME: 'startGame', diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js index e83b8e1..d292f24 100644 --- a/server/modules/GameManager.js +++ b/server/modules/GameManager.js @@ -530,7 +530,7 @@ function pruneStaleGames (activeGames, timerThreads, logger) { for (const [accessCode, game] of Object.entries(activeGames)) { if (game.createTime) { const createDate = new Date(game.createTime); - if (createDate.setHours(createDate.getHours() + globals.STALE_GAME_HOURS) < Date.now()) { // clear games created more than 12 hours ago + if (createDate.setHours(createDate.getHours() + globals.STALE_GAME_HOURS) < Date.now()) { logger.info('PRUNING STALE GAME ' + accessCode); delete activeGames[accessCode]; if (timerThreads[accessCode]) {