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 @@
+
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 = {
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,