commit de9ac74004f68318c682f0d87361fe32e96d84aa
parent fd20780c21372c1f8ea5fa2bb0b075204c18b198
Author: Hunter
Date:   Sun,  2 Nov 2025 23:39:35 -0500

wip pwa support

Diffstat:
M.gitignore | 8++++++++
Agenerate_manifests.py | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhost.py | 4+---
Mindex.html | 6++++++
Aresources/icon.png | 0
Mscript.js | 15++++++++++++++-
6 files changed, 213 insertions(+), 4 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -22,3 +22,11 @@ ideas.md *.swp *.swo *~ + +# auto-generated PWA files +manifest.json +resource-manifest.json +service-worker.js + +# Deployment script (personal) +push.py diff --git a/generate_manifests.py b/generate_manifests.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +Creates manifest.json, resource-manifest.json, and service-worker.js +based on the contents of tracks.json +""" + +import json +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.absolute() +TRACKS_JSON = SCRIPT_DIR / "tracks.json" + + +def generate_pwa_manifests(): + """Generate PWA manifest files based on tracks.json""" + print("Generating PWA manifests...") + + # Load tracks.json + if not TRACKS_JSON.exists(): + print("Error: tracks.json not found. Run scan.py first.") + return + + with open(TRACKS_JSON, 'r', encoding='utf-8') as f: + tracks = json.load(f) + + # Generate manifest.json + manifest = { + "id": "/vibe_capsule/", + "name": "vibe capsule", + "short_name": "vibe capsule", + "description": "mixtape as artifact", + "start_url": "/vibe_capsule/", + "scope": "/vibe_capsule/", + "display": "standalone", + "background_color": "#1a1a1a", + "theme_color": "#1a1a1a", + "icons": [ + { + "src": "resources/icon.png", + "sizes": "640x640", + "type": "image/png", + "purpose": "any maskable" + } + ] + } + + with open(SCRIPT_DIR / "manifest.json", 'w', encoding='utf-8') as f: + json.dump(manifest, f, indent=2) + print("✓ Generated manifest.json") + + # Generate resource-manifest.json + resource_manifest = { + "static_files": [ + "./", + "index.html", + "styles.css", + "script.js", + "tracks.json", + "resources/icon.png", + "resources/play.png", + "resources/pause.png", + "resources/prev.png", + "resources/next.png", + "resources/album_art.jpg" + ], + "tracks": [f"tracks/{track['filename']}" for track in tracks] + } + + with open(SCRIPT_DIR / "resource-manifest.json", 'w', encoding='utf-8') as f: + json.dump(resource_manifest, f, indent=2) + print("✓ Generated resource-manifest.json") + + # Generate service-worker.js + all_files = resource_manifest["static_files"] + resource_manifest["tracks"] + service_worker_content = f'''// Auto-generated service worker for vibe capsule PWA +const CACHE_NAME = 'vibe-capsule-v3'; +const urlsToCache = {json.dumps(all_files, indent=2)}; + +// Get the base path from the service worker location +const getBasePath = () => {{ + const swPath = self.location.pathname; + return swPath.substring(0, swPath.lastIndexOf('/') + 1); +}}; + +const basePath = getBasePath(); + +// Install event - cache all resources +self.addEventListener('install', (event) => {{ + console.log('Service Worker installing...', 'Base path:', basePath); + event.waitUntil( + caches.open(CACHE_NAME) + .then((cache) => {{ + console.log('Opened cache'); + // Make URLs absolute relative to service worker location + const absoluteUrls = urlsToCache.map(url => {{ + if (url === './') return basePath; + return new URL(url, basePath + 'index.html').href; + }}); + console.log('Caching', absoluteUrls.length, 'resources'); + console.log('URLs to cache:', absoluteUrls); + + // Cache files one by one with better error handling + return Promise.all( + absoluteUrls.map(url => + cache.add(url) + .then(() => console.log('✓ Cached:', url)) + .catch(err => console.error('✗ Failed to cache:', url, err)) + ) + ); + }}) + .then(() => {{ + console.log('All resources cached successfully'); + return self.skipWaiting(); + }}) + .catch(error => {{ + console.error('Service Worker installation failed:', error); + }}) + ); +}}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => {{ + console.log('Service Worker activating...'); + event.waitUntil( + caches.keys().then((cacheNames) => {{ + return Promise.all( + cacheNames.map((cacheName) => {{ + if (cacheName !== CACHE_NAME) {{ + console.log('Deleting old cache:', cacheName); + return caches.delete(cacheName); + }} + }}) + ); + }}).then(() => self.clients.claim()) + ); +}}); + +// Fetch event - serve from cache, fallback to network +self.addEventListener('fetch', (event) => {{ + event.respondWith( + caches.match(event.request) + .then((response) => {{ + // Cache hit - return response + if (response) {{ + console.log('Serving from cache:', event.request.url); + return response; + }} + + // Cache miss - try network + console.log('Fetching from network:', event.request.url); + return fetch(event.request).then((response) => {{ + // Check if valid response + if (!response || response.status !== 200) {{ + return response; + }} + + // Clone the response for caching + const responseToCache = response.clone(); + + caches.open(CACHE_NAME) + .then((cache) => {{ + cache.put(event.request, responseToCache); + }}); + + return response; + }}).catch((error) => {{ + console.error('Fetch failed; returning offline page if available:', error); + // If fetch fails, try to return from cache one more time + return caches.match(event.request); + }}); + }}) + ); +}}); +''' + + with open(SCRIPT_DIR / "service-worker.js", 'w', encoding='utf-8') as f: + f.write(service_worker_content) + print("✓ Generated service-worker.js") + print() + print("PWA manifests generated successfully!") + + +if __name__ == "__main__": + generate_pwa_manifests() diff --git a/host.py b/host.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 """ -Simple HTTP Server for vibe capsule MP3 player -Starts server and opens browser automatically +Starts HTTP server for local testing Automatically manages a virtual environment for dependencies """ import http.server import socketserver -import webbrowser import socket import sys import os diff --git a/index.html b/index.html @@ -4,6 +4,12 @@ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>💿</text></svg>"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <base href="/vibe_capsule/"> + <meta name="theme-color" content="#1a1a1a"> + <meta name="apple-mobile-web-app-capable" content="yes"> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> + <link rel="apple-touch-icon" href="resources/icon.png"> + <link rel="manifest" href="manifest.json"> <title>vibe capsule</title> <link rel="stylesheet" href="styles.css"> </head> diff --git a/resources/icon.png b/resources/icon.png Binary files differ. diff --git a/script.js b/script.js @@ -1,3 +1,16 @@ +// Register service worker for PWA functionality +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('service-worker.js') + .then(registration => { + console.log('Service Worker registered successfully:', registration.scope); + }) + .catch(error => { + console.log('Service Worker registration failed:', error); + }); + }); +} + const playPauseBtn = document.getElementById('playPause'); const prevBtn = document.getElementById('prev'); const nextBtn = document.getElementById('next'); @@ -56,7 +69,7 @@ fetch('tracks.json') }) .catch(error => { console.error('Error loading tracks:', error); - updateCurrentSongDisplay('Error: tracks.json not found. Run scan.py first.'); + updateCurrentSongDisplay('Unable to load tracks. Please check your connection.'); }); // Audio event listeners