Skip to content
Snippets Groups Projects
Verified Commit 06c5b9b4 authored by Miniontoby's avatar Miniontoby :writing_hand_tone1:
Browse files

Added offline access and alert

parent eb2ba070
Branches
Tags
No related merge requests found
Pipeline #122818 passed
...@@ -52,6 +52,8 @@ ...@@ -52,6 +52,8 @@
</div> </div>
</div> </div>
<div id="offlineMessage" class="alert" hidden>You are offline! Be aware that this website will still work, but some features may not!</div>
<div id="weekdays"> <div id="weekdays">
<div>Monday</div> <div>Monday</div>
<div>Tuesday</div> <div>Tuesday</div>
...@@ -65,6 +67,7 @@ ...@@ -65,6 +67,7 @@
<div id="calendar"></div> <div id="calendar"></div>
</div> </div>
<script src="../pwa.js"></script>
<script src="./script.js"></script> <script src="./script.js"></script>
</body> </body>
</html> </html>
...@@ -210,13 +210,10 @@ function initDarkmode() { ...@@ -210,13 +210,10 @@ function initDarkmode() {
if ( window.addEventListener ) { if ( window.addEventListener ) {
var kkeys = [], a29uYW1p = "38,38,40,40,37,39,37,39,66,65"; var kkeys = [], a29uYW1p = "38,38,40,40,37,39,37,39,66,65";
window.addEventListener("keydown", function(e){ window.addEventListener('offline', () => { let e; if (e = document.querySelector("#offlineMessage")) e.hidden = false; });
kkeys.push(e.keyCode); window.addEventListener('online', () => { let e; if (e = document.querySelector("#offlineMessage")) e.hidden = true; });
if (kkeys.toString().indexOf(a29uYW1p) >= 0 ) { window.addEventListener("keydown", function (e) { kkeys.push(e.keyCode); if (kkeys.toString().indexOf(a29uYW1p) >= 0 ) { setTheme(atob('aGFja2Vy')); kkeys = []; } }, true);
setTheme(atob('aGFja2Vy')); if (!navigator.onLine) window.dispatchEvent(new Event('offline'));
kkeys = [];
}
}, true);
} }
function ical_download(download=true){ function ical_download(download=true){
......
...@@ -168,4 +168,11 @@ nav i { ...@@ -168,4 +168,11 @@ nav i {
} }
.event2 { background-color: green; padding: 10px; border-radius: 10px; } .event2 { background-color: green; padding: 10px; border-radius: 10px; }
.alert {
padding: 20px;
background-color: #f44336;
color: white;
margin-bottom: 15px;
}
.theme-switch { display: inline-block; height: 26px; position: relative; width: 26px; } .theme-switch input { display:none; } [data-theme="hacker"] { /* Please just try to find out how to get it without cheating! */ --text-color: #fff; --background-color: #005c00; --background-day: #142f14; --background-day-hover: #016b03; --background-current-day: #00a813; --background-modal: #4caf50; } .darkmode { display: none !important; } input:checked + .icons .lightmode { display: none !important; } input:checked + .icons .darkmode { display: var(--fa-display,inline-block) !important; } .theme-switch { display: inline-block; height: 26px; position: relative; width: 26px; } .theme-switch input { display:none; } [data-theme="hacker"] { /* Please just try to find out how to get it without cheating! */ --text-color: #fff; --background-color: #005c00; --background-day: #142f14; --background-day-hover: #016b03; --background-current-day: #00a813; --background-modal: #4caf50; } .darkmode { display: none !important; } input:checked + .icons .lightmode { display: none !important; } input:checked + .icons .darkmode { display: var(--fa-display,inline-block) !important; }
...@@ -52,6 +52,8 @@ ...@@ -52,6 +52,8 @@
</div> </div>
</div> </div>
<div id="offlineMessage" class="alert" hidden>You are offline! Be aware that this website will still work, but some features may not!</div>
<table id="tableing"> <table id="tableing">
<tr id="weekdays"> <tr id="weekdays">
<td>Monday</td> <td>Monday</td>
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
{ {
"src": "/images/Plan2Go.png", "src": "/images/Plan2Go.png",
"type": "image/png", "type": "image/png",
"sizes": "692x450" "sizes": "693x450"
}, },
{ {
"src": "/images/Plan2GoBanner.png", "src": "/images/Plan2GoBanner.png",
......
window.addEventListener("load", () => { window.addEventListener("load", () => {
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
let root = location.pathname.replace(/\/demo\/.*/,'') const a = location.pathname.split("/"); a.pop(); const root = (a.join('/') + '/').replace(/\/demo\/.*/,'/');
navigator.serviceWorker.register(root + "/service-worker.js"); navigator.serviceWorker.register(root + "service-worker.js");
navigator.serviceWorker.addEventListener('message', event => console.log(event.data)); navigator.serviceWorker.addEventListener('message', event => console.log(event.data));
} }
if ("__TAURI__" in window) { if ("__TAURI__" in window) {
class TimestampTrigger{
constructor(timestamp) {this.timestamp = timestamp;}
}
window.TimestampTrigger = TimestampTrigger;
Notification.notifications = [];
Notification.showNotification = (title, option={}) => { Notification.showNotification = (title, option={}) => {
if (typeof(title) == 'object') option = title; if (typeof(title) == 'object') option = title;
if (!("title" in option)) option.title = title; if (!("title" in option)) option.title = title;
new Notification(option.title, option); if ("showTrigger" in option) Notification.notifications.push(setTimeout(() => new Notification(option.title, option), option.showTrigger.timestamp - new Date().getTime()));
else new Notification(option.title, option);
} }
class TimestampTrigger{ Notification.getNotifications = (options={}) => {
constructor(timestamp) {} return Notification.notifications.map((id, i) => {
return {
"close": () => {
clearTimeout(id);
Notification.notifications.pop(i);
}
}
});
} }
} }
}); });
...@@ -26,6 +39,7 @@ async function sendNotificationAfter(seconds=5, body='Hello World', timestamp=nu ...@@ -26,6 +39,7 @@ async function sendNotificationAfter(seconds=5, body='Hello World', timestamp=nu
alert('you need to allow push notifications'); alert('you need to allow push notifications');
} else { } else {
if (!timestamp) timestamp = new Date().getTime() + seconds * 1000; // now plus 5000ms if (!timestamp) timestamp = new Date().getTime() + seconds * 1000; // now plus 5000ms
const a = location.pathname.split("/"); a.pop(); const root = (a.join('/') + '/').replace(/\/demo\/.*/,'/');
reg.showNotification( reg.showNotification(
'Demo Push Notification', 'Demo Push Notification',
{ {
...@@ -35,8 +49,8 @@ async function sendNotificationAfter(seconds=5, body='Hello World', timestamp=nu ...@@ -35,8 +49,8 @@ async function sendNotificationAfter(seconds=5, body='Hello World', timestamp=nu
data: { data: {
url: window.location.href, // pass the current url to the notification url: window.location.href, // pass the current url to the notification
}, },
badge: '/images/icon.png', badge: root + 'images/icon.png',
icon: '/images/icon.png', icon: root + 'images/icon.png',
actions: [ actions: [
{ {
action: 'open', action: 'open',
...@@ -53,15 +67,14 @@ async function sendNotificationAfter(seconds=5, body='Hello World', timestamp=nu ...@@ -53,15 +67,14 @@ async function sendNotificationAfter(seconds=5, body='Hello World', timestamp=nu
}); });
} }
async function cancelNotifications() { async function cancelNotifications() {
if (!("__TAURI__" in window)) { let reg = await navigator.serviceWorker.getRegistration();
const reg = await navigator.serviceWorker.getRegistration(); if ("__TAURI__" in window) reg = Notification;
const notifications = await reg.getNotifications({ const notifications = await reg.getNotifications({
includeTriggered: true includeTriggered: true
}); });
notifications.forEach(notification => notification.close()); notifications.forEach(notification => notification.close());
return `${notifications.length} notification(s) cancelled`; return `${notifications.length} notification(s) cancelled`;
}
} }
document.querySelector('#notification-button')?.addEventListener("click", () => sendNotificationAfter(5)) document.querySelector('#notification-button')?.addEventListener("click", () => sendNotificationAfter(5))
document.querySelector('#notification-cancel')?.addEventListener("click", () => alert(cancelNotifications())) document.querySelector('#notification-cancel')?.addEventListener("click", async () => alert(await cancelNotifications()))
...@@ -20,22 +20,48 @@ const OFFLINE_VERSION = 1; ...@@ -20,22 +20,48 @@ const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline"; const CACHE_NAME = "offline";
// Customize this with a different URL if needed. // Customize this with a different URL if needed.
const OFFLINE_URL = "offline.html"; const OFFLINE_URL = "offline.html";
const a = location.pathname.split("/"); a.pop();
const root = (a.join('/') + '/').replace(/\/demo\/.*/,'/');
const appFiles = ["", "index.html", "pwa.js", "manifest.json", "offline.html", "demo/", "demo/index.html", "demo/script.js", "demo/style.css", "demo/test.html", "demo/test.js", "images/icon.png", "images/Plan2Go.png", "images/Plan2GoBanner.png"]
const contentToCache = [...appFiles.map((f)=>`${root}${f}`), "https://unpkg.com/ical.js", "https://kit.fontawesome.com/a0d8d27dcc.js"];
self.addEventListener("install", (event) => { self.addEventListener("install", (event) => {
console.log("[Service Worker] Install");
event.waitUntil( event.waitUntil(
(async () => { (async () => {
const cache = await caches.open(CACHE_NAME); const cache = await caches.open(CACHE_NAME);
console.log("[Service Worker] Caching all app files");
// Setting {cache: 'reload'} in the new request ensures that the // Setting {cache: 'reload'} in the new request ensures that the
// response isn't fulfilled from the HTTP cache; i.e., it will be // response isn't fulfilled from the HTTP cache; i.e., it will be
// from the network. // from the network.
await cache.add(new Request(OFFLINE_URL, { cache: "reload" })); // await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
// await cache.addAll(contentToCache);
await cache.addAll(contentToCache.map((urlToPrefetch) => new Request(urlToPrefetch, { cache: "reload" })))
console.log("[Service Worker] Cached all app files!");
})() })()
); );
// Force the waiting service worker to become the active service worker. // Force the waiting service worker to become the active service worker.
self.skipWaiting(); self.skipWaiting();
}); });
self.addEventListener("activate", (event) => { self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(
keyList.map((key) => {
if (key === CACHE_NAME) return;
return caches.delete(key);
})
);
})
);
event.waitUntil( event.waitUntil(
(async () => { (async () => {
// Enable navigation preload if it's supported. // Enable navigation preload if it's supported.
...@@ -56,27 +82,32 @@ self.addEventListener("fetch", (event) => { ...@@ -56,27 +82,32 @@ self.addEventListener("fetch", (event) => {
if (event.request.mode === "navigate") { if (event.request.mode === "navigate") {
event.respondWith( event.respondWith(
(async () => { (async () => {
const cache = await caches.open(CACHE_NAME);
try { try {
// First, try to use the navigation preload response if it's // First, try to use the navigation preload response if it's
// supported. // supported.
const preloadResponse = await event.preloadResponse; const preloadResponse = await event.preloadResponse;
if (preloadResponse) { if (preloadResponse) return preloadResponse;
return preloadResponse;
}
// Always try the network first. // Always try the network first.
const networkResponse = await fetch(event.request); const networkResponse = await fetch(event.request);
cache.put(event.request, networkResponse.clone());
return networkResponse; return networkResponse;
} catch (error) { } catch (error) {
// catch is only triggered if an exception is thrown, which is // catch is only triggered if an exception is thrown, which is
// likely due to a network error. // likely due to a network error.
// If fetch() returns a valid HTTP response with a response code in // If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called. // the 4xx or 5xx range, the catch() will NOT be called.
console.log("Fetch failed; returning offline page instead.", error); console.log("[Service Worker] Fetch failed; returning cached page instead.", error);
const cache = await caches.open(CACHE_NAME); let cacheUrl = event.request.url.replace(location.origin, '');
const cachedResponse = await cache.match(OFFLINE_URL); const cachedResponse = await caches.match(cacheUrl) ?? await cache.match(cacheUrl);
return cachedResponse; console.log(`[Service Worker] Fetching resource: ${cacheUrl}`);
if (cachedResponse) return cachedResponse;
else {
console.log("[Service Worker] Cannot find cached page; returning offline page instead.");
return await cache.match(OFFLINE_URL);
}
} }
})() })()
); );
...@@ -99,7 +130,7 @@ self.addEventListener('notificationclick', event => { ...@@ -99,7 +130,7 @@ self.addEventListener('notificationclick', event => {
clients[0].focus(); clients[0].focus();
clients[0].postMessage('Push notification clicked!'); clients[0].postMessage('Push notification clicked!');
} else { } else {
self.clients.openWindow('/plan2go/'); self.clients.openWindow(root);
} }
})); }));
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment