312 lines
11 KiB
HTML
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> |