confirmation module, tutorial updates

This commit is contained in:
AlecM33
2022-12-07 13:04:32 -05:00
parent 07e5202134
commit 07d452b2d2
16 changed files with 240 additions and 32 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

View File

@@ -0,0 +1,63 @@
import { toast } from './Toast.js';
export const Confirmation = (message, onYes) => {
document.querySelector('#confirmation')?.remove();
document.querySelector('#confirmation-background')?.remove();
let confirmation = document.createElement('div');
confirmation.setAttribute('id', 'confirmation');
confirmation.innerHTML =
`<div id="confirmation-message"></div>
<div class="confirmation-buttons">
<button id="confirmation-cancel-button" class="app-button cancel">Cancel</button>
<button id="confirmation-yes-button" class="app-button">Yes</button>
</div>`;
confirmation.querySelector('#confirmation-message').innerText = message;
let background = document.createElement('div');
background.setAttribute('id', 'confirmation-background');
const cancelHandler = () => {
confirmation.remove();
background.remove();
confirmation = null;
background = null;
};
const confirmHandler = () => {
const button = confirmation.querySelector('#confirmation-yes-button');
button.classList.add('disabled');
button.innerText = '...';
button.removeEventListener('click', confirmHandler);
new Promise((resolve, reject) => {
try {
resolve(onYes());
} catch (e) {
reject(e);
}
}).then((res) => {
if (res && typeof res === 'string') {
toast(res, 'success', true, true, 'medium', false);
}
confirmation.remove();
background.remove();
confirmation = null;
background = null;
}).catch((e) => {
if (typeof e === 'string') {
toast(e, 'error', true, true, 'medium', false);
}
button.addEventListener('click', confirmHandler);
button.classList.remove('disabled');
button.innerText = 'Yes';
});
};
confirmation.querySelector('#confirmation-cancel-button').addEventListener('click', cancelHandler);
confirmation.querySelector('#confirmation-yes-button').addEventListener('click', confirmHandler);
background.addEventListener('click', cancelHandler);
document.body.appendChild(background);
document.body.appendChild(confirmation);
};

View File

