Harden MapLibre style readiness
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
|
||||
- Added a compact frontend status strip for project, AOI, datasets, active map layer, QA/QC and exports.
|
||||
- The strip is driven by existing App state and suggests the next operator action in the V1 loop.
|
||||
- Hardened the MapLibre component so GeoJSON sources/layers wait for the map style to finish loading before updates run.
|
||||
- Added regression coverage to ensure the strip remains wired without introducing new API calls.
|
||||
- No backend behavior, migrations, API contracts, provider downloads, AI inference or new dependencies were introduced.
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ def test_geomap_exposes_v1_layer_controls_and_feature_inspection_contract() -> N
|
||||
assert "opacity?: number" in geomap
|
||||
assert "onFeatureSelect?: (feature: GeoJSON.Feature | null) => void" in geomap
|
||||
assert "queryRenderedFeatures" in geomap
|
||||
assert "mapStyleReady" in geomap
|
||||
assert "map.isStyleLoaded()" in geomap
|
||||
assert "setLayoutProperty('dataset-fill', 'visibility'" in geomap
|
||||
assert "setPaintProperty('dataset-fill', 'fill-opacity', opacity)" in geomap
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Changed:
|
||||
- Added `frontend/src/components/WorkbenchStatusStrip.tsx` to summarize existing V1 state for project, AOI, datasets, active map layer, QA/QC and exports.
|
||||
- Wired the status strip into `frontend/src/App.tsx` using existing orchestration state only.
|
||||
- Added compact status-strip styling and regression tests for the frontend wiring contract.
|
||||
- Added compact status-strip styling and regression tests for the frontend wiring contract.`n- Hardened `frontend/src/components/GeoMap.tsx` so MapLibre source/layer updates wait for style readiness before adding sources.
|
||||
- Updated frontend README, TODO and changelog.
|
||||
|
||||
Tested:
|
||||
|
||||
@@ -162,6 +162,7 @@ React + TypeScript + MapLibre foundation for project/area/dataset workflow.
|
||||
- The strip summarizes existing connected state for project, AOI, datasets, active map layer, persisted QA/QC results and exports.
|
||||
- It suggests the next operator action based on missing V1 loop state without calling new APIs or adding backend behavior.
|
||||
- The status strip is implemented in `src/components/WorkbenchStatusStrip.tsx` and remains driven by `App.tsx` orchestration state.
|
||||
- MapLibre source/layer updates now wait for style readiness to avoid runtime blank-screen failures during first render.
|
||||
|
||||
## Release hardening updates
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import maplibregl from 'maplibre-gl'
|
||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||
|
||||
@@ -63,6 +63,7 @@ function GeoMap({
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
const mapRef = useRef<maplibregl.Map | null>(null)
|
||||
const onFeatureSelectRef = useRef<GeoMapProps['onFeatureSelect']>(onFeatureSelect)
|
||||
const [mapStyleReady, setMapStyleReady] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
onFeatureSelectRef.current = onFeatureSelect
|
||||
@@ -80,6 +81,9 @@ function GeoMap({
|
||||
zoom: 9,
|
||||
})
|
||||
map.addControl(new maplibregl.NavigationControl(), 'top-right')
|
||||
map.on('load', () => {
|
||||
setMapStyleReady(true)
|
||||
})
|
||||
map.on('click', (event) => {
|
||||
const layers = ['dataset-fill', 'dataset-line', 'area-fill', 'area-line'].filter((layerId) => map.getLayer(layerId))
|
||||
if (layers.length === 0) {
|
||||
@@ -101,12 +105,13 @@ function GeoMap({
|
||||
return () => {
|
||||
map.remove()
|
||||
mapRef.current = null
|
||||
setMapStyleReady(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map) {
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -174,11 +179,11 @@ function GeoMap({
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [data])
|
||||
}, [data, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map) {
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -231,11 +236,11 @@ function GeoMap({
|
||||
map.fitBounds(bounds, { padding: 40 })
|
||||
}
|
||||
}
|
||||
}, [areaData, data])
|
||||
}, [areaData, data, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map) {
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
const visibility = visible ? 'visible' : 'none'
|
||||
@@ -247,11 +252,11 @@ function GeoMap({
|
||||
map.setLayoutProperty('dataset-line', 'visibility', visibility)
|
||||
map.setPaintProperty('dataset-line', 'line-opacity', visible ? 1 : 0)
|
||||
}
|
||||
}, [visible, opacity, data])
|
||||
}, [visible, opacity, data, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map) {
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
const visibility = areaVisible ? 'visible' : 'none'
|
||||
@@ -263,7 +268,7 @@ function GeoMap({
|
||||
map.setLayoutProperty('area-line', 'visibility', visibility)
|
||||
map.setPaintProperty('area-line', 'line-opacity', areaVisible ? 1 : 0)
|
||||
}
|
||||
}, [areaVisible, areaOpacity, areaData])
|
||||
}, [areaVisible, areaOpacity, areaData, mapStyleReady])
|
||||
|
||||
return <div className="map-container" ref={containerRef} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user