// ui.js import * as state from '../core/state.js'; import { GAME_STATES, CSS_CLASSES, DEFAULT_PLAYER_TIME_SECONDS } from '../config.js'; import audioManager from './audio.js'; // --- DOM Elements --- export const elements = { carousel: document.getElementById('carousel'), gameButton: document.getElementById('gameButton'), setupButton: document.getElementById('setupButton'), addPlayerButton: document.getElementById('addPlayerButton'), resetButton: document.getElementById('resetButton'), playerModal: document.getElementById('playerModal'), resetModal: document.getElementById('resetModal'), playerForm: document.getElementById('playerForm'), modalTitle: document.getElementById('modalTitle'), playerNameInput: document.getElementById('playerName'), playerTimeInput: document.getElementById('playerTime'), playerImageInput: document.getElementById('playerImage'), imagePreview: document.getElementById('imagePreview'), playerTimeContainer: document.getElementById('playerTimeContainer'), // Parent of playerTimeInput remainingTimeContainer: document.getElementById('remainingTimeContainer'), playerRemainingTimeInput: document.getElementById('playerRemainingTime'), deletePlayerButton: document.getElementById('deletePlayerButton'), cancelButton: document.getElementById('cancelButton'), // Modal cancel resetCancelButton: document.getElementById('resetCancelButton'), resetConfirmButton: document.getElementById('resetConfirmButton'), cameraButton: document.getElementById('cameraButton'), // Camera elements needed by camera.js, but listed here for central management if desired cameraContainer: document.getElementById('cameraContainer'), cameraView: document.getElementById('cameraView'), cameraCanvas: document.getElementById('cameraCanvas'), cameraCaptureButton: document.getElementById('cameraCaptureButton'), cameraCancelButton: document.getElementById('cameraCancelButton'), // Header buttons container for sound toggle headerButtons: document.querySelector('.header-buttons') }; let isDragging = false; let startX = 0; let currentX = 0; let carouselSwipeHandler = null; // To store the bound function for removal // --- Rendering Functions --- export function renderPlayers() { const players = state.getPlayers(); const currentIndex = state.getCurrentPlayerIndex(); const currentGameState = state.getGameState(); elements.carousel.innerHTML = ''; if (players.length === 0) { // Optionally display a message if there are no players elements.carousel.innerHTML = '

Add players to start

