Make PWA install hint dismiss reliable across reloads.
Use delegated clicks, explicit hide state, and init before service worker registration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+46
-13
@@ -4,6 +4,7 @@ const Pwa = (() => {
|
|||||||
const DISMISS_KEY = "pwa-hint-dismissed";
|
const DISMISS_KEY = "pwa-hint-dismissed";
|
||||||
let deferredPrompt = null;
|
let deferredPrompt = null;
|
||||||
let refreshInstallHint = null;
|
let refreshInstallHint = null;
|
||||||
|
let hintBound = false;
|
||||||
|
|
||||||
function isStandalone() {
|
function isStandalone() {
|
||||||
return window.matchMedia("(display-mode: standalone)").matches
|
return window.matchMedia("(display-mode: standalone)").matches
|
||||||
@@ -42,27 +43,64 @@ const Pwa = (() => {
|
|||||||
return "pwa.hintDesktop";
|
return "pwa.hintDesktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupInstallHint() {
|
function hideHint(hint, { persist = true } = {}) {
|
||||||
if (isStandalone()) return null;
|
if (persist) {
|
||||||
if (localStorage.getItem(DISMISS_KEY) === "1") return null;
|
try {
|
||||||
|
localStorage.setItem(DISMISS_KEY, "1");
|
||||||
|
} catch {
|
||||||
|
/* private mode with storage blocked – still hide for this session */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hint.hidden = true;
|
||||||
|
hint.classList.add("is-dismissed");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHint(hint) {
|
||||||
|
hint.hidden = false;
|
||||||
|
hint.classList.remove("is-dismissed");
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindHintActions(hint) {
|
||||||
|
if (hintBound) return;
|
||||||
|
hintBound = true;
|
||||||
|
|
||||||
|
hint.addEventListener("click", (event) => {
|
||||||
|
if (event.target.closest(".pwa-hint-dismiss")) {
|
||||||
|
event.preventDefault();
|
||||||
|
hideHint(hint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupInstallHint() {
|
||||||
const hint = document.getElementById("pwa-install-hint");
|
const hint = document.getElementById("pwa-install-hint");
|
||||||
if (!hint) return null;
|
if (!hint) return null;
|
||||||
|
|
||||||
|
bindHintActions(hint);
|
||||||
|
|
||||||
|
if (isStandalone()) {
|
||||||
|
hideHint(hint, { persist: false });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (localStorage.getItem(DISMISS_KEY) === "1") {
|
||||||
|
hideHint(hint, { persist: false });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const body = document.getElementById("pwa-hint-body");
|
const body = document.getElementById("pwa-hint-body");
|
||||||
const installBtn = document.getElementById("pwa-install-btn");
|
const installBtn = document.getElementById("pwa-install-btn");
|
||||||
const dismissBtn = document.getElementById("pwa-hint-dismiss");
|
const dismissBtn = hint.querySelector(".pwa-hint-dismiss");
|
||||||
|
|
||||||
const updateHintText = () => {
|
const updateHintText = () => {
|
||||||
const title = hint.querySelector("[data-i18n='pwa.hintTitle']");
|
const title = hint.querySelector("[data-i18n='pwa.hintTitle']");
|
||||||
if (title) title.textContent = t("pwa.hintTitle");
|
if (title) title.textContent = t("pwa.hintTitle");
|
||||||
if (body && !deferredPrompt) body.textContent = t(platformHintKey());
|
if (body && !deferredPrompt) body.textContent = t(platformHintKey());
|
||||||
if (body && deferredPrompt) body.textContent = t("pwa.hintBody");
|
if (body && deferredPrompt) body.textContent = t("pwa.hintBody");
|
||||||
if (dismissBtn) dismissBtn.title = t("actions.dismiss");
|
if (dismissBtn) dismissBtn.setAttribute("aria-label", t("actions.dismiss"));
|
||||||
if (installBtn && !installBtn.hidden) installBtn.textContent = t("pwa.install");
|
if (installBtn && !installBtn.hidden) installBtn.textContent = t("pwa.install");
|
||||||
};
|
};
|
||||||
|
|
||||||
hint.hidden = false;
|
showHint(hint);
|
||||||
updateHintText();
|
updateHintText();
|
||||||
|
|
||||||
window.addEventListener("beforeinstallprompt", (e) => {
|
window.addEventListener("beforeinstallprompt", (e) => {
|
||||||
@@ -73,11 +111,6 @@ const Pwa = (() => {
|
|||||||
updateHintText();
|
updateHintText();
|
||||||
});
|
});
|
||||||
|
|
||||||
dismissBtn?.addEventListener("click", () => {
|
|
||||||
localStorage.setItem(DISMISS_KEY, "1");
|
|
||||||
hint.hidden = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
installBtn?.addEventListener("click", async () => {
|
installBtn?.addEventListener("click", async () => {
|
||||||
if (!deferredPrompt) return;
|
if (!deferredPrompt) return;
|
||||||
deferredPrompt.prompt();
|
deferredPrompt.prompt();
|
||||||
@@ -86,7 +119,7 @@ const Pwa = (() => {
|
|||||||
installBtn.hidden = true;
|
installBtn.hidden = true;
|
||||||
if (outcome === "accepted") {
|
if (outcome === "accepted") {
|
||||||
if (body) body.textContent = t("pwa.installed");
|
if (body) body.textContent = t("pwa.installed");
|
||||||
setTimeout(() => { hint.hidden = true; }, 3000);
|
setTimeout(() => { hideHint(hint); }, 3000);
|
||||||
} else {
|
} else {
|
||||||
updateHintText();
|
updateHintText();
|
||||||
}
|
}
|
||||||
@@ -96,8 +129,8 @@ const Pwa = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await registerServiceWorker();
|
|
||||||
refreshInstallHint = setupInstallHint();
|
refreshInstallHint = setupInstallHint();
|
||||||
|
await registerServiceWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshHint() {
|
function refreshHint() {
|
||||||
|
|||||||
+22
-2
@@ -856,8 +856,9 @@ body.inv-chart-modal-open {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* PWA install hint */
|
/* PWA install hint */
|
||||||
.pwa-hint[hidden] {
|
.pwa-hint[hidden],
|
||||||
display: none;
|
.pwa-hint.is-dismissed {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pwa-hint {
|
.pwa-hint {
|
||||||
@@ -898,6 +899,25 @@ body.inv-chart-modal-open {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pwa-hint-dismiss {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-hint-dismiss:hover {
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
.landing-card .pwa-hint {
|
.landing-card .pwa-hint {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = "if-viewer-static-v4";
|
const CACHE = "if-viewer-static-v5";
|
||||||
const ASSETS = [
|
const ASSETS = [
|
||||||
"/static/style.css",
|
"/static/style.css",
|
||||||
"/static/favicon.svg",
|
"/static/favicon.svg",
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pwa-hint-actions">
|
<div class="pwa-hint-actions">
|
||||||
<button type="button" id="pwa-install-btn" class="viewer-copy-btn" hidden data-i18n="pwa.install">Install</button>
|
<button type="button" id="pwa-install-btn" class="viewer-copy-btn" hidden data-i18n="pwa.install">Install</button>
|
||||||
<button type="button" id="pwa-hint-dismiss" class="import-report-dismiss" data-i18n-title="actions.dismiss" title="Dismiss">×</button>
|
<button type="button" class="pwa-hint-dismiss" aria-label="Dismiss">×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user