280 lines
13 KiB
HTML
280 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="theme-color" content="#2c3e50">
|
|
<title>Game Timer</title>
|
|
|
|
<!-- Favicon links -->
|
|
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
|
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
|
|
|
<!-- Web app manifest -->
|
|
<link rel="manifest" href="/manifest.json">
|
|
|
|
<!-- App icons for various platforms -->
|
|
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
|
|
|
|
<!-- CSS stylesheets -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
|
<link rel="stylesheet" href="/css/styles.css">
|
|
|
|
</head>
|
|
<body>
|
|
<div class="app-container">
|
|
<header class="header">
|
|
<div class="push-notification-controls">
|
|
<button id="pushSettingsButton" class="header-button" title="Push Notification Settings">
|
|
<i class="fas fa-bell"></i>
|
|
</button>
|
|
</div>
|
|
<div class="game-controls">
|
|
<button id="gameButton" class="game-button">Start Game</button>
|
|
</div>
|
|
<div class="header-buttons">
|
|
<button id="resetButton" class="header-button" title="Reset All Data">
|
|
<i class="fas fa-redo-alt"></i>
|
|
</button>
|
|
<button id="addPlayerButton" class="header-button" title="Add New Player">
|
|
<i class="fas fa-user-plus"></i>
|
|
</button>
|
|
<button id="setupButton" class="header-button" title="Setup Current Player">
|
|
<i class="fas fa-cog"></i>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="carousel-container">
|
|
<div id="carousel" class="carousel">
|
|
<!-- Player cards will be dynamically inserted here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Player Edit Modal -->
|
|
<div id="playerModal" class="modal">
|
|
<div class="modal-content">
|
|
<h2 id="modalTitle">Edit Player</h2>
|
|
<form id="playerForm">
|
|
<div class="form-group">
|
|
<label for="playerName">Name</label>
|
|
<input type="text" id="playerName" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="playerImage">Image</label>
|
|
<div class="image-input-container">
|
|
<input type="file" id="playerImage" accept="image/*">
|
|
<button type="button" id="cameraButton" class="camera-button">
|
|
<i class="fas fa-camera"></i> Take Photo
|
|
</button>
|
|
</div>
|
|
<div id="imagePreview" class="player-image" style="margin-top: 0.5rem;">
|
|
<i class="fas fa-user"></i>
|
|
</div>
|
|
</div>
|
|
<div id="playerTimeContainer" class="form-group">
|
|
<label for="playerTime">Time (minutes)</label>
|
|
<input type="number" id="playerTime" min="1" max="180" value="5" required>
|
|
</div>
|
|
<div id="remainingTimeContainer" class="form-group" style="display: none;">
|
|
<label for="playerRemainingTime">Remaining Time (MM:SS)</label>
|
|
<input type="text" id="playerRemainingTime" pattern="[0-9]{2}:[0-9]{2}" placeholder="05:00">
|
|
<small>Format: Minutes:Seconds (e.g., 05:30)</small>
|
|
</div>
|
|
<div class="form-buttons">
|
|
<button type="button" id="cancelButton" class="cancel-button">Cancel</button>
|
|
<button type="submit" class="save-button">Save</button>
|
|
</div>
|
|
<div class="form-buttons delete-button-container">
|
|
<button type="button" id="deletePlayerButton" class="delete-button">Delete Player</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reset Confirmation Modal -->
|
|
<div id="resetModal" class="modal">
|
|
<div class="modal-content">
|
|
<h2>Reset All Data</h2>
|
|
<p>Are you sure you want to reset all players and timers? This action cannot be undone.</p>
|
|
<div class="form-buttons">
|
|
<button type="button" id="resetCancelButton" class="cancel-button">Cancel</button>
|
|
<button type="button" id="resetConfirmButton" class="save-button">Reset</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Camera Capture UI -->
|
|
<div id="cameraContainer" class="camera-container">
|
|
<div class="camera-view">
|
|
<video id="cameraView" autoplay playsinline></video>
|
|
<canvas id="cameraCanvas" style="display: none;"></canvas>
|
|
</div>
|
|
<div class="camera-controls">
|
|
<button id="cameraCancelButton" class="camera-button-cancel">Cancel</button>
|
|
<button id="cameraCaptureButton" class="camera-button-large"></button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Push Notification Settings Modal -->
|
|
<div id="pushSettingsModal" class="modal">
|
|
<div class="modal-content">
|
|
<h2>Push Notification Settings</h2>
|
|
<div class="notification-status-container">
|
|
<div class="notification-status">
|
|
<p><strong>Notification Permission: </strong><span id="notificationPermissionStatus">Unknown</span></p>
|
|
<p><strong>Subscription Status: </strong><span id="subscriptionStatus">Unknown</span></p>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="pushUsername">Username for Push Service</label>
|
|
<input type="text" id="pushUsername" placeholder="Enter username">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="pushPassword">Password</label>
|
|
<input type="password" id="pushPassword" placeholder="Enter password">
|
|
</div>
|
|
<div class="advanced-options">
|
|
<button type="button" id="pushUnsubscribeButton" class="cancel-button">Unsubscribe</button>
|
|
<button type="button" id="pushResubscribeButton" class="save-button">Subscribe</button>
|
|
</div>
|
|
<div class="form-buttons">
|
|
<button type="button" id="pushCancelButton" class="cancel-button">Cancel</button>
|
|
<button type="button" id="pushSaveButton" class="save-button">Save</button>
|
|
</div>
|
|
|
|
<!-- Message Monitor Section - No visible title -->
|
|
<div class="message-monitor-section">
|
|
<div class="monitor-controls">
|
|
<button type="button" id="simulateClickButton" class="action-button" style="margin-right: 10px;">Simulate Remote Button Click</button>
|
|
</div>
|
|
<pre id="swMessagesOutput" class="message-output">Monitoring for service worker messages...</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Load environment configuration first -->
|
|
<script src="/config.env.js"></script>
|
|
|
|
<!-- Main application script -->
|
|
<script type="module" src="/js/app.js"></script>
|
|
<script>
|
|
if ("serviceWorker" in navigator) {
|
|
navigator.serviceWorker.register("/sw.js")
|
|
.then(() => console.log("Service Worker Registered"))
|
|
.catch((err) => console.log("Service Worker Failed", err));
|
|
}
|
|
</script>
|
|
<script>
|
|
// Request notification permission on page load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if ('Notification' in window) {
|
|
Notification.requestPermission().then(permission => {
|
|
console.log('Notification permission:', permission);
|
|
});
|
|
}
|
|
|
|
// Helper function to get stored credentials
|
|
async function getStoredCredentials() {
|
|
const storedAuth = localStorage.getItem('basicAuthCredentials');
|
|
if (!storedAuth) return null;
|
|
|
|
try {
|
|
const credentials = JSON.parse(storedAuth);
|
|
if (!credentials.username || !credentials.password) return null;
|
|
return credentials;
|
|
} catch (error) {
|
|
console.error('Failed to parse stored credentials:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Function to simulate a button click with default values
|
|
async function simulateButtonClick() {
|
|
const action = 'SingleClick'; // Default action
|
|
const buttonName = 'Game-button'; // Default button name
|
|
const batteryLevel = 100; // Default battery level
|
|
|
|
const output = document.getElementById('swMessagesOutput');
|
|
// Don't show the simulating text
|
|
|
|
try {
|
|
// Get credentials
|
|
const credentials = await getStoredCredentials();
|
|
if (!credentials) {
|
|
output.textContent = 'No credentials found. Please set up credentials first.\n';
|
|
return;
|
|
}
|
|
|
|
// Create basic auth header
|
|
const authHeader = 'Basic ' + btoa(`${credentials.username}:${credentials.password}`);
|
|
|
|
// Create timestamp (current time)
|
|
const timestamp = new Date().toISOString();
|
|
|
|
// Prepare request to backend webhook
|
|
let backendUrl;
|
|
try {
|
|
const configModule = await import('/js/config.js');
|
|
backendUrl = configModule.getBackendUrl();
|
|
} catch (error) {
|
|
output.textContent = 'Failed to load backend URL. Please check configuration.\n';
|
|
return;
|
|
}
|
|
|
|
const webhookUrl = `${backendUrl}/webhook/${action}`;
|
|
output.textContent = `Sending request to: ${webhookUrl}\n`;
|
|
output.textContent += `Authorization: Basic ****\n`;
|
|
output.textContent += `Button-Name: ${buttonName}\n`;
|
|
output.textContent += `Timestamp: ${timestamp}\n`;
|
|
output.textContent += `Button-Battery-Level: ${batteryLevel}\n\n`;
|
|
|
|
// Headers similar to the curl command
|
|
const headers = {
|
|
'Authorization': authHeader,
|
|
'Button-Name': buttonName,
|
|
'Timestamp': timestamp,
|
|
'Button-Battery-Level': batteryLevel.toString()
|
|
};
|
|
|
|
// Send GET request to webhook
|
|
const response = await fetch(webhookUrl, {
|
|
method: 'GET',
|
|
headers: headers,
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (response.ok) {
|
|
let result;
|
|
try {
|
|
result = await response.json();
|
|
output.textContent += `Success! Response: ${JSON.stringify(result, null, 2)}\n`;
|
|
} catch (e) {
|
|
// Text response
|
|
result = await response.text();
|
|
output.textContent += `Success! Response: ${result}\n`;
|
|
}
|
|
} else {
|
|
let errorText;
|
|
try {
|
|
errorText = await response.text();
|
|
} catch (e) {
|
|
errorText = `Status ${response.status}`;
|
|
}
|
|
output.textContent += `Error: ${errorText}\n`;
|
|
}
|
|
} catch (error) {
|
|
output.textContent += `Error: ${error.message}\n`;
|
|
}
|
|
}
|
|
|
|
// Attach click event to the new button
|
|
const simulateClickButton = document.getElementById('simulateClickButton');
|
|
if (simulateClickButton) {
|
|
simulateClickButton.addEventListener('click', simulateButtonClick);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |