diff --git a/client/src/modules/front_end_components/Confirmation.js b/client/src/modules/front_end_components/Confirmation.js
index bd1c51c..18a83bd 100644
--- a/client/src/modules/front_end_components/Confirmation.js
+++ b/client/src/modules/front_end_components/Confirmation.js
@@ -1,6 +1,6 @@
import { toast } from './Toast.js';
-export const Confirmation = (message, onYes = null, isDOMNode = false) => {
+export const Confirmation = (message, onYes = null, isDOMNode = false, confirmButtonText = 'Yes') => {
document.querySelector('#confirmation')?.remove();
document.querySelector('#confirmation-background')?.remove();
@@ -11,7 +11,7 @@ export const Confirmation = (message, onYes = null, isDOMNode = false) => {
? `
diff --git a/client/src/modules/front_end_components/HTMLFragments.js b/client/src/modules/front_end_components/HTMLFragments.js
index 1e1b44e..09f9c2e 100644
--- a/client/src/modules/front_end_components/HTMLFragments.js
+++ b/client/src/modules/front_end_components/HTMLFragments.js
@@ -94,7 +94,7 @@ export const HTMLFragments = {
diff --git a/client/src/modules/game_creation/GameCreationStepManager.js b/client/src/modules/game_creation/GameCreationStepManager.js
index 3fa2cb8..67133f9 100644
--- a/client/src/modules/game_creation/GameCreationStepManager.js
+++ b/client/src/modules/game_creation/GameCreationStepManager.js
@@ -98,7 +98,7 @@ export class GameCreationStepManager {
this.incrementStep();
this.renderStep('creation-step-container', this.step);
} else {
- toast('Name must be between 1 and 30 characters.', 'error', true);
+ toast('Name must be between 1 and 40 characters.', 'error', true);
}
}
},
@@ -569,8 +569,8 @@ function initializeRemainingEventListeners (deckManager, roleBox) {
}
function processNewCustomRoleSubmission (name, description, team, deckManager, isUpdate, roleBox, option = null) {
- if (name.length > 40) {
- toast('Your name is too long (max 40 characters).', 'error', true);
+ if (name.length > 50) {
+ toast('Your name is too long (max 50 characters).', 'error', true);
return;
}
if (description.length > 500) {
@@ -596,5 +596,5 @@ function hasTimer (hours, minutes) {
}
function validateName (name) {
- return typeof name === 'string' && name.length > 0 && name.length <= 30;
+ return typeof name === 'string' && name.length > 0 && name.length <= 40;
}
diff --git a/client/src/modules/game_state/states/Ended.js b/client/src/modules/game_state/states/Ended.js
index 2a94d8d..40cc29d 100644
--- a/client/src/modules/game_state/states/Ended.js
+++ b/client/src/modules/game_state/states/Ended.js
@@ -53,6 +53,7 @@ function renderGroupOfPlayers (
for (const player of people) {
const playerEl = document.createElement('div');
playerEl.classList.add('game-player');
+ playerEl.dataset.pointer = player.id;
playerEl.innerHTML = HTMLFragments.GAME_PLAYER;
playerEl.querySelector('.game-player-name').innerText = player.name;
diff --git a/client/src/modules/game_state/states/InProgress.js b/client/src/modules/game_state/states/InProgress.js
index 1601f3d..1a25180 100644
--- a/client/src/modules/game_state/states/InProgress.js
+++ b/client/src/modules/game_state/states/InProgress.js
@@ -322,6 +322,7 @@ export class InProgress {
for (const player of people) {
const playerEl = document.createElement('div');
playerEl.classList.add('game-player');
+ playerEl.dataset.pointer = player.id;
// add a reference to the player's id for each corresponding element in the list
if (moderatorType) {
diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js
index bcd3ba8..92e878c 100644
--- a/client/src/modules/game_state/states/Lobby.js
+++ b/client/src/modules/game_state/states/Lobby.js
@@ -354,8 +354,9 @@ function getTimeString (gameState) {
function renderLobbyPerson (person, gameState, socket) {
const el = document.createElement('div');
+ el.dataset.pointer = person.id;
const personNameEl = document.createElement('div');
- personNameEl.classList.add('lobby-player-name');
+ personNameEl.classList.add('lobby-player-name', 'person-name-element');
const personTypeEl = document.createElement('div');
personNameEl.innerText = person.name;
personTypeEl.innerText = person.userType + USER_TYPE_ICONS[person.userType];
diff --git a/client/src/modules/game_state/states/shared/SharedStateUtil.js b/client/src/modules/game_state/states/shared/SharedStateUtil.js
index 893d83c..a3db71f 100644
--- a/client/src/modules/game_state/states/shared/SharedStateUtil.js
+++ b/client/src/modules/game_state/states/shared/SharedStateUtil.js
@@ -100,8 +100,9 @@ export const SharedStateUtil = {
} else {
for (const spectator of spectators) {
const spectatorEl = document.createElement('div');
+ spectatorEl.dataset.pointer = spectator.id;
spectatorEl.classList.add('spectator');
- spectatorEl.innerHTML = '
' +
+ spectatorEl.innerHTML = '
' +
'
' + 'spectator' + USER_TYPE_ICONS.spectator + '
';
spectatorEl.querySelector('.spectator-name').innerText = spectator.name;
list.appendChild(spectatorEl);
@@ -123,6 +124,7 @@ export const SharedStateUtil = {
},
displayCurrentModerator: (moderator) => {
+ document.getElementById('current-moderator').dataset.pointer = moderator.id;
document.getElementById('current-moderator-name').innerText = moderator.name;
document.getElementById('current-moderator-type').innerText = moderator.userType + USER_TYPE_ICONS[moderator.userType];
},
@@ -183,9 +185,30 @@ export const SharedStateUtil = {
});
},
- displayClientInfo: (name, userType) => {
- document.getElementById('client-name').innerText = name;
- document.getElementById('client-user-type').innerText = userType;
- document.getElementById('client-user-type').innerText += USER_TYPE_ICONS[userType];
+ displayClientInfo: (gameState, socket) => {
+ document.getElementById('client-name').innerText = gameState.client.name;
+ document.getElementById('client-user-type').innerText = gameState.client.userType;
+ document.getElementById('client-user-type').innerText += USER_TYPE_ICONS[gameState.client.userType];
+ const nameForm = document.createElement('form');
+ nameForm.setAttribute('id', 'name-change-form');
+ nameForm.onsubmit = (e) => {
+ e.preventDefault();
+ document.getElementById('confirmation-yes-button').click();
+ };
+ nameForm.innerHTML = HTMLFragments.NAME_CHANGE_FORM;
+ nameForm.querySelector('#client-new-name').value = gameState.client.name;
+ document.getElementById('edit-name-button').addEventListener('click', () => {
+ Confirmation(nameForm, () => {
+ socket.emit(
+ SOCKET_EVENTS.IN_GAME_MESSAGE,
+ EVENT_IDS.CHANGE_NAME,
+ gameState.accessCode,
+ { personId: gameState.client.id, newName: document.getElementById('client-new-name').value },
+ (response) => {
+ toast(response.message, response.errorFlag === 1 ? 'error' : 'success', true);
+ }
+ );
+ }, true, 'Update');
+ });
}
};
diff --git a/client/src/modules/page_handlers/gameHandler.js b/client/src/modules/page_handlers/gameHandler.js
index 8b6c923..a2445af 100644
--- a/client/src/modules/page_handlers/gameHandler.js
+++ b/client/src/modules/page_handlers/gameHandler.js
@@ -135,7 +135,7 @@ function processGameState (
});
}
- SharedStateUtil.displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
+ SharedStateUtil.displayClientInfo(currentGameState, socket);
switch (currentGameState.status) {
case STATUS.LOBBY:
@@ -236,6 +236,20 @@ function setClientSocketHandlers (stateBucket, socket) {
);
});
+ socket.on(EVENT_IDS.CHANGE_NAME, (changedId, newName) => {
+ const person = stateBucket.currentGameState.people.find(person => person.id === changedId);
+ if (person) {
+ person.name = newName;
+ if (stateBucket.currentGameState.client.id === changedId) {
+ stateBucket.currentGameState.client.name = newName;
+ SharedStateUtil.displayClientInfo(stateBucket.currentGameState, socket);
+ }
+ document.querySelectorAll('[data-pointer="' + person.id + '"]').forEach((node) => {
+ node.querySelector('.person-name-element').innerText = newName;
+ });
+ }
+ });
+
socket.on(EVENT_IDS.END_GAME, (people) => {
stateBucket.currentGameState.people = people;
stateBucket.currentGameState.status = STATUS.ENDED;
diff --git a/client/src/scripts/join.js b/client/src/scripts/join.js
index e5e1175..452f9c1 100644
--- a/client/src/scripts/join.js
+++ b/client/src/scripts/join.js
@@ -55,7 +55,7 @@ const joinHandler = (e) => {
resetJoinButtonState(e, joinHandler);
});
} else {
- toast('Name must be between 1 and 30 characters.', 'error', true, true, 'long');
+ toast('Name must be between 1 and 40 characters.', 'error', true, true, 'long');
}
};
@@ -90,7 +90,7 @@ function resetJoinButtonState (e, joinHandler) {
}
function validateName (name) {
- return typeof name === 'string' && name.length > 0 && name.length <= 30;
+ return typeof name === 'string' && name.length > 0 && name.length <= 40;
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
diff --git a/client/src/styles/game.css b/client/src/styles/game.css
index 1dadd42..c200c22 100644
--- a/client/src/styles/game.css
+++ b/client/src/styles/game.css
@@ -564,8 +564,34 @@ h1 {
}
#client-container {
- max-width: 35em;
+ max-width: 25em;
+ width: 75%;
margin: 1em 0;
+ position: relative;
+}
+
+#client-container button {
+ background-color: transparent;
+ height: fit-content;
+ margin: 0 8px;
+ cursor: pointer;
+ padding: 5px;
+ border-radius: 5px;
+ border: 1px solid transparent;
+ position: absolute;
+ right: -50px;
+ top: 32px;
+}
+
+#client-container button:hover {
+ cursor: pointer;
+ filter: brightness(1.5);
+ background-color: #8080804d;
+}
+
+#client-container button img {
+ width: 22px;
+ pointer-events: none;
}
#client {
@@ -575,16 +601,19 @@ h1 {
align-items: center;
justify-content: space-between;
border-radius: 5px;
- min-width: 15em;
border: 1px solid #46455299;
background: #4645525c;
}
#client-name {
+ max-width: 13em;
color: #e7e7e7;
font-family: 'signika-negative', sans-serif;
font-size: 25px;
margin: 0.25em 2em 0.25em 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
#client-user-type {
@@ -920,8 +949,22 @@ canvas {
justify-content: center;
}
-#change-name-modal-background {
- cursor: default;
+#change-name-form-content {
+ display: flex;
+ flex-direction: column;
+ margin: 0 auto;
+ max-width: 15em;
+ text-align: left;
+ margin: 2px auto;
+}
+
+#change-name-form-content input {
+ font-size: 20px;
+}
+
+#change-name-form-content label {
+ display: flex;
+ margin-bottom: 0.5em;
}
#lobby-people-container , #game-people-container {
@@ -954,6 +997,16 @@ canvas {
}
@media(max-width: 500px) {
+ #client-container button img {
+ width: 18px;
+ pointer-events: none;
+ }
+
+ #client-container button {
+ right: -46px;
+ top: 27px;
+ }
+
label {
font-size: 18px;
}
@@ -968,6 +1021,7 @@ canvas {
#client-name {
font-size: 20px;
+ margin: 0.25em 0 0.25em 0;
}
#client-user-type, #game-parameters {
diff --git a/server/config/globals.js b/server/config/globals.js
index 29ebc14..e49bbe2 100644
--- a/server/config/globals.js
+++ b/server/config/globals.js
@@ -10,7 +10,8 @@ const PRIMITIVES = {
USER_SIGNATURE_LENGTH: 25,
INSTANCE_ID_LENGTH: 75,
MAX_SPECTATORS: 25,
- MOCK_AUTH: 'mock_auth'
+ MOCK_AUTH: 'mock_auth',
+ MAX_PERSON_NAME_LENGTH: 40
};
const LOG_LEVEL = {
@@ -88,6 +89,7 @@ const SYNCABLE_EVENTS = function () {
EVENT_IDS.KILL_PLAYER,
EVENT_IDS.REVEAL_PLAYER,
EVENT_IDS.TRANSFER_MODERATOR,
+ EVENT_IDS.CHANGE_NAME,
EVENT_IDS.END_GAME,
EVENT_IDS.RESTART_GAME,
EVENT_IDS.PLAYER_JOINED,
diff --git a/server/modules/Events.js b/server/modules/Events.js
index b2369a7..1f4ab19 100644
--- a/server/modules/Events.js
+++ b/server/modules/Events.js
@@ -1,6 +1,6 @@
const GameStateCurator = require('./GameStateCurator');
const GameCreationRequest = require('../model/GameCreationRequest');
-const { EVENT_IDS, STATUS, USER_TYPES, GAME_PROCESS_COMMANDS, REDIS_CHANNELS } = require('../config/globals');
+const { EVENT_IDS, STATUS, USER_TYPES, GAME_PROCESS_COMMANDS, REDIS_CHANNELS, PRIMITIVES } = require('../config/globals');
const Events = [
{
@@ -55,6 +55,42 @@ const Events = [
);
}
},
+ {
+ id: EVENT_IDS.CHANGE_NAME,
+ stateChange: async (game, socketArgs, vars) => {
+ const toChangeIndex = game.people.findIndex(
+ (person) => person.id === socketArgs.personId
+ );
+ if (toChangeIndex >= 0) {
+ if (vars.gameManager.isNameTaken(game, socketArgs.newName)) {
+ vars.hasNameChanged = false;
+ if (game.people[toChangeIndex].name.toLowerCase().trim() === socketArgs.newName.toLowerCase().trim()) {
+ return;
+ }
+ vars.ackFn({ errorFlag: 1, message: 'This name is taken.' });
+ } else if (socketArgs.newName.length > PRIMITIVES.MAX_PERSON_NAME_LENGTH) {
+ vars.ackFn({ errorFlag: 1, message: 'Your new name is too long - the max is 30 characters.' });
+ vars.hasNameChanged = false;
+ } else if (socketArgs.newName.length === 0) {
+ vars.ackFn({ errorFlag: 1, message: 'Your new name cannot be empty.' });
+ vars.hasNameChanged = false;
+ } else {
+ game.people[toChangeIndex].name = socketArgs.newName;
+ vars.ackFn({ errorFlag: 0, message: 'Name updated!' });
+ vars.hasNameChanged = true;
+ }
+ }
+ },
+ communicate: async (game, socketArgs, vars) => {
+ if (vars.hasNameChanged) {
+ vars.gameManager.namespace.in(game.accessCode).emit(
+ EVENT_IDS.CHANGE_NAME,
+ socketArgs.personId,
+ socketArgs.newName
+ );
+ }
+ }
+ },
{
id: EVENT_IDS.UPDATE_GAME_ROLES,
stateChange: async (game, socketArgs, vars) => {
diff --git a/server/modules/singletons/GameManager.js b/server/modules/singletons/GameManager.js
index bed0f40..be7f2fd 100644
--- a/server/modules/singletons/GameManager.js
+++ b/server/modules/singletons/GameManager.js
@@ -182,7 +182,7 @@ class GameManager {
if (matchingPerson) {
return Promise.resolve(matchingPerson.cookie);
}
- if (isNameTaken(game, name)) {
+ if (this.isNameTaken(game, name)) {
return Promise.reject({ status: 400, reason: 'This name is taken.' });
}
if (joinAsSpectator
@@ -334,6 +334,11 @@ class GameManager {
findPersonByField = (game, fieldName, value) => {
return game.people.find(person => person[fieldName] === value);
}
+
+ isNameTaken (game, name) {
+ const processedName = name.toLowerCase().trim();
+ return game.people.find((person) => person.name.toLowerCase().trim() === processedName);
+ }
}
function getRandomInt (max) {
@@ -383,11 +388,6 @@ function createRandomId () {
return id;
}
-function isNameTaken (game, name) {
- const processedName = name.toLowerCase().trim();
- return game.people.find((person) => person.name.toLowerCase().trim() === processedName);
-}
-
async function addSpectator (game, name, logger, namespace, eventManager, instanceId, refreshGame) {
const spectator = new Person(
createRandomId(),
diff --git a/spec/unit/server/modules/singletons/GameManager_Spec.js b/spec/unit/server/modules/singletons/GameManager_Spec.js
index 6afa3ac..72a078a 100644
--- a/spec/unit/server/modules/singletons/GameManager_Spec.js
+++ b/spec/unit/server/modules/singletons/GameManager_Spec.js
@@ -18,7 +18,7 @@ describe('GameManager', () => {
const inObj = { emit: () => {} };
namespace = { in: () => { return inObj; }, to: () => { return inObj; } };
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
- gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, 'test');
+ gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENTS.PRODUCTION, 'test');
timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test');
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
eventManager.publisher = { publish: async (...a) => {} };