Merge pull request #21 from ThomasRubini/client_lobby_room_page

[Client] Add lobby page
This commit is contained in:
AudricV 2023-01-09 16:33:59 +01:00 committed by GitHub
commit d2868a23d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 696 additions and 76 deletions

View File

@ -0,0 +1,216 @@
/* Global properties */
html {
background-color: black;
color: white;
}
:root {
--button-background-color: #FF0000;
}
.multi_player_mode_choice_title, .multi_player_mode_waiting_for_host, .player_name, .players_title, .rounds_count_title, .room_code_text_title, .room_title {
font-family: "Titan One", sans-serif;
margin-bottom: 0.5em;
margin-top: 0.5em;
}
.join_room_view, .room_view {
height: calc(100vh - var(--body-margin) * 2);
}
/* Action buttons */
.action_button {
border-color: black;
border-style: solid;
border-width: 0.125em;
background-color: var(--button-background-color);
border-radius: var(--button-and-dialog-border-radius);
color: white;
cursor: pointer;
font-family: "Titan One", sans-serif;
margin-left: auto;
margin-right: auto;
padding-top: 0.5em;
padding-bottom: 0.5em;
padding-left: 1em;
padding-right: 1em;
text-transform: uppercase;
overflow: hidden;
transition: box-shadow 0.5s, transform 0.5s;
}
.action_button:hover {
transform: translate(0.1em, 0.1em);
box-shadow: 10px 10px 0px 0px black;
}
.multi_player_mode_choice .action_button, .room_code_text .action_button {
font-size: 1.5em;
min-width: 10em;
}
/* Room view major elements */
.room_title {
color: var(--button-background-color);
font-family: "Spicy Rice", sans-serif;
font-weight: bold;
font-size: 4em;
margin: 0.25em;
}
.room_view_container {
align-items: center;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
}
/* Room code */
.room_code {
color: var(--button-background-color);
text-decoration: none;
}
.room_code_text {
align-items: center;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
}
.room_code_text_title {
font-size: 2em;
margin: 0.25em;
}
#invite_friends_button {
font-size: 1em;
text-transform: none;
background-color: #c2c0c0;
border-radius: 0.5em;
}
#invite_friends_button:hover {
transform: translate(0.1em, 0.1em);
box-shadow: 10px 10px 0px 0px black;
}
/* Waiting for host */
.multi_player_mode_waiting_for_host {
font-size: 2.5em;
max-width: 20em;
text-align: center;
}
/* Multi-player mode choice */
.multi_player_mode_choices {
padding: 1em;
}
.multi_player_mode_choice {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
text-align: center;
}
.multi_player_mode_choice_title {
font-size: 2em;
margin: 0.5em;
}
.multi_player_mode_choice_number {
align-items: center;
display: flex;
flex-direction: row;
margin: 0.75em;
}
.multi_player_challenge_mode_invalid_input {
color: var(--button-background-color);
font-family: "Roboto Mono", sans-serif;
font-size: 1em;
font-weight: bold;
margin: 0.5em;
}
/* Rounds count */
#rounds_count {
background-color: white;
border: none;
border-radius: 0.5em;
color: black;
font-family: "Titan One", sans-serif;
font-size: 1em;
padding: 0.5em;
width: 2.5em;
}
.rounds_count_title {
font-size: 1.375em;
margin: 0.5em;
}
/* Players list */
.players_title {
align-content: center;
align-items: center;
display: flex;
font-size: 3em;
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
margin: 1em;
}
.player_names {
border: 0.25em white solid;
border-radius: 0.75em;
max-height: 12em;
overflow-y: scroll;
}
.player_name {
font-size: 1.5em;
margin-bottom: 0.5em;
margin-top: 0.5em;
text-align: center;
color: white;
}
/* Game join view */
.join_room_view {
align-items: center;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
}
#game_username {
background-color: white;
border: none;
border-radius: 0.375em;
color: black;
font-family: "Titan One", sans-serif;
font-size: 1.25em;
margin: 0.5em;
padding: 0.5em;
}
#join_game_button {
font-size: 1.25em;
margin: 1em;
}
/* Game start failure */
.game_start_failed {
color: var(--button-background-color);
font-family: "Roboto Mono", sans-serif;
font-weight: bold;
font-size: 1em;
}

View File

@ -175,8 +175,7 @@ input::placeholder {
}
#game_username {
margin: 0.5em;
width: calc(100% - 1.5em);
width: calc(100% - 1.25em);
}
#play_button {

