Aplikacja kelnera jako PWA
This commit is contained in:
9
public/waiter/.htaccess
Normal file
9
public/waiter/.htaccess
Normal file
@@ -0,0 +1,9 @@
|
||||
<IfModule mod_mime.c>
|
||||
AddType application/manifest+json .webmanifest
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "^(index\.php|manifest\.webmanifest|sw\.js)$">
|
||||
Header set Cache-Control "no-cache"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
@@ -87,7 +87,8 @@ body {
|
||||
50% { opacity: 1; transform: scale(1.1); }
|
||||
}
|
||||
|
||||
.notify-banner {
|
||||
.notify-banner,
|
||||
.install-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -99,13 +100,21 @@ body {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.notify-banner p {
|
||||
.install-banner {
|
||||
background: linear-gradient(135deg, #14532d 0%, #1e293b 100%);
|
||||
border-color: #22c55e;
|
||||
}
|
||||
|
||||
.notify-banner p,
|
||||
.install-banner p {
|
||||
margin-top: 4px;
|
||||
font-size: 0.82rem;
|
||||
color: #cbd5e1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notify-banner {
|
||||
|
||||
.btn {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
|
||||
@@ -7,6 +7,8 @@ const syncDot = document.getElementById('syncDot');
|
||||
const syncLabel = document.getElementById('syncLabel');
|
||||
const notifyBanner = document.getElementById('notifyBanner');
|
||||
const enableNotifyBtn = document.getElementById('enableNotifyBtn');
|
||||
const installBanner = document.getElementById('installBanner');
|
||||
const installAppBtn = document.getElementById('installAppBtn');
|
||||
|
||||
const statPending = document.getElementById('statPending');
|
||||
const statWaiter = document.getElementById('statWaiter');
|
||||
@@ -18,6 +20,49 @@ let feedInitialized = false;
|
||||
let pollTimer = null;
|
||||
let lastPayload = '';
|
||||
let swRegistration = null;
|
||||
let deferredInstallPrompt = null;
|
||||
|
||||
function isStandaloneDisplay() {
|
||||
return window.matchMedia('(display-mode: standalone)').matches
|
||||
|| window.navigator.standalone === true;
|
||||
}
|
||||
|
||||
function updateInstallBanner() {
|
||||
if (!installBanner) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isStandaloneDisplay() || !deferredInstallPrompt) {
|
||||
installBanner.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
installBanner.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function setupInstallPrompt() {
|
||||
window.addEventListener('beforeinstallprompt', (event) => {
|
||||
event.preventDefault();
|
||||
deferredInstallPrompt = event;
|
||||
updateInstallBanner();
|
||||
});
|
||||
|
||||
window.addEventListener('appinstalled', () => {
|
||||
deferredInstallPrompt = null;
|
||||
updateInstallBanner();
|
||||
});
|
||||
}
|
||||
|
||||
async function promptInstallApp() {
|
||||
if (!deferredInstallPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
deferredInstallPrompt.prompt();
|
||||
await deferredInstallPrompt.userChoice;
|
||||
deferredInstallPrompt = null;
|
||||
updateInstallBanner();
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '')
|
||||
@@ -230,6 +275,10 @@ enableNotifyBtn?.addEventListener('click', () => {
|
||||
requestNotifications();
|
||||
});
|
||||
|
||||
installAppBtn?.addEventListener('click', () => {
|
||||
promptInstallApp();
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
pollFeed();
|
||||
@@ -237,13 +286,9 @@ document.addEventListener('visibilitychange', () => {
|
||||
});
|
||||
|
||||
(async function init() {
|
||||
setupInstallPrompt();
|
||||
updateInstallBanner();
|
||||
updateNotifyBanner();
|
||||
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
await registerServiceWorker();
|
||||
} else if ('Notification' in window && Notification.permission === 'default') {
|
||||
notifyBanner.classList.remove('hidden');
|
||||
}
|
||||
|
||||
await registerServiceWorker();
|
||||
startPolling();
|
||||
})();
|
||||
|
||||
BIN
public/waiter/icons/icon-192.png
Normal file
BIN
public/waiter/icons/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 999 B |
BIN
public/waiter/icons/icon-512.png
Normal file
BIN
public/waiter/icons/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/waiter/icons/icon-maskable-512.png
Normal file
BIN
public/waiter/icons/icon-maskable-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -9,9 +9,14 @@ $vJs = publicAssetVersion($waiterDir, 'app.js');
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<meta name="theme-color" content="#0f172a">
|
||||
<meta name="description" content="Wezwania kelnera i prośby o rachunek – panel Biesiada">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="Kelner">
|
||||
<title>Kelner – wezwania</title>
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<link rel="apple-touch-icon" href="icons/icon-192.png">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="app.css?v=<?= assetVersionAttr($vCss) ?>">
|
||||
</head>
|
||||
@@ -27,6 +32,14 @@ $vJs = publicAssetVersion($waiterDir, 'app.js');
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="install-banner hidden" id="installBanner">
|
||||
<div>
|
||||
<strong>Zainstaluj aplikację</strong>
|
||||
<p>Dodaj panel kelnera na ekran główny telefonu — szybszy dostęp bez paska adresu.</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" id="installAppBtn">Instaluj</button>
|
||||
</section>
|
||||
|
||||
<section class="notify-banner hidden" id="notifyBanner">
|
||||
<div>
|
||||
<strong>Powiadomienia wyłączone</strong>
|
||||
|
||||
33
public/waiter/manifest.webmanifest
Normal file
33
public/waiter/manifest.webmanifest
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"id": "/app/public/waiter/",
|
||||
"name": "Panel kelnera – Biesiada",
|
||||
"short_name": "Kelner",
|
||||
"description": "Wezwania kelnera i prośby o rachunek",
|
||||
"start_url": "./index.php",
|
||||
"scope": "./",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#0f172a",
|
||||
"background_color": "#0f172a",
|
||||
"lang": "pl",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
const START_URL = './index.php';
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(self.skipWaiting());
|
||||
});
|
||||
@@ -6,6 +8,27 @@ self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((list) => {
|
||||
for (const client of list) {
|
||||
if ('focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(START_URL);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
if (!data || data.type !== 'notify') {
|
||||
|
||||
Reference in New Issue
Block a user