Commit a1c7e9c
committed
feat(web): PWA support with offline area downloads
Lay the groundwork for using OpenMapX as an installed app on a phone.
Touches the manifest, service worker, an offline fallback page, and
adds a Settings surface for downloading map areas to use without a
network. None of this existed before; the existing serwist setup only
ran-time-cached the assets the user happened to fetch.
What's added
- Web app manifest (`app/manifest.ts`) with icons, shortcuts,
share_target (`/share`), `protocol_handlers` for `geo:`/`web+maps:`,
and `launch_handler: focus-existing`. Placeholder icon set in
`public/icons/app/` (replace with branded artwork later).
- Viewport / themeColor / apple-web-app metadata in `layout.tsx` and
`--omx-safe-*` CSS variables wired into every floating chrome
element so the UI doesn't get eaten by notches in standalone mode.
- An /offline page served as the SW navigation fallback.
- A /settings/offline page where the user picks a bbox + zoom range
on an embedded MapLibre instance and downloads the tiles, sprite,
glyphs, TileJSON, and style JSON for both light and dark variants
into a per-area Cache Storage entry. Covered styles are deduped
across variants so theme switches don't double-download.
- A Storage dialog (from the hamburger menu) showing
`navigator.storage.estimate()` and per-cache sizes, plus a "Clear
cached tiles" action that preserves user-pinned offline areas.
- An "Install OpenMapX" entry that captures `beforeinstallprompt`
(listener attached at module load, not in the Drawer subtree, so
the event isn't lost). On iOS the entry opens an Add-to-Home-Screen
hint dialog rendered as a sibling of the Drawer so the dialog
survives the drawer's unmount.
- A `SwUpdateNotice` Snackbar wired to serwist's `waiting` event;
reload only fires on `controlling` events whose `isUpdate` is true,
so first installs don't surprise the user with a reload.
- A Web Share Target endpoint (`/share`) that extracts a query from
`text` / `url` / `title` (treating blanks as missing) and 303's
back to `/?q=…`. ShareIntentHandler consumes `?q`, `?geo`, and
`?action`, then strips them from the URL. Re-share of the same URL
is processed correctly — the dedup signature is reset whenever the
params disappear.
Service worker
- `withOfflineFirst(strategy)` wrapper: any handler that opted in
consults user-downloaded `offline-area-*` caches before falling
through to the runtime strategy. Applied to MapTiler tiles,
self-hosted vector tiles (.pbf), and a new `/styles/*` rule that
also covers self-hosted style JSON / sprite / TileJSON for
`NEXT_PUBLIC_MAP_STYLE_URL` deployments.
- Auth deny rule (`/api/auth/*` → `fetch(request)` passthrough) so
the SW never caches credential-bearing responses.
- The app-shell cache (`app-shell-v1`) now precaches `/`, `/offline`,
and `/manifest.webmanifest`. The navigation handler tries the
exact URL in app-shell before falling back to /offline, so users
with downloaded areas reach the map even after the runtime `pages`
cache has expired.
- `setLocaleAndReload` refreshes the cached `/` and `/offline`
responses in-place after a language switch (the cookie is set
first, so the responses are localized) instead of wiping the cache.
- `skipWaiting: false`; the page posts a `SKIP_WAITING` message when
the user accepts the update Snackbar.
- The downloader bails on `QuotaExceededError` and `TypeError`
(network drop) instead of swallowing them, so a half-downloaded
area no longer falsely reports "ready".
Build / deploy
- Custom esbuild path for `src/sw.ts` is kept and documented
inline. `tsconfig.sw.json` adds the `webworker` lib for that file
only; `pnpm check-types` runs both projects.
- Traefik gets two dynamic middlewares: `sw-no-cache@file` for
`/sw.js` and `manifest-short-cache@file` for
`/manifest.webmanifest`, applied via `additionalRoutes` on the
`app-web` service so an upstream CDN can't pin a stale worker
through a deploy.
Persistence
- TanStack Query cache is persisted to localStorage via
`@tanstack/query-sync-storage-persister` with an allowlist of
query roots (`place`, `weather`, `nearby`, `isochrone`,
`sun-times`, `directions`, `route`, `geocode`). Admin / live data
re-fetches from network as before.1 parent 1dcc6d8 commit a1c7e9c
54 files changed
Lines changed: 2536 additions & 95 deletions
File tree
- apps/web
- public/icons/app
- src
- app
- (map)
- offline
- settings
- offline
- components
- command-palette
- map
- panels
- pwa
- search
- settings
- weather
- lib
- offlineAreas
- packages/i18n/locales
- services
- app-web
- traefik/config/dynamic
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
| 36 | + | |
35 | 37 | | |
36 | 38 | | |
37 | 39 | | |
| |||
Loading
Loading
Loading
Loading
Loading
Loading
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | 1 | | |
4 | | - | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
| 2 | + | |
10 | 3 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
17 | 21 | | |
18 | 22 | | |
19 | 23 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
11 | 14 | | |
12 | 15 | | |
13 | 16 | | |
| |||
22 | 25 | | |
23 | 26 | | |
24 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
25 | 56 | | |
26 | 57 | | |
27 | 58 | | |
| |||
39 | 70 | | |
40 | 71 | | |
41 | 72 | | |
42 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
43 | 85 | | |
44 | 86 | | |
45 | 87 | | |
| |||
0 commit comments