From 4109b6c72eb9d0912e103ec67fae65f52171836c Mon Sep 17 00:00:00 2001 From: Alec Maier Date: Sat, 25 Apr 2020 00:36:15 -0400 Subject: [PATCH] first draft editing of custom roles --- assets/images/delete.svg | 2 +- assets/images/pencil_green.svg | 6 +- static/modules/card-manager.js | 94 ++++++++++ static/setup.js | 322 +++++++++++++++++---------------- static/styles.css | 29 ++- static/util.js | 29 ++- views/learn.html | 2 +- 7 files changed, 323 insertions(+), 161 deletions(-) create mode 100644 static/modules/card-manager.js diff --git a/assets/images/delete.svg b/assets/images/delete.svg index 304e9e1..8938fe0 100644 --- a/assets/images/delete.svg +++ b/assets/images/delete.svg @@ -2,7 +2,7 @@ background - + diff --git a/assets/images/pencil_green.svg b/assets/images/pencil_green.svg index 4eabe39..53c2bd5 100644 --- a/assets/images/pencil_green.svg +++ b/assets/images/pencil_green.svg @@ -1,14 +1,14 @@ - + background - + Layer 1 - + diff --git a/static/modules/card-manager.js b/static/modules/card-manager.js new file mode 100644 index 0000000..22e96d4 --- /dev/null +++ b/static/modules/card-manager.js @@ -0,0 +1,94 @@ +const finishedArtArray = ["Villager", "Werewolf", "Seer", "Shadow", "Hunter", "Mason", "Minion", "Sorcerer", "Dream Wolf"]; + +export class CardManager { + constructor() {} + + static createCard(card) { + return new Card(card.role, card.team, card.description, card.quantity=0, card.isTypeOfWerewolf, card.custom, card.saved); + } + + // builds element for the informational role modal on the setup page + static constructModalRoleElement(card) { + const modalRole = document.createElement("div"); + modalRole.setAttribute("class", "modal-role"); + const roleClass = card.team === "good" ? "role-village" : "role-wolf"; + let roleImage; + if (card.custom === true) { + roleImage = "No art"; + } else { + roleImage = finishedArtArray.includes(card.role) ? + "No art" + : "Art soon."; + } + modalRole.innerHTML = + "
" + + roleImage + + "
" + + "

" + card.role + "

" + + "

" + card.team + "

" + + "
" + + "
" + + "

" + card.description + "

"; + return modalRole; + } + + static constructDeckBuilderElement(card, index) { + const cardContainer = document.createElement("div"); + + const quantityClass = card.team === "good" ? "card-quantity quantity-village" : "card-quantity quantity-wolf"; + + let cardClass = card.isTypeOfWerewolf ? "card card-werewolf" : "card"; + cardContainer.setAttribute("class", cardClass); + cardContainer.setAttribute("id", "card-" + index); + cardContainer.innerHTML = + "
" + + "
" + + "
" + + "

" + card.role + "

" + + "
" + card.quantity + "
" + + "
" + + "

+

" + + "
" + + "
"; + cardContainer.innerHTML = card.custom + ? cardContainer.innerHTML += "" + card.role + "" + : cardContainer.innerHTML +="" + card.role + ""; + cardContainer.innerHTML += + "
" + + "

-

" + + "
"; + + return cardContainer; + } + + static constructCustomCardIndicator() { + let customCard = document.createElement("div"); + customCard.classList.add("card", "custom-card"); + customCard.setAttribute("id", "custom"); + + let cardHeader = document.createElement("h1"); + cardHeader.innerText = "Add Custom Role"; + + let cardBody = document.createElement("div"); + cardBody.innerText = "+"; + + customCard.appendChild(cardHeader); + customCard.appendChild(cardBody); + + return customCard; + } + +} + +class Card { + constructor(role, team, description, quantity, isTypeOfWerewolf, custom, saved) { + this.id = null; + this.role = role; + this.isTypeOfWerewolf = isTypeOfWerewolf; + this.team = team; + this.description = description; + this.quantity = quantity; + this.custom = custom; + this.saved = saved; + } +} diff --git a/static/setup.js b/static/setup.js index c28238a..dded4b7 100644 --- a/static/setup.js +++ b/static/setup.js @@ -1,20 +1,8 @@ import {cards} from './cards.js' import {utility} from './util.js' +import {CardManager} from './modules/card-manager.js' const socket = io(); -const finishedArtArray = ["Villager", "Werewolf", "Seer", "Shadow", "Hunter", "Mason", "Minion", "Sorcerer", "Dream Wolf"]; - -// important declarations -class Card { - constructor(role, team, description, isTypeOfWerewolf) { - this.id = null; - this.role = role; - this.isTypeOfWerewolf = isTypeOfWerewolf; - this.team = team; - this.description = description; - this.quantity = 0; - } -} class Game { constructor(accessCode, size, deck, time, hasDreamWolf) { @@ -58,198 +46,229 @@ function renderAvailableCards() { }); document.getElementById("card-select").innerHTML = ""; document.getElementById("roles").innerHTML = ""; + document.getElementById("custom-roles").innerHTML = ""; for (let i = 0; i < cards.length; i ++) { - const newCard = new Card(cards[i].role, cards[i].team, cards[i].description, cards[i].isTypeOfWerewolf); - // put card info in the informational role description modal - const modalRole = document.createElement("div"); - modalRole.setAttribute("class", "modal-role"); - const roleClass = cards[i].team === "good" ? "role-village" : "role-wolf"; - let roleImage; - if (cards[i].custom === true) { - roleImage = "No art"; - } else { - roleImage = finishedArtArray.includes(cards[i].role) ? - "No art" - : "Art soon."; + const card = CardManager.createCard(cards[i]); + if (card.custom) { + renderCustomRoleInModal(cards[i], i); } - modalRole.innerHTML = - "
" + - roleImage + - "
" + - "

" + cards[i].role + "

" + - "

" + cards[i].team + "

" + - "
" + - "
" + - "

" + cards[i].description + "

"; + fullDeck.push(card); - document.getElementById("roles").appendChild(modalRole); + document.getElementById("roles").appendChild(CardManager.constructModalRoleElement(card)); + document.getElementById("card-select").appendChild(CardManager.constructDeckBuilderElement(card, i)); - fullDeck.push(newCard); - - const cardContainer = document.createElement("div"); - - const quantityClass = cards[i].team === "good" ? "card-quantity quantity-village" : "card-quantity quantity-wolf"; - - let cardClass = newCard.isTypeOfWerewolf ? "card card-werewolf" : "card"; - cardContainer.setAttribute("class", cardClass); - cardContainer.setAttribute("id", "card-" + i); - cardContainer.innerHTML = - "
" + - "
" + - "
" + - "

" + newCard.role + "

" + - "
" + newCard.quantity + "
" + - "
" + - "

+

" + - "
" + - "
"; - cardContainer.innerHTML = cards[i].custom - ? cardContainer.innerHTML += "" + newCard.role + "" - : cardContainer.innerHTML +="" + newCard.role + ""; - cardContainer.innerHTML += - "
" + - "

-

" + - "
"; - document.getElementById("card-select").appendChild(cardContainer); + // Add event listeners to the top and bottom halves of the card to change the quantity. let cardTop = document.getElementById("card-" + i).getElementsByClassName("card-top")[0]; let cardQuantity = document.getElementById("card-" + i).getElementsByClassName("card-quantity")[0]; let cardBottom = document.getElementById("card-" + i).getElementsByClassName("card-bottom")[0]; cardTop.addEventListener("click", incrementCardQuantity, false); cardBottom.addEventListener("click", decrementCardQuantity, false); - cardTop.card = newCard; + cardTop.card = card; cardTop.quantityEl = cardQuantity; - cardBottom.card = newCard; + cardBottom.card = card; cardBottom.quantityEl = cardQuantity; } - renderCustomCard(); - resetCardQuantities(); -} -function renderCustomCard() { - let customCard = document.createElement("div"); - customCard.classList.add("card", "custom-card"); - customCard.setAttribute("id", "custom"); + if (document.getElementById("custom-roles").getElementsByClassName("custom-role-edit").length === 0) { + document.getElementById("custom-roles").innerHTML = "

You haven't added any custom cards.

"; + } - let cardHeader = document.createElement("h1"); - cardHeader.innerText = "Add Custom Role"; - - let cardBody = document.createElement("div"); - cardBody.innerText = "+"; - - customCard.appendChild(cardHeader); - customCard.appendChild(cardBody); - document.getElementById("card-select").appendChild(customCard); - - customCard.addEventListener("click", function() { + let customCardElement = CardManager.constructCustomCardIndicator(); + document.getElementById("card-select").appendChild(customCardElement); + customCardElement.addEventListener("click", function() { displayModal("custom-card-modal"); }); } function addCustomCardToRoles(e) { e.preventDefault(); - let newCard = { - role: document.getElementById("custom-role-name").value, - team: document.getElementById("custom-role-team").value, - description: document.getElementById("custom-role-desc").value, - isTypeOfWerewolf: document.getElementById("custom-role-wolf").checked, - custom: true, - saved: document.getElementById("custom-role-remember").checked - }; - cards.push(newCard); - renderAvailableCards(); + if (!cards.find((card) => card.role === document.getElementById("custom-role-name").value)) { + let newCard = { + role: document.getElementById("custom-role-name").value, + team: document.getElementById("custom-role-team").value, + description: document.getElementById("custom-role-desc").value, + isTypeOfWerewolf: document.getElementById("custom-role-wolf").checked, + custom: true, + saved: document.getElementById("custom-role-remember").checked + }; + cards.push(newCard); + renderAvailableCards(); - if (newCard.saved === true) { - let existingRoles = localStorage.getItem("play-werewolf-custom-roles"); - if (existingRoles !== null) { - let rolesArray; - try { - rolesArray = JSON.parse(existingRoles); - } catch(e) { - console.error(e.message); + if (newCard.saved === true) { + let existingRoles = localStorage.getItem("play-werewolf-custom-roles"); + if (existingRoles !== null) { + let rolesArray; + try { + rolesArray = JSON.parse(existingRoles); + } catch (e) { + console.error(e.message); + } + if (rolesArray) { + rolesArray.push(newCard); + } + localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(rolesArray)); + } else { + localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(new Array(newCard))); } - if (rolesArray) { - rolesArray.push(newCard); - } - localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(rolesArray)); - } else { - localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(new Array(newCard))); } + updateCustomRoleModal(); + closeModal(); + document.getElementById("custom-role-form").reset(); + } else { + alert("A custom or standard card already exists with that name!") } - updateCustomRoleModal(); - closeModal(); - document.getElementById("custom-role-form").reset(); } function updateCustomRoleModal() { document.getElementById("custom-roles").innerHTML = ""; - cards.forEach((card) => { - if (card.custom) { - renderCustomRoleInModal(card); + for (let i = 0; i < cards.length; i++){ + if (cards[i].custom) { + renderCustomRoleInModal(cards[i], i); } - }); + } } function readInUserCustomRoles() { - let existingRoles = localStorage.getItem("play-werewolf-custom-roles"); - if (existingRoles !== null) { - let rolesArray; - try { - rolesArray = JSON.parse(existingRoles); - } catch(e) { - console.error(e.message); + let expectedKeys = ["role", "description", "team", "isTypeOfWerewolf", "custom", "saved"]; + let userCustomRoles = utility.validateCustomRolesJsonObject("play-werewolf-custom-roles", expectedKeys); + if (userCustomRoles) { + for (let i = 0; i < userCustomRoles.length; i++) { + cards.push(userCustomRoles[i]); } - if (rolesArray) { - rolesArray.forEach((card) => { - renderCustomRoleInModal(card); - cards.push(card); - }) - } - } - if (document.getElementById("custom-roles").getElementsByClassName("custom-role-edit").length === 0) { - document.getElementById("custom-roles").innerHTML = "

You haven't added any custom roles.

"; } } -function renderCustomRoleInModal(card) { +function renderCustomRoleInModal(card, index) { let roleElement = document.createElement("div"); let editRemoveContainer = document.createElement("div"); + let roleLabel = document.createElement("div"); let roleName = document.createElement("p"); let remove = document.createElement("img"); + let edit = document.createElement("img"); + let editForm = buildRoleEditForm(index); // TODO: add edit functionality roleName.innerText = card.role; remove.setAttribute("src", "../assets/images/delete.svg"); remove.setAttribute("title", "Delete"); remove.addEventListener("click", function() { removeCustomRole(card.role) }); + + edit.setAttribute("src", "../assets/images/pencil_green.svg"); + edit.setAttribute("title", "Edit"); + edit.addEventListener("click", function(e) { toggleEditForm(e, index) }); roleElement.setAttribute("class", "custom-role-edit"); editRemoveContainer.appendChild(remove); - roleElement.appendChild(roleName); - roleElement.appendChild(editRemoveContainer); + editRemoveContainer.appendChild(edit); + roleLabel.appendChild(roleName); + roleLabel.appendChild(editRemoveContainer); + roleElement.appendChild(roleLabel); + roleElement.appendChild(editForm); + document.getElementById("custom-roles").appendChild(roleElement); + + document.getElementById("edit-form-" + index).addEventListener("submit", function(e) { + updateCustomRole(e, index); + }); +} + +function toggleEditForm(event, index) { + event.preventDefault(); + let displayRule = document.getElementById("edit-form-" + index).style.display; + document.getElementById("edit-form-" + index).style.display = displayRule === "none" ? "block" : "none"; + + if (document.getElementById("edit-form-" + index).style.display === "block") { + populateEditRoleForm(cards[index], index); + } +} + +function buildRoleEditForm(index) { + let infoForm = document.createElement("div"); + infoForm.style.display = "none"; + infoForm.setAttribute("id", "edit-form-" + index); + infoForm.innerHTML = + "
" + + "" + + "" + + "" + + "" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "

" + + "" + + "
"; + return infoForm; +} + +function populateEditRoleForm(card, index) { + document.getElementById("edit-role-desc-" + index).value = card.description; + document.getElementById("edit-role-team-" + index).value = card.team; + document.getElementById("edit-role-wolf-" + index).checked = card.isTypeOfWerewolf; + document.getElementById("edit-role-remember-" + index).checked = card.saved; } function removeCustomRole(name) { - let matchingCards = cards.filter((card) => card.role === name); - matchingCards.forEach((card) => { - cards.splice(cards.indexOf(card), 1); - }); - let existingRoles = localStorage.getItem("play-werewolf-custom-roles"); - if (existingRoles !== null) { - let rolesArray; - try { - rolesArray = JSON.parse(existingRoles); - } catch (e) { - console.error(e.message); + if (confirm("Delete this role?")) { + let matchingCards = cards.filter((card) => card.role === name); + matchingCards.forEach((card) => { + cards.splice(cards.indexOf(card), 1); + }); + let expectedKeys = ["role", "description", "team", "isTypeOfWerewolf", "custom", "saved"]; + let userCustomRoles = utility.validateCustomRolesJsonObject("play-werewolf-custom-roles", expectedKeys); + if (userCustomRoles) { + userCustomRoles = userCustomRoles.filter((card) => card.role !== name); + localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(userCustomRoles)); + } + updateCustomRoleModal(); + renderAvailableCards(); + } +} + +function updateCustomRole(event, index) { + event.preventDefault(); + if (index >= 0 && index < cards.length) { + let cardToUpdate = cards[index]; + cardToUpdate.team = document.getElementById("edit-role-team-" + index).value; + cardToUpdate.description = document.getElementById("edit-role-desc-" + index).value; + cardToUpdate.isTypeOfWerewolf = document.getElementById("edit-role-wolf-" + index).checked; + cardToUpdate.saved = document.getElementById("edit-role-remember-" + index).checked; + + removeOrAddSavedRoleIfNeeded(cardToUpdate); + toggleEditForm(event, index); + renderAvailableCards(); + } +} + +function removeOrAddSavedRoleIfNeeded(card) { + let expectedKeys = ["role", "description", "team", "isTypeOfWerewolf", "custom", "saved"]; + let userCustomRoles = utility.validateCustomRolesJsonObject("play-werewolf-custom-roles", expectedKeys); + if (userCustomRoles) { + if (card.saved) { + let roleToUpdate = userCustomRoles.find((savedCard) => savedCard.role === card.role); + if (roleToUpdate) { + userCustomRoles[userCustomRoles.indexOf(roleToUpdate)] = card; + } else { + userCustomRoles.push(card); + } + localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(userCustomRoles)); + } else { + let roleToRemove = userCustomRoles.find((savedCard) => savedCard.role === card.role); + if (roleToRemove) { + userCustomRoles.splice(userCustomRoles.indexOf(roleToRemove), 1); + localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(userCustomRoles)); + } } - if (rolesArray) { - rolesArray = rolesArray.filter((card) => card.role !== name); - } - localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(rolesArray)); } - updateCustomRoleModal(); - renderAvailableCards(); } @@ -305,9 +324,8 @@ function buildDeckFromQuantities() { let playerDeck = []; for (const card of fullDeck) { for (let i = 0; i < card.quantity; i++) { - let newCard = new Card(card.role, card.team, card.description, card.isTypeOfWerewolf); - newCard.id = utility.generateID(); - playerDeck.push(newCard); + card.id = utility.generateID(); + playerDeck.push(card); } } return playerDeck; diff --git a/static/styles.css b/static/styles.css index d5d9f1d..c4d50ac 100644 --- a/static/styles.css +++ b/static/styles.css @@ -446,12 +446,13 @@ button { } #custom-roles img { - width: 20px; + width: 25px; } #custom-roles .custom-role-edit { display: flex; width: 100%; + flex-direction: column; justify-content: space-around; font-size: 19px; align-items: center; @@ -460,6 +461,27 @@ button { margin: 0.3em; } +.edit-role-form { + background-color: #1a1a1a; + padding: 1em; +} + +.custom-role-edit > div:nth-child(1) { + display: flex; + width: 100%; + justify-content: space-around; +} + +.custom-role-edit > div:nth-child(1) div { + display: flex; + align-items: center; +} + +.custom-role-edit > div:nth-child(2) { + display: flex; + width: 100%; +} + .custom-role-edit p { text-overflow: ellipsis; overflow-x: hidden; @@ -470,6 +492,7 @@ button { #custom-roles .custom-role-edit div > img { margin: 0 1em; cursor: pointer; + user-select: none; } #custom-roles .custom-role-edit div > img:hover { @@ -977,7 +1000,7 @@ label { } .hidden { - display: none; + display: none !important; } #lobby-subheader { @@ -1193,7 +1216,7 @@ label { padding: 5px; display: flex; align-items: center; - justify-content: space-around; + justify-content: space-between; box-shadow: 3px 10px 10px rgba(0,0,0,0.6); margin: 0.3em; position: relative; diff --git a/static/util.js b/static/util.js index 402e832..90fddb5 100644 --- a/static/util.js +++ b/static/util.js @@ -19,5 +19,32 @@ export const utility = [array[i], array[j]] = [array[j], array[i]]; } return array; -} + }, + + validateCustomRolesJsonObject(name, expectedKeys) { + let value = localStorage.getItem(name); + if (value !== null) { + let valueJson; + try { + valueJson = JSON.parse(value); + } catch(e) { + console.error(e.message); + localStorage.removeItem(name); + return false; + } + if (valueJson && Array.isArray(valueJson)) { // some defensive programming - check if it's an array, and that the object has the expected structure + for (let i = 0; i < valueJson.length; i++){ + if (expectedKeys.some((key) => !Object.keys(valueJson[i]).includes(key))) { + console.error("tried to read invalid object: " + valueJson[i] + " with expected keys: " + expectedKeys); + valueJson.splice(i, 1); + localStorage.setItem(name, JSON.stringify(valueJson)); + } + } + return valueJson; + } else { // object has been messed with. remove it. + localStorage.removeItem(name); + return false; + } + } + } }; diff --git a/views/learn.html b/views/learn.html index 06542b1..1646d83 100644 --- a/views/learn.html +++ b/views/learn.html @@ -51,7 +51,7 @@ wolves as villagers, the wolves win (as they can kill a villager at night, and then have the majority to kill the remaining villager during the day). If the village manages to kill every wolf, then they win. In the scenario where there is one villager and one wolf remaining, if the remaining villager is a Hunter, then the village wins. - There are several "power roles" such as the Hunter, which can help the village or the wolves. If you are a power + There are several "power cards" such as the Hunter, which can help the village or the wolves. If you are a power role, you can read the description on your card to find out what your special ability is.