Files
multiplayer_crosswords/multiplayer_crosswords/webui/index.html
2025-11-13 18:41:34 +01:00

312 lines
11 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="./favicon.png" />
<link rel="manifest" href="./manifest.json">
<!-- Polyfills only needed for Firefox and Edge. -->
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@latest/webcomponents-loader.js"></script>
<!-- Works only on browsers that support Javascript modules like
Chrome, Safari, Firefox 60, Edge 17 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<notification-area id="notification-area"></notification-area>
<main id="main-container">
<!-- Menu will be shown first -->
<crossword-menu id="menu"></crossword-menu>
<!-- Grid will be shown after session creation -->
<div id="grid-container" style="display: none;">
<div class="container">
<h2>Crossword Grid</h2>
<crossword-grid id="grid" rows="10" cols="10"></crossword-grid>
</div>
</div>
</main>
<mobile-keyboard id="keyboard" style="display: none;"></mobile-keyboard>
<script type="module">
import './menu.js';
import './grid.js';
import './keyboard.js';
import './notification-area.js';
import './clue_area.js';
import wsManager from './websocket.js';
import notificationManager from './notification-manager.js';
const menu = document.getElementById('menu');
const gridContainer = document.getElementById('grid-container');
const keyboard = document.getElementById('keyboard');
let currentSessionId = null;
let clueArea = null;
let gridElement = null;
// Test notifications
notificationManager.success('App loaded successfully');
// Helper function to get session ID from URL params
function getSessionIdFromUrl() {
const params = new URLSearchParams(window.location.search);
return params.get('session_id');
}
// Helper function to update URL with session ID
function updateUrlWithSessionId(sessionId) {
const params = new URLSearchParams(window.location.search);
params.set('session_id', sessionId);
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
}
// Helper function to subscribe to a session
function subscribeToSession(sessionId) {
console.log('Subscribing to session:', sessionId);
currentSessionId = sessionId;
const message = {
type: 'subscribe_session',
session_id: sessionId
};
wsManager.send(message);
notificationManager.info('Loading session...');
}
// Handle session creation response
wsManager.onMessage('session_created', (message) => {
console.log('Session created:', message);
currentSessionId = message.session_id;
// Update URL with session ID
updateUrlWithSessionId(message.session_id);
// Hide menu, show loading state
menu.style.display = 'none';
gridContainer.style.display = 'block';
keyboard.style.display = 'block';
// Show loading indicator
gridContainer.innerHTML = '<div class="loading-spinner">Loading game...</div>';
notificationManager.info('Session created, loading game...');
// Subscribe to session
subscribeToSession(message.session_id);
});
// Handle full session state (grid, clues, etc.)
wsManager.onMessage('full_session_state', (message) => {
console.log('Full session state received:', message);
if (message.session_id !== currentSessionId) {
console.warn('Received session state for different session, ignoring');
return;
}
// Destroy existing clue area if it exists
if (clueArea && clueArea.parentNode) {
clueArea.remove();
clueArea = null;
}
// Create grid from session state
const gridRows = message.grid.length;
const gridCols = message.grid[0].length;
// Create container with close button
gridContainer.innerHTML = `
<div class="game-header">
<h2>Crossword</h2>
<button class="close-game-btn" aria-label="Close game">✕</button>
</div>
<div class="game-content">
</div>
`;
const gameContent = gridContainer.querySelector('.game-content');
const closeBtn = gridContainer.querySelector('.close-game-btn');
// Create new grid element
const gridElementNew = document.createElement('crossword-grid');
gridElementNew.id = 'grid';
gridElementNew.setAttribute('rows', gridRows);
gridElementNew.setAttribute('cols', gridCols);
gridElementNew.sessionId = message.session_id; // Set session ID for message sending
gridElement = gridElementNew;
// Parse walls from grid data (walls are marked with '#')
const wallPositions = [];
for (let r = 0; r < gridRows; r++) {
for (let c = 0; c < gridCols; c++) {
if (message.grid[r][c] === '#') {
wallPositions.push([r, c]);
}
}
}
// Add grid to game content
gameContent.appendChild(gridElement);
// Wait for grid to be fully rendered, then set walls and letters
setTimeout(() => {
gridElement.setWalls(wallPositions);
// Set all letters from the server's grid state
for (let r = 0; r < gridRows; r++) {
for (let c = 0; c < gridCols; c++) {
const cell = message.grid[r][c];
// Skip walls and empty cells
if (cell !== '#' && cell !== '') {
gridElement._grid[r][c] = cell;
}
}
}
// Mark solved positions
if (message.solved_positions) {
for (const [col, row] of message.solved_positions) {
const cellKey = `${row},${col}`;
gridElement._solvedCells.add(cellKey);
}
}
// Populate clue numbers for display
gridElement.populateClueNumbers(message.clue_positions_across, message.clue_positions_down);
gridElement.requestUpdate();
console.log(`Grid created: ${gridRows}x${gridCols} with ${wallPositions.length} walls`);
console.log(`Clues: ${Object.keys(message.clues_across).length} across, ${Object.keys(message.clues_down).length} down`);
if (message.solved_positions) {
console.log(`Solved positions: ${message.solved_positions.length}`);
}
}, 0);
// Create and add clue area
clueArea = document.createElement('clue-area');
clueArea.cluesAcross = message.clues_across;
clueArea.cluesDown = message.clues_down;
clueArea.cluePositionsAcross = message.clue_positions_across;
clueArea.cluePositionsDown = message.clue_positions_down;
clueArea.grid = message.grid; // Pass grid for dimension calculation
clueArea.selectedRow = 0;
clueArea.selectedCol = 0;
clueArea.selectedMode = 'horizontal';
document.body.insertBefore(clueArea, document.body.firstChild);
// Listen for cell selection changes
gridElement.addEventListener('cell-selected', (e) => {
clueArea.selectedRow = e.detail.row;
clueArea.selectedCol = e.detail.col;
clueArea.selectedMode = e.detail.mode;
clueArea.requestUpdate();
});
// Close button handler
closeBtn.addEventListener('click', closeGame);
notificationManager.success('Game loaded successfully');
});
// Function to close game and return to menu
function closeGame() {
console.log('Closing game');
// Clear session ID from URL
window.history.replaceState({}, '', window.location.pathname);
// Reset state
currentSessionId = null;
// Destroy clue area - check multiple ways it could be in the DOM
if (clueArea) {
if (clueArea.parentNode) {
clueArea.parentNode.removeChild(clueArea);
}
clueArea = null;
}
// Also remove any clue-area elements that might exist
const allClueAreas = document.querySelectorAll('clue-area');
allClueAreas.forEach(elem => {
if (elem.parentNode) {
elem.parentNode.removeChild(elem);
}
});
// Destroy grid element
if (gridElement) {
gridElement = null;
}
// Hide grid, show menu
menu.style.display = 'block';
gridContainer.style.display = 'none';
keyboard.style.display = 'none';
gridContainer.innerHTML = '';
// Close and reopen WebSocket to interrupt connection
wsManager.close();
// Reconnect WebSocket after a short delay
setTimeout(() => {
const wsUrl = menu._getWebsocketUrl ? menu._getWebsocketUrl() : (() => {
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const host = window.location.hostname;
const port = 8765;
return `${protocol}://${host}:${port}`;
})();
wsManager.connect(wsUrl);
notificationManager.info('Returned to menu');
}, 100);
}
// Handle errors
wsManager.onMessage('error', (message) => {
console.error('Server error:', message);
// Show menu again
menu.style.display = 'block';
gridContainer.style.display = 'none';
keyboard.style.display = 'none';
gridContainer.innerHTML = '';
notificationManager.error(message.error_message || 'An error occurred');
});
// Check on page load if we have an existing session ID
window.addEventListener('load', () => {
const existingSessionId = getSessionIdFromUrl();
if (existingSessionId) {
console.log('Found existing session ID in URL:', existingSessionId);
// Wait for WebSocket to connect before subscribing
if (wsManager.isConnected()) {
subscribeToSession(existingSessionId);
} else {
// Register handler to subscribe once connected
wsManager.onMessage('open', () => {
subscribeToSession(existingSessionId);
});
}
// Hide menu immediately
menu.style.display = 'none';
gridContainer.style.display = 'block';
keyboard.style.display = 'block';
gridContainer.innerHTML = '<div class="loading-spinner">Reconnecting to session...</div>';
}
});
</script>
</body>
</html>