Add an Alt+click debug inspector, NPMap4-infobox style
NPMap4 has infoboxControl: a generic "click anything, show every raw property" debug view, kept separate from a dataset's own curated popup. Not portable code (Leaflet), but the concept is: Alt+click anywhere now opens a popup listing every rendered feature at that point via map.queryRenderedFeatures — base map OSM tags included, not just this app's own GPX/FIT/radar layers — with a raw key/value table per feature. No dedicated UI for it on purpose: a keyboard-modified click needs no button, toggle state, or cursor change of its own, and it's fine for it to coexist with a layer's own click handler firing on the same click (e.g. a GPX waypoint's normal popup) since this is a power-user/debugging affordance, not a replacement for those. Feature properties can come from an uploaded GPX/FIT file (untrusted input) via this app's own layers, so every value is escaped the same way gpx.js's popups already are.
This commit is contained in:
1 parent
d58580f4d9
commit
8fa3391c6b
4 files changed
+99
-1
No files matched your search
@@ -154,6 +154,24 @@ manages in the page's own sidebar — both call the same
|
|||||||
`LayerToggleControl#toggle()`, so there's one source of truth for the
|
`LayerToggleControl#toggle()`, so there's one source of truth for the
|
||||||
panel's open/closed state either way.
|
panel's open/closed state either way.
|
||||||
|
|
||||||
|
=== Debug inspector (Alt+click)
|
||||||
|
|
||||||
|
Alt+click anywhere on the map opens a popup listing every rendered
|
||||||
|
feature at that point — across all layers, not just this app's own GPX/
|
||||||
|
FIT/radar layers — with its raw properties (`public/js/map.js`, a plain
|
||||||
|
`map.on("click", ...)` handler gated on `event.originalEvent.altKey`,
|
||||||
|
calling `map.queryRenderedFeatures(event.point)`). Inspired by NPMap4's
|
||||||
|
`infoboxControl`, a similar generic "show me everything here" debug
|
||||||
|
view kept separate from the curated popups those same features already
|
||||||
|
have. Deliberately no dedicated UI for it — a keyboard-modified click
|
||||||
|
needs no button, toggle state, or cursor change of its own, and it
|
||||||
|
coexists fine with layer-specific click handlers (e.g. a GPX waypoint's
|
||||||
|
normal popup) firing on the same click, since this is a power-user/
|
||||||
|
debugging affordance, not a replacement for those. Feature properties
|
||||||
|
can come straight from an uploaded GPX/FIT file (untrusted input) via
|
||||||
|
this app's own layers, not just the base map, so every key/value is
|
||||||
|
HTML-escaped the same way `gpx.js`'s popups already are.
|
||||||
|
|
||||||
== Weather radar overlay
|
== Weather radar overlay
|
||||||
|
|
||||||
`public/js/radar.js` adds a "Radar" map control that overlays recent
|
`public/js/radar.js` adds a "Radar" map control that overlays recent
|
||||||
|
|||||||
@@ -266,8 +266,16 @@ plugins (several still at `0.0.1-x`, pre-1.0), a few of which (`overview`,
|
|||||||
`carto`, `wms`) aren't even merged to `main` yet — see above. v5 is the
|
`carto`, `wms`) aren't even merged to `main` yet — see above. v5 is the
|
||||||
direction NPS is migrating to, not (yet) a feature-for-feature
|
direction NPS is migrating to, not (yet) a feature-for-feature
|
||||||
replacement; expect this gap to close over time, not to stay this way.
|
replacement; expect this gap to close over time, not to stay this way.
|
||||||
Two things worth noting from v4:
|
Three things worth noting from v4:
|
||||||
|
|
||||||
|
* *`infoboxControl`* — example:
|
||||||
|
https://www.nps.gov/lib/npmap.js/4.2.1/examples/basic/infobox.html. A
|
||||||
|
generic "click anything, show every raw property in a table" debug
|
||||||
|
view, kept separate from a dataset's own curated popup. *Adopted* — not
|
||||||
|
the code (Leaflet, not portable) but the concept: this app now has the
|
||||||
|
same thing as an Alt+click handler (see `docs/architecture.adoc`'s
|
||||||
|
"Debug inspector" section), showing `queryRenderedFeatures` results —
|
||||||
|
base map OSM tags included, not just this app's own layers.
|
||||||
* *Geocoder control* — example:
|
* *Geocoder control* — example:
|
||||||
https://www.nps.gov/lib/npmap.js/4.2.1/examples/controls/geocoder-control.html.
|
https://www.nps.gov/lib/npmap.js/4.2.1/examples/controls/geocoder-control.html.
|
||||||
Config is `geocoderControl: { searchPlaces: true }`: a search box that
|
Config is `geocoderControl: { searchPlaces: true }`: a search box that
|
||||||
|
|||||||
@@ -438,6 +438,40 @@ html, body {
|
|||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.debug-popup .maplibregl-popup-content {
|
||||||
|
max-height: 320px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-popup__feature {
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-popup__feature + .debug-popup__feature {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-popup__feature table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-popup__feature th {
|
||||||
|
text-align: left;
|
||||||
|
color: #888;
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 1px 6px 1px 0;
|
||||||
|
vertical-align: top;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-popup__feature td {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.gpx-popup__time {
|
.gpx-popup__time {
|
||||||
color: #888;
|
color: #888;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Map as MaplibreMap,
|
Map as MaplibreMap,
|
||||||
NavigationControl,
|
NavigationControl,
|
||||||
GeolocateControl,
|
GeolocateControl,
|
||||||
|
Popup,
|
||||||
addProtocol,
|
addProtocol,
|
||||||
} from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
} from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
import { RadarControl } from "/js/radar.js";
|
import { RadarControl } from "/js/radar.js";
|
||||||
@@ -263,3 +264,40 @@ fetch("/api/offline-tiles")
|
|||||||
// is rather than guessing — this is the same-origin request itself
|
// is rather than guessing — this is the same-origin request itself
|
||||||
// failing, not evidence offline tiles are actually unavailable.
|
// failing, not evidence offline tiles are actually unavailable.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Alt+click anywhere shows every rendered feature at that point — base map
|
||||||
|
// (OSM tags via OpenFreeMap/protomaps), GPX/FIT layers, radar, all of it —
|
||||||
|
// with its raw properties, inspired by NPMap4's infoboxControl (a generic
|
||||||
|
// "show me everything here" debug view, kept separate from the curated
|
||||||
|
// popups those same features already have via their own layer-specific
|
||||||
|
// click handlers, e.g. gpx.js's waypoint popups — both can fire on the same
|
||||||
|
// click, which is fine, this is a power-user/debugging affordance, not
|
||||||
|
// something replacing the normal popups). No dedicated UI for this
|
||||||
|
// deliberately — a keyboard-modified click needs no button, state, or
|
||||||
|
// cursor change of its own.
|
||||||
|
map.on("click", (event) => {
|
||||||
|
if (!event.originalEvent.altKey) return;
|
||||||
|
|
||||||
|
const features = map.queryRenderedFeatures(event.point);
|
||||||
|
if (features.length === 0) return;
|
||||||
|
|
||||||
|
const html = features
|
||||||
|
.map((feature) => {
|
||||||
|
const rows = Object.entries(feature.properties)
|
||||||
|
.map(([key, value]) => `<tr><th>${escapeHtml(key)}</th><td>${escapeHtml(String(value))}</td></tr>`)
|
||||||
|
.join("");
|
||||||
|
return `<div class="debug-popup__feature"><strong>${escapeHtml(feature.layer.id)}</strong><table>${rows || "<tr><td>(no properties)</td></tr>"}</table></div>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
new Popup({ maxWidth: "320px", className: "debug-popup" }).setLngLat(event.lngLat).setHTML(html).addTo(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Feature properties can come straight from an uploaded GPX/FIT file (an
|
||||||
|
// untrusted source) via this app's own layers, not just the base map, so
|
||||||
|
// every value is escaped the same way gpx.js's popups already do.
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user