diff --git a/site/Caddyfile b/site/Caddyfile index a36626b..5d09ecb 100644 --- a/site/Caddyfile +++ b/site/Caddyfile @@ -8,6 +8,10 @@ # Compression encode gzip + # Service worker must not be cached + @sw path /sw.js + header @sw Cache-Control "no-cache, no-store, must-revalidate" + # Cache static assets @static { path *.jpg *.jpeg *.png *.gif *.ico *.css *.js *.pdf *.svg *.woff *.woff2 diff --git a/site/package.json b/site/package.json index d9bd10f..c77f213 100644 --- a/site/package.json +++ b/site/package.json @@ -22,7 +22,6 @@ "lucide-react": "^0.562.0", "react": "^19.2.3", "react-dom": "^19.2.3", - "react-pdf": "^10.3.0", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.18" }, diff --git a/site/public/icons/apple-touch-icon.png b/site/public/icons/apple-touch-icon.png new file mode 100644 index 0000000..6d3d115 Binary files /dev/null and b/site/public/icons/apple-touch-icon.png differ diff --git a/site/public/icons/icon-192.png b/site/public/icons/icon-192.png new file mode 100644 index 0000000..ecffad7 Binary files /dev/null and b/site/public/icons/icon-192.png differ diff --git a/site/public/icons/icon-512-maskable.png b/site/public/icons/icon-512-maskable.png new file mode 100644 index 0000000..c1533f3 Binary files /dev/null and b/site/public/icons/icon-512-maskable.png differ diff --git a/site/public/icons/icon-512.png b/site/public/icons/icon-512.png new file mode 100644 index 0000000..f47edb8 Binary files /dev/null and b/site/public/icons/icon-512.png differ diff --git a/site/public/manifest.json b/site/public/manifest.json new file mode 100644 index 0000000..5f064e4 --- /dev/null +++ b/site/public/manifest.json @@ -0,0 +1,32 @@ +{ + "name": "Electronics Reference Library", + "short_name": "Mims Library", + "description": "Classic electronics reference notebooks from Forrest M. Mims III", + "start_url": "/", + "display": "standalone", + "background_color": "#f7f3ee", + "theme_color": "#3b5998", + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "/icons/icon-512-maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/favicon.svg", + "sizes": "any", + "type": "image/svg+xml" + } + ] +} diff --git a/site/public/offline.html b/site/public/offline.html new file mode 100644 index 0000000..b63cf50 --- /dev/null +++ b/site/public/offline.html @@ -0,0 +1,66 @@ + + +
+ + +Previously viewed pages and downloaded PDFs may still be available. Check your connection and try again.
+ + + diff --git a/site/public/sw.js b/site/public/sw.js new file mode 100644 index 0000000..84f4f24 --- /dev/null +++ b/site/public/sw.js @@ -0,0 +1,75 @@ +const CACHE_NAME = 'mims-library-v1'; +const PRECACHE_URLS = [ + '/', + '/offline.html', + '/favicon.svg', + '/manifest.json' +]; + +// Install: precache essential resources +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)) + ); + self.skipWaiting(); +}); + +// Activate: clean old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))) + ) + ); + self.clients.claim(); +}); + +// Fetch: tiered caching strategy +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // Only handle same-origin + if (url.origin !== location.origin) return; + + // Static assets: cache-first + if (/\.(css|js|jpg|jpeg|png|gif|svg|woff|woff2|ico)$/.test(url.pathname)) { + event.respondWith( + caches.match(request).then((cached) => + cached || fetch(request).then((response) => { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); + return response; + }) + ) + ); + return; + } + + // PDFs: network-first, cache on success + if (/\.pdf$/.test(url.pathname)) { + event.respondWith( + fetch(request) + .then((response) => { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); + return response; + }) + .catch(() => caches.match(request)) + ); + return; + } + + // HTML pages: network-first with offline fallback + event.respondWith( + fetch(request) + .then((response) => { + const clone = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); + return response; + }) + .catch(() => + caches.match(request).then((cached) => cached || caches.match('/offline.html')) + ) + ); +}); diff --git a/site/src/components/PdfViewer.tsx b/site/src/components/PdfViewer.tsx deleted file mode 100644 index edcc3c7..0000000 --- a/site/src/components/PdfViewer.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useState } from 'react'; -import { Button } from '@/components/ui/button'; - -interface PdfViewerProps { - pdfUrl: string; -} - -export default function PdfViewer({ pdfUrl }: PdfViewerProps) { - const [isFullscreen, setIsFullscreen] = useState(false); - - return ( -- PDF not displaying? Try{' '} - - opening directly - {' '} - or{' '} - - downloading the file - . -
-