fix: consolidate PWA banners — install in browser, update in PWA only
Remove duplicate purple update banner from website/shell.ts that overlapped with the server/shell.ts banner. Now a single banner system: - Browser: "Install rSpace app" prompt (via beforeinstallprompt) - Installed PWA: "New version available" prompt (via SW update detection) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
26c07a7672
commit
bb19b4bc89
|
|
@ -350,10 +350,15 @@ export function renderShell(opts: ShellOptions): string {
|
|||
|
||||
<script type="module">
|
||||
import '/shell.js';
|
||||
// ── Service worker registration + update detection ──
|
||||
// ── PWA: single consolidated banner ──
|
||||
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|
||||
|| window.navigator.standalone === true;
|
||||
|
||||
// Service worker registration + update detection
|
||||
if ("serviceWorker" in navigator && location.hostname !== "localhost") {
|
||||
navigator.serviceWorker.register("/sw.js?v=5").then((reg) => {
|
||||
function showUpdateBanner() {
|
||||
if (!isStandalone) return; // Only show update prompt in installed PWA
|
||||
const b = document.getElementById('pwa-update-banner');
|
||||
if (!b || b.style.display !== 'none') return;
|
||||
b.style.display = '';
|
||||
|
|
@ -375,31 +380,36 @@ export function renderShell(opts: ShellOptions): string {
|
|||
location.reload();
|
||||
});
|
||||
}
|
||||
// ── PWA install banner ──
|
||||
window.addEventListener("beforeinstallprompt", (e) => {
|
||||
e.preventDefault();
|
||||
window.__rspaceInstallPrompt = () => { e.prompt(); return e.userChoice; };
|
||||
window.dispatchEvent(new CustomEvent("rspace-install-available"));
|
||||
if (localStorage.getItem('rspace_install_dismissed') !== '1') {
|
||||
const b = document.getElementById('pwa-install-banner');
|
||||
if (b) { b.style.display = ''; document.body.classList.add('rspace-banner-visible'); }
|
||||
}
|
||||
});
|
||||
document.getElementById('pwa-install-btn')?.addEventListener('click', async () => {
|
||||
if (window.__rspaceInstallPrompt) {
|
||||
const choice = await window.__rspaceInstallPrompt();
|
||||
if (choice?.outcome === 'accepted') {
|
||||
document.getElementById('pwa-install-banner').style.display = 'none';
|
||||
document.body.classList.remove('rspace-banner-visible');
|
||||
localStorage.setItem('rspace_install_dismissed', '1');
|
||||
|
||||
// Install banner — browser only (not shown in installed PWA)
|
||||
if (!isStandalone) {
|
||||
window.addEventListener("beforeinstallprompt", (e) => {
|
||||
e.preventDefault();
|
||||
window.__rspaceInstallPrompt = () => { e.prompt(); return e.userChoice; };
|
||||
window.dispatchEvent(new CustomEvent("rspace-install-available"));
|
||||
if (localStorage.getItem('rspace_install_dismissed') !== '1') {
|
||||
const b = document.getElementById('pwa-install-banner');
|
||||
if (b) { b.style.display = ''; document.body.classList.add('rspace-banner-visible'); }
|
||||
}
|
||||
}
|
||||
});
|
||||
document.getElementById('pwa-install-close')?.addEventListener('click', () => {
|
||||
document.getElementById('pwa-install-banner').style.display = 'none';
|
||||
document.body.classList.remove('rspace-banner-visible');
|
||||
localStorage.setItem('rspace_install_dismissed', '1');
|
||||
});
|
||||
});
|
||||
document.getElementById('pwa-install-btn')?.addEventListener('click', async () => {
|
||||
if (window.__rspaceInstallPrompt) {
|
||||
const choice = await window.__rspaceInstallPrompt();
|
||||
if (choice?.outcome === 'accepted') {
|
||||
document.getElementById('pwa-install-banner').style.display = 'none';
|
||||
document.body.classList.remove('rspace-banner-visible');
|
||||
localStorage.setItem('rspace_install_dismissed', '1');
|
||||
}
|
||||
}
|
||||
});
|
||||
document.getElementById('pwa-install-close')?.addEventListener('click', () => {
|
||||
document.getElementById('pwa-install-banner').style.display = 'none';
|
||||
document.body.classList.remove('rspace-banner-visible');
|
||||
localStorage.setItem('rspace_install_dismissed', '1');
|
||||
});
|
||||
}
|
||||
|
||||
// Update banner button handlers (always wire, shown conditionally above)
|
||||
document.getElementById('pwa-update-btn')?.addEventListener('click', () => {
|
||||
navigator.serviceWorker?.getRegistration().then((reg) => { reg?.waiting?.postMessage({ type: 'SKIP_WAITING' }); });
|
||||
});
|
||||
|
|
@ -407,10 +417,6 @@ export function renderShell(opts: ShellOptions): string {
|
|||
document.getElementById('pwa-update-banner').style.display = 'none';
|
||||
document.body.classList.remove('rspace-banner-visible');
|
||||
});
|
||||
if (window.matchMedia('(display-mode: standalone)').matches) {
|
||||
const ib = document.getElementById('pwa-install-banner');
|
||||
if (ib) ib.style.display = 'none';
|
||||
}
|
||||
// ── Header minimize toggle ──
|
||||
if (localStorage.getItem('rspace_headers_minimized') === '1') {
|
||||
document.body.classList.add('rspace-headers-minimized');
|
||||
|
|
|
|||
|
|
@ -133,88 +133,5 @@ document.addEventListener("auth-change", (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── SW Update Banner ──
|
||||
// Show "new version available" when a new service worker activates.
|
||||
// The SW calls skipWaiting() so it activates immediately — we detect the
|
||||
// controller change and prompt the user to reload for the fresh content.
|
||||
if ("serviceWorker" in navigator && location.hostname !== "localhost") {
|
||||
// Only listen if there's already a controller (skip first-time install)
|
||||
if (navigator.serviceWorker.controller) {
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
showUpdateBanner();
|
||||
});
|
||||
}
|
||||
|
||||
// Also detect waiting workers (edge case: skipWaiting didn't fire yet)
|
||||
navigator.serviceWorker.getRegistration().then((reg) => {
|
||||
if (!reg) return;
|
||||
if (reg.waiting && navigator.serviceWorker.controller) {
|
||||
showUpdateBanner();
|
||||
return;
|
||||
}
|
||||
reg.addEventListener("updatefound", () => {
|
||||
const newWorker = reg.installing;
|
||||
if (!newWorker) return;
|
||||
newWorker.addEventListener("statechange", () => {
|
||||
if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
|
||||
showUpdateBanner();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showUpdateBanner() {
|
||||
if (document.getElementById("sw-update-banner")) return;
|
||||
|
||||
const banner = document.createElement("div");
|
||||
banner.id = "sw-update-banner";
|
||||
banner.setAttribute("role", "alert");
|
||||
banner.innerHTML = `
|
||||
<span>New version available</span>
|
||||
<button id="sw-update-btn">Tap to update</button>
|
||||
<button id="sw-update-dismiss" aria-label="Dismiss">×</button>
|
||||
`;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
#sw-update-banner {
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 10000;
|
||||
display: flex; align-items: center; justify-content: center; gap: 12px;
|
||||
padding: 10px 16px;
|
||||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||
color: white; font-size: 14px; font-weight: 500;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.3);
|
||||
animation: sw-slide-down 0.3s ease-out;
|
||||
}
|
||||
@keyframes sw-slide-down {
|
||||
from { transform: translateY(-100%); }
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
#sw-update-btn {
|
||||
padding: 5px 14px; border-radius: 6px; border: 1.5px solid rgba(255,255,255,0.5);
|
||||
background: rgba(255,255,255,0.15); color: white;
|
||||
font-size: 13px; font-weight: 600; cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
#sw-update-btn:hover { background: rgba(255,255,255,0.3); }
|
||||
#sw-update-dismiss {
|
||||
position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
|
||||
background: none; border: none; color: rgba(255,255,255,0.7);
|
||||
font-size: 20px; cursor: pointer; padding: 4px 8px; line-height: 1;
|
||||
}
|
||||
#sw-update-dismiss:hover { color: white; }
|
||||
`;
|
||||
|
||||
document.head.appendChild(style);
|
||||
document.body.prepend(banner);
|
||||
|
||||
banner.querySelector("#sw-update-btn")!.addEventListener("click", () => {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
banner.querySelector("#sw-update-dismiss")!.addEventListener("click", () => {
|
||||
banner.remove();
|
||||
});
|
||||
}
|
||||
// SW update banner is handled by the inline script in server/shell.ts
|
||||
// (single #pwa-update-banner element, no duplicate)
|
||||
|
|
|
|||
Loading…
Reference in New Issue