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.
|
- 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.
|
- 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.
|
- 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.
|
- 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 "opacity?: number" in geomap
|
||||||
assert "onFeatureSelect?: (feature: GeoJSON.Feature | null) => void" in geomap
|
assert "onFeatureSelect?: (feature: GeoJSON.Feature | null) => void" in geomap
|
||||||
assert "queryRenderedFeatures" in geomap
|
assert "queryRenderedFeatures" in geomap
|
||||||
|
assert "mapStyleReady" in geomap
|
||||||
|
assert "map.isStyleLoaded()" in geomap
|
||||||
assert "setLayoutProperty('dataset-fill', 'visibility'" in geomap
|
assert "setLayoutProperty('dataset-fill', 'visibility'" in geomap
|
||||||
assert "setPaintProperty('dataset-fill', 'fill-opacity', opacity)" in geomap
|
assert "setPaintProperty('dataset-fill', 'fill-opacity', opacity)" in geomap
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Changed:
|
Changed:
|
||||||
- Added `frontend/src/components/WorkbenchStatusStrip.tsx` to summarize existing V1 state for project, AOI, datasets, active map layer, QA/QC and exports.
|
- 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.
|
- 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.
|
- Updated frontend README, TODO and changelog.
|
||||||
|
|
||||||
Tested:
|
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.
|
- 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.
|
- 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.
|
- 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
|
## Release hardening updates
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import maplibregl from 'maplibre-gl'
|
import maplibregl from 'maplibre-gl'
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||||
|
|
||||||
@@ -63,6 +63,7 @@ function GeoMap({
|
|||||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const mapRef = useRef<maplibregl.Map | null>(null)
|
const mapRef = useRef<maplibregl.Map | null>(null)
|
||||||
const onFeatureSelectRef = useRef<GeoMapProps['onFeatureSelect']>(onFeatureSelect)
|
const onFeatureSelectRef = useRef<GeoMapProps['onFeatureSelect']>(onFeatureSelect)
|
||||||
|
const [mapStyleReady, setMapStyleReady] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onFeatureSelectRef.current = onFeatureSelect
|
onFeatureSelectRef.current = onFeatureSelect
|
||||||
@@ -80,6 +81,9 @@ function GeoMap({
|
|||||||
zoom: 9,
|
zoom: 9,
|
||||||
})
|
})
|
||||||
map.addControl(new maplibregl.NavigationControl(), 'top-right')
|
map.addControl(new maplibregl.NavigationControl(), 'top-right')
|
||||||
|
map.on('load', () => {
|
||||||
|
setMapStyleReady(true)
|
||||||
|
})
|
||||||
map.on('click', (event) => {
|
map.on('click', (event) => {
|
||||||
const layers = ['dataset-fill', 'dataset-line', 'area-fill', 'area-line'].filter((layerId) => map.getLayer(layerId))
|
const layers = ['dataset-fill', 'dataset-line', 'area-fill', 'area-line'].filter((layerId) => map.getLayer(layerId))
|
||||||
if (layers.length === 0) {
|
if (layers.length === 0) {
|
||||||
@@ -101,12 +105,13 @@ function GeoMap({
|
|||||||
return () => {
|
return () => {
|
||||||
map.remove()
|
map.remove()
|
||||||
mapRef.current = null
|
mapRef.current = null
|
||||||
|
setMapStyleReady(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current
|
const map = mapRef.current
|
||||||
if (!map) {
|
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,11 +179,11 @@ function GeoMap({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [data])
|
}, [data, mapStyleReady])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current
|
const map = mapRef.current
|
||||||
if (!map) {
|
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,11 +236,11 @@ function GeoMap({
|
|||||||
map.fitBounds(bounds, { padding: 40 })
|
map.fitBounds(bounds, { padding: 40 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [areaData, data])
|
}, [areaData, data, mapStyleReady])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current
|
const map = mapRef.current
|
||||||
if (!map) {
|
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const visibility = visible ? 'visible' : 'none'
|
const visibility = visible ? 'visible' : 'none'
|
||||||
@@ -247,11 +252,11 @@ function GeoMap({
|
|||||||
map.setLayoutProperty('dataset-line', 'visibility', visibility)
|
map.setLayoutProperty('dataset-line', 'visibility', visibility)
|
||||||
map.setPaintProperty('dataset-line', 'line-opacity', visible ? 1 : 0)
|
map.setPaintProperty('dataset-line', 'line-opacity', visible ? 1 : 0)
|
||||||
}
|
}
|
||||||
}, [visible, opacity, data])
|
}, [visible, opacity, data, mapStyleReady])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current
|
const map = mapRef.current
|
||||||
if (!map) {
|
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const visibility = areaVisible ? 'visible' : 'none'
|
const visibility = areaVisible ? 'visible' : 'none'
|
||||||
@@ -263,7 +268,7 @@ function GeoMap({
|
|||||||
map.setLayoutProperty('area-line', 'visibility', visibility)
|
map.setLayoutProperty('area-line', 'visibility', visibility)
|
||||||
map.setPaintProperty('area-line', 'line-opacity', areaVisible ? 1 : 0)
|
map.setPaintProperty('area-line', 'line-opacity', areaVisible ? 1 : 0)
|
||||||
}
|
}
|
||||||
}, [areaVisible, areaOpacity, areaData])
|
}, [areaVisible, areaOpacity, areaData, mapStyleReady])
|
||||||
|
|
||||||
return <div className="map-container" ref={containerRef} />
|
return <div className="map-container" ref={containerRef} />
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user