@@ -63,8 +63,8 @@ export const HTMLFragments = {
<p id='role-description'></p>
</div>
<div id='game-role-back'>
<h4>Double-tap here to show your role</h4>
<p>(Double-tap here again to hide)</p>
<h4>Double-click here to show your role</h4>
<p>(Double-click here again to hide)</p>
</div>
<div id='game-people-container'>
<label id='players-alive-label'></label>
@@ -141,8 +141,8 @@ export const HTMLFragments = {
<p id='role-description'></p>
</div>
<div id='game-role-back'>
<h4>Double-tap here to show your role</h4>
<p>(Double-tap here again to hide)</p>
<h4>Double-click here to show your role</h4>
<p>(Double-click here again to hide)</p>
</div>
<div id='game-people-container'>
<label id='players-alive-label'></label>

View File

@@ -43,7 +43,7 @@ function getNavbarLinks (page = null, device) {
'<a class="' + linkClass + '" href="/">Home</a>' +
'<a class="' + linkClass + '" href="/create">Create</a>' +
'<a class="' + linkClass + '" href="/how-to-use">How to Use</a>' +
'<a class="' + linkClass + ' "href="mailto:play.werewolf.contact@gmail.com?Subject=Werewolf App" target="_top">Contact</a>' +
'<a class="' + linkClass + ' "href="mailto:play.werewolf.contact@gmail.com?Subject=Werewolf App" target="_top">Feedback</a>' +
'<a class="' + linkClass + ' "href="https://github.com/alecm33/Werewolf" target="_top">Github</a>' +
'<a class="' + linkClass + '" href="https://www.buymeacoffee.com/alecm33">Support the App</a>';
}

View File

@@ -134,6 +134,7 @@ export class DeckStateManager {
document.getElementById('deck-list').appendChild(placeholder);
};
// TODO: refactor
updateDeckStatus = () => {
document.getElementById('deck-count').innerText = this.getDeckSize() + ' Players';
if (this.deck.length > 0) {
@@ -186,10 +187,14 @@ export class DeckStateManager {
const infoHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
nameEl.classList.remove(globals.ALIGNMENT.GOOD);
nameEl.classList.remove(globals.ALIGNMENT.EVIL);
e.preventDefault();
document.getElementById('custom-role-info-modal-name').innerText = sortedDeck[i].role;
nameEl.innerText = sortedDeck[i].role;
nameEl.classList.add(sortedDeck[i].team);
alignmentEl.classList.add(sortedDeck[i].team);
document.getElementById('custom-role-info-modal-description').innerText = sortedDeck[i].description;
alignmentEl.innerText = sortedDeck[i].team;

View File

@@ -3,6 +3,7 @@ import { globals } from '../../config/globals.js';
import { defaultRoles } from '../../config/defaultRoles.js';
import { toast } from '../front_end_components/Toast.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
import { Confirmation } from '../front_end_components/Confirmation.js';
export class RoleBox {
constructor (container, deckManager) {
@@ -218,13 +219,13 @@ export class RoleBox {
if (remove) {
const removeHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
if (confirm("Delete the role '" + name + "'?")) {
Confirmation("Delete the role '" + name + "'?", () => {
e.preventDefault();
this.removeFromCustomRoles(name);
if (this.category === 'custom') {
this.displayCustomRoles(document.getElementById('role-select'));
}
}
});
}
};
role.querySelector('.role-remove').addEventListener('click', removeHandler);
@@ -234,8 +235,11 @@ export class RoleBox {
const infoHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
nameEl.classList.remove(globals.ALIGNMENT.GOOD);
nameEl.classList.remove(globals.ALIGNMENT.EVIL);
e.preventDefault();
let role;
if (isCustom) {
@@ -243,7 +247,8 @@ export class RoleBox {
} else {
role = this.getDefaultRole(name);
}
document.getElementById('custom-role-info-modal-name').innerText = name;
nameEl.innerText = name;
nameEl.classList.add(role.team);
alignmentEl.classList.add(role.team);
document.getElementById('custom-role-info-modal-description').innerText = role.description;
alignmentEl.innerText = role.team;

View File

@@ -6,6 +6,7 @@ import { XHRUtility } from '../utility/XHRUtility.js';
import { UserUtility } from '../utility/UserUtility.js';
// QRCode module via: https://github.com/soldair/node-qrcode
import { QRCode } from '../third_party/qrcode.js';
import { Confirmation } from '../front_end_components/Confirmation.js';
export class GameStateRenderer {
constructor (stateBucket, socket) {
@@ -16,9 +17,9 @@ export class GameStateRenderer {
this.transferModHandlers = {};
this.startGameHandler = (e) => { // TODO: prevent multiple emissions of this event (recommend converting to XHR)
e.preventDefault();
if (confirm('Start the game and deal roles?')) {
Confirmation('Start game and deal roles?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.START_GAME, stateBucket.currentGameState.accessCode);
}
});
};
this.restartGameHandler = (e) => {
e.preventDefault();
@@ -356,9 +357,9 @@ export class GameStateRenderer {
}
} else if (!player.out && moderatorType) {
killPlayerHandlers[player.id] = () => {
if (confirm('KILL ' + player.name + '?')) {
Confirmation('Kill \'' + player.name + '\'?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.KILL_PLAYER, accessCode, { personId: player.id });
}
});
};
playerEl.querySelector('.kill-player-button').addEventListener('click', killPlayerHandlers[player.id]);
}
@@ -371,9 +372,9 @@ export class GameStateRenderer {
}
} else if (!player.revealed && moderatorType) {
revealRoleHandlers[player.id] = () => {
if (confirm('REVEAL ' + player.name + '?')) {
Confirmation('Reveal \'' + player.name + '\'?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.REVEAL_PLAYER, accessCode, { personId: player.id });
}
});
};
playerEl.querySelector('.reveal-role-button').addEventListener('click', revealRoleHandlers[player.id]);
}
@@ -398,13 +399,19 @@ function renderPotentialMods (gameState, group, transferModHandlers, socket) {
container.innerText = member.name;
transferModHandlers[member.id] = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
if (confirm('Transfer moderator powers to ' + member.name + '?')) {
ModalManager.dispelModal('transfer-mod-modal', 'transfer-mod-modal-background');
Confirmation('Transfer moderator powers to \'' + member.name + '\'?', () => {
const transferPrompt = document.getElementById('transfer-mod-prompt');
if (transferPrompt !== null) {
transferPrompt.innerHTML = '';
}
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.TRANSFER_MODERATOR, gameState.accessCode, { personId: member.id });
}
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.TRANSFER_MODERATOR,
gameState.accessCode,
{ personId: member.id }
);
});
}
};
@@ -532,13 +539,13 @@ function createEndGamePromptComponent (socket, stateBucket) {
div.innerHTML = HTMLFragments.END_GAME_PROMPT;
div.querySelector('#end-game-button').addEventListener('click', (e) => {
e.preventDefault();
if (confirm('End the game?')) {
Confirmation('End the game?', () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.END_GAME,
stateBucket.currentGameState.accessCode
);
}
});
});
document.getElementById('game-content').appendChild(div);
}

View File

@@ -307,7 +307,7 @@ button {
}
#how-to-use-container h1 {
color: #4b6bfa;
color: #d7d7d7;
font-family: signika-negative, sans-serif;
background-color: #1e1b26;
width: fit-content;
@@ -359,7 +359,7 @@ input {
color: #d7d7d7;
display: flex;
flex-direction: column;
margin: 1em auto 0 auto;
margin: 1em auto 2em auto;
width: 90%;
max-width: 64em;
line-height: 1.5;
@@ -370,10 +370,39 @@ input {
font-weight: bold;
}
#tutorial-links {text-align: left;
justify-content: center;
align-items: center;
display: flex;
}
#tutorial-links li {
font-size: 25px;
color: #768df0;
text-decoration: underline;
}
#tutorial-links li a {
color: #768df0;
text-decoration: underline;
cursor: pointer;
font-family: 'signika-negative', sans-serif;
font-size: 18px;
width: fit-content;
}
#tutorial-links li a:hover {
color: gray;
}
.tutorial-image-small {
width: 30em !important;
}
.tutorial-image-small-portrait {
width: 20em !important;
}
#desktop-links > a:nth-child(1), #mobile-links a:nth-child(1) {
margin: 0 0.5em;
width: 50px;

