diff --git a/client/src/images/tutorial/player-view.gif b/client/src/images/tutorial/player-view.gif new file mode 100644 index 0000000..fafa5ee Binary files /dev/null and b/client/src/images/tutorial/player-view.gif differ diff --git a/client/src/modules/front_end_components/Confirmation.js b/client/src/modules/front_end_components/Confirmation.js new file mode 100644 index 0000000..17cb9da --- /dev/null +++ b/client/src/modules/front_end_components/Confirmation.js @@ -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 = + `
+
+ + +
`; + + 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); +}; diff --git a/client/src/modules/front_end_components/HTMLFragments.js b/client/src/modules/front_end_components/HTMLFragments.js index e8bba82..176a5a9 100644 --- a/client/src/modules/front_end_components/HTMLFragments.js +++ b/client/src/modules/front_end_components/HTMLFragments.js @@ -63,8 +63,8 @@ export const HTMLFragments = {

-

Double-tap here to show your role

-

(Double-tap here again to hide)

+

Double-click here to show your role

+

(Double-click here again to hide)

@@ -141,8 +141,8 @@ export const HTMLFragments = {

-

Double-tap here to show your role

-

(Double-tap here again to hide)

+

Double-click here to show your role

+

(Double-click here again to hide)

diff --git a/client/src/modules/front_end_components/Navbar.js b/client/src/modules/front_end_components/Navbar.js index 731cc18..cd14850 100644 --- a/client/src/modules/front_end_components/Navbar.js +++ b/client/src/modules/front_end_components/Navbar.js @@ -43,7 +43,7 @@ function getNavbarLinks (page = null, device) { 'Home' + 'Create' + 'How to Use' + - 'Contact' + + 'Feedback' + 'Github' + 'Support the App'; } diff --git a/client/src/modules/game_creation/DeckStateManager.js b/client/src/modules/game_creation/DeckStateManager.js index da2a9d9..c48df14 100644 --- a/client/src/modules/game_creation/DeckStateManager.js +++ b/client/src/modules/game_creation/DeckStateManager.js @@ -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; diff --git a/client/src/modules/game_creation/RoleBox.js b/client/src/modules/game_creation/RoleBox.js index b88e8af..019e129 100644 --- a/client/src/modules/game_creation/RoleBox.js +++ b/client/src/modules/game_creation/RoleBox.js @@ -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; diff --git a/client/src/modules/game_state/GameStateRenderer.js b/client/src/modules/game_state/GameStateRenderer.js index 49d28d6..97935e0 100644 --- a/client/src/modules/game_state/GameStateRenderer.js +++ b/client/src/modules/game_state/GameStateRenderer.js @@ -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); } diff --git a/client/src/styles/GLOBAL.css b/client/src/styles/GLOBAL.css index 2eb8a8f..3284e8f 100644 --- a/client/src/styles/GLOBAL.css +++ b/client/src/styles/GLOBAL.css @@ -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; diff --git a/client/src/styles/confirmation.css b/client/src/styles/confirmation.css new file mode 100644 index 0000000..512d035 --- /dev/null +++ b/client/src/styles/confirmation.css @@ -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; +} diff --git a/client/src/styles/game.css b/client/src/styles/game.css index 4bcdd20..5d61825 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -843,7 +843,7 @@ canvas { } #game-role-back h4 { - font-size: 24px; + font-size: 22px; } h2 { diff --git a/client/src/styles/modal.css b/client/src/styles/modal.css index 0ed0ed7..1223df0 100644 --- a/client/src/styles/modal.css +++ b/client/src/styles/modal.css @@ -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 { diff --git a/client/src/view_templates/CreateTemplate.js b/client/src/view_templates/CreateTemplate.js index 87a6bc2..9950510 100644 --- a/client/src/view_templates/CreateTemplate.js +++ b/client/src/view_templates/CreateTemplate.js @@ -35,7 +35,9 @@ const template =