feat(pwa): better offline page and offline request handler

This commit is contained in:
Herculino Trotta
2025-01-24 14:22:30 -03:00
parent d50c84f8e6
commit dbea78cd3c
5 changed files with 206 additions and 7 deletions

View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline</title>
<style>
.offline, body {
text-align: center;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #222;
color: #fbb700;
font-family: Arial, sans-serif;
}
.wifi-icon {
width: 100px;
height: 100px;
}
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.flashing {
animation: flash 1s infinite;
}
#offline-countdown {
margin-top: 20px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="offline">
<svg class="wifi-icon flashing" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.08 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z" fill="#fbb700"/>
<path d="M23 21L1 3" stroke="#fbb700" stroke-width="2"/>
</svg>
<p>Either you or your WYGIWYH instance is offline.</p>
<div id="offline-countdown"></div>
</div>
<script>
function attemptReload() {
const countdownElement = document.getElementById('offline-countdown');
let secondsLeft = 30;
function updateCountdown() {
countdownElement.textContent = `Retrying in ${secondsLeft} seconds...`;
secondsLeft--;
if (secondsLeft < 0) {
window.location.reload();
} else {
setTimeout(updateCountdown, 1000);
}
}
updateCountdown();
}
// Start the reload attempt process immediately
attemptReload();
// Also attempt reload when coming back online
window.addEventListener('online', () => {
window.location.reload();
});
// For HTMX compatibility
document.body.addEventListener('htmx:load', attemptReload);
</script>
</body>
</html>

View File

@@ -0,0 +1,74 @@
// Base Service Worker implementation. To use your own Service Worker, set the PWA_SERVICE_WORKER_PATH variable in settings.py
var staticCacheName = "django-pwa-v" + new Date().getTime();
var filesToCache = [
'/offline/',
'/static/css/django-pwa-app.css',
'/static/img/favicon/android-icon-192x192.png',
'/static/img/favicon/apple-icon-180x180.png',
'/static/img/pwa/splash-640x1136.png',
'/static/img/pwa/splash-750x1334.png',
];
// Cache on install
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
);
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("django-pwa-")))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
}
return fetch(event.request).catch(() => {
const isHtmxRequest = event.request.headers.get('HX-Request') === 'true';
const isHtmxBoosted = event.request.headers.get('HX-Boosted') === 'true';
if (!isHtmxRequest || isHtmxBoosted) {
// Serve offline content without changing URL
return caches.match('/offline/').then(offlineResponse => {
if (offlineResponse) {
return offlineResponse.text().then(offlineText => {
return new Response(offlineText, {
status: 200,
headers: {'Content-Type': 'text/html'}
});
});
}
// If offline page is not in cache, return a simple offline message
return new Response('<h1>Offline</h1><p>The page is not available offline.</p>', {
status: 200,
headers: {'Content-Type': 'text/html'}
});
});
} else {
// For non-boosted HTMX requests, let it fail normally
throw new Error('Network request failed');
}
});
})
);
});