diff --git a/js/app.js b/js/app.js index 8119b5f..9f65dcf 100644 --- a/js/app.js +++ b/js/app.js @@ -545,7 +545,9 @@ document.addEventListener('DOMContentLoaded', async () => { window.addEventListener('beforeinstallprompt', (e) => { e.preventDefault(); deferredPrompt = e; - showInstallPrompt(deferredPrompt); + if (!localStorage.getItem('installPromptDismissed')) { + showInstallPrompt(deferredPrompt); + } }); if (!localStorage.getItem('shortcuts-shown')) { @@ -596,6 +598,7 @@ function showInstallPrompt(deferredPrompt) { document.getElementById('dismiss-install').addEventListener('click', () => { notification.remove(); + localStorage.setItem('installPromptDismissed', 'true'); }); } diff --git a/manifest.json b/manifest.json index 5453afc..f7588b4 100644 --- a/manifest.json +++ b/manifest.json @@ -4,6 +4,9 @@ "description": "A minimalist music streaming application", "start_url": "/", "display": "standalone", + "display_override": [ + "window-controls-overlay" + ], "background_color": "#000000", "theme_color": "#000000", "orientation": "portrait-primary", diff --git a/styles.css b/styles.css index b8f665b..04ea83e 100644 --- a/styles.css +++ b/styles.css @@ -2813,3 +2813,22 @@ input:checked + .slider::before { font-size: 4rem; } } + +/* Window Controls Overlay */ +@media (display-mode: window-controls-overlay) { + .app-container { + margin-top: env(titlebar-area-height, 0); + } + + .main-header { + -webkit-app-region: drag; + } + + .main-header * { + -webkit-app-region: no-drag; + } + + .sidebar { + padding-top: max(1.5rem, env(titlebar-area-height, 0)); + } +} diff --git a/sw.js b/sw.js index bf80be7..f4882e1 100644 --- a/sw.js +++ b/sw.js @@ -1,5 +1,6 @@ //sw.js -const CACHE_NAME = 'monochrome-v2'; +const CACHE_NAME = 'monochrome-v4'; +const IMAGE_CACHE_NAME = 'monochrome-images-v1'; const urlsToCache = [ '/', '/index.html', @@ -11,11 +12,20 @@ const urlsToCache = [ '/js/ui.js', '/js/utils.js', '/js/cache.js', - '/manifest.json' + '/js/router.js', + '/js/events.js', + '/js/ui-interactions.js', + '/js/settings.js', + '/js/lastfm.js', + '/js/lyrics.js', + '/js/downloads.js', + '/manifest.json', + '/assets/logo.svg', + '/assets/appicon.png' ]; self.addEventListener('install', event => { - self.skipWaiting(); // Force activation + self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(urlsToCache)) @@ -23,19 +33,40 @@ self.addEventListener('install', event => { }); self.addEventListener('fetch', event => { + const url = new URL(event.request.url); + + // Cache Images (Cache-First) + if (url.hostname === 'resources.tidal.com' || url.hostname === 'picsum.photos') { + event.respondWith( + caches.open(IMAGE_CACHE_NAME).then(cache => { + return cache.match(event.request).then(response => { + return response || fetch(event.request).then(networkResponse => { + if (networkResponse.ok) { + cache.put(event.request, networkResponse.clone()); + } + return networkResponse; + }); + }); + }) + ); + return; + } + + // Static Assets & App Shell (Cache-First) event.respondWith( caches.match(event.request) - .then(response => response || fetch(event.request)) - .catch(() => { - // Return 404 or handle offline fallback here if needed - // For now, just ensuring the promise doesn't reject uncaught - return new Response('Network error', { status: 408 }); + .then(response => { + // Return cached response if found + if (response) return response; + + // Otherwise fetch from network + return fetch(event.request); }) ); }); self.addEventListener('activate', event => { - const cacheWhitelist = [CACHE_NAME]; + const cacheWhitelist = [CACHE_NAME, IMAGE_CACHE_NAME]; event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( @@ -44,7 +75,7 @@ self.addEventListener('activate', event => { return caches.delete(cacheName); } }) - ).then(() => self.clients.claim()); // Take control immediately + ).then(() => self.clients.claim()); }) ); }); \ No newline at end of file