diff --git a/client/src/images/3-vertical-dots-icon.svg b/client/src/images/3-vertical-dots-icon.svg new file mode 100644 index 0000000..77d571a --- /dev/null +++ b/client/src/images/3-vertical-dots-icon.svg @@ -0,0 +1 @@ +3-vertical-dots diff --git a/client/src/modules/front_end_components/HTMLFragments.js b/client/src/modules/front_end_components/HTMLFragments.js index 51ef1ba..adf2542 100644 --- a/client/src/modules/front_end_components/HTMLFragments.js +++ b/client/src/modules/front_end_components/HTMLFragments.js @@ -296,9 +296,6 @@ export const HTMLFragments = {

🏁 The moderator has ended the game. Roles are revealed.

- - -
diff --git a/client/src/modules/game_state/states/Ended.js b/client/src/modules/game_state/states/Ended.js index 069b8c6..9c5cf8d 100644 --- a/client/src/modules/game_state/states/Ended.js +++ b/client/src/modules/game_state/states/Ended.js @@ -15,7 +15,7 @@ export class Ended { gameState.client.userType === globals.USER_TYPES.MODERATOR || gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) { - document.getElementById('end-of-game-buttons').prepend(SharedStateUtil.createRestartButton(this.stateBucket)); + document.getElementById('end-of-game-buttons').prepend(SharedStateUtil.createReturnToLobbyButton(this.stateBucket)); } SharedStateUtil.displayCurrentModerator(this.stateBucket.currentGameState.people .find((person) => person.userType === globals.USER_TYPES.MODERATOR diff --git a/client/src/modules/game_state/states/InProgress.js b/client/src/modules/game_state/states/InProgress.js index 21892e2..60eb3b9 100644 --- a/client/src/modules/game_state/states/InProgress.js +++ b/client/src/modules/game_state/states/InProgress.js @@ -500,7 +500,7 @@ function createEndGamePromptComponent (socket, stateBucket) { ); }); }); - div.querySelector('#game-control-prompt').prepend(SharedStateUtil.createRestartButton(stateBucket)); + div.querySelector('#game-control-prompt').prepend(SharedStateUtil.createReturnToLobbyButton(stateBucket)); document.getElementById('game-content').appendChild(div); } } diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js index 9f39ba5..4ef53c8 100644 --- a/client/src/modules/game_state/states/Lobby.js +++ b/client/src/modules/game_state/states/Lobby.js @@ -62,7 +62,7 @@ export class Lobby { const spectatorHandler = (e) => { if (e.type === 'click' || e.code === 'Enter') { Confirmation(SharedStateUtil.buildSpectatorList(this.stateBucket.currentGameState.people - .filter(p => p.userType === globals.USER_TYPES.SPECTATOR)), null, true); + .filter(p => p.userType === globals.USER_TYPES.SPECTATOR), this.stateBucket.currentGameState.client), null, true); } }; @@ -95,7 +95,7 @@ export class Lobby { } ); for (const person of sorted.filter(p => p.userType !== globals.USER_TYPES.SPECTATOR)) { - lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name, person.userType)); + lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name, person.userType, this.stateBucket.currentGameState.client)); } const playerCount = this.stateBucket.currentGameState.people.filter( p => p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR @@ -192,7 +192,7 @@ function getTimeString (gameState) { } } -function renderLobbyPerson (name, userType) { +function renderLobbyPerson (name, userType, client) { const el = document.createElement('div'); const personNameEl = document.createElement('div'); personNameEl.classList.add('lobby-player-name'); @@ -207,5 +207,9 @@ function renderLobbyPerson (name, userType) { el.appendChild(personNameEl); el.appendChild(personTypeEl); + if (client.userType === globals.USER_TYPES.MODERATOR || client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { + SharedStateUtil.addPlayerOptions(el); + } + return el; } diff --git a/client/src/modules/game_state/states/shared/SharedStateUtil.js b/client/src/modules/game_state/states/shared/SharedStateUtil.js index edb8d5f..ddaea3d 100644 --- a/client/src/modules/game_state/states/shared/SharedStateUtil.js +++ b/client/src/modules/game_state/states/shared/SharedStateUtil.js @@ -23,9 +23,10 @@ export const SharedStateUtil = { ); }, - restartHandler: (stateBucket) => { + restartHandler: (stateBucket, status = globals.STATUS.IN_PROGRESS) => { + console.log("HEY") XHRUtility.xhr( - '/api/games/' + stateBucket.currentGameState.accessCode + '/restart', + '/api/games/' + stateBucket.currentGameState.accessCode + '/restart?status=' + status, 'PATCH', null, JSON.stringify({ @@ -45,7 +46,7 @@ export const SharedStateUtil = { const restartGameButton = document.createElement('button'); restartGameButton.classList.add('app-button'); restartGameButton.setAttribute('id', 'restart-game-button'); - restartGameButton.innerText = 'Restart'; + restartGameButton.innerText = 'Quick Restart'; restartGameButton.addEventListener('click', () => { Confirmation('Restart the game, dealing everyone new roles?', () => { SharedStateUtil.restartHandler(stateBucket); @@ -55,6 +56,20 @@ export const SharedStateUtil = { return restartGameButton; }, + createReturnToLobbyButton: (stateBucket) => { + const returnToLobbyButton = document.createElement('button'); + returnToLobbyButton.classList.add('app-button'); + returnToLobbyButton.setAttribute('id', 'return-to-lobby-button'); + returnToLobbyButton.innerText = 'Return to Lobby'; + returnToLobbyButton.addEventListener('click', () => { + Confirmation('Return everyone to the Lobby?', () => { + SharedStateUtil.restartHandler(stateBucket, globals.STATUS.LOBBY); + }); + }); + + return returnToLobbyButton; + }, + setClientSocketHandlers: (stateBucket, socket) => { const startGameStateAckFn = (gameState) => { SharedStateUtil.gameStateAckFn(gameState, socket); @@ -63,7 +78,7 @@ export const SharedStateUtil = { const restartGameStateAckFn = (gameState) => { SharedStateUtil.gameStateAckFn(gameState, socket); - toast('Game restarted!', 'success'); + toast('Everyone has returned to the Lobby!', 'success'); }; const fetchGameStateHandler = (ackFn) => { @@ -160,7 +175,17 @@ export const SharedStateUtil = { } }, - buildSpectatorList (people) { + addPlayerOptions: (personEl) => { + const kickButton = document.createElement('img'); + kickButton.setAttribute('tabIndex', '0'); + kickButton.setAttribute('className', 'role-remove'); + kickButton.setAttribute('src', '../images/3-vertical-dots-icon.svg'); + kickButton.setAttribute('title', 'Kick Player'); + kickButton.setAttribute('alt', 'Kick Player'); + personEl.appendChild(kickButton); + }, + + buildSpectatorList (people, client) { const list = document.createElement('div'); const spectators = people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR); if (spectators.length === 0) { @@ -173,6 +198,10 @@ export const SharedStateUtil = { '
' + 'spectator' + globals.USER_TYPE_ICONS.spectator + '
'; spectatorEl.querySelector('.spectator-name').innerText = spectator.name; list.appendChild(spectatorEl); + + if (client.userType === globals.USER_TYPES.MODERATOR || client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { + this.addPlayerOptions(spectatorEl); + } } } diff --git a/client/src/styles/game.css b/client/src/styles/game.css index 0d5e7d8..b196860 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -13,6 +13,30 @@ margin: 0 auto 0.25em auto; } +.lobby-player, .spectator { + position: relative; +} + +.lobby-player img, .spectator img { + height: 18px; + margin: 0 8px; + cursor: pointer; + padding: 5px; + border-radius: 5px; + border: 1px solid transparent; + position: absolute; + right: -33px; +} + +.lobby-player img:active, .spectator img:active { + border: 1px solid whitesmoke; +} + +.lobby-player img:hover, .spectator img:hover { + filter: brightness(1.5); + background-color: #8080804d; +} + .moderator { border: 2px solid #c58f13 !important; } @@ -82,13 +106,13 @@ max-width: 17em; } -#restart-game-button, #mod-transfer-button { +#return-to-lobby-button, #end-of-game-buttons #return-to-lobby-button, #mod-transfer-button { background-color: #045EA6; border: 2px solid #024070; } -#restart-game-button:hover, #mod-transfer-button:hover { - background-color: #0078D773; +#return-to-lobby-button:hover, #end-of-game-buttons #return-to-lobby-button:hover, #mod-transfer-button:hover { + background-color: rgba(0, 120, 215, 0.45); border: 2px solid #045EA6; } @@ -160,7 +184,7 @@ h1 { font-size: 18px; } -#end-of-game-header #restart-game-button { +#end-of-game-header #return-to-lobby-button { margin-bottom: 1em !important; animation: shadow-pulse 1.5s infinite ease-out; padding: 10px; @@ -543,7 +567,7 @@ label[for='moderator'] { box-shadow: 0 -6px 40px black; } -#start-game-button, #end-game-button, #restart-game-button { +#start-game-button, #end-game-button, #return-to-lobby-button { font-family: 'signika-negative', sans-serif !important; padding: 10px; border-radius: 5px; @@ -910,7 +934,7 @@ canvas { height: 65px; } - #start-game-button, #end-game-button, #restart-game-button { + #start-game-button, #end-game-button, #return-to-lobby-button { font-size: 20px; padding: 5px; } diff --git a/server/api/GamesAPI.js b/server/api/GamesAPI.js index 6bb3ce5..6616ca2 100644 --- a/server/api/GamesAPI.js +++ b/server/api/GamesAPI.js @@ -102,7 +102,9 @@ router.patch('/:code/restart', async function (req, res) { } else { const game = await gameManager.getActiveGame(req.body.accessCode); if (game) { - gameManager.restartGame(game, gameManager.namespace).then((data) => { + gameManager.restartGame(game, gameManager.namespace, req.query.status).then((data) => { + console.log(req.query.status); + console.log(req.query.toLobby); res.status(200).send(); }).catch((code) => { res.status(code).send(); diff --git a/server/modules/singletons/GameManager.js b/server/modules/singletons/GameManager.js index aed5e58..f7b329e 100644 --- a/server/modules/singletons/GameManager.js +++ b/server/modules/singletons/GameManager.js @@ -215,7 +215,7 @@ class GameManager { } }; - restartGame = async (game, namespace) => { + restartGame = async (game, namespace, status = globals.STATUS.IN_PROGRESS) => { // kill any outstanding timer threads const subProcess = this.timerManager.timerThreads[game.accessCode]; if (subProcess) { @@ -260,15 +260,19 @@ class GameManager { } } - // start the new game - game.status = globals.STATUS.IN_PROGRESS; - if (game.hasTimer) { - game.timerParams.paused = true; - game.timerParams.timeRemaining = convertFromHoursToMilliseconds(game.timerParams.hours) + - convertFromMinutesToMilliseconds(game.timerParams.minutes); - await this.timerManager.runTimer(game, namespace, this.eventManager, this); + if (status === globals.STATUS.IN_PROGRESS) { + game.status = globals.STATUS.IN_PROGRESS; + if (game.hasTimer) { + game.timerParams.paused = true; + game.timerParams.timeRemaining = convertFromHoursToMilliseconds(game.timerParams.hours) + + convertFromMinutesToMilliseconds(game.timerParams.minutes); + await this.timerManager.runTimer(game, namespace, this.eventManager, this); + } + } else { + game.status = globals.STATUS.LOBBY; } + await this.refreshGame(game); await this.eventManager.publisher?.publish( globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,