commit de9ac74004f68318c682f0d87361fe32e96d84aa
parent fd20780c21372c1f8ea5fa2bb0b075204c18b198
Author: Hunter
Date: Sun, 2 Nov 2025 23:39:35 -0500
wip pwa support
Diffstat:
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