added URL scheme/deep linking
This commit is contained in:
65
apps.js
65
apps.js
@@ -677,6 +677,65 @@ deletePlayerButton.addEventListener('click', () => {
|
||||
audioManager.play('modalClose');
|
||||
});
|
||||
|
||||
// Flic button action handler - Parse URL parameters and execute corresponding actions
|
||||
function handleDeepLink() {
|
||||
if (!window.location.hash) return;
|
||||
|
||||
// Parse the hash to get action parameters
|
||||
const params = new URLSearchParams(window.location.hash.substring(1));
|
||||
const action = params.get('action');
|
||||
|
||||
console.log('Received action from deep link:', action);
|
||||
|
||||
// Execute action based on the parameter
|
||||
switch (action) {
|
||||
case 'start':
|
||||
if (gameState === 'setup' || gameState === 'paused') {
|
||||
if (players.length < 2) {
|
||||
console.log('Cannot start: Need at least 2 players');
|
||||
return;
|
||||
}
|
||||
gameState = 'running';
|
||||
audioManager.play('gameStart');
|
||||
startTimer();
|
||||
updateGameButton();
|
||||
renderPlayers();
|
||||
saveData();
|
||||
}
|
||||
break;
|
||||
case 'pause':
|
||||
if (gameState === 'running') {
|
||||
gameState = 'paused';
|
||||
audioManager.play('gamePause');
|
||||
stopTimer();
|
||||
updateGameButton();
|
||||
renderPlayers();
|
||||
saveData();
|
||||
}
|
||||
break;
|
||||
case 'toggle':
|
||||
// Toggle between start/pause depending on current state
|
||||
gameButton.click();
|
||||
break;
|
||||
case 'nextplayer':
|
||||
if (gameState === 'running') {
|
||||
const nextIndex = findNextPlayerWithTimeCircular(currentPlayerIndex, 1);
|
||||
if (nextIndex !== -1 && nextIndex !== currentPlayerIndex) {
|
||||
currentPlayerIndex = nextIndex;
|
||||
audioManager.play('playerSwitch');
|
||||
renderPlayers();
|
||||
saveData();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown action:', action);
|
||||
}
|
||||
|
||||
// Clear the hash to prevent duplicate actions if page is refreshed
|
||||
history.replaceState(null, null, ' ');
|
||||
}
|
||||
|
||||
// Service Worker Registration
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
@@ -690,6 +749,12 @@ if ('serviceWorker' in navigator) {
|
||||
});
|
||||
}
|
||||
|
||||
// Check for deep links when the page loads
|
||||
window.addEventListener('load', handleDeepLink);
|
||||
|
||||
// Also check for hash changes (needed for handling link activation when app is already open)
|
||||
window.addEventListener('hashchange', handleDeepLink);
|
||||
|
||||
// Make sure to handle rotation by adding window event listener for orientation changes
|
||||
window.addEventListener('orientationchange', () => {
|
||||
// If camera is active, adjust video dimensions
|
||||
|
||||
57
index.html
57
index.html
@@ -8,6 +8,15 @@
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Deep Linking - App Links for Android -->
|
||||
<link rel="alternate" href="android-app://eu.virtonline.gametimer/https://game-timer.virtonline.eu" />
|
||||
|
||||
<!-- Deep Linking - Universal Links for iOS -->
|
||||
<meta name="apple-itunes-app" content="app-id=yourAppID, app-argument=https://game-timer.virtonline.eu">
|
||||
|
||||
<!-- Deep Linking - Web App URL Handling -->
|
||||
<link rel="alternate" href="web+gametimer://action" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
@@ -88,6 +97,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Camera Capture UI -->
|
||||
<div id="cameraContainer" class="camera-container">
|
||||
<div class="camera-view">
|
||||
<video id="cameraView" autoplay playsinline></video>
|
||||
@@ -99,6 +109,53 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script for handling URL scheme and deep links -->
|
||||
<script type="module">
|
||||
// Register the custom URL protocol handler (web+gametimer://)
|
||||
if ('registerProtocolHandler' in navigator) {
|
||||
try {
|
||||
navigator.registerProtocolHandler(
|
||||
'web+gametimer',
|
||||
'https://game-timer.virtonline.eu/?action=%s',
|
||||
'Game Timer'
|
||||
);
|
||||
console.log('Protocol handler registered');
|
||||
} catch (e) {
|
||||
console.error('Failed to register protocol handler:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to parse URL parameters
|
||||
function getUrlParams() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
||||
|
||||
// Check search parameters first (for direct links)
|
||||
const action = searchParams.get('action');
|
||||
if (action) {
|
||||
// Clean the action parameter (remove 'web+gametimer://' if present)
|
||||
return action.replace('web+gametimer://', '');
|
||||
}
|
||||
|
||||
// Then check hash parameters (for deep links)
|
||||
return hashParams.get('action');
|
||||
}
|
||||
|
||||
// Initialize URL handling
|
||||
function initUrlHandling() {
|
||||
const action = getUrlParams();
|
||||
if (action) {
|
||||
console.log('URL action detected:', action);
|
||||
// Set the action in the hash to be processed by the main app
|
||||
window.location.hash = `action=${action}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Run initialization when DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', initUrlHandling);
|
||||
</script>
|
||||
|
||||
<!-- Main application script -->
|
||||
<script type="module" src="audio.js"></script>
|
||||
<script type="module" src="apps.js"></script>
|
||||
<script>
|
||||
|
||||
213
index.html?action=toggle
Normal file
213
index.html?action=toggle
Normal file
@@ -0,0 +1,213 @@
|
||||
<!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>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Deep Linking - App Links for Android -->
|
||||
<link rel="alternate" href="android-app://eu.virtonline.gametimer/https/game-timer.virtonline.eu" />
|
||||
|
||||
<!-- Deep Linking - Universal Links for iOS -->
|
||||
<meta name="apple-itunes-app" content="app-id=yourAppID, app-argument=https://game-timer.virtonline.eu">
|
||||
|
||||
<!-- Deep Linking - Web App URL Handling -->
|
||||
<link rel="alternate" href="web+gametimer://action" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<header class="header">
|
||||
<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>
|
||||
|
||||
<!-- Script for handling URL scheme and deep links -->
|
||||
<script type="module">
|
||||
// Register the custom URL protocol handler (web+gametimer://)
|
||||
if ('registerProtocolHandler' in navigator) {
|
||||
try {
|
||||
navigator.registerProtocolHandler(
|
||||
'web+gametimer',
|
||||
'https://game-timer.virtonline.eu/?action=%s',
|
||||
'Game Timer'
|
||||
);
|
||||
console.log('Protocol handler registered');
|
||||
} catch (e) {
|
||||
console.error('Failed to register protocol handler:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to parse URL parameters
|
||||
function getUrlParams() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
||||
|
||||
// Check search parameters first (for direct links)
|
||||
const action = searchParams.get('action');
|
||||
if (action) {
|
||||
// Clean the action parameter (remove 'web+gametimer://' if present)
|
||||
return action.replace('web+gametimer://', '');
|
||||
}
|
||||
|
||||
// Then check hash parameters (for deep links)
|
||||
return hashParams.get('action');
|
||||
}
|
||||
|
||||
// Initialize URL handling
|
||||
function initUrlHandling() {
|
||||
const action = getUrlParams();
|
||||
if (action) {
|
||||
console.log('URL action detected:', action);
|
||||
// Set the action in the hash to be processed by the main app
|
||||
window.location.hash = `action=${action}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Run initialization when DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', initUrlHandling);
|
||||
</script>
|
||||
|
||||
<!-- Main application script -->
|
||||
<script type="module" src="audio.js"></script>
|
||||
<script type="module" src="apps.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>
|
||||
<footer class="app-footer">
|
||||
<div class="author-info">
|
||||
<p>Vibe coded by Martin</p>
|
||||
<p>Version 0.0.1</p>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Code injected by live-server -->
|
||||
<script>
|
||||
// <![CDATA[ <-- For SVG support
|
||||
if ('WebSocket' in window) {
|
||||
(function () {
|
||||
function refreshCSS() {
|
||||
var sheets = [].slice.call(document.getElementsByTagName("link"));
|
||||
var head = document.getElementsByTagName("head")[0];
|
||||
for (var i = 0; i < sheets.length; ++i) {
|
||||
var elem = sheets[i];
|
||||
var parent = elem.parentElement || head;
|
||||
parent.removeChild(elem);
|
||||
var rel = elem.rel;
|
||||
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
|
||||
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
|
||||
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
|
||||
}
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
}
|
||||
var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
|
||||
var address = protocol + window.location.host + window.location.pathname + '/ws';
|
||||
var socket = new WebSocket(address);
|
||||
socket.onmessage = function (msg) {
|
||||
if (msg.data == 'reload') window.location.reload();
|
||||
else if (msg.data == 'refreshcss') refreshCSS();
|
||||
};
|
||||
if (sessionStorage && !sessionStorage.getItem('IsThisFirstTime_Log_From_LiveServer')) {
|
||||
console.log('Live reload enabled.');
|
||||
sessionStorage.setItem('IsThisFirstTime_Log_From_LiveServer', true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
else {
|
||||
console.error('Upgrade your browser. This Browser is NOT supported WebSocket for Live-Reloading.');
|
||||
}
|
||||
// ]]>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
213
index.html?action=toggle.1
Normal file
213
index.html?action=toggle.1
Normal file
@@ -0,0 +1,213 @@
|
||||
<!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>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Deep Linking - App Links for Android -->
|
||||
<link rel="alternate" href="android-app://eu.virtonline.gametimer/https/game-timer.virtonline.eu" />
|
||||
|
||||
<!-- Deep Linking - Universal Links for iOS -->
|
||||
<meta name="apple-itunes-app" content="app-id=yourAppID, app-argument=https://game-timer.virtonline.eu">
|
||||
|
||||
<!-- Deep Linking - Web App URL Handling -->
|
||||
<link rel="alternate" href="web+gametimer://action" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<header class="header">
|
||||
<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>
|
||||
|
||||
<!-- Script for handling URL scheme and deep links -->
|
||||
<script type="module">
|
||||
// Register the custom URL protocol handler (web+gametimer://)
|
||||
if ('registerProtocolHandler' in navigator) {
|
||||
try {
|
||||
navigator.registerProtocolHandler(
|
||||
'web+gametimer',
|
||||
'https://game-timer.virtonline.eu/?action=%s',
|
||||
'Game Timer'
|
||||
);
|
||||
console.log('Protocol handler registered');
|
||||
} catch (e) {
|
||||
console.error('Failed to register protocol handler:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to parse URL parameters
|
||||
function getUrlParams() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
||||
|
||||
// Check search parameters first (for direct links)
|
||||
const action = searchParams.get('action');
|
||||
if (action) {
|
||||
// Clean the action parameter (remove 'web+gametimer://' if present)
|
||||
return action.replace('web+gametimer://', '');
|
||||
}
|
||||
|
||||
// Then check hash parameters (for deep links)
|
||||
return hashParams.get('action');
|
||||
}
|
||||
|
||||
// Initialize URL handling
|
||||
function initUrlHandling() {
|
||||
const action = getUrlParams();
|
||||
if (action) {
|
||||
console.log('URL action detected:', action);
|
||||
// Set the action in the hash to be processed by the main app
|
||||
window.location.hash = `action=${action}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Run initialization when DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', initUrlHandling);
|
||||
</script>
|
||||
|
||||
<!-- Main application script -->
|
||||
<script type="module" src="audio.js"></script>
|
||||
<script type="module" src="apps.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>
|
||||
<footer class="app-footer">
|
||||
<div class="author-info">
|
||||
<p>Vibe coded by Martin</p>
|
||||
<p>Version 0.0.1</p>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Code injected by live-server -->
|
||||
<script>
|
||||
// <![CDATA[ <-- For SVG support
|
||||
if ('WebSocket' in window) {
|
||||
(function () {
|
||||
function refreshCSS() {
|
||||
var sheets = [].slice.call(document.getElementsByTagName("link"));
|
||||
var head = document.getElementsByTagName("head")[0];
|
||||
for (var i = 0; i < sheets.length; ++i) {
|
||||
var elem = sheets[i];
|
||||
var parent = elem.parentElement || head;
|
||||
parent.removeChild(elem);
|
||||
var rel = elem.rel;
|
||||
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
|
||||
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
|
||||
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
|
||||
}
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
}
|
||||
var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
|
||||
var address = protocol + window.location.host + window.location.pathname + '/ws';
|
||||
var socket = new WebSocket(address);
|
||||
socket.onmessage = function (msg) {
|
||||
if (msg.data == 'reload') window.location.reload();
|
||||
else if (msg.data == 'refreshcss') refreshCSS();
|
||||
};
|
||||
if (sessionStorage && !sessionStorage.getItem('IsThisFirstTime_Log_From_LiveServer')) {
|
||||
console.log('Live reload enabled.');
|
||||
sessionStorage.setItem('IsThisFirstTime_Log_From_LiveServer', true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
else {
|
||||
console.error('Upgrade your browser. This Browser is NOT supported WebSocket for Live-Reloading.');
|
||||
}
|
||||
// ]]>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
213
index.html?action=toggle.2
Normal file
213
index.html?action=toggle.2
Normal file
@@ -0,0 +1,213 @@
|
||||
<!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>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Deep Linking - App Links for Android -->
|
||||
<link rel="alternate" href="android-app://eu.virtonline.gametimer/https/game-timer.virtonline.eu" />
|
||||
|
||||
<!-- Deep Linking - Universal Links for iOS -->
|
||||
<meta name="apple-itunes-app" content="app-id=yourAppID, app-argument=https://game-timer.virtonline.eu">
|
||||
|
||||
<!-- Deep Linking - Web App URL Handling -->
|
||||
<link rel="alternate" href="web+gametimer://action" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<header class="header">
|
||||
<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>
|
||||
|
||||
<!-- Script for handling URL scheme and deep links -->
|
||||
<script type="module">
|
||||
// Register the custom URL protocol handler (web+gametimer://)
|
||||
if ('registerProtocolHandler' in navigator) {
|
||||
try {
|
||||
navigator.registerProtocolHandler(
|
||||
'web+gametimer',
|
||||
'https://game-timer.virtonline.eu/?action=%s',
|
||||
'Game Timer'
|
||||
);
|
||||
console.log('Protocol handler registered');
|
||||
} catch (e) {
|
||||
console.error('Failed to register protocol handler:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to parse URL parameters
|
||||
function getUrlParams() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
||||
|
||||
// Check search parameters first (for direct links)
|
||||
const action = searchParams.get('action');
|
||||
if (action) {
|
||||
// Clean the action parameter (remove 'web+gametimer://' if present)
|
||||
return action.replace('web+gametimer://', '');
|
||||
}
|
||||
|
||||
// Then check hash parameters (for deep links)
|
||||
return hashParams.get('action');
|
||||
}
|
||||
|
||||
// Initialize URL handling
|
||||
function initUrlHandling() {
|
||||
const action = getUrlParams();
|
||||
if (action) {
|
||||
console.log('URL action detected:', action);
|
||||
// Set the action in the hash to be processed by the main app
|
||||
window.location.hash = `action=${action}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Run initialization when DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', initUrlHandling);
|
||||
</script>
|
||||
|
||||
<!-- Main application script -->
|
||||
<script type="module" src="audio.js"></script>
|
||||
<script type="module" src="apps.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>
|
||||
<footer class="app-footer">
|
||||
<div class="author-info">
|
||||
<p>Vibe coded by Martin</p>
|
||||
<p>Version 0.0.1</p>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Code injected by live-server -->
|
||||
<script>
|
||||
// <![CDATA[ <-- For SVG support
|
||||
if ('WebSocket' in window) {
|
||||
(function () {
|
||||
function refreshCSS() {
|
||||
var sheets = [].slice.call(document.getElementsByTagName("link"));
|
||||
var head = document.getElementsByTagName("head")[0];
|
||||
for (var i = 0; i < sheets.length; ++i) {
|
||||
var elem = sheets[i];
|
||||
var parent = elem.parentElement || head;
|
||||
parent.removeChild(elem);
|
||||
var rel = elem.rel;
|
||||
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
|
||||
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
|
||||
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
|
||||
}
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
}
|
||||
var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
|
||||
var address = protocol + window.location.host + window.location.pathname + '/ws';
|
||||
var socket = new WebSocket(address);
|
||||
socket.onmessage = function (msg) {
|
||||
if (msg.data == 'reload') window.location.reload();
|
||||
else if (msg.data == 'refreshcss') refreshCSS();
|
||||
};
|
||||
if (sessionStorage && !sessionStorage.getItem('IsThisFirstTime_Log_From_LiveServer')) {
|
||||
console.log('Live reload enabled.');
|
||||
sessionStorage.setItem('IsThisFirstTime_Log_From_LiveServer', true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
else {
|
||||
console.error('Upgrade your browser. This Browser is NOT supported WebSocket for Live-Reloading.');
|
||||
}
|
||||
// ]]>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
213
index.html?action=toggle.3
Normal file
213
index.html?action=toggle.3
Normal file
@@ -0,0 +1,213 @@
|
||||
<!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>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Deep Linking - App Links for Android -->
|
||||
<link rel="alternate" href="android-app://eu.virtonline.gametimer/https/game-timer.virtonline.eu" />
|
||||
|
||||
<!-- Deep Linking - Universal Links for iOS -->
|
||||
<meta name="apple-itunes-app" content="app-id=yourAppID, app-argument=https://game-timer.virtonline.eu">
|
||||
|
||||
<!-- Deep Linking - Web App URL Handling -->
|
||||
<link rel="alternate" href="web+gametimer://action" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<header class="header">
|
||||
<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>
|
||||
|
||||
<!-- Script for handling URL scheme and deep links -->
|
||||
<script type="module">
|
||||
// Register the custom URL protocol handler (web+gametimer://)
|
||||
if ('registerProtocolHandler' in navigator) {
|
||||
try {
|
||||
navigator.registerProtocolHandler(
|
||||
'web+gametimer',
|
||||
'https://game-timer.virtonline.eu/?action=%s',
|
||||
'Game Timer'
|
||||
);
|
||||
console.log('Protocol handler registered');
|
||||
} catch (e) {
|
||||
console.error('Failed to register protocol handler:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to parse URL parameters
|
||||
function getUrlParams() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
||||
|
||||
// Check search parameters first (for direct links)
|
||||
const action = searchParams.get('action');
|
||||
if (action) {
|
||||
// Clean the action parameter (remove 'web+gametimer://' if present)
|
||||
return action.replace('web+gametimer://', '');
|
||||
}
|
||||
|
||||
// Then check hash parameters (for deep links)
|
||||
return hashParams.get('action');
|
||||
}
|
||||
|
||||
// Initialize URL handling
|
||||
function initUrlHandling() {
|
||||
const action = getUrlParams();
|
||||
if (action) {
|
||||
console.log('URL action detected:', action);
|
||||
// Set the action in the hash to be processed by the main app
|
||||
window.location.hash = `action=${action}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Run initialization when DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', initUrlHandling);
|
||||
</script>
|
||||
|
||||
<!-- Main application script -->
|
||||
<script type="module" src="audio.js"></script>
|
||||
<script type="module" src="apps.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>
|
||||
<footer class="app-footer">
|
||||
<div class="author-info">
|
||||
<p>Vibe coded by Martin</p>
|
||||
<p>Version 0.0.1</p>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Code injected by live-server -->
|
||||
<script>
|
||||
// <![CDATA[ <-- For SVG support
|
||||
if ('WebSocket' in window) {
|
||||
(function () {
|
||||
function refreshCSS() {
|
||||
var sheets = [].slice.call(document.getElementsByTagName("link"));
|
||||
var head = document.getElementsByTagName("head")[0];
|
||||
for (var i = 0; i < sheets.length; ++i) {
|
||||
var elem = sheets[i];
|
||||
var parent = elem.parentElement || head;
|
||||
parent.removeChild(elem);
|
||||
var rel = elem.rel;
|
||||
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
|
||||
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
|
||||
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
|
||||
}
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
}
|
||||
var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
|
||||
var address = protocol + window.location.host + window.location.pathname + '/ws';
|
||||
var socket = new WebSocket(address);
|
||||
socket.onmessage = function (msg) {
|
||||
if (msg.data == 'reload') window.location.reload();
|
||||
else if (msg.data == 'refreshcss') refreshCSS();
|
||||
};
|
||||
if (sessionStorage && !sessionStorage.getItem('IsThisFirstTime_Log_From_LiveServer')) {
|
||||
console.log('Live reload enabled.');
|
||||
sessionStorage.setItem('IsThisFirstTime_Log_From_LiveServer', true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
else {
|
||||
console.error('Upgrade your browser. This Browser is NOT supported WebSocket for Live-Reloading.');
|
||||
}
|
||||
// ]]>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -45,5 +45,41 @@
|
||||
"sizes": "1082x2402",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"url_handlers": [
|
||||
{
|
||||
"origin": "https://game-timer.virtonline.eu"
|
||||
}
|
||||
],
|
||||
"handle_links": "preferred",
|
||||
"file_handlers": [],
|
||||
"protocol_handlers": [
|
||||
{
|
||||
"protocol": "web+gametimer",
|
||||
"url": "/?action=%s"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Start Game",
|
||||
"short_name": "Start",
|
||||
"description": "Start the game timer",
|
||||
"url": "/?action=start",
|
||||
"icons": [{ "src": "/icons/play.png", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Pause Game",
|
||||
"short_name": "Pause",
|
||||
"description": "Pause the game timer",
|
||||
"url": "/?action=pause",
|
||||
"icons": [{ "src": "/icons/pause.png", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Next Player",
|
||||
"short_name": "Next",
|
||||
"description": "Go to next player",
|
||||
"url": "/?action=nextplayer",
|
||||
"icons": [{ "src": "/icons/next.png", "sizes": "192x192" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
159
sw.js
159
sw.js
@@ -1,52 +1,153 @@
|
||||
// Updated service worker code - sw.js
|
||||
const CACHE_NAME = 'timer-cache-v1';
|
||||
const urlsToCache = [
|
||||
// Service Worker version
|
||||
const CACHE_VERSION = 'v1.0.0';
|
||||
const CACHE_NAME = `game-timer-${CACHE_VERSION}`;
|
||||
|
||||
// Files to cache
|
||||
const CACHE_FILES = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/styles.css',
|
||||
'/apps.js',
|
||||
'/app.js',
|
||||
'/audio.js',
|
||||
'/styles.css',
|
||||
'/manifest.json',
|
||||
'/icons/android-chrome-192x192.png',
|
||||
'/icons/android-chrome-512x512.png',
|
||||
'/icons/apple-touch-icon.png',
|
||||
'/icons/favicon-32x32.png',
|
||||
'/icons/favicon-16x16.png',
|
||||
'/favicon.ico',
|
||||
'/manifest.json',
|
||||
'/site.webmanifest'
|
||||
'/icons/favicon-16x16.png'
|
||||
];
|
||||
|
||||
// Install event - Cache files
|
||||
self.addEventListener('install', event => {
|
||||
console.log('[ServiceWorker] Install');
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then(cache => {
|
||||
console.log('Opened cache');
|
||||
// Use individual cache.add calls in a Promise.all to handle failures better
|
||||
return Promise.all(
|
||||
urlsToCache.map(url => {
|
||||
return cache.add(url).catch(err => {
|
||||
console.log('Failed to cache:', url, err);
|
||||
// Continue despite individual failures
|
||||
return Promise.resolve();
|
||||
});
|
||||
console.log('[ServiceWorker] Caching app shell');
|
||||
return cache.addAll(CACHE_FILES);
|
||||
})
|
||||
);
|
||||
.then(() => {
|
||||
console.log('[ServiceWorker] Skip waiting on install');
|
||||
return self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(response => {
|
||||
// Cache hit - return response
|
||||
if (response) {
|
||||
return response;
|
||||
// Activate event - Clean old caches
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('[ServiceWorker] Activate');
|
||||
event.waitUntil(
|
||||
caches.keys().then(keyList => {
|
||||
return Promise.all(keyList.map(key => {
|
||||
if (key !== CACHE_NAME) {
|
||||
console.log('[ServiceWorker] Removing old cache', key);
|
||||
return caches.delete(key);
|
||||
}
|
||||
return fetch(event.request);
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Fetch handler failed:', err);
|
||||
.then(() => {
|
||||
console.log('[ServiceWorker] Claiming clients');
|
||||
return self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event - Serve from cache, fallback to network
|
||||
self.addEventListener('fetch', event => {
|
||||
console.log('[ServiceWorker] Fetch', event.request.url);
|
||||
|
||||
// For navigation requests that include our deep link parameters,
|
||||
// skip the cache and go straight to network
|
||||
if (event.request.mode === 'navigate') {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Check if request has action parameter or hash
|
||||
if (url.searchParams.has('action') || url.hash.includes('action=')) {
|
||||
console.log('[ServiceWorker] Processing deep link navigation');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(response => {
|
||||
return response || fetch(event.request)
|
||||
.then(res => {
|
||||
// Check if we should cache this response
|
||||
if (shouldCacheResponse(event.request, res)) {
|
||||
return caches.open(CACHE_NAME)
|
||||
.then(cache => {
|
||||
console.log('[ServiceWorker] Caching new resource:', event.request.url);
|
||||
cache.put(event.request, res.clone());
|
||||
return res;
|
||||
});
|
||||
}
|
||||
return res;
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('[ServiceWorker] Fetch failed; returning offline page', error);
|
||||
// You could return a custom offline page here
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Helper function to determine if a response should be cached
|
||||
function shouldCacheResponse(request, response) {
|
||||
// Only cache GET requests
|
||||
if (request.method !== 'GET') return false;
|
||||
|
||||
// Don't cache errors
|
||||
if (!response || response.status !== 200) return false;
|
||||
|
||||
// Check if URL should be cached
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Don't cache query parameters (except common ones for content)
|
||||
if (url.search && !url.search.match(/\?(v|version|cache)=/)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle deep links from other apps (including Flic)
|
||||
self.addEventListener('message', event => {
|
||||
if (event.data && event.data.type === 'PROCESS_ACTION') {
|
||||
const action = event.data.action;
|
||||
console.log('[ServiceWorker] Received action message:', action);
|
||||
|
||||
// Broadcast the action to all clients
|
||||
self.clients.matchAll().then(clients => {
|
||||
clients.forEach(client => {
|
||||
client.postMessage({
|
||||
type: 'ACTION',
|
||||
action: action
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// This helps with navigation after app is installed
|
||||
self.addEventListener('notificationclick', event => {
|
||||
event.notification.close();
|
||||
|
||||
// This looks to see if the current is already open and focuses if it is
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({
|
||||
type: 'window'
|
||||
})
|
||||
.then(clientList => {
|
||||
// Check if there is already a window/tab open with the target URL
|
||||
for (const client of clientList) {
|
||||
// If so, just focus it
|
||||
if (client.url.startsWith(self.location.origin) && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
// If not, open a new window/tab
|
||||
if (self.clients.openWindow) {
|
||||
return self.clients.openWindow('/');
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user