1919 v-if =" props .activeSource === ' osm' || props .activeSource === ' overture' || props .activeSource === ' conflated' "
2020 />
2121
22- <div class =" basemap-switcher" >
22+ <!-- Desktop: native select -->
23+ <div class =" basemap-switcher basemap-switcher-desktop" >
2324 <select v-model =" selectedStyle" @change =" switchBaseMap" >
24- <option
25- v-for =" s in baseMapStyles"
26- :key =" s.key"
27- :value =" s.key"
28- >
25+ <option v-for =" s in baseMapStyles" :key =" s.key" :value =" s.key" >
2926 {{ s.label }}
3027 </option >
3128 </select >
3229 </div >
3330
31+ <!-- Mobile: button + centered modal -->
32+ <button class =" basemap-switcher basemap-mobile-btn" @click =" basemapModalOpen = true" >
33+ {{ baseMapStyles.find(s => s.key === selectedStyle)?.label }} ▾
34+ </button >
35+ <Teleport to="body">
36+ <div v-if =" basemapModalOpen" class =" basemap-modal-overlay" @click.self =" basemapModalOpen = false" >
37+ <div class =" basemap-modal" >
38+ <div class =" basemap-modal-title" >Base Map</div >
39+ <button
40+ v-for =" s in baseMapStyles"
41+ :key =" s.key"
42+ :class =" ['basemap-modal-option', { active: selectedStyle === s.key }]"
43+ @click =" selectBasemap(s.key)"
44+ >
45+ {{ s.label }}
46+ </button >
47+ </div >
48+ </div >
49+ </Teleport >
50+
3451 <!-- Popup overlay anchor (managed by OL Overlay, not Vue v-if) -->
3552 <div ref =" popupEl" >
3653 <PoiPopup :feature =" selectedFeature " @close =" closePopup " />
@@ -45,8 +62,6 @@ import {
4562import Map from ' ol/Map'
4663import View from ' ol/View'
4764import Overlay from ' ol/Overlay'
48- import TileLayer from ' ol/layer/Tile'
49- import XYZ from ' ol/source/XYZ'
5065import { fromLonLat , transformExtent } from ' ol/proj'
5166import { apply } from ' ol-mapbox-style'
5267import PoiPopup from ' ./PoiPopup.vue'
@@ -95,6 +110,7 @@ const popupOverlay = shallowRef(null)
95110const selectedFeature = shallowRef (null )
96111const loading = ref (false )
97112const selectedStyle = ref (' positron' )
113+ const basemapModalOpen = ref (false )
98114const currentZoom = ref (INITIAL_ZOOM )
99115const baseMapStyles = BASE_MAP_STYLES
100116
@@ -117,14 +133,6 @@ onMounted(async () => {
117133 minZoom: 14 ,
118134 })
119135
120- // Start with a raster fallback so the map is visible immediately
121- const fallbackBase = new TileLayer ({
122- source: new XYZ ({
123- url: ' https://tile.openstreetmap.org/{z}/{x}/{y}.png' ,
124- maxZoom: 19 ,
125- }),
126- })
127-
128136 const osmLyr = getOsmLayer ()
129137 const overtureLyr = getOvertureLayer ()
130138 const conflatedLyr = getConflatedLayer ()
@@ -135,10 +143,12 @@ onMounted(async () => {
135143 const olMap = new Map ({
136144 target: mapEl .value ,
137145 view,
138- layers: [fallbackBase, osmLyr, overtureLyr, conflatedLyr],
146+ layers: [osmLyr, overtureLyr, conflatedLyr],
139147 })
140148 map .value = olMap
141149
150+ document .getElementById (' initial-loader' )? .remove ()
151+
142152 // Try to apply vector tile style (replaces fallback raster)
143153 applyBaseStyle (' positron' )
144154
@@ -221,6 +231,12 @@ function switchBaseMap() {
221231 applyBaseStyle (selectedStyle .value )
222232}
223233
234+ function selectBasemap (key ) {
235+ selectedStyle .value = key
236+ basemapModalOpen .value = false
237+ applyBaseStyle (key)
238+ }
239+
224240// ---- Data loading ----
225241
226242async function loadData () {
@@ -429,4 +445,8 @@ watch(
429445 { deep: true }
430446)
431447
448+ // Fire initial load once DuckDB is ready (covers the case where the view
449+ // never changes and moveend never fires).
450+ watch (duckReady, (isReady ) => { if (isReady) loadData () })
451+
432452< / script>
0 commit comments