mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-17 01:58:33 +02:00
feat: make Grimoire installable as a PWA
- Created web manifest (site.webmanifest) with app metadata - Added app name, description, theme colors - Configured standalone display mode for native-like experience - Included all required icon sizes (192x192, 512x512) - Added keyboard shortcut for command palette - Generated 192x192 icon for PWA requirements - Added manifest and theme-color meta tags to index.html - Implemented service worker (sw.js) for offline functionality - Network-first caching strategy for optimal performance - Precaches core assets on install - Provides offline fallback for navigation requests - Registered service worker in main.tsx Users can now install Grimoire as a standalone app on desktop and mobile devices.
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<meta name="theme-color" content="#b366ff" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
||||
BIN
public/favicon-192x192.png
Normal file
BIN
public/favicon-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
49
public/site.webmanifest
Normal file
49
public/site.webmanifest
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "Grimoire - A Nostr Client for Magicians",
|
||||
"short_name": "Grimoire",
|
||||
"description": "A tiling window manager interface for exploring the Nostr protocol. Each window is a Nostr app (profile viewer, event feed, NIP documentation, etc.). Commands are launched Unix-style via Cmd+K palette.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#020817",
|
||||
"theme_color": "#b366ff",
|
||||
"orientation": "any",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-16x16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-32x32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"categories": ["social", "utilities", "productivity"],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Command Palette",
|
||||
"short_name": "Commands",
|
||||
"description": "Open the command palette to launch Nostr apps",
|
||||
"url": "/?action=command-palette"
|
||||
}
|
||||
]
|
||||
}
|
||||
84
public/sw.js
Normal file
84
public/sw.js
Normal file
@@ -0,0 +1,84 @@
|
||||
// Grimoire Service Worker - v1.0.0
|
||||
const CACHE_NAME = "grimoire-v1";
|
||||
const RUNTIME_CACHE = "grimoire-runtime";
|
||||
|
||||
// Core assets to cache on install
|
||||
const PRECACHE_URLS = [
|
||||
"/",
|
||||
"/index.html",
|
||||
"/favicon.ico",
|
||||
"/favicon-192x192.png",
|
||||
"/favicon-512x512.png",
|
||||
];
|
||||
|
||||
// Install event - precache core assets
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => {
|
||||
return cache.addAll(PRECACHE_URLS);
|
||||
}),
|
||||
);
|
||||
// Activate immediately
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames
|
||||
.filter((name) => name !== CACHE_NAME && name !== RUNTIME_CACHE)
|
||||
.map((name) => caches.delete(name)),
|
||||
);
|
||||
}),
|
||||
);
|
||||
// Take control immediately
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
// Fetch event - network first, fallback to cache
|
||||
self.addEventListener("fetch", (event) => {
|
||||
// Skip non-GET requests
|
||||
if (event.request.method !== "GET") return;
|
||||
|
||||
// Skip cross-origin requests
|
||||
if (!event.request.url.startsWith(self.location.origin)) return;
|
||||
|
||||
// Network first strategy for app shell and assets
|
||||
event.respondWith(
|
||||
fetch(event.request)
|
||||
.then((response) => {
|
||||
// Clone the response before caching
|
||||
const responseClone = response.clone();
|
||||
|
||||
// Cache successful responses
|
||||
if (response.status === 200) {
|
||||
caches.open(RUNTIME_CACHE).then((cache) => {
|
||||
cache.put(event.request, responseClone);
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
})
|
||||
.catch(() => {
|
||||
// Fallback to cache if network fails
|
||||
return caches.match(event.request).then((cachedResponse) => {
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// If no cache, return offline page for navigation requests
|
||||
if (event.request.mode === "navigate") {
|
||||
return caches.match("/index.html");
|
||||
}
|
||||
|
||||
// For other requests, just fail
|
||||
return new Response("Offline", {
|
||||
status: 503,
|
||||
statusText: "Service Unavailable",
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
14
src/main.tsx
14
src/main.tsx
@@ -35,3 +35,17 @@ createRoot(document.getElementById("root")!).render(
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
// Register service worker for PWA functionality
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker
|
||||
.register("/sw.js")
|
||||
.then((registration) => {
|
||||
console.log("SW registered:", registration);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("SW registration failed:", error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user