View File

@ -31,76 +31,6 @@ function checkWebSocketAvailability() {
}
}
/**
* Set the current theme for the game.
*
* <p>
* The theme preference is read from the local storage.
* </p>
*
* <p>
* If accessing to the local storage is not allow, an error message which prevents playing the game
* and requesting user to enable localStorage is shown, and the error is logged in the console.
* </p>
*/
function setCurrentTheme() {
const htmlElement = document.getElementsByTagName("html")[0];
try {
const currentTheme = localStorage.getItem("pref_theme");
if (currentTheme == "light") {
htmlElement.classList.remove("dark");
htmlElement.classList.add("light");
} else {
// Use dark theme by default
htmlElement.classList.remove("light");
htmlElement.classList.add("dark");
}
const btn = document.getElementsByClassName("theme_switcher")[0];
btn.addEventListener("pointerup", changeTheme);
} catch (e) {
console.error("Unable to set theme from localStorage", e);
htmlElement.classList.add("dark");
showUnsupportedBrowserMessage("Votre navigateur ne semble pas supporter le localStorage. Certains navigateurs nécessitant l'autorisation d'utiliser des cookies pour utiliser le localStorage, vérifiez que les cookies sont autorisés pour le site du jeu dans le vôtre.");
}
}
/**
* Change the theme from the current theme to its opposite.
*
* <p>
* If the current theme is "dark", it will become "light" and vice versa.
* </p>
*
* <p>
* The new theme is saved in the localStorage, if the browser allows this action; otherwise, an
* error message is shown in the console.
* </p>
*/
function changeTheme() {
const currentTheme = localStorage.getItem("pref_theme");
const htmlElement = document.getElementsByTagName("html")[0];
let newTheme;
if (currentTheme == "light") {
htmlElement.classList.remove("light");
htmlElement.classList.add("dark");
newTheme = "dark";
} else {
htmlElement.classList.remove("dark");
htmlElement.classList.add("light");
newTheme = "light";
}
try {
localStorage.setItem("pref_theme", newTheme);
} catch (e) {
console.error("Unable to save theme change to localStorage", e);
}
}
/**
* Show the unsupported browser dialog, which disables ability to play the game, using the given
* unsupported browser message text.
@ -136,6 +66,5 @@ function showAlertDialog(element) {
// Execution of main functions
setCurrentTheme();
detectIEBrowsers();
checkWebSocketAvailability();

View File

@ -0,0 +1,336 @@
// Display functions
/**
* Display the invalid rounds count message element, by removing the hidden CSS class.
*
* @param {Element} invalidRoundsCountMessageElement the invalid rounds counts message
*/
function displayInvalidRoundsCountErrorMessage(invalidRoundsCountMessageElement) {
invalidRoundsCountMessageElement.classList.remove("hidden");
}
/**
* Get the room code and display the room code element.
*/
function displayRoomCode() {
let roomCode = getRoomCode();
let roomCodeElement = document.getElementsByClassName("room_code")[0];
roomCodeElement.textContent = roomCode;
roomCodeElement.setAttribute("href", "/lobby/" + roomCode);
document.getElementsByClassName("room_code_text")[0].classList.remove("hidden");
}
/**
* Display the players list element.
*/
function displayPlayerList() {
document.getElementsByClassName("players_list")[0].classList.remove("hidden");
}
/**
* Display the multi player mode choices, by removing the hidden CSS class on the first
* multi_player_mode_choices element.
*/
function displayMultiPlayerModeChoices() {
document.getElementsByClassName("multi_player_mode_choices")[0].classList.remove("hidden");
}
/**
* Display the room view, by removing the hidden CSS class on the first room_view element.
*/
function displayRoomView() {
document.getElementsByClassName("room_view")[0].classList.remove("hidden");
}
/**
* Display the join room view, by removing the hidden CSS class on the first join_room_view
* element.
*/
function displayJoinRoomView() {
document.getElementsByClassName("join_room_view")[0].classList.remove("hidden");
}
/**
* Show an error message on the first game_start_failed CSS element.
*
* <p>
* The current error message text will be replaced by the given message and the element will be
* shown, by removing the hidden CSS class on the element.
* </p>
*
* @param {boolean} errorMessage the error message to show
*/
function displayInvalidNickNameErrorMessage(errorMessage) {
let gameStartFailedElement = document.getElementsByClassName("game_start_failed")[0];
gameStartFailedElement.textContent = errorMessage;
gameStartFailedElement.classList.remove("hidden");
}
/**
* Hide an error message on the first game_start_failed CSS element.
*
* <p>
* The element will be hidden by removing the hidden CSS class on the element.
* </p>
*/
function hideInvalidNickNameErrorMessage() {
document.getElementsByClassName("game_start_failed")[0].classList.add("hidden");
}
/**
* Hide the invalid rounds count message element, by adding the hidden CSS class.
*
* @param {Element} invalidRoundsCountMessageElement the invalid rounds counts message
*/
function hideInvalidRoundsCountErrorMessage(invalidRoundsCountMessageElement) {
invalidRoundsCountMessageElement.classList.add("hidden");
}
// Start game functions
function startHistoryGame() {
//TODO: start the history game and handle server errors + connection errors
}
function startChallengeGame() {
let roundsCount = getChallengeModeRoundsCount();
if (roundsCount == -1) {
return;
}
alert("Ce mode de jeu n'est malheureusement pas disponible.");
}
// Join room functions
function joinRoom() {
unsetListenerToJoinRoomButton();
if (isNickNameInvalid()) {
displayInvalidNickNameErrorMessage("Le nom saisi n'est pas valide.");
setListenerToJoinRoomButton();
return;
}
hideInvalidNickNameErrorMessage();
//TODO: join the game room and handle server errors + connection errors
}
// Room code functions
/**
* Copy the room code to the clipboard.
*
* <p>
* In order to not make an additional API call to get the room code, we use the value from the
* room code HTML element and generate a HTTP link from this value, copied to the clipboard using
* {@link copyTextToClipboard}.
* </p>
*/
function copyCode() {
// Get the room code from the displayed text to avoid an extra API call
let roomCode = document.getElementsByClassName("room_code")[0].textContent;
if (roomCode == "") {
alert("Veuillez patientez, le code d'équipe est en cours de génération.");
}
copyTextToClipboard(window.location.protocol + "//" + window.location.hostname + ":"
+ window.location.port + "/lobby/" + roomCode);
}
// Listeners functions
/**
* Set listeners to game buttons.
*
* <p>
* This function adds a click event listener on start game buttons.
* </p>
*/
function setListenersToGameButtons() {
document.getElementById("multi_player_history_start_button").addEventListener("click", startHistoryGame);
document.getElementById("multi_player_challenge_start_button").addEventListener("click", startChallengeGame);
}
/**
* Set listeners to the join room button.
*
* <p>
* This function adds a click event listener on the join room button.
* </p>
*/
function setListenerToJoinRoomButton() {
document.getElementById("join_game_button").addEventListener("click", joinRoom);
}
/**
* Set listeners to the copy room code button.
*
* <p>
* This function adds a click event listener on the copy room code button.
* </p>
*/
function setListenerToCopyCodeButton() {
document.getElementById("invite_friends_button").addEventListener("click", copyCode);
}
/**
* Unset listeners to game buttons.
*
* <p>
* This function removes the click event listener set with {@link setListenersToGameButtons} on
* start game buttons.
* </p>
*/
function unsetListenersToButtons() {
document.getElementById("multi_player_history_start_button").removeEventListener("click", startHistoryGame);
document.getElementById("multi_player_challenge_start_button").removeEventListener("click", startChallengeGame);
}
/**
* Unset listeners to the join room button.
*
* <p>
* This function removes the click event listener set with {@link setListenerToJoinRoomButton} on
* the join room button.
* </p>
*/
function unsetListenerToJoinRoomButton() {
document.getElementById("join_game_button").removeEventListener("click", joinRoom);
}
/**
* Unset listeners to the copy room code button.
*
* <p>
* This function removes the click event listener set with {@link setListenerToCopyCodeButton} on
* the copy room code button.
* </p>
*/
function unsetListenerToCopyCodeButton() {
document.getElementById("invite_friends_button").removeEventListener("click", copyCode);
}
// Utility functions
function isRoomOwner() {
//FIXME: check if player is room owner
return true;
}
function hasJoinedRoom() {
//FIXME: check if player has joined the room
return true;
}
/**
* Copy the given text in the clipboard, if the browser allows it.
*
* <p>
* A JavaScript alert is created witn an appropriate message, regardless of whether the copy succeeded.
* </p>
*
* <p>
* This function uses the Clipboard API. In the case it is not supported by the browser used, a JavaScript alert is shown..
* </p>
*
* @param {string}} textToCopy the text to copy to the clipboard
*/
function copyTextToClipboard(textToCopy) {
if (!navigator.clipboard) {
alert("Votre navigateur ne supporte pas l'API Clipboard. Veuillez copier le texte en ouvrant le menu contextuel de votre navigateur sur le lien et sélectionner l'option pour copier le lien.");
}
navigator.clipboard.writeText(textToCopy).then(() => {
alert("Code copié avec succès dans le presse-papiers.");
}, () => {
alert("Impossible de copier le texte. Vérifiez si vous avez donné la permission d'accès au presse-papiers pour le site de Thruth Inquiry dans les paramètres de votre navigateur.");
});
}
/**
* Determine whether a nickname is invalid.
*
* <p>
* A nickname is invalid when it only contains spaces characters or is empty.
* </p>
*
* @returns whether a nickname is invalid
*/
function isNickNameInvalid() {
return document.getElementById("game_username").value.trim() == "";
}
/**
* Get the rounds count for the challenge mode from the user input.
*
* <p>
* As browsers allow to enter any character on a number imput, we need to validate the user value.
* A regular expression which checks that every character is a number digit is used.
* </p>
*
* <p>
* If the user input isn't matched by the regular expression, an error message is shown to the user.
* </p>
*
* @returns the rounds count or -1 if it is invalid
*/
function getChallengeModeRoundsCount() {
let roundsCountText = document.getElementById("rounds_count").value;
let errorElement = document.getElementsByClassName("multi_player_challenge_mode_invalid_input")[0];
if (!/^\d+$/.test(roundsCountText)) {
displayInvalidRoundsCountErrorMessage(errorElement);
return -1;
}
let roundsCountNumber = parseInt(roundsCountText);
if (roundsCountNumber < 5 || roundsCountNumber > 15) {
displayInvalidRoundsCountErrorMessage(errorElement);
return -1;
}
hideInvalidRoundsCountErrorMessage(errorElement);
return roundsCountNumber;
}
/**
* Get the code of the room.
*
* @returns the code of the room
*/
function getRoomCode() {
//FIXME get the real room code
return "ABCDEF";
}
// Lobby initialization
/**
* Initialize the lobby page.
*
* <p>
* If the player has joined the room, the room view will be shown. In the case the player is the
* owner of the room, the room code and the multi player mode choice will be shown and the
* listeners to the game buttons will be done.
* </p>
*
* <p>
* If the player has not joined the room, the join room view will be shown and a listener to the
* join room button will be set.
* </p>
*/
function initLobby() {
if (hasJoinedRoom()) {
displayRoomView();
if (isRoomOwner()) {
displayRoomCode();
displayMultiPlayerModeChoices();
setListenersToGameButtons();
setListenerToCopyCodeButton();
}
displayPlayerList();
} else {
displayJoinRoomView();
setListenerToJoinRoomButton();
}
}
initLobby();

