mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
refactor join to intermediary page
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="119.66505mm"
|
||||
height="109.59733mm"
|
||||
viewBox="0 0 119.66505 109.59733"
|
||||
viewBox="0 -12 125.66505 130.59733"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -1,9 +1,10 @@
|
||||
export class Game {
|
||||
constructor (deck, hasTimer, hasDedicatedModerator, timerParams = null) {
|
||||
constructor (deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams = null) {
|
||||
this.deck = deck;
|
||||
this.hasTimer = hasTimer;
|
||||
this.timerParams = timerParams;
|
||||
this.hasDedicatedModerator = hasDedicatedModerator;
|
||||
this.moderatorName = moderatorName
|
||||
this.accessCode = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { XHRUtility } from './XHRUtility.js';
|
||||
import { globals } from '../config/globals.js';
|
||||
import { templates } from './Templates.js';
|
||||
import { defaultCards } from '../config/defaultCards';
|
||||
import {UserUtility} from "./UserUtility";
|
||||
|
||||
export class GameCreationStepManager {
|
||||
constructor (deckManager) {
|
||||
@@ -50,50 +51,69 @@ export class GameCreationStepManager {
|
||||
},
|
||||
3: {
|
||||
title: 'Set an optional timer:',
|
||||
forwardHandler: () => {
|
||||
const hours = parseInt(document.getElementById('game-hours').value);
|
||||
const minutes = parseInt(document.getElementById('game-minutes').value);
|
||||
if ((isNaN(hours) && isNaN(minutes))
|
||||
|| (isNaN(hours) && minutes > 0 && minutes < 60)
|
||||
|| (isNaN(minutes) && hours > 0 && hours < 6)
|
||||
|| (hours === 0 && minutes > 0 && minutes < 60)
|
||||
|| (minutes === 0 && hours > 0 && hours < 6)
|
||||
|| (hours > 0 && hours < 6 && minutes >= 0 && minutes < 60)
|
||||
) {
|
||||
if (hasTimer(hours, minutes)) {
|
||||
this.currentGame.hasTimer = true;
|
||||
this.currentGame.timerParams = {
|
||||
hours: hours,
|
||||
minutes: minutes
|
||||
};
|
||||
forwardHandler: (e) => {
|
||||
if (e.type === 'click' || e.code === 'Enter') {
|
||||
const hours = parseInt(document.getElementById('game-hours').value);
|
||||
const minutes = parseInt(document.getElementById('game-minutes').value);
|
||||
if ((isNaN(hours) && isNaN(minutes))
|
||||
|| (isNaN(hours) && minutes > 0 && minutes < 60)
|
||||
|| (isNaN(minutes) && hours > 0 && hours < 6)
|
||||
|| (hours === 0 && minutes > 0 && minutes < 60)
|
||||
|| (minutes === 0 && hours > 0 && hours < 6)
|
||||
|| (hours > 0 && hours < 6 && minutes >= 0 && minutes < 60)
|
||||
) {
|
||||
if (hasTimer(hours, minutes)) {
|
||||
this.currentGame.hasTimer = true;
|
||||
this.currentGame.timerParams = {
|
||||
hours: hours,
|
||||
minutes: minutes
|
||||
};
|
||||
} else {
|
||||
this.currentGame.hasTimer = false;
|
||||
this.currentGame.timerParams = null;
|
||||
}
|
||||
cancelCurrentToast();
|
||||
this.removeStepElementsFromDOM(this.step);
|
||||
this.incrementStep();
|
||||
this.renderStep('creation-step-container', this.step);
|
||||
} else {
|
||||
this.currentGame.hasTimer = false;
|
||||
this.currentGame.timerParams = null;
|
||||
}
|
||||
cancelCurrentToast();
|
||||
this.removeStepElementsFromDOM(this.step);
|
||||
this.incrementStep();
|
||||
this.renderStep('creation-step-container', this.step);
|
||||
} else {
|
||||
if (hours === 0 && minutes === 0) {
|
||||
toast('You must enter a non-zero amount of time.', 'error', true);
|
||||
} else {
|
||||
toast('Invalid timer options. Hours can be a max of 5, Minutes a max of 59.', 'error', true);
|
||||
if (hours === 0 && minutes === 0) {
|
||||
toast('You must enter a non-zero amount of time.', 'error', true);
|
||||
} else {
|
||||
toast('Invalid timer options. Hours can be a max of 5, Minutes a max of 59.', 'error', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
backHandler: this.defaultBackHandler
|
||||
},
|
||||
4: {
|
||||
title: 'Enter your name:',
|
||||
forwardHandler: (e) => {
|
||||
if (e.type === 'click' || e.code === 'Enter') {
|
||||
let name = document.getElementById("moderator-name").value;
|
||||
if (validateName(name)) {
|
||||
this.currentGame.moderatorName = name;
|
||||
this.removeStepElementsFromDOM(this.step);
|
||||
this.incrementStep();
|
||||
this.renderStep('creation-step-container', this.step);
|
||||
} else {
|
||||
toast('Name must be between 1 and 30 characters.', 'error', true);
|
||||
}
|
||||
}
|
||||
},
|
||||
backHandler: this.defaultBackHandler
|
||||
},
|
||||
5: {
|
||||
title: 'Review and submit:',
|
||||
backHandler: this.defaultBackHandler,
|
||||
forwardHandler: (deck, hasTimer, hasDedicatedModerator, timerParams) => {
|
||||
forwardHandler: (deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams) => {
|
||||
XHRUtility.xhr(
|
||||
'/api/games/create',
|
||||
'POST',
|
||||
null,
|
||||
JSON.stringify(
|
||||
new Game(deck, hasTimer, hasDedicatedModerator, timerParams)
|
||||
new Game(deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams)
|
||||
)
|
||||
)
|
||||
.then((res) => {
|
||||
@@ -102,7 +122,9 @@ export class GameCreationStepManager {
|
||||
&& Object.prototype.hasOwnProperty.call(res, 'content')
|
||||
&& typeof res.content === 'string'
|
||||
) {
|
||||
window.location = ('/game/' + res.content);
|
||||
let json = JSON.parse(res.content);
|
||||
UserUtility.setAnonymousUserId(json.cookie, json.environment)
|
||||
window.location = ('/game/' + json.accessCode);
|
||||
}
|
||||
}).catch((e) => {
|
||||
const button = document.getElementById('create-game');
|
||||
@@ -144,10 +166,14 @@ export class GameCreationStepManager {
|
||||
showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler);
|
||||
break;
|
||||
case 3:
|
||||
renderTimerStep(containerId, step, this.currentGame);
|
||||
renderTimerStep(containerId, step, this.currentGame, this.steps);
|
||||
showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler);
|
||||
break;
|
||||
case 4:
|
||||
renderNameStep(containerId, step, this.currentGame, this.steps);
|
||||
showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler);
|
||||
break;
|
||||
case 5:
|
||||
renderReviewAndCreateStep(containerId, step, this.currentGame);
|
||||
showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler, this.currentGame);
|
||||
break;
|
||||
@@ -162,6 +188,18 @@ export class GameCreationStepManager {
|
||||
}
|
||||
}
|
||||
|
||||
function renderNameStep(containerId, step, game, steps) {
|
||||
const stepContainer = document.createElement('div');
|
||||
setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container step' });
|
||||
|
||||
stepContainer.innerHTML = templates.ENTER_NAME_STEP;
|
||||
document.getElementById(containerId).appendChild(stepContainer);
|
||||
let nameInput = document.querySelector('#moderator-name');
|
||||
nameInput.value = game.moderatorName;
|
||||
nameInput.addEventListener('keyup', steps["4"].forwardHandler)
|
||||
nameInput.focus();
|
||||
}
|
||||
|
||||
function renderModerationTypeStep (game, containerId, stepNumber) {
|
||||
const stepContainer = document.createElement('div');
|
||||
setAttributes(stepContainer, { id: 'step-' + stepNumber, class: 'flex-row-container step' });
|
||||
@@ -277,7 +315,7 @@ function renderRoleSelectionStep (game, containerId, step, deckManager) {
|
||||
initializeRemainingEventListeners(deckManager);
|
||||
}
|
||||
|
||||
function renderTimerStep (containerId, stepNumber, game) {
|
||||
function renderTimerStep (containerId, stepNumber, game, steps) {
|
||||
const div = document.createElement('div');
|
||||
div.setAttribute('id', 'step-' + stepNumber);
|
||||
div.classList.add('step');
|
||||
@@ -290,6 +328,7 @@ function renderTimerStep (containerId, stepNumber, game) {
|
||||
hoursLabel.setAttribute('for', 'game-hours');
|
||||
hoursLabel.innerText = 'Hours (max 5)';
|
||||
const hours = document.createElement('input');
|
||||
hours.addEventListener('keyup', steps[stepNumber].forwardHandler);
|
||||
setAttributes(hours, { type: 'number', id: 'game-hours', name: 'game-hours', min: '0', max: '5', value: game.timerParams?.hours });
|
||||
|
||||
const minutesDiv = document.createElement('div');
|
||||
@@ -297,6 +336,7 @@ function renderTimerStep (containerId, stepNumber, game) {
|
||||
minsLabel.setAttribute('for', 'game-minutes');
|
||||
minsLabel.innerText = 'Minutes';
|
||||
const minutes = document.createElement('input');
|
||||
minutes.addEventListener('keyup', steps[stepNumber].forwardHandler);
|
||||
setAttributes(minutes, { type: 'number', id: 'game-minutes', name: 'game-minutes', min: '1', max: '60', value: game.timerParams?.minutes });
|
||||
|
||||
hoursDiv.appendChild(hoursLabel);
|
||||
@@ -316,6 +356,10 @@ function renderReviewAndCreateStep (containerId, stepNumber, game) {
|
||||
div.classList.add('step');
|
||||
|
||||
div.innerHTML =
|
||||
'<div>' +
|
||||
"<label for='mod-name'>Your name</label>" +
|
||||
"<div id='mod-name' class='review-option'></div>" +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
"<label for='mod-option'>Moderation</label>" +
|
||||
"<div id='mod-option' class='review-option'></div>" +
|
||||
@@ -330,8 +374,8 @@ function renderReviewAndCreateStep (containerId, stepNumber, game) {
|
||||
'</div>';
|
||||
|
||||
div.querySelector('#mod-option').innerText = game.hasDedicatedModerator
|
||||
? "I will be the dedicated mod. Don't deal me a card."
|
||||
: 'The first person out will mod. Deal me into the game.';
|
||||
? "Dedicated Moderator - don't deal me a card."
|
||||
: 'Temporary Moderator - deal me into the game.';
|
||||
|
||||
if (game.hasTimer) {
|
||||
const formattedHours = !isNaN(game.timerParams.hours)
|
||||
@@ -353,6 +397,8 @@ function renderReviewAndCreateStep (containerId, stepNumber, game) {
|
||||
div.querySelector('#roles-option').appendChild(roleEl);
|
||||
}
|
||||
|
||||
div.querySelector("#mod-name").innerText = game.moderatorName;
|
||||
|
||||
document.getElementById(containerId).appendChild(div);
|
||||
}
|
||||
|
||||
@@ -406,6 +452,7 @@ function showButtons (back, forward, forwardHandler, backHandler, builtGame = nu
|
||||
builtGame.deck.filter((card) => card.quantity > 0),
|
||||
builtGame.hasTimer,
|
||||
builtGame.hasDedicatedModerator,
|
||||
builtGame.moderatorName,
|
||||
builtGame.timerParams
|
||||
);
|
||||
});
|
||||
@@ -701,3 +748,7 @@ function updateDeckStatus (deckManager) {
|
||||
function hasTimer (hours, minutes) {
|
||||
return (!isNaN(hours) || !isNaN(minutes));
|
||||
}
|
||||
|
||||
function validateName (name) {
|
||||
return typeof name === 'string' && name.length > 0 && name.length <= 30;
|
||||
}
|
||||
|
||||
@@ -43,18 +43,7 @@ export class GameStateRenderer {
|
||||
title.innerText = 'Lobby';
|
||||
document.getElementById('game-title').appendChild(title);
|
||||
const gameLinkContainer = document.getElementById('game-link');
|
||||
const linkDiv = document.createElement('div');
|
||||
linkDiv.innerText = window.location;
|
||||
gameLinkContainer.prepend(linkDiv);
|
||||
const linkCopyHandler = (e) => {
|
||||
if (e.type === 'click' || e.code === 'Enter') {
|
||||
navigator.clipboard.writeText(gameLinkContainer.innerText).then(() => {
|
||||
toast('Link copied!', 'success', true);
|
||||
});
|
||||
}
|
||||
};
|
||||
gameLinkContainer.addEventListener('click', linkCopyHandler);
|
||||
gameLinkContainer.addEventListener('keyup', linkCopyHandler);
|
||||
|
||||
const copyImg = document.createElement('img');
|
||||
copyImg.setAttribute('src', '../images/copy.svg');
|
||||
gameLinkContainer.appendChild(copyImg);
|
||||
@@ -63,8 +52,8 @@ export class GameStateRenderer {
|
||||
const playerCount = document.getElementById('game-player-count');
|
||||
playerCount.innerText = getGameSize(this.stateBucket.currentGameState.deck) + ' Players';
|
||||
|
||||
let timeString = '';
|
||||
if (this.stateBucket.currentGameState.timerParams) {
|
||||
let timeString = '';
|
||||
const hours = this.stateBucket.currentGameState.timerParams.hours;
|
||||
const minutes = this.stateBucket.currentGameState.timerParams.minutes;
|
||||
if (hours) {
|
||||
@@ -79,8 +68,30 @@ export class GameStateRenderer {
|
||||
}
|
||||
time.innerText = timeString;
|
||||
} else {
|
||||
time.innerText = 'untimed';
|
||||
timeString = 'untimed'
|
||||
time.innerText = timeString;
|
||||
}
|
||||
|
||||
let link = window.location.protocol + "//" + window.location.host
|
||||
+ '/join/' + this.stateBucket.currentGameState.accessCode
|
||||
+ '?playerCount=' + getGameSize(this.stateBucket.currentGameState.deck)
|
||||
+ '&timer=' + encodeURIComponent(timeString);
|
||||
|
||||
const linkCopyHandler = (e) => {
|
||||
if (e.type === 'click' || e.code === 'Enter') {
|
||||
navigator.clipboard.writeText(link)
|
||||
.then(() => {
|
||||
toast('Link copied!', 'success', true);
|
||||
});
|
||||
}
|
||||
};
|
||||
gameLinkContainer.addEventListener('click', linkCopyHandler);
|
||||
gameLinkContainer.addEventListener('keyup', linkCopyHandler);
|
||||
|
||||
const linkDiv = document.createElement('div');
|
||||
linkDiv.innerText = link;
|
||||
|
||||
gameLinkContainer.prepend(linkDiv);
|
||||
}
|
||||
|
||||
renderLobbyFooter () {
|
||||
|
||||
@@ -2,7 +2,7 @@ export const templates = {
|
||||
LOBBY:
|
||||
"<div id='lobby-header'>" +
|
||||
'<div>' +
|
||||
"<label for='game-link'>Share Link</label>" +
|
||||
"<label for='game-link'>Copy Share Link</label>" +
|
||||
"<div tabindex='0' id='game-link'></div>" +
|
||||
'</div>' +
|
||||
"<div id='game-parameters'>" +
|
||||
@@ -28,6 +28,12 @@ export const templates = {
|
||||
"<div id='game-deck'></div>" +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
ENTER_NAME_STEP:
|
||||
`<div id='step-4'>
|
||||
<div>
|
||||
<input type="text" id="moderator-name" autocomplete='given-name' placeholder="enter your name...">
|
||||
</div>
|
||||
</div>`,
|
||||
START_GAME_PROMPT:
|
||||
"<div id='start-game-prompt'>" +
|
||||
"<button id='start-game-button'>Start Game</button>" +
|
||||
|
||||
@@ -12,26 +12,6 @@ import { XHRUtility } from '../modules/XHRUtility.js';
|
||||
|
||||
const game = () => {
|
||||
injectNavbar();
|
||||
const socket = io('/in-game');
|
||||
const timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url));
|
||||
const gameTimerManager = new GameTimerManager(stateBucket, socket);
|
||||
const gameStateRenderer = new GameStateRenderer(stateBucket, socket);
|
||||
socket.on('disconnect', () => {
|
||||
toast('Disconnected. Attempting reconnect...', 'error', true, false);
|
||||
});
|
||||
socket.on('connect', () => {
|
||||
if (!stateBucket.joinRequestInFlight) {
|
||||
prepareGamePage(
|
||||
stateBucket,
|
||||
stateBucket.accessCode,
|
||||
gameTimerManager,
|
||||
gameStateRenderer,
|
||||
timerWorker,
|
||||
socket,
|
||||
UserUtility.validateAnonUserSignature(stateBucket.environment)
|
||||
);
|
||||
}
|
||||
});
|
||||
XHRUtility.xhr(
|
||||
'/api/games/environment',
|
||||
'GET',
|
||||
@@ -40,81 +20,50 @@ const game = () => {
|
||||
)
|
||||
.then((res) => {
|
||||
stateBucket.environment = res.content;
|
||||
joinGame(res, gameTimerManager, gameStateRenderer, socket, timerWorker);
|
||||
const socket = io('/in-game');
|
||||
const timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url));
|
||||
const gameTimerManager = new GameTimerManager(stateBucket, socket);
|
||||
const gameStateRenderer = new GameStateRenderer(stateBucket, socket);
|
||||
socket.on('connect', () => {
|
||||
syncWithGame(
|
||||
stateBucket,
|
||||
gameTimerManager,
|
||||
gameStateRenderer,
|
||||
timerWorker,
|
||||
socket,
|
||||
UserUtility.validateAnonUserSignature(res.content)
|
||||
);
|
||||
});
|
||||
socket.on('disconnect', () => {
|
||||
toast('Disconnected. Attempting reconnect...', 'error', true, false);
|
||||
});
|
||||
setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager);
|
||||
}).catch((res) => {
|
||||
toast(res.content, 'error', true);
|
||||
});
|
||||
toast(res.content, 'error', true);
|
||||
});
|
||||
};
|
||||
|
||||
function joinGame (environmentResponse, gameTimerManager, gameStateRenderer, socket, timerWorker) {
|
||||
let cookie = UserUtility.validateAnonUserSignature(environmentResponse.content);
|
||||
function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie) {
|
||||
const splitUrl = window.location.href.split('/game/');
|
||||
const accessCode = splitUrl[1];
|
||||
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
|
||||
XHRUtility.xhr(
|
||||
'/api/games/' + accessCode + '/players',
|
||||
'PATCH',
|
||||
null,
|
||||
JSON.stringify({ cookie: cookie })
|
||||
)
|
||||
.then((res) => {
|
||||
UserUtility.setAnonymousUserId(res.content, environmentResponse.content);
|
||||
stateBucket.accessCode = accessCode;
|
||||
stateBucket.joinRequestInFlight = false;
|
||||
cookie = res.content;
|
||||
setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager);
|
||||
prepareGamePage(stateBucket, accessCode, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie);
|
||||
}).catch((res) => {
|
||||
if (res.status === 404) {
|
||||
window.location = '/not-found?reason=' + encodeURIComponent('game-not-found');
|
||||
} else if (res.status >= 500) {
|
||||
toast(
|
||||
'The server is experiencing problems. Please try again later',
|
||||
'error',
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, cookie, function (gameState) {
|
||||
if (gameState === null) {
|
||||
window.location = '/not-found?reason=' + encodeURIComponent('game-not-found');
|
||||
} else {
|
||||
stateBucket.currentGameState = gameState;
|
||||
document.querySelector('.spinner-container')?.remove();
|
||||
document.querySelector('.spinner-background')?.remove();
|
||||
document.getElementById('game-content').innerHTML = templates.INITIAL_GAME_DOM;
|
||||
toast('You are connected.', 'success', true, true, 2);
|
||||
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code');
|
||||
}
|
||||
}
|
||||
|
||||
function prepareGamePage (stateBucket, accessCode, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie) {
|
||||
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, cookie, function (gameState) {
|
||||
stateBucket.currentGameState = gameState;
|
||||
document.querySelector('.spinner-container')?.remove();
|
||||
document.querySelector('.spinner-background')?.remove();
|
||||
document.getElementById('game-content').innerHTML = templates.INITIAL_GAME_DOM;
|
||||
toast('You are connected.', 'success', true, true, 2);
|
||||
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
|
||||
if (!gameState.client.hasEnteredName) {
|
||||
document.getElementById('prompt').innerHTML = templates.NAME_CHANGE_MODAL;
|
||||
document.getElementById('change-name-form').onsubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const name = document.getElementById('player-new-name').value;
|
||||
if (validateName(name)) {
|
||||
socket.emit(globals.COMMANDS.CHANGE_NAME, gameState.accessCode, {
|
||||
name: name,
|
||||
personId: gameState.client.id
|
||||
}, (result) => {
|
||||
switch (result) {
|
||||
case 'taken':
|
||||
toast('This name is already taken.', 'error', true, true, 8);
|
||||
break;
|
||||
case 'changed':
|
||||
ModalManager.dispelModal('change-name-modal', 'change-name-modal-background');
|
||||
toast('Name set.', 'success', true, true, 5);
|
||||
propagateNameChange(stateBucket.currentGameState, name, stateBucket.currentGameState.client.id);
|
||||
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toast('Name must be between 1 and 30 characters.', 'error', true, true, 8);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processGameState (currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker, refreshPrompt = true) {
|
||||
displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
|
||||
if (refreshPrompt) {
|
||||
|
||||
@@ -28,7 +28,7 @@ function attemptToJoinGame (code) {
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
window.location = '/game/' + res.content;
|
||||
window.location = '/join/' + res.content;
|
||||
}
|
||||
}).catch((res) => {
|
||||
if (res.status === 404) {
|
||||
|
||||
74
client/src/scripts/join.js
Normal file
74
client/src/scripts/join.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import {injectNavbar} from "../modules/Navbar.js";
|
||||
import {toast} from "../modules/Toast.js";
|
||||
import {XHRUtility} from "../modules/XHRUtility.js";
|
||||
import {UserUtility} from "../modules/UserUtility.js";
|
||||
import {globals} from "../config/globals.js";
|
||||
|
||||
const join = () => {
|
||||
injectNavbar();
|
||||
const splitUrl = window.location.pathname.split('/join/');
|
||||
const accessCode = splitUrl[1];
|
||||
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
|
||||
document.getElementById("game-code").innerText = accessCode;
|
||||
document.getElementById("game-time").innerText
|
||||
= decodeURIComponent((new URL(document.location)).searchParams.get('timer'));
|
||||
document.getElementById("game-player-count").innerText
|
||||
= decodeURIComponent((new URL(document.location)).searchParams.get('playerCount')) + ' Players';
|
||||
let playerCount = decodeURIComponent((new URL(document.location)).searchParams.get('playerCount'))
|
||||
let form = document.getElementById('join-game-form');
|
||||
document.getElementById("player-new-name").focus();
|
||||
form.onsubmit = joinHandler;
|
||||
} else {
|
||||
window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code');
|
||||
}
|
||||
}
|
||||
|
||||
const joinHandler = (e) => {
|
||||
const splitUrl = window.location.pathname.split('/join/');
|
||||
const accessCode = splitUrl[1];
|
||||
e.preventDefault();
|
||||
const name = document.getElementById('player-new-name').value;
|
||||
if (validateName(name)) {
|
||||
document.getElementById('join-game-form').onsubmit = null;
|
||||
document.getElementById("submit-new-name").classList.add('submitted');
|
||||
document.getElementById("submit-new-name").setAttribute('value', 'Joining...');
|
||||
XHRUtility.xhr(
|
||||
'/api/games/' + accessCode + '/players',
|
||||
'PATCH',
|
||||
null,
|
||||
JSON.stringify({ playerName: name, accessCode: accessCode })
|
||||
)
|
||||
.then((res) => {
|
||||
let json = JSON.parse(res.content);
|
||||
UserUtility.setAnonymousUserId(json.cookie, json.environment);
|
||||
window.location = '/game/' + accessCode;
|
||||
}).catch((res) => {
|
||||
document.getElementById('join-game-form').onsubmit = joinHandler;
|
||||
document.getElementById("submit-new-name").classList.remove('submitted');
|
||||
document.getElementById("submit-new-name").setAttribute('value', 'Join Game');
|
||||
if (res.status === 404) {
|
||||
toast('This game was not found.', 'error', true, true, 8);
|
||||
} else if (res.status === 400) {
|
||||
toast('This name is already taken.', 'error', true, true, 8);
|
||||
} else if (res.status >= 500) {
|
||||
toast(
|
||||
'The server is experiencing problems. Please try again later',
|
||||
'error',
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toast('Name must be between 1 and 30 characters.', 'error', true, true, 8);
|
||||
}
|
||||
};
|
||||
|
||||
function validateName (name) {
|
||||
return typeof name === 'string' && name.length > 0 && name.length <= 30;
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = join;
|
||||
} else {
|
||||
join();
|
||||
}
|
||||
@@ -186,6 +186,23 @@ button {
|
||||
color: #00a718;
|
||||
}
|
||||
|
||||
#game-parameters {
|
||||
font-family: signika-negative, sans-serif;
|
||||
color: #d7d7d7;
|
||||
font-size: 25px;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
#game-parameters > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#game-parameters img {
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#how-to-use-container img {
|
||||
max-width: 98%;
|
||||
border: 1px solid #57566a;
|
||||
|
||||
@@ -231,7 +231,7 @@ option {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#step-4 > div {
|
||||
#step-5 > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -242,7 +242,19 @@ option {
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
#step-4 > div label {
|
||||
#step-4 {
|
||||
width: 95%;
|
||||
max-width: 25em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#step-4 input {
|
||||
padding: 15px 5px;
|
||||
width: 95%;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#step-5 > div label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
min-width: 15em;
|
||||
border: 2px solid transparent;
|
||||
margin: 0.5em 0;
|
||||
box-shadow: 2px 2px 5px rgb(0 0 0 / 40%);
|
||||
}
|
||||
|
||||
#lobby-players {
|
||||
@@ -125,6 +126,7 @@ h1 {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
transition: background-color 0.2s;
|
||||
max-width: 20em;
|
||||
}
|
||||
|
||||
#game-link > div {
|
||||
@@ -624,22 +626,6 @@ label[for='moderator'] {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#game-parameters {
|
||||
color: #d7d7d7;
|
||||
font-size: 25px;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
#game-parameters > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#game-parameters img {
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#players-alive-label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
|
||||
28
client/src/styles/join.css
Normal file
28
client/src/styles/join.css
Normal file
@@ -0,0 +1,28 @@
|
||||
#join-game-modal {
|
||||
border-left: 5px solid #b1afcd;
|
||||
animation: entrance 0.5s forwards;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
#game-parameters div:nth-child(1) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#game-code {
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
|
||||
@keyframes entrance {
|
||||
0% {
|
||||
transform: translate(-50%, calc(-50% + 40px));
|
||||
opacity: 0;
|
||||
}
|
||||
95% {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,7 @@
|
||||
<div id="tracker-step-2" class="creation-step"></div>
|
||||
<div id="tracker-step-3" class="creation-step"></div>
|
||||
<div id="tracker-step-4" class="creation-step"></div>
|
||||
<div id="tracker-step-5" class="creation-step"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="creation-step-container">
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Active Game</title>
|
||||
<meta name="description" content="Join this game of werewolf!">
|
||||
<meta name="description" content="Spectate this game of Werewolf">
|
||||
<meta property="og:title" content="Werewolf Utility - Active Game">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:description" content="Join this game of werewolf!">
|
||||
<meta property="og:description" content="Spectate this game of Werewolf">
|
||||
<meta property="og:image" content="image.png">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
|
||||
53
client/src/views/join.html
Normal file
53
client/src/views/join.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Active Game</title>
|
||||
<meta name="description" content="Join this game of werewolf!">
|
||||
<meta property="og:title" content="Werewolf Utility - Join Game">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:description" content="Join this game of werewolf!">
|
||||
<meta property="og:image" content="image.png">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<link rel="stylesheet" href="/styles/GLOBAL.css">
|
||||
<link rel="stylesheet" href="/styles/join.css">
|
||||
<link rel="stylesheet" href="/styles/modal.css">
|
||||
<link rel="stylesheet" href="/styles/hamburgers.css">
|
||||
<link rel="preload" href="/webfonts/SignikaNegative-Light.woff2" as="font" type="font/woff2" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mobile-menu-background-overlay"></div>
|
||||
<div id="prompt"></div>
|
||||
<div id="navbar"></div>
|
||||
<div id="change-name-modal-background" class="modal-background"></div>
|
||||
<div tabindex="-1" id="join-game-modal" class="modal">
|
||||
<div id='game-parameters'>
|
||||
<div>
|
||||
<div>Game <span id='game-code'></span></div>
|
||||
</div>
|
||||
<div>
|
||||
<img alt='clock' src='/images/clock.svg'/>
|
||||
<div id='game-time'></div>
|
||||
</div>
|
||||
<div>
|
||||
<img alt='person' src='/images/person.svg'/>
|
||||
<div id='game-player-count'></div>
|
||||
</div>
|
||||
</div>
|
||||
<form id="join-game-form">
|
||||
<div id="join-game-form-content">
|
||||
<label for="player-new-name">Your name:</label>
|
||||
<input id="player-new-name" autocomplete="given-name" type="text"/>
|
||||
</div>
|
||||
<div class="modal-button-container">
|
||||
<input type="submit" id="submit-new-name" value="Join Game"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="/dist/join-bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,8 @@ module.exports = {
|
||||
home: './client/src/scripts/home.js',
|
||||
create: './client/src/scripts/create.js',
|
||||
notFound: './client/src/scripts/notFound.js',
|
||||
howToUse: './client/src/scripts/howToUse.js'
|
||||
howToUse: './client/src/scripts/howToUse.js',
|
||||
join: './client/src/scripts/join.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist'),
|
||||
|
||||
@@ -6,7 +6,9 @@ module.exports = {
|
||||
game: './client/src/scripts/game.js',
|
||||
home: './client/src/scripts/home.js',
|
||||
create: './client/src/scripts/create.js',
|
||||
notFound: './client/src/scripts/notFound.js'
|
||||
notFound: './client/src/scripts/notFound.js',
|
||||
howToUse: './client/src/scripts/howToUse.js',
|
||||
join: './client/src/scripts/join.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist'),
|
||||
|
||||
Reference in New Issue
Block a user