diff --git a/src/app/debug/coverage/CoverageMap.js b/src/app/debug/coverage/CoverageMap.js index a3dd440..7925eed 100644 --- a/src/app/debug/coverage/CoverageMap.js +++ b/src/app/debug/coverage/CoverageMap.js @@ -44,6 +44,10 @@ export default function CoverageMap({ if (!containerRef.current || mapRef.current) return; const map = L.map(containerRef.current, { preferCanvas: true }); + // Bottom-left: from lg the panorama inspector floats over the bottom-right + // corner, and tile attribution has to stay visible. The map option is a + // boolean, so the position is set on the control itself. + map.attributionControl.setPosition('bottomleft'); const tiles = getTileConfig(); L.tileLayer(tiles.url, tiles.options).addTo(map); map.setView([16.0, 107.0], 6); @@ -84,10 +88,25 @@ export default function CoverageMap({ }; map.on('click', pick); + // Leaflet caches the container size and only re-reads it on a window + // resize, so opening the inspector -- which shrinks the map on phones, + // where it stacks rather than floats -- would otherwise leave the map + // drawing at its old height. + // Zero size means the map is display:none -- the phone layout swaps it out + // for the inspector. Measuring then would hand the loader a degenerate + // bbox and wipe the point count, so wait until it is on screen again. + const resizeObserver = new ResizeObserver(() => { + const el = containerRef.current; + if (!el || !el.clientWidth || !el.clientHeight) return; + map.invalidateSize(); + }); + resizeObserver.observe(containerRef.current); + mapRef.current = map; return () => { clearTimeout(moveTimer); + resizeObserver.disconnect(); map.off('moveend', report); map.off('click', pick); map.remove(); @@ -173,5 +192,11 @@ export default function CoverageMap({ }).addTo(map); }, [selectedId, panos]); - return
; + // Sized by insets, so the caller must position its wrapper. A percentage + // height would collapse: the wrapper is a flex item, whose flexed main-axis + // height is indefinite for percentage resolution. + // `isolate` keeps Leaflet's own ladder (panes at 400, controls at 800-1000) + // inside this element; without it those values escape to the caller's + // stacking context and paint over anything layered on top of the map. + return
; } diff --git a/src/app/debug/coverage/page.js b/src/app/debug/coverage/page.js index 7fffba9..d3d50a6 100644 --- a/src/app/debug/coverage/page.js +++ b/src/app/debug/coverage/page.js @@ -15,7 +15,7 @@ const CoverageMap = dynamic(() => import('./CoverageMap'), {
Loading map...
@@ -125,6 +125,21 @@ export default function CoveragePage() { setPanoLoading(false); }, []); + // The inspector covers part of the map, so Escape closes it -- the X in its + // header is the only other way out. + useEffect(() => { + if (!selected) return undefined; + const handleKeyDown = (event) => { + if (event.key !== 'Escape') return; + // An open select owns Escape; closing the inspector behind it would make + // one keypress do two things. + if (document.querySelector('[data-radix-popper-content-wrapper]')) return; + closePano(); + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [selected, closePano]); + const handleBoundsChange = useCallback( (nextBbox) => { load(region, nextBbox); @@ -133,7 +148,7 @@ export default function CoveragePage() { ); return ( -
+
{/* Same title pattern as the other debug pages, with the tool's own controls beside it. The shared debug layout owns the app chrome. */}
@@ -185,8 +200,25 @@ export default function CoveragePage() { {loading && loading…}
-
-
+ {/* The map is the tool, so it gets the whole surface. From lg the + inspector floats over its right edge: the old two-column grid held + half the width empty until a point was picked, and reflowing the map + on selection moved the very dot the user had just clicked. A phone has + neither the width to float into nor the height to split -- 112px of + map is not a map -- so there the inspector takes the surface instead + and its close button brings the map back. + min-h-64 is the floor that keeps that promise: the page chrome takes + 225px, so on a landscape phone a purely flexed surface collapsed to + 98px. The floor makes
scroll instead of shrinking the map + below the point of being one. */} +
+ {/* Kept mounted while hidden, so coming back costs no reload; the + map's ResizeObserver re-measures it when it reappears. */} +
{selected && ( -
+ )}
diff --git a/src/app/debug/layout.js b/src/app/debug/layout.js index 3ed351e..9a8c2ca 100644 --- a/src/app/debug/layout.js +++ b/src/app/debug/layout.js @@ -7,9 +7,11 @@ import DebugNav from './DebugNav'; // One shell for every debug page, styled like the game screen's app bar, so // the hub and both tools share identical chrome and only differ in content. // The column fills the body's sticky-footer layout (viewport minus the global -// footer strip) with the scroll INSIDE
: document-style -// pages (hub, bbox) scroll there, while the coverage map takes h-full and -// manages its own panes. +// footer strip) with the scroll INSIDE
: document-style pages (hub, +// bbox) scroll there, while the coverage map fills it and manages its own +// panes.
is itself a flex column so a full-height page can claim the +// space with flex-1 -- `h-full` cannot, because main's height comes from +// flexing inside a min-height container and so is indefinite. export default function DebugLayout({ children }) { return (
@@ -35,7 +37,7 @@ export default function DebugLayout({ children }) { -
{children}
+
{children}
); }