View File

@ -138,8 +138,84 @@ function joinMultiPlayerRoom() {
//TODO: code to join multi player game
}
/**
* Set the current theme for the game.
*
* <p>
* The theme preference is read from the local storage.
* </p>
*
* <p>
* If accessing to the local storage is not allow, an error message which prevents playing the game
* and requesting user to enable localStorage is shown, and the error is logged in the console.
* </p>
*/
function setCurrentTheme() {
const htmlElement = document.getElementsByTagName("html")[0];
try {
const currentTheme = localStorage.getItem("pref_theme");
if (currentTheme == "light") {
htmlElement.classList.remove("dark");
htmlElement.classList.add("light");
} else {
// Use dark theme by default
htmlElement.classList.remove("light");
htmlElement.classList.add("dark");
}
const btn = document.getElementsByClassName("theme_switcher")[0];
btn.addEventListener("pointerup", changeTheme);
} catch (e) {
console.error("Unable to set theme from localStorage", e);
htmlElement.classList.add("dark");
showUnsupportedBrowserMessage("Votre navigateur ne semble pas supporter le localStorage. Certains navigateurs nécessitant l'autorisation d'utiliser des cookies pour utiliser le localStorage, vérifiez que les cookies sont autorisés pour le site du jeu dans le vôtre.");
}
}
/**
* Change the theme from the current theme to its opposite.
*
* <p>
* If the current theme is "dark", it will become "light" and vice versa.
* </p>
*
* <p>
* The new theme is saved in the localStorage, if the browser allows this action; otherwise, an
* error message is shown in the console.
* </p>
*/
function changeTheme() {
const currentTheme = localStorage.getItem("pref_theme");
const htmlElement = document.getElementsByTagName("html")[0];
let newTheme;
if (currentTheme == "light") {
htmlElement.classList.remove("light");
htmlElement.classList.add("dark");
newTheme = "dark";
} else {
htmlElement.classList.remove("dark");
htmlElement.classList.add("light");
newTheme = "light";
}
try {
localStorage.setItem("pref_theme", newTheme);
} catch (e) {
console.error("Unable to save theme change to localStorage", e);
}
}
// Set event listeners
document.getElementById("play_button").addEventListener("click", showGameModeSelection);
document.getElementById("back_button").addEventListener("click", hideGameModeSelection);
document.getElementById("start_solo_game_button").addEventListener("click", startSoloGame);
document.getElementById("create_room_button").addEventListener("click", createMultiPlayerRoom);
document.getElementById("join_room_button").addEventListener("click", joinMultiPlayerRoom);
// Execution of functions
setCurrentTheme();

