fix: keep Mol database layers explicit on map
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 11:01:36 +02:00
parent 1ba479b50b
commit aae6315981
9 changed files with 190 additions and 18 deletions
+24 -14
View File
@@ -56,11 +56,11 @@ const workspaceNavGroups: Array<{ label: string; keys: WorkspaceKey[] }> = [
function App(): JSX.Element {
const [activeWorkspace, setActiveWorkspace] = useState<WorkspaceKey>('overview')
const [inspectorOpen, setInspectorOpen] = useState(false)
const [mapContentMode, setMapContentMode] = useState<'dataset' | 'analysis'>('dataset')
useEffect(() => {
window.scrollTo({ top: 0, left: 0 })
setInspectorOpen(false)
}, [activeWorkspace])
const {
projects,
selectedProject,
@@ -83,6 +83,10 @@ function App(): JSX.Element {
setProjectForm,
setAreaForm,
} = useProjectWorkspace()
const selectProject = (projectId: string) => {
setMapContentMode('dataset')
setSelectedProjectId(projectId)
}
const {
selectedDatasetId,
selectedDataset,
@@ -351,7 +355,8 @@ function App(): JSX.Element {
featureCount: selectedDatasetSummary?.feature_count ?? selectedDataset?.feature_count ?? null,
isVectorDatasetType,
})
const analysisMapLayerActive = Boolean(changeDetectionResult?.geojson || segmentationGeoJson || detectionGeoJson)
const analysisMapLayerAvailable = Boolean(changeDetectionResult?.geojson || segmentationGeoJson || detectionGeoJson)
const analysisMapLayerActive = mapContentMode === 'analysis' && analysisMapLayerAvailable
const viewportVectorLayerActive = viewportVectorLayer.enabled && !analysisMapLayerActive
const datasetMapContent = viewportVectorLayer.enabled ? viewportVectorLayer.data : datasetContent
const useRasterTileManifestForSegmentation = () => {
@@ -410,9 +415,9 @@ function App(): JSX.Element {
setSelectedMapFeature,
} = useMapWorkspaceState({
areas,
changeDetectionGeoJson: changeDetectionResult?.geojson ?? null,
segmentationGeoJson,
detectionGeoJson,
changeDetectionGeoJson: analysisMapLayerActive ? changeDetectionResult?.geojson ?? null : null,
segmentationGeoJson: analysisMapLayerActive ? segmentationGeoJson : null,
detectionGeoJson: analysisMapLayerActive ? detectionGeoJson : null,
datasetContent: datasetMapContent,
selectedDataset,
datasetLayerActive: Boolean(selectedDataset && isVectorDatasetType(selectedDataset.dataset_type)),
@@ -479,7 +484,7 @@ function App(): JSX.Element {
loadSegmentationRuns,
loadQualityChecks,
loadExports,
setSelectedProjectId,
setSelectedProjectId: selectProject,
setSelectedDatasetId,
setSelectedMapAreaId,
setQaCandidateDatasetId,
@@ -521,28 +526,28 @@ function App(): JSX.Element {
const selectedArea = areas.find((area) => area.id === selectedMapAreaId) ?? null
const activeWorkspaceItem = workspaceNavItems.find((item) => item.key === activeWorkspace) ?? workspaceNavItems[0]
const mapLayerSourceLabel = useMemo(() => {
if (changeDetectionResult?.geojson) {
if (analysisMapLayerActive && changeDetectionResult?.geojson) {
return 'Change detection'
}
if (segmentationGeoJson) {
if (analysisMapLayerActive && segmentationGeoJson) {
return 'Segmentation run'
}
if (detectionGeoJson) {
if (analysisMapLayerActive && detectionGeoJson) {
return 'Detection run'
}
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
return `${selectedDataset.dataset_type} dataset`
}
return 'No active vector or result layer'
}, [changeDetectionResult?.geojson, datasetMapContent, detectionGeoJson, segmentationGeoJson, selectedDataset, viewportVectorLayer.enabled])
}, [analysisMapLayerActive, changeDetectionResult?.geojson, datasetMapContent, detectionGeoJson, segmentationGeoJson, selectedDataset, viewportVectorLayer.enabled])
const mapLayerProvenance = useMemo(() => {
if (changeDetectionResult?.geojson) {
if (analysisMapLayerActive && changeDetectionResult?.geojson) {
return `source ${changeSourceDatasetId || 'n/a'} -> target ${changeTargetDatasetId || 'n/a'}`
}
if (segmentationGeoJson) {
if (analysisMapLayerActive && segmentationGeoJson) {
return selectedSegmentationRunId ? `analysis run ${selectedSegmentationRunId}` : 'segmentation results loaded'
}
if (detectionGeoJson) {
if (analysisMapLayerActive && detectionGeoJson) {
return selectedDetectionRunId ? `analysis run ${selectedDetectionRunId}` : 'detection results loaded'
}
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
@@ -550,6 +555,7 @@ function App(): JSX.Element {
}
return 'Open a dataset, detection run, segmentation run or change result to draw it here.'
}, [
analysisMapLayerActive,
changeDetectionResult?.geojson,
changeSourceDatasetId,
changeTargetDatasetId,
@@ -565,6 +571,7 @@ function App(): JSX.Element {
if (selectedProjectId) {
loadDatasetDetails(selectedProjectId, dataset)
}
setMapContentMode('dataset')
setMapLayerVisible(true)
setActiveWorkspace('map')
}
@@ -867,7 +874,7 @@ function App(): JSX.Element {
demoWorkflowMessage={demoWorkflowMessage}
onCreateProject={createProject}
onUpdateProjectForm={setProjectForm}
onSelectProject={setSelectedProjectId}
onSelectProject={selectProject}
onLoadDemoWorkflow={loadDemoWorkflow}
/>
@@ -925,6 +932,8 @@ function App(): JSX.Element {
viewportVectorStatus={viewportVectorLayerActive ? viewportVectorLayer.statusMessage : null}
viewportVectorTone={viewportVectorLayer.error ? 'error' : viewportVectorLayer.truncated ? 'warning' : viewportVectorLayer.loading || viewportVectorLayer.zoomRequired ? 'pending' : 'ready'}
fitMapDataOnChange={!viewportVectorLayerActive}
mapContentMode={analysisMapLayerActive ? 'analysis' : 'dataset'}
analysisLayerAvailable={analysisMapLayerAvailable}
selectedMapFeature={selectedMapFeature}
mapSelectionBbox={mapSelectionBbox}
mapSelectionResult={mapSelectionResult}
@@ -952,6 +961,7 @@ function App(): JSX.Element {
onSetAreaLayerOpacity={setAreaLayerOpacity}
onSetMapLayerVisible={setMapLayerVisible}
onSetMapLayerOpacity={setMapLayerOpacity}
onSetMapContentMode={setMapContentMode}
onSelectMapFeature={setSelectedMapFeature}
onMapViewportChange={viewportVectorLayer.setViewport}
onSetMapSelectionBbox={setMapSelectionBbox}
@@ -193,6 +193,8 @@ interface MapWorkspaceProps {
viewportVectorStatus: string | null
viewportVectorTone: 'ready' | 'pending' | 'warning' | 'error'
fitMapDataOnChange: boolean
mapContentMode: 'dataset' | 'analysis'
analysisLayerAvailable: boolean
selectedMapFeature: GeoJSON.Feature | null
selectedFeature?: GeoJSON.Feature | null
mapSelectionBbox: VectorSelectionBBox | null
@@ -220,6 +222,7 @@ interface MapWorkspaceProps {
onSetAreaLayerOpacity: (opacity: number) => void
onSetMapLayerVisible: (visible: boolean) => void
onSetMapLayerOpacity: (opacity: number) => void
onSetMapContentMode: (mode: 'dataset' | 'analysis') => void
onSelectMapFeature: (feature: GeoJSON.Feature | null) => void
onMapViewportChange: (viewport: MapViewportState) => void
onSetMapSelectionBbox: (bbox: VectorSelectionBBox | null) => void
@@ -256,6 +259,8 @@ export function MapWorkspace({
viewportVectorStatus,
viewportVectorTone,
fitMapDataOnChange,
mapContentMode,
analysisLayerAvailable,
selectedMapFeature,
selectedFeature = selectedMapFeature,
mapSelectionBbox,
@@ -283,6 +288,7 @@ export function MapWorkspace({
onSetAreaLayerOpacity,
onSetMapLayerVisible,
onSetMapLayerOpacity,
onSetMapContentMode,
onSelectMapFeature,
onMapViewportChange,
onSetMapSelectionBbox,
@@ -508,6 +514,26 @@ export function MapWorkspace({
</div>
) : null}
<div className="map-toolbar">
<div className="map-layer-mode" aria-label="Map content mode">
<span>Map content</span>
<div role="group" aria-label="Map content source">
<button
type="button"
aria-pressed={mapContentMode === 'dataset'}
onClick={() => onSetMapContentMode('dataset')}
>
Database
</button>
<button
type="button"
aria-pressed={mapContentMode === 'analysis'}
disabled={!analysisLayerAvailable}
onClick={() => onSetMapContentMode('analysis')}
>
Analysis result
</button>
</div>
</div>
<label>
Database layer
<select
+50 -1
View File
@@ -4969,7 +4969,7 @@ section {
}
.map-workspace-shell .map-toolbar {
grid-template-columns: minmax(13rem, 1.25fr) minmax(10rem, 1fr) repeat(2, minmax(8rem, 0.85fr)) minmax(11rem, 1fr);
grid-template-columns: minmax(10rem, 0.9fr) minmax(13rem, 1.25fr) minmax(10rem, 1fr) repeat(2, minmax(8rem, 0.85fr)) minmax(11rem, 1fr);
gap: 0.5rem;
}
@@ -4977,6 +4977,55 @@ section {
margin-top: 0;
}
.map-layer-mode {
display: grid;
align-content: start;
gap: 0.34rem;
min-width: 0;
}
.map-layer-mode > span {
color: var(--muted);
font-size: 0.7rem;
font-weight: 700;
}
.map-layer-mode > div {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
overflow: hidden;
border: 1px solid var(--line-strong);
border-radius: 6px;
background: var(--panel-soft);
}
.map-layer-mode button {
min-width: 0;
min-height: 2.2rem;
border: 0;
border-radius: 0;
padding: 0.36rem 0.42rem;
background: transparent;
color: var(--muted);
font-size: 0.68rem;
font-weight: 750;
line-height: 1.15;
}
.map-layer-mode button + button {
border-left: 1px solid var(--line-strong);
}
.map-layer-mode button[aria-pressed='true'] {
background: var(--accent-strong);
color: #ffffff;
}
.map-layer-mode button:disabled {
cursor: not-allowed;
opacity: 0.45;
}
.layer-control-card,
.map-status {
border-radius: 6px;
+14 -3
View File
@@ -936,7 +936,7 @@ details.ai-lab-model-surface > summary strong {
.map-workspace-shell .map-toolbar {
display: grid;
grid-template-columns: minmax(13rem, 1.3fr) minmax(10rem, 0.9fr) minmax(8rem, 0.65fr) minmax(8rem, 0.65fr) minmax(10rem, 0.9fr);
grid-template-columns: minmax(10rem, 0.85fr) minmax(13rem, 1.3fr) minmax(10rem, 0.9fr) minmax(8rem, 0.65fr) minmax(8rem, 0.65fr);
gap: 0.6rem;
align-items: end;
}
@@ -956,7 +956,10 @@ details.ai-lab-model-surface > summary strong {
.map-status {
display: grid;
min-height: 3.9rem;
grid-column: 1 / -1;
grid-template-columns: minmax(13rem, 1.35fr) repeat(3, minmax(10rem, 1fr));
gap: 0.25rem 0.85rem;
min-height: auto;
align-content: center;
border-left: 3px solid var(--accent);
border-radius: 4px;
@@ -969,7 +972,15 @@ details.ai-lab-model-surface > summary strong {
}
.map-status span {
display: none;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.map-status .viewport-vector-status {
grid-column: 1 / -1;
white-space: normal;
}
.basemap-policy-notice {