import { Game } from '../model/Game.js'; import { cancelCurrentToast, toast } from './Toast.js'; import { ModalManager } from './ModalManager.js'; import { XHRUtility } from './XHRUtility.js'; import { globals } from '../config/globals.js'; import { HTMLFragments } from './HTMLFragments.js'; import { UserUtility } from './UserUtility.js'; import { RoleBox } from './RoleBox.js'; export class GameCreationStepManager { constructor (deckManager) { this.step = 1; this.currentGame = new Game(null, null, null, null); this.deckManager = deckManager; this.roleBox = null; this.defaultBackHandler = () => { cancelCurrentToast(); this.removeStepElementsFromDOM(this.step); this.decrementStep(); this.renderStep('creation-step-container', this.step); }; this.steps = { 1: { title: 'Select your method of moderation:', forwardHandler: () => { if (this.currentGame.hasDedicatedModerator !== null) { cancelCurrentToast(); this.removeStepElementsFromDOM(this.step); this.incrementStep(); this.renderStep('creation-step-container', this.step); } else { toast('You must select a moderation option.', 'error', true); } } }, 2: { title: 'Create your deck of cards:', forwardHandler: () => { if (this.deckManager.getDeckSize() >= 3 && this.deckManager.getDeckSize() <= 50) { this.currentGame.deck = this.deckManager.deck.filter((card) => card.quantity > 0); cancelCurrentToast(); this.removeStepElementsFromDOM(this.step); this.incrementStep(); this.renderStep('creation-step-container', this.step); } else { toast('You must have a deck for between 3 and 50 players', 'error', true); } }, backHandler: this.defaultBackHandler }, 3: { title: 'Set an optional timer:', 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 { 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') { const 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: () => { const button = document.getElementById('create-game'); button.removeEventListener('click', this.steps['5'].forwardHandler); button.classList.add('submitted'); button.innerText = 'Creating'; XHRUtility.xhr( '/api/games/create', 'POST', null, JSON.stringify( new Game( this.currentGame.deck.filter((card) => card.quantity > 0), this.currentGame.hasTimer, this.currentGame.hasDedicatedModerator, this.currentGame.moderatorName, this.currentGame.timerParams ) ) ) .then((res) => { if (res && typeof res === 'object' && Object.prototype.hasOwnProperty.call(res, 'content') && typeof res.content === 'string' ) { const json = JSON.parse(res.content); UserUtility.setAnonymousUserId(json.cookie, json.environment); window.location = ('/game/' + json.accessCode); } }).catch((e) => { const button = document.getElementById('create-game'); button.innerText = 'Create'; button.classList.remove('submitted'); button.addEventListener('click', this.steps['5'].forwardHandler); toast(e.content, 'error', true, true, 'medium'); if (e.status === 429) { toast('You\'ve sent this request too many times.', 'error', true, true, 'medium'); } }); } } }; } incrementStep () { if (this.step < Object.keys(this.steps).length) { this.step += 1; } } decrementStep () { if (this.step > 1) { this.step -= 1; } } renderStep (containerId, step) { document.querySelectorAll('.animated-placeholder').forEach((el) => el.remove()); document.querySelectorAll('.placeholder-row').forEach((el) => el.remove()); document.getElementById('step-title').innerText = this.steps[step].title; switch (step) { case 1: renderModerationTypeStep(this.currentGame, containerId, step); showButtons(false, true, this.steps[step].forwardHandler, null); break; case 2: this.renderRoleSelectionStep(this.currentGame, containerId, step, this.deckManager); showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler); break; case 3: 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, this.deckManager); showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler, this.currentGame); break; default: break; } updateTracker(step); } removeStepElementsFromDOM (stepNumber) { document.getElementById('step-' + stepNumber)?.remove(); } renderRoleSelectionStep = (game, containerId, step, deckManager) => { const stepContainer = document.createElement('div'); setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container-left-align step' }); stepContainer.innerHTML += HTMLFragments.CREATE_GAME_DECK_STATUS; 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')); deckManager.loadDeckTemplates(this.roleBox); const exportHandler = (e) => { if (e.type === 'click' || e.code === 'Enter') { e.preventDefault(); this.roleBox.downloadCustomRoles('play-werewolf-custom-roles', JSON.stringify( this.roleBox.customRoles .map((option) => ( { role: option.role, team: option.team, description: option.description, custom: option.custom } )) )); toast('Custom roles downloading', 'success', true, true); document.getElementById('custom-role-actions').style.display = 'none'; } }; document.querySelector('#custom-roles-export').addEventListener('click', exportHandler); document.querySelector('#custom-roles-export').addEventListener('keyup', exportHandler); const importHandler = (e) => { if (e.type === 'click' || e.code === 'Enter') { e.preventDefault(); ModalManager.displayModal('upload-custom-roles-modal', 'modal-background', 'close-upload-custom-roles-modal-button'); } }; document.querySelector('#custom-roles-import').addEventListener('click', importHandler); document.querySelector('#custom-roles-import').addEventListener('keyup', importHandler); document.getElementById('upload-custom-roles-form').onsubmit = (e) => { e.preventDefault(); const fileList = document.getElementById('upload-custom-roles').files; if (fileList.length > 0) { const file = fileList[0]; if (file.size > 1000000) { toast('Your file is too large (max 1MB)', 'error', true, true, 'medium'); return; } if (file.type !== 'text/plain') { toast('Your file must be a text file', 'error', true, true, 'medium'); return; } this.roleBox.loadCustomRolesFromFile(file); } else { toast('You must upload a text file', 'error', true, true, 'medium'); } }; const clickHandler = () => { const actions = document.getElementById('custom-role-actions'); if (window.getComputedStyle(actions, null).display !== 'none') { actions.style.display = 'none'; } else { actions.style.display = 'block'; } }; document.getElementById('custom-role-hamburger').addEventListener('click', clickHandler); deckManager.updateDeckStatus(); initializeRemainingEventListeners(deckManager, this.roleBox); }; } function renderNameStep (containerId, step, game, steps) { const stepContainer = document.createElement('div'); setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container step' }); stepContainer.innerHTML = HTMLFragments.ENTER_NAME_STEP; document.getElementById(containerId).appendChild(stepContainer); const nameInput = document.querySelector('#moderator-name'); nameInput.value = game.moderatorName; nameInput.addEventListener('keyup', steps['4'].forwardHandler); } function renderModerationTypeStep (game, containerId, stepNumber) { const stepContainer = document.createElement('div'); setAttributes(stepContainer, { id: 'step-' + stepNumber, class: 'flex-row-container step' }); stepContainer.innerHTML = "