View File

@ -1,3 +1,67 @@
lobby.html template
<br>
<a href="/multi">Go to the multi game</a>
<!DOCTYPE html>
<html lang="fr">
<head>
<title>Truth Inquiry - Salon</title>
<link rel="stylesheet" href="/static/css/game_ui.css">
<link rel="stylesheet" href="/static/css/game_ui_lobby.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
</head>
<body class="game_app">
<div class="join_room_view hidden">
<h1 class="room_title">Salon</h1>
<input type="text" id="game_username" placeholder="Entrez un pseudo" value="" required="required" maxlength="20">
<button id="join_game_button" class="action_button">Rejoindre</button>
<p class="game_start_failed hidden">Une erreur s'est produite. Réessayez ultérieurement.</p>
</div>
<div class="room_view hidden">
<div class="room_view_container">
<h1 class="room_title">Salon</h1>
<div class="room_code_text hidden">
<h3 class="room_code_text_title">Code&nbsp;: <a href="" target="_blank" class="room_code"></a></h3>
<button id="invite_friends_button" class="action_button">Copier le lien d'invitation</button>
</div>
</div>
<div class="room_view_container">
<div class="players_list hidden">
<h2 class="players_title">Joueurs</h2>
<div class="player_names"></div>
</div>
<div class="multi_player_mode_choices hidden">
<div class="multi_player_mode_choice">
<h3 class="multi_player_mode_choice_title">Jouer au mode Histoire</h3>
<button id="multi_player_history_start_button" class="action_button">Lancer</button>
</div>
<div class="multi_player_mode_choice">
<h3 class="multi_player_mode_choice_title">Jouer au mode Challenge</h3>
<button id="multi_player_challenge_start_button" class="action_button">Lancer</button>
<div class="multi_player_mode_choice_number">
<h3 class="rounds_count_title">Nombre de tours&nbsp;:</h3>
<input id="rounds_count" type="number" min="5" max="15" value="5">
</div>
<p class="multi_player_challenge_mode_invalid_input hidden">Nombre de tours invalide. Veuillez entrer un nombre valide entre 5 inclus et 15 inclus.</p>
</div>
</div>
<h3 class="multi_player_mode_waiting_for_host hidden">En attente du démarrage de la partie par l'hôte&nbsp;</h3>
</div>
</div>
<div class="alert_dialog_background"></div>
<div class="unsupported_browser">
<div id="unsupported_browser_dialog" class="alert_dialog">
<h3 class="alert_dialog_title">Navigateur non supporté</h3>
<p class="alert_dialog_msg unsupported_browser_msg"></p>
</div>
</div>
<noscript>
<div class="alert_dialog_background" style="display: block;"></div>
<div class="js_requirement">
<div id="js_requirement_dialog" class="alert_dialog" style="display: block;">
<h3 class="alert_dialog_title" style="display: block;">JavaScript nécessaire</h3>
<p class="alert_dialog_msg unsupported_browser_msg" style="display: block;">Désolé, mais JavaScript est nécessaire pour faire fonctionner Truth Inquiry. Veuillez l'activer dans votre navigateur ou en utiliser un qui le supporte afin de pouvoir jouer au jeu.</p>
</div>
</div>
</noscript>
<script src="/static/js/game_common.js"></script>
<script src="/static/js/game_lobby.js"></script>
</body>
</html>