| [7bc636b] | 1 | /**
|
|---|
| 2 | * install-app.js — handles the "Installeer app" button in the footer.
|
|---|
| 3 | *
|
|---|
| 4 | * Behavior:
|
|---|
| 5 | * - Hides itself if the user is already running the site as an installed PWA
|
|---|
| 6 | * (display-mode: standalone) or iOS web-app.
|
|---|
| 7 | * - On click, opens a modal with platform-specific instructions:
|
|---|
| 8 | * iOS → "Voeg toe aan beginscherm" via Safari share sheet
|
|---|
| 9 | * Android → choice between PWA install (1-tap if available) and APK download
|
|---|
| 10 | * Desktop → instructions for Chrome/Edge install icon, or native prompt if available
|
|---|
| 11 | * - Listens for `beforeinstallprompt` (Android Chrome / desktop Chromium) and
|
|---|
| 12 | * deferreds it so the modal's "Install as PWA" button can fire it.
|
|---|
| 13 | *
|
|---|
| 14 | * Markup contract: <button data-pcms-install-app data-apk-url="/path/to.apk">
|
|---|
| 15 | *
|
|---|
| 16 | * CSS: .install-app-btn / .install-modal* / .install-choice* — defined in
|
|---|
| 17 | * style.css (carried over from v9 import).
|
|---|
| 18 | */
|
|---|
| 19 | (function() {
|
|---|
| 20 | 'use strict';
|
|---|
| 21 | const btn = document.querySelector('[data-pcms-install-app]');
|
|---|
| 22 | if (!btn) return;
|
|---|
| 23 |
|
|---|
| 24 | // Already running standalone → no point showing the button.
|
|---|
| 25 | const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|
|---|
| 26 | || window.navigator.standalone === true;
|
|---|
| 27 | if (isStandalone) { btn.style.display = 'none'; return; }
|
|---|
| 28 |
|
|---|
| 29 | const ua = navigator.userAgent;
|
|---|
| 30 | const isIOS = /iPad|iPhone|iPod/.test(ua) && !window.MSStream;
|
|---|
| 31 | const isAndroid = /Android/.test(ua);
|
|---|
| 32 | const apkUrl = btn.dataset.apkUrl || '';
|
|---|
| 33 |
|
|---|
| 34 | // Capture Chrome/Edge native install prompt so we can fire it from our modal.
|
|---|
| 35 | let deferredPrompt = null;
|
|---|
| 36 | window.addEventListener('beforeinstallprompt', (e) => {
|
|---|
| 37 | e.preventDefault();
|
|---|
| 38 | deferredPrompt = e;
|
|---|
| 39 | btn.classList.add('has-native-prompt');
|
|---|
| 40 | });
|
|---|
| 41 | window.addEventListener('appinstalled', () => {
|
|---|
| 42 | deferredPrompt = null;
|
|---|
| 43 | btn.style.display = 'none';
|
|---|
| 44 | });
|
|---|
| 45 |
|
|---|
| 46 | function showModal(innerHTML) {
|
|---|
| 47 | const m = document.createElement('div');
|
|---|
| 48 | m.className = 'install-modal';
|
|---|
| 49 | m.innerHTML = `
|
|---|
| 50 | <div class="install-modal-backdrop"></div>
|
|---|
| 51 | <div class="install-modal-panel" role="dialog" aria-labelledby="install-title" aria-modal="true">
|
|---|
| 52 | ${innerHTML}
|
|---|
| 53 | <div class="install-modal-actions">
|
|---|
| 54 | <button type="button" class="btn btn-ghost" data-close>Sluiten</button>
|
|---|
| 55 | </div>
|
|---|
| 56 | </div>
|
|---|
| 57 | `;
|
|---|
| 58 | document.body.appendChild(m);
|
|---|
| 59 | const close = () => m.remove();
|
|---|
| 60 | m.querySelector('.install-modal-backdrop').addEventListener('click', close);
|
|---|
| 61 | m.querySelector('[data-close]').addEventListener('click', close);
|
|---|
| 62 | document.addEventListener('keydown', function onEsc(e) {
|
|---|
| 63 | if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onEsc); }
|
|---|
| 64 | });
|
|---|
| 65 |
|
|---|
| 66 | // If a [data-install-pwa] button is in the modal AND a deferredPrompt is
|
|---|
| 67 | // available: clicking triggers the native install dialog.
|
|---|
| 68 | const pwaBtn = m.querySelector('[data-install-pwa]');
|
|---|
| 69 | if (pwaBtn) {
|
|---|
| 70 | pwaBtn.addEventListener('click', () => {
|
|---|
| 71 | if (!deferredPrompt) { close(); return; }
|
|---|
| 72 | deferredPrompt.prompt();
|
|---|
| 73 | deferredPrompt.userChoice.finally(() => {
|
|---|
| 74 | deferredPrompt = null;
|
|---|
| 75 | close();
|
|---|
| 76 | });
|
|---|
| 77 | });
|
|---|
| 78 | }
|
|---|
| 79 | }
|
|---|
| 80 |
|
|---|
| 81 | function iosInstructions() {
|
|---|
| 82 | showModal(`
|
|---|
| 83 | <h3 id="install-title" style="margin:0 0 .5rem">📲 Installeer op iOS</h3>
|
|---|
| 84 | <p style="margin:0 0 1rem;opacity:.8">Om deze site als app op je home-scherm te zetten:</p>
|
|---|
| 85 | <ol class="install-steps">
|
|---|
| 86 | <li>Tik op het <strong>deel-icoon</strong>
|
|---|
| 87 | <svg width="16" height="16" viewBox="0 0 24 24" style="vertical-align:middle;margin:0 2px"><path d="M12 2L8 6h3v9h2V6h3l-4-4zm7 10v8H5v-8H3v8a2 2 0 002 2h14a2 2 0 002-2v-8h-2z" fill="currentColor"/></svg>
|
|---|
| 88 | onderaan in Safari
|
|---|
| 89 | </li>
|
|---|
| 90 | <li>Scroll tot je <strong>"Voeg toe aan beginscherm"</strong> ziet (soms achter "Meer…")</li>
|
|---|
| 91 | <li>Geef de app eventueel een naam en tik op <strong>Voeg toe</strong></li>
|
|---|
| 92 | </ol>
|
|---|
| 93 | <p style="margin:1rem 0 0;opacity:.7;font-size:.85rem">⚠️ Werkt alleen in Safari — niet in Chrome op iOS (Apple-beleid).</p>
|
|---|
| 94 | `);
|
|---|
| 95 | }
|
|---|
| 96 |
|
|---|
| 97 | function androidInstructions() {
|
|---|
| 98 | const nativeLine = deferredPrompt
|
|---|
| 99 | ? `<button type="button" class="install-choice-cta" data-install-pwa>📱 Installeer als PWA (1 tik)</button>`
|
|---|
| 100 | : `<p class="install-choice-note">Open deze site in <strong>Chrome</strong> en kies in het <strong>⋮-menu → "App installeren"</strong> (of "Aan beginscherm toevoegen").</p>`;
|
|---|
| 101 |
|
|---|
| 102 | const apkBlock = apkUrl ? `
|
|---|
| 103 | <div class="install-choice install-choice-apk">
|
|---|
| 104 | <h4>🤖 Optie B — APK (native Android-app)</h4>
|
|---|
| 105 | <div class="install-pros-cons">
|
|---|
| 106 | <div class="install-pros">
|
|---|
| 107 | <strong>Voordelen</strong>
|
|---|
| 108 | <ul>
|
|---|
| 109 | <li>Echte app in app-lade, naast WhatsApp/Gmail</li>
|
|---|
| 110 | <li>Beste achtergrond-audio ondersteuning</li>
|
|---|
| 111 | <li>Lock-screen controls werken optimaal</li>
|
|---|
| 112 | <li>Blijft geïnstalleerd ongeacht browser-cache</li>
|
|---|
| 113 | </ul>
|
|---|
| 114 | </div>
|
|---|
| 115 | <div class="install-cons">
|
|---|
| 116 | <strong>Nadelen</strong>
|
|---|
| 117 | <ul>
|
|---|
| 118 | <li>Moet eenmalig "onbekende bronnen" toestaan</li>
|
|---|
| 119 | <li>Geen auto-updates — nieuwe versie = APK opnieuw downloaden</li>
|
|---|
| 120 | <li>Browser toont beveiligings-waarschuwing bij install</li>
|
|---|
| 121 | <li>Alleen Android (Android 5+ / API 21+)</li>
|
|---|
| 122 | </ul>
|
|---|
| 123 | </div>
|
|---|
| 124 | </div>
|
|---|
| 125 | <a href="${apkUrl}" class="install-choice-cta" download>
|
|---|
| 126 | <svg width="18" height="18" viewBox="0 0 24 24" style="vertical-align:middle;margin-right:.35rem"><path d="M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z" fill="currentColor"/></svg>
|
|---|
| 127 | Download APK
|
|---|
| 128 | </a>
|
|---|
| 129 | <ol class="install-steps" style="margin-top:.75rem">
|
|---|
| 130 | <li>Tik op de gedownloade APK</li>
|
|---|
| 131 | <li>Sta eenmalig <strong>"installeren van onbekende bronnen"</strong> toe voor je browser</li>
|
|---|
| 132 | <li>Tik <strong>Installeer</strong> in de popup</li>
|
|---|
| 133 | <li>Open de app vanuit je app-lade</li>
|
|---|
| 134 | </ol>
|
|---|
| 135 | </div>
|
|---|
| 136 | ` : '';
|
|---|
| 137 |
|
|---|
| 138 | showModal(`
|
|---|
| 139 | <h3 id="install-title" style="margin:0 0 .5rem">🤖 Installeer op Android</h3>
|
|---|
| 140 | <p style="margin:0 0 1.25rem;opacity:.8">Twee manieren. Kies wat bij je past:</p>
|
|---|
| 141 |
|
|---|
| 142 | <div class="install-choice install-choice-pwa">
|
|---|
| 143 | <h4>📱 Optie A — Home-screen shortcut (PWA)</h4>
|
|---|
| 144 | <div class="install-pros-cons">
|
|---|
| 145 | <div class="install-pros">
|
|---|
| 146 | <strong>Voordelen</strong>
|
|---|
| 147 | <ul>
|
|---|
| 148 | <li>Instant — geen download of toestemmingen</li>
|
|---|
| 149 | <li>Updates automatisch bij elke site-wijziging</li>
|
|---|
| 150 | <li>Geen beveiligings-waarschuwing</li>
|
|---|
| 151 | <li>Minder opslagruimte op je telefoon</li>
|
|---|
| 152 | </ul>
|
|---|
| 153 | </div>
|
|---|
| 154 | <div class="install-cons">
|
|---|
| 155 | <strong>Nadelen</strong>
|
|---|
| 156 | <ul>
|
|---|
| 157 | <li>Vereist Chrome als browser</li>
|
|---|
| 158 | <li>Audio-in-achtergrond iets minder robuust dan native</li>
|
|---|
| 159 | <li>Kan verdwijnen als je browser-data wist</li>
|
|---|
| 160 | </ul>
|
|---|
| 161 | </div>
|
|---|
| 162 | </div>
|
|---|
| 163 | ${nativeLine}
|
|---|
| 164 | </div>
|
|---|
| 165 |
|
|---|
| 166 | ${apkBlock}
|
|---|
| 167 |
|
|---|
| 168 | <p style="margin:1rem 0 0;opacity:.65;font-size:.82rem">💡 Weet je niet wat te kiezen? Begin met <strong>Optie A (shortcut)</strong>. Te beperkt? Ga dan voor de APK.</p>
|
|---|
| 169 | `);
|
|---|
| 170 | }
|
|---|
| 171 |
|
|---|
| 172 | function desktopInstructions() {
|
|---|
| 173 | if (deferredPrompt) {
|
|---|
| 174 | deferredPrompt.prompt();
|
|---|
| 175 | deferredPrompt.userChoice.finally(() => { deferredPrompt = null; });
|
|---|
| 176 | return;
|
|---|
| 177 | }
|
|---|
| 178 | showModal(`
|
|---|
| 179 | <h3 id="install-title" style="margin:0 0 .5rem">💻 Installeer op desktop</h3>
|
|---|
| 180 | <p style="margin:0 0 1rem;opacity:.8">In Chrome, Edge of een andere Chromium-browser:</p>
|
|---|
| 181 | <ol class="install-steps">
|
|---|
| 182 | <li>Zoek het <strong>installeer-icoon</strong>
|
|---|
| 183 | <svg width="16" height="16" viewBox="0 0 24 24" style="vertical-align:middle;margin:0 2px"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" fill="currentColor"/></svg>
|
|---|
| 184 | in de adresbalk</li>
|
|---|
| 185 | <li>Klik erop en bevestig</li>
|
|---|
| 186 | </ol>
|
|---|
| 187 | <p style="margin:1rem 0 0;opacity:.7;font-size:.85rem">Firefox/Safari desktop ondersteunen PWA-install niet — gebruik een bookmark.</p>
|
|---|
| 188 | `);
|
|---|
| 189 | }
|
|---|
| 190 |
|
|---|
| 191 | btn.addEventListener('click', () => {
|
|---|
| 192 | if (isIOS) return iosInstructions();
|
|---|
| 193 | if (isAndroid) return androidInstructions();
|
|---|
| 194 | return desktopInstructions();
|
|---|
| 195 | });
|
|---|
| 196 | })();
|
|---|