View File

@@ -0,0 +1,48 @@
#confirmation {
border-radius: 2px;
text-align: center;
position: fixed;
border: 2px solid #333243;
width: 85%;
z-index: 100001;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #191920;
align-items: center;
justify-content: center;
max-width: 25em;
font-family: 'signika-negative', sans-serif;
flex-direction: column;
padding: 1em;
}
#confirmation-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: calc(100% + 100px);
background-color: rgba(0, 0, 0, 0.80);
z-index: 100000;
cursor: pointer;
}
#confirmation-message {
font-size: 20px;
color: #e7e7e7;
margin: 1em 0 2em 0;
}
.confirmation-buttons button {
min-width: 5em;
}
.confirmation-buttons {
display: flex;
justify-content: space-between;
}
#confirmation-cancel-button {
background-color: #762323 !important;
}

View File

@@ -843,7 +843,7 @@ canvas {
}
#game-role-back h4 {
font-size: 24px;
font-size: 22px;
}
h2 {

View File

@@ -15,6 +15,7 @@
flex-direction: column;
padding: 1em;
display: none;
border: 2px solid #333243;
}
.modal-background {
@@ -55,14 +56,20 @@
color: #d7d7d7;
text-align: left;
font-family: signika-negative, sans-serif;
align-items: flex-start;
}
#custom-role-info-modal h3 {
margin: 0 0 0.5em 0;
}
#custom-role-info-modal-description {
margin: 2em 0;
border-radius: 3px;
background-color: black;
max-height: 10em;
overflow: auto;
padding-top: 10px;
border-top: 1px solid whitesmoke
padding: 5px;
margin-bottom: 2em;
}
#custom-role-info-modal-name {
@@ -70,9 +77,16 @@
font-size: 23px;
}
#custom-role-info-modal label {
margin: 5px 0;
}
#custom-role-info-modal-alignment {
font-size: 20px;
border-radius: 3px;
background-color: black;
font-size: 18px;
font-weight: bold;
padding: 5px;
}
#change-name-modal, #transfer-mod-modal, #role-info-modal {

View File

@@ -35,7 +35,9 @@ const template =
</div>
<div tabindex="-1" id="custom-role-info-modal" class="modal">
<h3 id="custom-role-info-modal-name"></h3>
<label for="custom-role-info-modal-alignment">alignment:</label>
<div id="custom-role-info-modal-alignment"></div>
<label for="custom-role-info-modal-alignment">description:</label>
<div id="custom-role-info-modal-description"></div>
<div class="modal-button-container">
<button id="close-custom-role-info-modal-button" class="cancel app-button">Close</button>

View File

@@ -16,6 +16,7 @@
<link rel="stylesheet" href="./styles/GLOBAL.css">
<link rel="stylesheet" href="./styles/create.css">
<link rel="stylesheet" href="./styles/modal.css">
<link rel="stylesheet" href="./styles/confirmation.css">
<link rel="stylesheet" href="/styles/hamburgers.css">
</head>
<body>

