diff --git a/client/src/App.js b/client/src/App.js index bf42f84..96a1692 100755 --- a/client/src/App.js +++ b/client/src/App.js @@ -39,12 +39,18 @@ const Table = () => { /* We do not set the socket as bound until the 'open' message * comes through */ setWs(event.target); + + /* Request a game-update */ event.target.send(JSON.stringify({ type: 'game-update' })); }; const onWsMessage = (event) => { const data = JSON.parse(event.data); switch (data.type) { + case 'error': + console.error(data.error); + window.alert(data.error); + break; case 'game-update': console.log(`ws: message - ${data.type}`, data.update); if ('name' in data.update && data.update.name !== name) { diff --git a/client/src/MediaControl.css b/client/src/MediaControl.css index 1349b5f..9124861 100644 --- a/client/src/MediaControl.css +++ b/client/src/MediaControl.css @@ -11,7 +11,6 @@ display: flex; position: relative; flex-direction: row; - flex-grow: 1; justify-content: flex-end; align-items: center; } diff --git a/client/src/PlayerList.css b/client/src/PlayerList.css index 7bcc827..38518c9 100644 --- a/client/src/PlayerList.css +++ b/client/src/PlayerList.css @@ -6,6 +6,22 @@ flex-direction: column; } +.PlayerList .Name { + display: flex; + flex-grow: 1; +} + +.PlayerList .NoNetwork { + display: flex; + justify-self: flex-end; + width: 1em; + height: 1em; + background-image: url("./assets/no-network.png"); + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + .PlayerList .Unselected { display: flex; flex-direction: row; @@ -18,6 +34,7 @@ align-items: center; margin: 0.25rem; } + .PlayerList .PlayerSelector .PlayerColor { width: 1em; height: 1em; @@ -27,7 +44,6 @@ display: inline-flex; flex-wrap: wrap; flex-direction: row; - justify-content: space-between; } .PlayerList .PlayerSelector.MuiList-padding { @@ -48,7 +64,6 @@ border: 1px solid rgba(0,0,0,0); border-radius: 0.5em; min-width: 10em; - justify-content: space-between; } .PlayerList .PlayerSelector .PlayerEntry { @@ -68,9 +83,9 @@ background-color: rgba(255, 255, 0, 0.5); } -.PlayerList .PlayerEntry > *:last-child { +.PlayerList .PlayerEntry .MediaControl { display: flex; - flex-grow: 1; + justify-self: flex-end; } diff --git a/client/src/PlayerList.js b/client/src/PlayerList.js index f0e8697..2bea0a6 100644 --- a/client/src/PlayerList.js +++ b/client/src/PlayerList.js @@ -90,8 +90,10 @@ const PlayerList = () => { className="PlayerEntry" onClick={() => { inLobby && selectable && toggleSelected(key) }} key={`player-${key}`}> - {name ? name : 'Available' } - { name && } + +
{name ? name : 'Available' }
+ { name && !item.live &&
} + { name && item.live && } { !name &&
} ); diff --git a/server/routes/games.js b/server/routes/games.js index d835277..d240b83 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -854,7 +854,11 @@ const setPlayerName = (game, session, name) => { session.name = name; } else { if (rejoin) { - message = `${name} has rejoined the game! Welcome back, ${name}.`; + if (session.color) { + message = `${name} has reconnected to the game.`; + } else { + message = `${name} has rejoined the lobby.`; + } session.name = name; if (session.ws && id in audio) { part(audio[id], session, game.id); @@ -910,7 +914,7 @@ const setPlayerColor = (game, session, color) => { } /* Verify selection is valid */ - if (!(color in game.players)) { + if (color && !(color in game.players)) { return `An invalid player selection was attempted.`; } @@ -958,7 +962,7 @@ const setPlayerColor = (game, session, color) => { /* Rebuild the unselected list */ game.unselected = []; for (let id in game.sessions) { - if (!game.sessions.color[id] && game.sessions[id].name) { + if (!game.sessions[id].color && game.sessions[id].name) { game.unselected.push(game.sessions[id]); } } @@ -2887,7 +2891,7 @@ const saveGame = async (game) => { }); } -const departLobby = (game, session) => { +const departLobby = (game, session, color) => { const update = {}; update.unselected = getFilteredUnselected(game); @@ -2897,10 +2901,18 @@ const departLobby = (game, session) => { } if (session.name) { - addChatMessage(game, null, `${session.name} has left the lobby.`); + if (session.color) { + addChatMessage(game, null, `${session.name} has disconnected from the game.`); + } else { + addChatMessage(game, null, `${session.name} has left the lobby.`); + } update.chat = game.chat; } + + sendToPlayers(game, update); +} +const sendToPlayers = async (game, update) => { const message = JSON.stringify({ type: 'game-update', update @@ -2912,6 +2924,7 @@ const departLobby = (game, session) => { } _session.ws.send(message); } + await saveGame(game); } const getFilteredUnselected = (game) => { @@ -2951,14 +2964,6 @@ router.ws("/ws/:id", async (ws, req) => { departLobby(game, session); }); - ws.on('open', async (event) => { - console.log(`WebSocket open: `, event.message); - const game = await loadGame(gameId); - if (game) { - resetDisconnectCheck(game, req); - } - }); - ws.on('close', async (event) => { const game = await loadGame(gameId); if (!game) { @@ -3075,6 +3080,8 @@ router.ws("/ws/:id", async (ws, req) => { session.ws.send(JSON.stringify({ type: 'error', error })); break; } + /* Can't use sendToPlayers as the player name is a top level field + * and is unique to each player */ for (let key in game.sessions) { const _session = game.sessions[key]; if (!_session.ws) { @@ -3096,7 +3103,8 @@ router.ws("/ws/:id", async (ws, req) => { case 'color': error = setPlayerColor(game, session, data.value); if (error) { - session.ws.send(JSON.stringify({ type: error, error })); + console.warn(error); + session.ws.send(JSON.stringify({ type: 'error', error })); break; } for (let key in game.sessions) { @@ -3157,27 +3165,8 @@ router.ws("/ws/:id", async (ws, req) => { case 'chat': console.log(`${id}:${session.id} - ${data.type} - ${data.message}`) - - /* Update the chat array */ addChatMessage(game, session, `${session.name}: ${data.message}`); - - /* Send the update to all players */ - message = JSON.stringify({ - type: 'game-update', - update: { - chat: game.chat - } - }); - for (let key in game.sessions) { - const _session = game.sessions[key]; - if (!_session.ws) { - continue; - } - _session.ws.send(message); - } - - /* Save the current game state to disk */ - await saveGame(game); + sendToPlayers(game, { chat: game.chat }); break; } }); @@ -3193,21 +3182,24 @@ router.ws("/ws/:id", async (ws, req) => { const session = getSession(game, req.session); session.lastActive = Date.now(); + session.ws = ws; + + if (session.name) { + if (session.color) { + addChatMessage(game, null, `${session.name} has reconnected to the game.`); + } else { + addChatMessage(game, null, `${session.name} has rejoined the lobby.`); + } + sendToPlayers(game, { chat: game.chat }); + } resetDisconnectCheck(game, req); - - console.log(`WebSocket connect from game ${id}:${session.name ? session.name : "Unnamed"}`); + console.log(`WebSocket connect from game ${id}:${getName(session)}`); - if (session) { - console.log(`WebSocket connected for ${session.name ? session.name : "Unnamed"}`); - session.ws = ws; - if (session.keepAlive) { - clearTimeout(session.keepAlive); - } - session.keepAlive = setTimeout(() => { ping(session); }, 2500); - } else { - console.log(`No session found for WebSocket with id ${id}`); - } + if (session.keepAlive) { + clearTimeout(session.keepAlive); + } + session.keepAlive = setTimeout(() => { ping(session); }, 2500); }); const debugChat = (game, preamble) => {