'; return; } players.forEach((player, index) => { const card = document.createElement('div'); const isActive = index === currentIndex; card.className = `player-card ${isActive ? CSS_CLASSES.ACTIVE_PLAYER : CSS_CLASSES.INACTIVE_PLAYER}`; const minutes = Math.floor(player.remainingTime / 60); const seconds = player.remainingTime % 60; const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; const timerClasses = []; if (isActive && currentGameState === GAME_STATES.RUNNING) { timerClasses.push(CSS_CLASSES.TIMER_ACTIVE); } if (player.remainingTime <= 0) { timerClasses.push(CSS_CLASSES.TIMER_FINISHED); } card.innerHTML = `
${player.image ? `${player.name}` : ''}
${player.name}
${timeString}
`; elements.carousel.appendChild(card); }); updateCarouselPosition(); } export function updateCarouselPosition() { const currentIndex = state.getCurrentPlayerIndex(); elements.carousel.style.transform = `translateX(${-100 * currentIndex}%)`; } export function updateGameButton() { const currentGameState = state.getGameState(); switch (currentGameState) { case GAME_STATES.SETUP: elements.gameButton.textContent = 'Start Game'; break; case GAME_STATES.RUNNING: elements.gameButton.textContent = 'Pause Game'; break; case GAME_STATES.PAUSED: elements.gameButton.textContent = 'Resume Game'; break; case GAME_STATES.OVER: elements.gameButton.textContent = 'Game Over'; break; } // Disable button if less than 2 players in setup elements.gameButton.disabled = currentGameState === GAME_STATES.SETUP && state.getPlayers().length < 2; } // --- Modal Functions --- export function showPlayerModal(isNewPlayer, player = null) { const currentGameState = state.getGameState(); if (isNewPlayer) { elements.modalTitle.textContent = 'Add New Player'; elements.playerNameInput.value = `Player ${state.getPlayers().length + 1}`; elements.playerTimeInput.value = DEFAULT_PLAYER_TIME_SECONDS / 60; // Use default time from config elements.remainingTimeContainer.style.display = 'none'; elements.playerTimeContainer.style.display = 'block'; // Ensure Time field is visible for new players elements.imagePreview.innerHTML = ''; elements.deletePlayerButton.style.display = 'none'; } else if (player) { elements.modalTitle.textContent = 'Edit Player'; elements.playerNameInput.value = player.name; elements.playerTimeInput.value = player.timeInSeconds / 60; if (currentGameState === GAME_STATES.PAUSED || currentGameState === GAME_STATES.OVER) { elements.remainingTimeContainer.style.display = 'block'; elements.playerTimeContainer.style.display = 'none'; // Hide Time field when Remaining Time is shown const minutes = Math.floor(player.remainingTime / 60); const seconds = player.remainingTime % 60; elements.playerRemainingTimeInput.value = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } else { elements.remainingTimeContainer.style.display = 'none'; elements.playerTimeContainer.style.display = 'block'; // Ensure Time field is visible otherwise } elements.imagePreview.innerHTML = player.image ? `${player.name}` : ''; elements.deletePlayerButton.style.display = 'block'; } // Reset file input and captured image data elements.playerImageInput.value = ''; elements.playerImageInput.dataset.capturedImage = ''; elements.playerModal.classList.add(CSS_CLASSES.MODAL_ACTIVE); audioManager.play('modalOpen'); } export function hidePlayerModal() { elements.playerModal.classList.remove(CSS_CLASSES.MODAL_ACTIVE); audioManager.play('modalClose'); // Potentially call camera cleanup here if it wasn't done elsewhere } export function showResetModal() { elements.resetModal.classList.add(CSS_CLASSES.MODAL_ACTIVE); audioManager.play('modalOpen'); } export function hideResetModal() { elements.resetModal.classList.remove(CSS_CLASSES.MODAL_ACTIVE); audioManager.play('modalClose'); } export function updateImagePreviewFromFile(file) { if (file) { const reader = new FileReader(); reader.onload = (event) => { elements.imagePreview.innerHTML = `Player Preview`; // Clear any previously captured image data if a file is selected elements.playerImageInput.dataset.capturedImage = ''; }; reader.readAsDataURL(file); } } export function updateImagePreviewFromDataUrl(dataUrl) { elements.imagePreview.innerHTML = `Player Preview`; // Store data URL and clear file input elements.playerImageInput.dataset.capturedImage = dataUrl; elements.playerImageInput.value = ''; } // --- Carousel Touch Handling --- function handleTouchStart(e) { startX = e.touches[0].clientX; currentX = startX; isDragging = true; // Optional: Add a class to the carousel for visual feedback during drag elements.carousel.style.transition = 'none'; // Disable transition during drag } function handleTouchMove(e) { if (!isDragging) return; currentX = e.touches[0].clientX; const diff = currentX - startX; const currentIndex = state.getCurrentPlayerIndex(); const currentTranslate = -100 * currentIndex + (diff / elements.carousel.offsetWidth * 100); elements.carousel.style.transform = `translateX(${currentTranslate}%)`; } function handleTouchEnd(e) { if (!isDragging) return; isDragging = false; elements.carousel.style.transition = ''; // Re-enable transition const diff = currentX - startX; const threshold = elements.carousel.offsetWidth * 0.1; // 10% swipe threshold if (Math.abs(diff) > threshold) { // Call the handler passed during initialization if (carouselSwipeHandler) { carouselSwipeHandler(diff < 0 ? 1 : -1); // Pass direction: 1 for next, -1 for prev } } else { // Snap back if swipe wasn't enough updateCarouselPosition(); } } // --- UI Initialization --- // Add sound toggle button function createSoundToggleButton() { const soundButton = document.createElement('button'); soundButton.id = 'soundToggleButton'; soundButton.className = 'header-button'; soundButton.title = 'Toggle Sound'; soundButton.innerHTML = audioManager.muted ? '' : ''; soundButton.addEventListener('click', () => { const isMuted = audioManager.toggleMute(); soundButton.innerHTML = isMuted ? '' : ''; if (!isMuted) audioManager.play('buttonClick'); // Feedback only when unmuting }); elements.headerButtons.prepend(soundButton); // Add to the beginning } // Parse time string (MM:SS) to seconds - Helper needed for form processing export function parseTimeString(timeString) { if (!/^\d{1,2}:\d{2}$/.test(timeString)) { console.error('Invalid time format:', timeString); return null; // Indicate error } const parts = timeString.split(':'); const minutes = parseInt(parts[0], 10); const seconds = parseInt(parts[1], 10); if (isNaN(minutes) || isNaN(seconds) || seconds > 59) { console.error('Invalid time value:', timeString); return null; } return (minutes * 60) + seconds; } // Sets up basic UI elements and listeners that primarily affect the UI itself export function initUI(options) { // Store the swipe handler provided by app.js carouselSwipeHandler = options.onCarouselSwipe; createSoundToggleButton(); // Carousel touch events elements.carousel.addEventListener('touchstart', handleTouchStart, { passive: true }); elements.carousel.addEventListener('touchmove', handleTouchMove, { passive: true }); elements.carousel.addEventListener('touchend', handleTouchEnd); // Image file input preview elements.playerImageInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { updateImagePreviewFromFile(file); } }); // Initial render renderPlayers(); updateGameButton(); }