web: make the studio installable, and give it a shell that opens offline
The three things a browser asks for, without a plugin: a manifest in
public/ (name, /app as the start, three icons cut from the one piece of
art this repo has), a service worker, and the two metas iOS reads
instead of the manifest.
The worker caches the shell — /, /app, /library, all one document under
the SPA fallback — and the hashed assets the build emits. A navigation
is network-first, so a deploy is never pinned behind the cache; a
hashed asset or the wasm is cache-first, because under a given build
those never change. /api and any non-GET go straight out: a worker is a
cache, not a proxy. nginx serves sw.js and manifest.json `no-cache`
(both names outlive their contents) with the isolation headers the
worker script needs under COEP.
The offer is the app's own dialog, not Chromium's mini-infobar: the
event is held, and it is spent either after the visitor has been in the
studio two minutes or the moment an export lands — the point at which
the app has done their work. Safari never fires the event, so it gets
the Share > Add to Home Screen line instead. A refusal is remembered and
never asked again.
node scripts/make-icons.mjs 192x192 39785B / 512x512 159296B / maskable 512x512 123723B
node scripts/pwa-check.mjs manifest 3 icons · worker activated · shell cached
· offline reload of /app paints
off (https://localhost:8090) same four, through nginx
This commit is contained in:
@@ -10,11 +10,14 @@ import { Workspace } from './App';
|
||||
import { Admin } from './Admin';
|
||||
import { ProfilePage } from './ProfilePage';
|
||||
import { PhotosPage } from './PhotosPage';
|
||||
import { Library } from './Library';
|
||||
import { InstallPrompt } from './ui/InstallPrompt';
|
||||
import { installTracking } from './track';
|
||||
|
||||
// Five routes, no router: the landing page, the workspace, the strip moderation
|
||||
// screen, the member's own profile and the member's photo folder. nginx serves
|
||||
// index.html for all of them (SPA fallback), so this is a pathname check.
|
||||
// Six routes, no router: the landing page, the workspace, the strip moderation
|
||||
// screen, the member's own profile, the member's photo folder and the local
|
||||
// catalogue of folders on the visitor's own disk. nginx serves index.html for
|
||||
// all of them (SPA fallback), so this is a pathname check.
|
||||
const path = window.location.pathname;
|
||||
const page = path.startsWith('/app') ? (
|
||||
<Workspace />
|
||||
@@ -22,6 +25,8 @@ const page = path.startsWith('/app') ? (
|
||||
<Admin />
|
||||
) : path.startsWith('/photos') ? (
|
||||
<PhotosPage />
|
||||
) : path.startsWith('/library') ? (
|
||||
<Library />
|
||||
) : path.startsWith('/profile') ? (
|
||||
<ProfilePage />
|
||||
) : (
|
||||
@@ -35,9 +40,24 @@ if (!root) throw new Error('#root missing');
|
||||
installTracking();
|
||||
|
||||
// No StrictMode: it double-invokes effects, which would load CanvasKit twice and
|
||||
// run the render pipeline twice on every mount.
|
||||
// run the render pipeline twice on every mount. The install offer hangs off the
|
||||
// i18n provider because its copy comes from the dictionaries.
|
||||
createRoot(root).render(
|
||||
<ThemeProvider>
|
||||
<I18nProvider>{page}</I18nProvider>
|
||||
<I18nProvider>
|
||||
{page}
|
||||
<InstallPrompt />
|
||||
</I18nProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
// Service worker, production only: in dev it would answer the browser with the
|
||||
// last build's shell instead of vite's modules. Registered after `load` so its
|
||||
// precache does not race the studio's own 20MB of wasm for the connection, and a
|
||||
// browser that refuses it (private mode, a plain-http origin) just loses the
|
||||
// offline shell — nothing else depends on it.
|
||||
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js').catch(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user