View File

@@ -15,6 +15,7 @@
<link rel="stylesheet" href="/styles/GLOBAL.css">
<link rel="stylesheet" href="/styles/game.css">
<link rel="stylesheet" href="/styles/modal.css">
<link rel="stylesheet" href="/styles/confirmation.css">
<link rel="stylesheet" href="/styles/hamburgers.css">
<link rel="preload" href="/webfonts/SignikaNegative-Light.woff2" as="font" type="font/woff2" crossorigin>
</head>

View File

@@ -22,7 +22,23 @@
<div id="mobile-menu-background-overlay"></div>
<div id="navbar"></div>
<div id="how-to-use-container">
<h1 class="how-to-use-header">Purpose of the Application</h1>
<div id="tutorial-links">
<ul>
<li>
<a href="#purpose-of-the-app">Purpose of the App</a>
</li>
<li>
<a href="#creating-a-game">Creating a Game</a>
</li>
<li>
<a href="#being-the-moderator">Being the Moderator</a>
</li>
<li>
<a href="#being-a-player">Being a Player</a>
</li>
</ul>
</div>
<h1 class="how-to-use-header" id="purpose-of-the-app">Purpose of the Application</h1>
<div class="how-to-use-section">This app serves as a means of running games in a social setting where a traditional
running of the game is hindered. This might be when people are meeting virtually, and thus roles can't be handed
out in-person, or when people are in-person but don't have Werewolf cards with them. You can use a deck of regular
@@ -32,7 +48,7 @@
players. This app attempts to provide the utilities necessary to run Werewolf with all the different roles you want,
wherever you can access the internet.
</div>
<h1 class="how-to-use-header">Creating a Game</h1>
<h1 class="how-to-use-header" id="creating-a-game">Creating a Game</h1>
<div class="how-to-use-section">
Creating a game through the app has 3 main components:
<br>
@@ -80,7 +96,7 @@
end. Whether or not the game ends immediately after that or continues longer is up to the moderator.
<br><br>
</div>
<h1 class="how-to-use-header">Being the Moderator</h1>
<h1 class="how-to-use-header" id="being-the-moderator">Being the Moderator</h1>
<div class="how-to-use-section">
This is an example of what a <span class="emphasized">dedicated moderator</span> sees during the game:
<br><br>
@@ -105,8 +121,23 @@
become a spectator:
<br><br>
<img class='tutorial-image-small' src="../images/tutorial/transfer-mod.gif"/>
</div>
<h1 class="how-to-use-header" id="being-a-player">Being a Player</h1>
<div class="how-to-use-section">
This is an example of what a <span class="emphasized">player</span> is seeing. The timer is running, and they view their
role by double-clicking it:
<br><br>
<img class='tutorial-image-small-portrait' src="../images/tutorial/player-view.gif"/>
<br><br>
There are three main things - the <span class="emphasized">timer</span>, your <span class="emphasized">role card</span>
and the <span class="emphasized">player list</span>. Players can view the timer, but only the current moderator can play and pause it.
<span class="emphasized">Your role card starts flipped over</span> - this is useful if you are in-person and don't want someone else accidentally seeing your role as
it is dealt. <span class="emphasized">You can view your role at any time by double-clicking/double-tapping it</span>. Requiring a double-click guards against the possibility
of accidentally flipping your role when tapping other things. Within the <span class="emphasized">player list</span>, you can see
<span class="emphasized">who is alive or dead</span> and <span class="emphasized">who has had their role revealed</span>. There is
also a <span class="emphasized">role info button</span> that, when pressed, displays all the different roles in the current game,
including their descriptions and alignment (good/evil).
<br><br>
<p class="teaser">More content coming soon.</p>
</div>
</div>
<script src="/dist/howToUse-bundle.js"></script>

View File

@@ -8,7 +8,9 @@ module.exports = function(config) {
{ pattern: 'client/src/modules/*/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/config/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/model/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/view_templates/*.js', type: 'module', included: true, served: true }
{ pattern: 'client/src/view_templates/*.js', type: 'module', included: true, served: true },
{ pattern: 'core-js/stable', type: 'module', included: true, served: true },
{ pattern: 'regenerator-runtime/runtime', type: 'module', included: true, served: true }
],
reporters: ['progress'],
port: 9876,