import { Game } from '../../model/Game.js'; import { cancelCurrentToast, toast } from '../front_end_components/Toast.js'; import { ModalManager } from '../front_end_components/ModalManager.js'; import { XHRUtility } from '../utility/XHRUtility.js'; import { globals } from '../../config/globals.js'; import { HTMLFragments } from '../front_end_components/HTMLFragments.js'; import { UserUtility } from '../utility/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 (you can edit this later):', forwardHandler: () => { if (this.deckManager.getDeckSize() > 50) { toast('Your deck is too large. The max is 50 cards.', 'error', true); } else if (this.deckManager.getDeckSize() < 1) { toast('You must add at least one card', 'error', true); } else { 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); } }, backHandler: this.defaultBackHandler }, 3: { title: 'Set an optional timer:', forwardHandler: (e) => { if (e.type === 'click' || e.code === 'Enter') { let hours = parseInt(document.getElementById('game-hours').value); let minutes = parseInt(document.getElementById('game-minutes').value); hours = isNaN(hours) ? null : hours; minutes = isNaN(minutes) ? null : minutes; if ((hours === null && minutes === null) || (hours === null && minutes > 0 && minutes < 60) || (minutes === null && 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...'; const restoreButton = () => { button.innerText = 'Create'; button.classList.remove('submitted'); button.addEventListener('click', this.steps['5'].forwardHandler); }; 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, this.currentGame.isTestGame ) ) ) .then((res) => { if (res.status === 201) { try { const json = JSON.parse(res.content); UserUtility.setAnonymousUserId(json.cookie, json.environment); window.location.replace( window.location.protocol + '//' + window.location.host + '/game/' + json.accessCode ); } catch (e) { restoreButton(); toast( 'There was a problem creating the game. Please contact the developers.', 'error', true, true ); } } }).catch((e) => { restoreButton(); if (e.status === 429) { toast('You\'ve sent this request too many times.', 'error', true, true, 'medium'); } else if (e.status === 413) { toast('Your request is too large.', 'error', true, true); } else { toast(e.content, '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); 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) => { 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, this.deckManager); this.deckManager.roleBox = this.roleBox; this.roleBox.loadDefaultRoles(); this.roleBox.loadCustomRolesFromCookies(); this.roleBox.displayDefaultRoles(document.getElementById('role-select')); this.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); this.deckManager.updateDeckStatus(); initializeRemainingEventListeners(this.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); const testGameInput = document.getElementById('test-game'); testGameInput.onchange = (event) => { game.isTestGame = testGameInput.value === 'yes'; }; testGameInput.value = game.isTestGame ? 'yes' : 'no'; } function renderModerationTypeStep (game, containerId, stepNumber) { const stepContainer = document.createElement('div'); setAttributes(stepContainer, { id: 'step-' + stepNumber, class: 'flex-row-container step' }); stepContainer.innerHTML = "