Add a debug page for comparing offline base-map themes side by side
Renders the bare protomaps-themes-base themes next to the app's actual offline/online styles (each real app instance, embedded), with pan/zoom kept in sync via the existing URL-hash mechanism, to make it easy to see where the hand-tuned offline palette diverges from the online one.
This commit is contained in:
1 parent
9223410b40
commit
0874e8f8ed
2 files changed
+122
No files matched your search
@@ -23,6 +23,17 @@ explicit cache headers, so an edit to `public/` (or `views/`) can
|
|||||||
appear not to have taken effect on the next reload. Force a real
|
appear not to have taken effect on the next reload. Force a real
|
||||||
reload (e.g. Ctrl/Cmd+Shift+R) if that happens.
|
reload (e.g. Ctrl/Cmd+Shift+R) if that happens.
|
||||||
|
|
||||||
|
`/theme_preview.html` is a standalone debug page (not linked from the
|
||||||
|
app itself) for comparing offline base-map themes side by side: a bare
|
||||||
|
`protomaps-themes-base` theme, an iframe of the real app's offline
|
||||||
|
style, and one of its online style, with pan/zoom kept in sync across
|
||||||
|
all three via the same URL-hash mechanism `map.js` already uses for
|
||||||
|
shareable links. It's a plain static file, so nothing gates it to
|
||||||
|
development — it's reachable on a production instance too, same as any
|
||||||
|
other file under `public/`. Harmless if it stays that way (it shows
|
||||||
|
nothing `/` doesn't already), but worth knowing if that's ever not the
|
||||||
|
case.
|
||||||
|
|
||||||
== Offline map data
|
== Offline map data
|
||||||
|
|
||||||
To enable the offline style, place a PMTiles file at
|
To enable the offline style, place a PMTiles file at
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Offline theme preview</title>
|
||||||
|
<link rel="stylesheet" href="/vendor/maplibre-gl/maplibre-gl.css">
|
||||||
|
<style>
|
||||||
|
body { margin: 0; font-family: sans-serif; background: #222; }
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
|
||||||
|
.cell { position: relative; height: 90vh; }
|
||||||
|
.cell .map, .cell iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
|
||||||
|
.cell .label {
|
||||||
|
position: absolute; top: 4px; left: 4px; z-index: 1;
|
||||||
|
background: rgba(0,0,0,.6); color: #fff; padding: 2px 8px;
|
||||||
|
font-size: 13px; border-radius: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="cell">
|
||||||
|
<div class="label">light (bare base theme)</div>
|
||||||
|
<div class="map" id="light-map"></div>
|
||||||
|
</div>
|
||||||
|
<div class="cell">
|
||||||
|
<div class="label">offline (current app)</div>
|
||||||
|
<iframe id="offline-frame" src="/"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="cell">
|
||||||
|
<div class="label">online (current app)</div>
|
||||||
|
<iframe id="online-frame" src="/"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/vendor/pmtiles/pmtiles.js"></script>
|
||||||
|
<script src="/vendor/protomaps-themes-base/protomaps-themes-base.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import { Map as MaplibreMap, addProtocol } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
|
|
||||||
|
const protocol = new pmtiles.Protocol();
|
||||||
|
addProtocol("pmtiles", protocol.tile);
|
||||||
|
|
||||||
|
const CENTER = [17.1077, 48.1486];
|
||||||
|
const ZOOM = 13;
|
||||||
|
const pmtilesUrl = `${window.location.origin}/tiles/offline.pmtiles`;
|
||||||
|
|
||||||
|
const lightMap = new MaplibreMap({
|
||||||
|
container: "light-map",
|
||||||
|
center: CENTER,
|
||||||
|
zoom: ZOOM,
|
||||||
|
style: {
|
||||||
|
version: 8,
|
||||||
|
glyphs: `${window.location.origin}/vendor/fonts/{fontstack}/{range}.pbf`,
|
||||||
|
sources: {
|
||||||
|
offline: { type: "vector", url: `pmtiles://${pmtilesUrl}` },
|
||||||
|
},
|
||||||
|
layers: protomaps_themes_base.layers("offline", protomaps_themes_base.namedTheme("light"), { lang: "en" }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// "offline" frame needs nothing: the app already defaults to its offline
|
||||||
|
// style whenever /api/offline-tiles reports data present (see map.js).
|
||||||
|
// "online" frame: click its own real Online button, same button a user
|
||||||
|
// would click — reuses the app's actual ONLINE_STYLE (patched Liberty)
|
||||||
|
// instead of re-fetching/re-patching a second copy here.
|
||||||
|
const offlineFrame = document.getElementById("offline-frame");
|
||||||
|
const onlineFrame = document.getElementById("online-frame");
|
||||||
|
const clickOnline = () => {
|
||||||
|
const button = onlineFrame.contentDocument?.querySelector('.basemap-panel__option[data-style="online"]');
|
||||||
|
if (button) button.click();
|
||||||
|
};
|
||||||
|
onlineFrame.addEventListener("load", () => {
|
||||||
|
clickOnline();
|
||||||
|
setTimeout(clickOnline, 1000); // retry once in case map.js was still mid-init
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep all three views looking at the same spot. The two iframes are the
|
||||||
|
// real app (separate documents, own module scope — nothing to reach into
|
||||||
|
// directly), but map.js already syncs its map to the URL hash (`hash:
|
||||||
|
// "map"`, format #map=zoom/lat/lon) for shareable links. Reusing that: poll
|
||||||
|
// each source's position, and whichever moved gets pushed to the other
|
||||||
|
// two — for a frame, by writing its location.hash, which fires its own
|
||||||
|
// hashchange and lets its Hash control jumpTo for us.
|
||||||
|
const HASH_RE = /map=([\d.]+)\/(-?[\d.]+)\/(-?[\d.]+)/;
|
||||||
|
function frameHash(frame) {
|
||||||
|
const match = HASH_RE.exec(frame.contentWindow?.location.hash ?? "");
|
||||||
|
return match ? { zoom: +match[1], lat: +match[2], lon: +match[3] } : null;
|
||||||
|
}
|
||||||
|
function lightPos() {
|
||||||
|
const c = lightMap.getCenter();
|
||||||
|
return { zoom: lightMap.getZoom(), lat: c.lat, lon: c.lng };
|
||||||
|
}
|
||||||
|
function samePos(a, b) {
|
||||||
|
return Math.abs(a.zoom - b.zoom) < 0.005 && Math.abs(a.lat - b.lat) < 1e-4 && Math.abs(a.lon - b.lon) < 1e-4;
|
||||||
|
}
|
||||||
|
let current = { zoom: ZOOM, lat: CENTER[1], lon: CENTER[0] };
|
||||||
|
setInterval(() => {
|
||||||
|
const sources = { light: lightPos(), offline: frameHash(offlineFrame), online: frameHash(onlineFrame) };
|
||||||
|
const leader = Object.keys(sources).find((name) => sources[name] && !samePos(sources[name], current));
|
||||||
|
if (!leader) return;
|
||||||
|
current = sources[leader];
|
||||||
|
if (leader !== "light") lightMap.jumpTo({ center: [current.lon, current.lat], zoom: current.zoom });
|
||||||
|
if (leader !== "offline" && offlineFrame.contentWindow) {
|
||||||
|
offlineFrame.contentWindow.location.hash = `map=${current.zoom}/${current.lat}/${current.lon}`;
|
||||||
|
}
|
||||||
|
if (leader !== "online" && onlineFrame.contentWindow) {
|
||||||
|
onlineFrame.contentWindow.location.hash = `map=${current.zoom}/${current.lat}/${current.lon}`;
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user