From 41330c5e7f93cb79009eaa5e90efaa077a07319b Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 12 May 2022 18:17:27 -0400 Subject: [PATCH 1/9] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3029d22..c5970c6 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,10 @@ The application prioritizes responsiveness. A key scenario would be when a group This is a Node.js application. It is written purely using JavaScript/HTML/CSS. The main dependencies are Express.js and Socket.io. It runs as a containerized application -via Google Cloud Run. +via Google Cloud Run. There is no data persisted in any database. +Currently there is one container instance, which is sufficient scaling at this time. In the event I need to scale to multiple containers, I will likely +integrate with a message queue like Redis. ## Contributing and Developers' Guide ### Running Locally From 0e46309157c958744a066d35d1e7d9c67e748f8a Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 12 May 2022 18:40:37 -0400 Subject: [PATCH 2/9] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index c5970c6..932173e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@

+- [Features](https://github.com/AlecM33/Werewolf#features) +- [Tech Stack] +- [Contributing and Developers' Guide] +- [Testing] +- [Code Formatting] + [![Node.js CI](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml) Find the latest production deployment at: https://play-werewolf.app/ From aacd7d241c28c531fc63fbbb0facdae3e42c99c4 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 12 May 2022 18:41:21 -0400 Subject: [PATCH 3/9] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 932173e..13b89a6 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@

- [Features](https://github.com/AlecM33/Werewolf#features) -- [Tech Stack] -- [Contributing and Developers' Guide] -- [Testing] -- [Code Formatting] +- [Tech Stack](https://github.com/AlecM33/Werewolf/blob/master/README.md#tech-stack) +- [Contributing and Developers' Guide](https://github.com/AlecM33/Werewolf/blob/master/README.md#contributing-and-developers-guide) +- [Testing](https://github.com/AlecM33/Werewolf/blob/master/README.md#testing) +- [Code Formatting](https://github.com/AlecM33/Werewolf/blob/master/README.md#code-formatting) [![Node.js CI](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml) From 125272807a58a14ce3f8539df80259c12b3d0927 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 12 May 2022 18:42:20 -0400 Subject: [PATCH 4/9] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 13b89a6..2875c64 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@

-- [Features](https://github.com/AlecM33/Werewolf#features) -- [Tech Stack](https://github.com/AlecM33/Werewolf/blob/master/README.md#tech-stack) -- [Contributing and Developers' Guide](https://github.com/AlecM33/Werewolf/blob/master/README.md#contributing-and-developers-guide) -- [Testing](https://github.com/AlecM33/Werewolf/blob/master/README.md#testing) -- [Code Formatting](https://github.com/AlecM33/Werewolf/blob/master/README.md#code-formatting) +- [Features](#features) +- [Tech Stack](#tech-stack) +- [Contributing and Developers' Guide](#contributing-and-developers-guide) +- [Testing](#testing) +- [Code Formatting](#code-formatting) [![Node.js CI](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml) From 76d10fea489324700f2673a1b65b528003375f7d Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 12 May 2022 18:42:59 -0400 Subject: [PATCH 5/9] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2875c64..bb80aef 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@

+[![Node.js CI](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml) + +Find the latest production deployment at: https://play-werewolf.app/ + - [Features](#features) - [Tech Stack](#tech-stack) - [Contributing and Developers' Guide](#contributing-and-developers-guide) - [Testing](#testing) - [Code Formatting](#code-formatting) -[![Node.js CI](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/AlecM33/Werewolf/actions/workflows/node.js.yml) - -Find the latest production deployment at: https://play-werewolf.app/ - An application to run games of Werewolf (Mafia) smoothly when you don't have a deck, or when you and your friends are together virtually. Basically, a host builds a game and deals a role to everyone's device, and then the app keeps track of the game state (timer, player statuses, etc). From ff1b4e0ad8d4c748e983f7708c31ac337d6b8361 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sun, 12 Jun 2022 13:54:13 -0400 Subject: [PATCH 6/9] tweak end of game page styling --- client/src/modules/HTMLFragments.js | 4 +--- client/src/styles/game.css | 10 +++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/modules/HTMLFragments.js b/client/src/modules/HTMLFragments.js index e91a2d5..7ed5a29 100644 --- a/client/src/modules/HTMLFragments.js +++ b/client/src/modules/HTMLFragments.js @@ -226,9 +226,7 @@ export const HTMLFragments = {
`, RESTART_GAME_BUTTON: - `
- -
`, + ``, CREATE_GAME_DECK: `
diff --git a/client/src/styles/game.css b/client/src/styles/game.css index 8cb87c1..b3d187f 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -104,9 +104,17 @@ h1 { align-items: center; } +#end-of-game-header h2 { + border: 1px solid #333243; + border-radius: 5px; + background-color: #1a1726; + padding: 7px; + margin: 0.5em; +} + #end-of-game-header button { margin: 0.5em; - min-width: 10em; + min-width: 12em; } .potential-moderator { display: flex; From 43344b217ab6341e5a1e63dc20bd2ebf869c3a2a Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sun, 12 Jun 2022 13:58:37 -0400 Subject: [PATCH 7/9] lint --- client/src/modules/HTMLFragments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/modules/HTMLFragments.js b/client/src/modules/HTMLFragments.js index 7ed5a29..2dd103b 100644 --- a/client/src/modules/HTMLFragments.js +++ b/client/src/modules/HTMLFragments.js @@ -226,7 +226,7 @@ export const HTMLFragments = {
`, RESTART_GAME_BUTTON: - ``, + '', CREATE_GAME_DECK: `
From 99a5455713c4a6e5021600374f80e3f00facf0a5 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sun, 19 Jun 2022 15:32:12 -0400 Subject: [PATCH 8/9] Deck templates --- client/src/modules/DeckStateManager.js | 75 ++++++++++++++++++- client/src/modules/GameCreationStepManager.js | 11 +++ client/src/modules/GameStateRenderer.js | 4 +- client/src/modules/HTMLFragments.js | 10 ++- client/src/modules/RoleBox.js | 5 +- client/src/scripts/game.js | 2 +- client/src/styles/create.css | 51 ++++++++++++- client/src/styles/game.css | 35 ++++----- client/src/styles/modal.css | 2 +- client/src/view_templates/CreateTemplate.js | 7 ++ spec/e2e/create_spec.js | 9 +++ 11 files changed, 182 insertions(+), 29 deletions(-) diff --git a/client/src/modules/DeckStateManager.js b/client/src/modules/DeckStateManager.js index 23a79bf..dede3bc 100644 --- a/client/src/modules/DeckStateManager.js +++ b/client/src/modules/DeckStateManager.js @@ -1,10 +1,43 @@ import { globals } from '../config/globals.js'; import { HTMLFragments } from './HTMLFragments.js'; import { toast } from './Toast.js'; +import {ModalManager} from "./ModalManager.js"; export class DeckStateManager { constructor () { this.deck = []; + this.templates = { + '5 Players': { + 'Villager': 1, + 'Werewolf': 1, + 'Sorceress': 1, + 'Parity Hunter': 1, + 'Seer': 1 + }, + '7 Players': { + 'Villager': 6, + 'Werewolf': 1 + }, + '9 Players': { + 'Villager': 7, + 'Werewolf': 2 + }, + '11 Players': { + 'Villager': 8, + 'Werewolf': 2, + 'Seer': 1 + }, + '13 Players': { + 'Villager': 10, + 'Werewolf': 2, + 'Seer': 1 + }, + '15 Players': { + 'Villager': 12, + 'Werewolf': 2, + 'Seer': 1 + } + } } addToDeck (role) { @@ -54,6 +87,46 @@ export class DeckStateManager { return total; } + loadDeckTemplates = (roleBox) => { + if (document.querySelectorAll('.template-option').length === 0) { + for (let templateName of Object.keys(this.templates)) { + let templateOption = document.createElement('div'); + templateOption.classList.add('template-option'); + templateOption.innerHTML = HTMLFragments.DECK_TEMPLATE; + templateOption.querySelector('.template-option-name').innerText = templateName; + for (let i = 0; i < Object.keys(this.templates[templateName]).length; i++) { + let role = Object.keys(this.templates[templateName])[i]; + let roleEl = document.createElement('span'); + roleEl.innerText = this.templates[templateName][role] + ' ' + role; + if (i < Object.keys(this.templates[templateName]).length - 1) { // construct comma-delimited list + roleEl.innerText += ', '; + } + roleEl.classList.add(roleBox.defaultRoles.find((entry) => entry.role === role).team); + templateOption.querySelector('.template-option-roles').appendChild(roleEl); + } + templateOption.addEventListener('click', (e) => { + e.preventDefault(); + for (let card of this.deck) { + card.quantity = 0; + } + for (let role of Object.keys(this.templates[templateName])) { + let roleObj = roleBox.getDefaultRole(role); + if (!this.hasRole(roleObj.role)) { + this.addToDeck(roleObj); + } + for (let i = roleObj.quantity; i < this.templates[templateName][role]; i++) { + this.addCopyOfCard(roleObj.role); + } + } + this.updateDeckStatus(); + ModalManager.dispelModal('deck-template-modal', 'modal-background'); + toast('Template loaded', 'success', true, true, 'short'); + }); + document.getElementById('deck-template-container').appendChild(templateOption); + } + } + } + displayDeckPlaceHolder = () => { const placeholder = document.createElement('div'); placeholder.setAttribute('id', 'deck-list-placeholder'); @@ -69,7 +142,7 @@ export class DeckStateManager { } const sortedDeck = this.deck.sort((a, b) => { if (a.team !== b.team) { - return a.team === globals.ALIGNMENT.GOOD ? 1 : -1; + return a.team === globals.ALIGNMENT.GOOD ? -1 : 1; } return a.role.localeCompare(b.role); }); diff --git a/client/src/modules/GameCreationStepManager.js b/client/src/modules/GameCreationStepManager.js index bc26b5b..1eb9399 100644 --- a/client/src/modules/GameCreationStepManager.js +++ b/client/src/modules/GameCreationStepManager.js @@ -198,6 +198,7 @@ export class GameCreationStepManager { } renderRoleSelectionStep = (game, containerId, step, deckManager) => { + const stepContainer = document.createElement('div'); setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container-left-align step' }); @@ -205,11 +206,14 @@ export class GameCreationStepManager { stepContainer.innerHTML += HTMLFragments.CREATE_GAME_DECK_STATUS; document.getElementById(containerId).appendChild(stepContainer); + this.roleBox = new RoleBox(stepContainer, deckManager); this.roleBox.loadDefaultRoles(); this.roleBox.loadCustomRolesFromCookies(); this.roleBox.displayDefaultRoles(document.getElementById('role-select')); + deckManager.loadDeckTemplates(this.roleBox); + const exportHandler = (e) => { if (e.type === 'click' || e.code === 'Enter') { e.preventDefault(); @@ -502,6 +506,13 @@ function initializeRemainingEventListeners (deckManager, roleBox) { } } }; + document.getElementById('deck-template-button').addEventListener('click', () => { + ModalManager.displayModal( + 'deck-template-modal', + 'modal-background', + 'close-deck-template-modal-button' + ); + }); document.getElementById('custom-role-btn').addEventListener( 'click', () => { const createBtn = document.getElementById('create-role-button'); diff --git a/client/src/modules/GameStateRenderer.js b/client/src/modules/GameStateRenderer.js index 1ae1bdd..4538989 100644 --- a/client/src/modules/GameStateRenderer.js +++ b/client/src/modules/GameStateRenderer.js @@ -234,7 +234,9 @@ export class GameStateRenderer { }); } document.querySelectorAll('.game-player').forEach((el) => el.remove()); - sortPeopleByStatus(this.stateBucket.currentGameState.people); + /* TODO: UX issue - it's easier to parse visually when players are sorted this way, + but shifting players around when they are killed or revealed is bad UX for the moderator. */ + // sortPeopleByStatus(this.stateBucket.currentGameState.people); const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null; renderGroupOfPlayers( this.stateBucket.currentGameState.people, diff --git a/client/src/modules/HTMLFragments.js b/client/src/modules/HTMLFragments.js index 2dd103b..604d6ac 100644 --- a/client/src/modules/HTMLFragments.js +++ b/client/src/modules/HTMLFragments.js @@ -100,7 +100,7 @@ export const HTMLFragments = {
- +
@@ -238,6 +238,9 @@ export const HTMLFragments = {
`, + DECK_TEMPLATE: + `
+
`, CREATE_GAME_CUSTOM_ROLES: `
`, CREATE_GAME_DECK_STATUS: `
-
0 Players
+
+
0 Players
+ +
`, DECK_SELECT_ROLE: diff --git a/client/src/modules/RoleBox.js b/client/src/modules/RoleBox.js index 82eeee7..d5bbe5a 100644 --- a/client/src/modules/RoleBox.js +++ b/client/src/modules/RoleBox.js @@ -11,7 +11,6 @@ export class RoleBox { this.category = 'default'; this.deckManager = deckManager; this.defaultRoles = []; - console.log('hi'); this.customRoles = []; container.innerHTML += HTMLFragments.CREATE_GAME_CUSTOM_ROLES; this.defaultButton = document.getElementById('role-category-default'); @@ -36,7 +35,7 @@ export class RoleBox { loadDefaultRoles = () => { this.defaultRoles = defaultRoles.sort((a, b) => { if (a.team !== b.team) { - return a.team === globals.ALIGNMENT.GOOD ? 1 : -1; + return a.team === globals.ALIGNMENT.GOOD ? -1 : 1; } return a.role.localeCompare(b.role); }).map((role) => { @@ -162,7 +161,7 @@ export class RoleBox { this.categoryTransition.play(); this.customRoles.sort((a, b) => { if (a.team !== b.team) { - return a.team === globals.ALIGNMENT.GOOD ? 1 : -1; + return a.team === globals.ALIGNMENT.GOOD ? -1 : 1; } return a.role.localeCompare(b.role); }); diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js index 22b7f7a..995b34c 100644 --- a/client/src/scripts/game.js +++ b/client/src/scripts/game.js @@ -383,7 +383,7 @@ function updateDOMWithNameChange (gameState, gameStateRenderer) { function activateRoleInfoButton (deck) { deck.sort((a, b) => { - return a.team === globals.ALIGNMENT.GOOD ? 1 : -1; + return a.team === globals.ALIGNMENT.GOOD ? -1 : 1; }); document.getElementById('role-info-button').addEventListener('click', (e) => { e.preventDefault(); diff --git a/client/src/styles/create.css b/client/src/styles/create.css index fa1a988..02902e5 100644 --- a/client/src/styles/create.css +++ b/client/src/styles/create.css @@ -101,6 +101,42 @@ width: fit-content; } +.template-option { + color: #d7d7d7; + display: flex; + justify-content: space-between; + background-color: #0f0f10; + border: 2px solid #333243; + padding: 5px; + border-radius: 3px; + font-size: 16px; + flex-direction: column; + align-items: flex-start; + text-align: left; + height: 4em; + margin: 10px 0; +} + +#deck-template-container { + margin: 1em 0; + max-height: 30em; + overflow-y: auto; +} + +.template-option-name { + font-size: 20px; +} + +.template-option:hover, .template-option:active { + border: 2px solid #e7e7e7; + background-color: #33343c; + cursor: pointer; +} + +#deck-template-modal h2 { + font-size: 20px; +} + #custom-roles-container { width: 95%; max-width: 25em; @@ -136,15 +172,20 @@ #deck-count { font-size: 30px; - position: sticky; - top: 0; - left: 5px; background-color: #333243; width: fit-content; padding: 0 5px; border-radius: 3px; } +#deck-status-header { + position: sticky; + z-index: 25; + top: 0; + display: flex; + justify-content: space-between; +} + #deck-list { margin-top: 0.5em; } @@ -486,6 +527,10 @@ input[type="number"] { margin-bottom: 4em; } +#step-2 .app-button { + padding: 5px; +} + #tracker-container { display: flex; align-items: center; diff --git a/client/src/styles/game.css b/client/src/styles/game.css index b3d187f..140f950 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -505,6 +505,7 @@ label[for='moderator'] { .paused { animation: pulse 0.75s linear infinite alternate; + border: 1px solid #ffc83d !important; } .paused-low { @@ -515,11 +516,11 @@ label[for='moderator'] { display: flex; flex-wrap: wrap; flex-direction: column; - align-items: flex-start; + align-items: center; } #game-header button { - min-width: 10em; + min-width: 12em; } #timer-container-moderator { @@ -595,7 +596,7 @@ label[for='moderator'] { background-color: #586a6e; } -.reveal-role-button:hover, #mod-transfer-button:hover { +.reveal-role-button:hover { background-color: #4e5664; } @@ -627,15 +628,15 @@ label[for='moderator'] { align-items: center; } -.make-mod-button { - background-color: #3f5256; - font-size: 18px; - padding: 10px; - border: 2px transparent; - border-radius: 3px; - color: #d7d7d7; - font-family: signika-negative, sans-serif; -} +/*.make-mod-button {*/ +/* background-color: #3f5256;*/ +/* font-size: 18px;*/ +/* padding: 10px;*/ +/* border: 2px transparent;*/ +/* border-radius: 3px;*/ +/* color: #d7d7d7;*/ +/* font-family: signika-negative, sans-serif;*/ +/*}*/ .make-mod-button:hover { cursor: pointer; @@ -734,10 +735,10 @@ label[for='moderator'] { width: 45px; } - .make-mod-button { - font-size: 16px; - padding: 5px; - } + /*.make-mod-button {*/ + /* font-size: 16px;*/ + /* padding: 5px;*/ + /*}*/ .game-player-name { font-size: 16px; @@ -749,7 +750,7 @@ label[for='moderator'] { height: 30px; } - #role-info-button { + #role-info-button, #mod-transfer-button { padding: 7px; } diff --git a/client/src/styles/modal.css b/client/src/styles/modal.css index 06e1044..0a86111 100644 --- a/client/src/styles/modal.css +++ b/client/src/styles/modal.css @@ -11,7 +11,7 @@ align-items: center; justify-content: center; max-width: 25em; - font-family: sans-serif; + font-family: 'signika-negative', sans-serif; flex-direction: column; padding: 1em; display: none; diff --git a/client/src/view_templates/CreateTemplate.js b/client/src/view_templates/CreateTemplate.js index 3d43086..87a6bc2 100644 --- a/client/src/view_templates/CreateTemplate.js +++ b/client/src/view_templates/CreateTemplate.js @@ -41,6 +41,13 @@ const template = +

Create A Game

diff --git a/spec/e2e/create_spec.js b/spec/e2e/create_spec.js index bd7cc5b..f1c6884 100644 --- a/spec/e2e/create_spec.js +++ b/spec/e2e/create_spec.js @@ -95,5 +95,14 @@ describe('Create page', function () { .querySelector('.role-name').innerText ).role).toEqual('Test name edited'); }); + + it('should load a deck template', () => { + document.getElementById('role-category-default').click(); + document.getElementById('deck-template-button').click(); + document.querySelectorAll('.template-option')[0].click(); + + expect(gameCreationStepManager.deckManager.deck.length).toEqual(5); + expect(document.querySelectorAll('.added-role').length).toEqual(5); + }); }); }); From c86e106060ad487d10930ad4fd3f8a0ef3ef3901 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sun, 19 Jun 2022 16:24:47 -0400 Subject: [PATCH 9/9] lint, add spec, add info button to added roles --- client/src/modules/DeckStateManager.js | 74 +++++++++++-------- client/src/modules/GameCreationStepManager.js | 2 +- client/src/modules/GameStateRenderer.js | 13 ---- client/src/modules/HTMLFragments.js | 1 + client/src/styles/create.css | 3 +- spec/e2e/create_spec.js | 15 ++++ 6 files changed, 64 insertions(+), 44 deletions(-) diff --git a/client/src/modules/DeckStateManager.js b/client/src/modules/DeckStateManager.js index dede3bc..5eed73b 100644 --- a/client/src/modules/DeckStateManager.js +++ b/client/src/modules/DeckStateManager.js @@ -1,43 +1,43 @@ import { globals } from '../config/globals.js'; import { HTMLFragments } from './HTMLFragments.js'; import { toast } from './Toast.js'; -import {ModalManager} from "./ModalManager.js"; +import { ModalManager } from './ModalManager.js'; export class DeckStateManager { constructor () { this.deck = []; this.templates = { '5 Players': { - 'Villager': 1, - 'Werewolf': 1, - 'Sorceress': 1, + Villager: 1, + Werewolf: 1, + Sorceress: 1, 'Parity Hunter': 1, - 'Seer': 1 + Seer: 1 }, '7 Players': { - 'Villager': 6, - 'Werewolf': 1 + Villager: 6, + Werewolf: 1 }, '9 Players': { - 'Villager': 7, - 'Werewolf': 2 + Villager: 7, + Werewolf: 2 }, '11 Players': { - 'Villager': 8, - 'Werewolf': 2, - 'Seer': 1 + Villager: 8, + Werewolf: 2, + Seer: 1 }, '13 Players': { - 'Villager': 10, - 'Werewolf': 2, - 'Seer': 1 + Villager: 10, + Werewolf: 2, + Seer: 1 }, '15 Players': { - 'Villager': 12, - 'Werewolf': 2, - 'Seer': 1 + Villager: 12, + Werewolf: 2, + Seer: 1 } - } + }; } addToDeck (role) { @@ -89,14 +89,14 @@ export class DeckStateManager { loadDeckTemplates = (roleBox) => { if (document.querySelectorAll('.template-option').length === 0) { - for (let templateName of Object.keys(this.templates)) { - let templateOption = document.createElement('div'); + for (const templateName of Object.keys(this.templates)) { + const templateOption = document.createElement('div'); templateOption.classList.add('template-option'); templateOption.innerHTML = HTMLFragments.DECK_TEMPLATE; templateOption.querySelector('.template-option-name').innerText = templateName; - for (let i = 0; i < Object.keys(this.templates[templateName]).length; i++) { - let role = Object.keys(this.templates[templateName])[i]; - let roleEl = document.createElement('span'); + for (let i = 0; i < Object.keys(this.templates[templateName]).length; i ++) { + const role = Object.keys(this.templates[templateName])[i]; + const roleEl = document.createElement('span'); roleEl.innerText = this.templates[templateName][role] + ' ' + role; if (i < Object.keys(this.templates[templateName]).length - 1) { // construct comma-delimited list roleEl.innerText += ', '; @@ -106,15 +106,15 @@ export class DeckStateManager { } templateOption.addEventListener('click', (e) => { e.preventDefault(); - for (let card of this.deck) { + for (const card of this.deck) { card.quantity = 0; } - for (let role of Object.keys(this.templates[templateName])) { - let roleObj = roleBox.getDefaultRole(role); + for (const role of Object.keys(this.templates[templateName])) { + const roleObj = roleBox.getDefaultRole(role); if (!this.hasRole(roleObj.role)) { this.addToDeck(roleObj); } - for (let i = roleObj.quantity; i < this.templates[templateName][role]; i++) { + for (let i = roleObj.quantity; i < this.templates[templateName][role]; i ++) { this.addCopyOfCard(roleObj.role); } } @@ -125,7 +125,7 @@ export class DeckStateManager { document.getElementById('deck-template-container').appendChild(templateOption); } } - } + }; displayDeckPlaceHolder = () => { const placeholder = document.createElement('div'); @@ -182,6 +182,22 @@ export class DeckStateManager { }; roleEl.querySelector('.role-remove').addEventListener('click', minusOneHandler); roleEl.querySelector('.role-remove').addEventListener('keyup', minusOneHandler); + + const infoHandler = (e) => { + if (e.type === 'click' || e.code === 'Enter') { + const alignmentEl = document.getElementById('custom-role-info-modal-alignment'); + alignmentEl.classList.remove(globals.ALIGNMENT.GOOD); + alignmentEl.classList.remove(globals.ALIGNMENT.EVIL); + e.preventDefault(); + document.getElementById('custom-role-info-modal-name').innerText = sortedDeck[i].role; + alignmentEl.classList.add(sortedDeck[i].team); + document.getElementById('custom-role-info-modal-description').innerText = sortedDeck[i].description; + alignmentEl.innerText = sortedDeck[i].team; + ModalManager.displayModal('custom-role-info-modal', 'modal-background', 'close-custom-role-info-modal-button'); + } + }; + roleEl.querySelector('.role-info').addEventListener('click', infoHandler); + roleEl.querySelector('.role-info').addEventListener('keyup', infoHandler); } } else { sortedDeck[i].markedForRemoval = true; diff --git a/client/src/modules/GameCreationStepManager.js b/client/src/modules/GameCreationStepManager.js index 1eb9399..b6fdfa9 100644 --- a/client/src/modules/GameCreationStepManager.js +++ b/client/src/modules/GameCreationStepManager.js @@ -198,7 +198,6 @@ export class GameCreationStepManager { } renderRoleSelectionStep = (game, containerId, step, deckManager) => { - const stepContainer = document.createElement('div'); setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container-left-align step' }); @@ -208,6 +207,7 @@ export class GameCreationStepManager { document.getElementById(containerId).appendChild(stepContainer); this.roleBox = new RoleBox(stepContainer, deckManager); + deckManager.roleBox = this.roleBox; this.roleBox.loadDefaultRoles(); this.roleBox.loadCustomRolesFromCookies(); this.roleBox.displayDefaultRoles(document.getElementById('role-select')); diff --git a/client/src/modules/GameStateRenderer.js b/client/src/modules/GameStateRenderer.js index 4538989..97b560d 100644 --- a/client/src/modules/GameStateRenderer.js +++ b/client/src/modules/GameStateRenderer.js @@ -345,19 +345,6 @@ function renderLobbyPerson (name, userType) { return el; } -function sortPeopleByStatus (people) { - people.sort((a, b) => { - if (a.out !== b.out) { - return a.out ? 1 : -1; - } else { - if (a.revealed !== b.revealed) { - return a.revealed ? -1 : 1; - } - return a.name >= b.name ? 1 : -1; - } - }); -} - function getGameSize (cards) { let quantity = 0; for (const card of cards) { diff --git a/client/src/modules/HTMLFragments.js b/client/src/modules/HTMLFragments.js index 604d6ac..d2517be 100644 --- a/client/src/modules/HTMLFragments.js +++ b/client/src/modules/HTMLFragments.js @@ -286,5 +286,6 @@ export const HTMLFragments = { `
remove one + info
` }; diff --git a/client/src/styles/create.css b/client/src/styles/create.css index 02902e5..f55c90a 100644 --- a/client/src/styles/create.css +++ b/client/src/styles/create.css @@ -119,8 +119,9 @@ #deck-template-container { margin: 1em 0; - max-height: 30em; + max-height: 64vh; overflow-y: auto; + padding: 0 0.5em; } .template-option-name { diff --git a/spec/e2e/create_spec.js b/spec/e2e/create_spec.js index f1c6884..0c04938 100644 --- a/spec/e2e/create_spec.js +++ b/spec/e2e/create_spec.js @@ -104,5 +104,20 @@ describe('Create page', function () { expect(gameCreationStepManager.deckManager.deck.length).toEqual(5); expect(document.querySelectorAll('.added-role').length).toEqual(5); }); + + it('clear existing added cards and leave only what roles are part of the template', () => { + document.getElementById('role-category-default').click(); + const roles = document.querySelectorAll('.default-role'); + roles.forEach((el) => { + const plusElement = el.querySelector('.role-include'); + plusElement.click(); + }); + + document.getElementById('deck-template-button').click(); + document.querySelectorAll('.template-option')[0].click(); + + expect(gameCreationStepManager.deckManager.deck.length).toEqual(5); + expect(document.querySelectorAll('.added-role').length).toEqual(5); + }); }); });