Skip to content

Commit aa97c3e

Browse files
committed
UI improvements and mobile compatibility.
1 parent 88556b5 commit aa97c3e

5 files changed

Lines changed: 178 additions & 24 deletions

File tree

site/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,29 @@
1313
</head>
1414
<body>
1515
<div id="app"></div>
16+
<div id="initial-loader" style="
17+
position: fixed; top: 50%; left: 50%;
18+
transform: translate(-50%, -50%);
19+
z-index: 9999;
20+
background: rgba(255,255,255,0.85);
21+
padding: 16px 28px;
22+
border-radius: 8px;
23+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
24+
font-size: 14px;
25+
display: flex;
26+
align-items: center;
27+
gap: 10px;
28+
">
29+
<div style="
30+
width: 20px; height: 20px;
31+
border: 2px solid #ddd;
32+
border-top-color: #2563eb;
33+
border-radius: 50%;
34+
animation: spin 0.8s linear infinite;
35+
"></div>
36+
Loading...
37+
</div>
38+
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
1639
<script type="module" src="/src/main.js"></script>
1740
</body>
1841
</html>

site/src/assets/styles.css

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ body,
3030
border-bottom: 1px solid #ddd;
3131
z-index: 100;
3232
flex-shrink: 0;
33-
flex-wrap: wrap;
33+
flex-wrap: nowrap;
34+
overflow: hidden;
3435
}
3536

3637
/* Brand logo */
@@ -61,6 +62,7 @@ body,
6162
font-size: 13px;
6263
border-radius: 4px;
6364
transition: all 0.15s;
65+
white-space: nowrap;
6466
}
6567

6668
.source-toggle button.active {
@@ -74,11 +76,106 @@ body,
7476
cursor: not-allowed;
7577
}
7678

79+
/* Source dropdown (narrow screens) */
80+
.source-select {
81+
display: none;
82+
padding: 6px 10px;
83+
border: 1px solid #ccc;
84+
border-radius: 4px;
85+
font-size: 13px;
86+
background: #fff;
87+
cursor: pointer;
88+
}
89+
90+
/* ≤ 600px: hide logo so the header stays on one line */
91+
@media (max-width: 600px) {
92+
.brand-logo-link { display: none; }
93+
}
94+
95+
/* Mobile basemap modal trigger button */
96+
.basemap-mobile-btn {
97+
display: none;
98+
padding: 4px 8px;
99+
border: 1px solid #ccc;
100+
border-radius: 4px;
101+
font-size: 12px;
102+
background: #fff;
103+
cursor: pointer;
104+
}
105+
106+
@media (max-width: 600px) {
107+
.basemap-switcher-desktop { display: none; }
108+
.basemap-mobile-btn { display: block; }
109+
}
110+
111+
/* Basemap modal */
112+
.basemap-modal-overlay {
113+
position: fixed;
114+
inset: 0;
115+
background: rgba(0, 0, 0, 0.4);
116+
z-index: 1000;
117+
display: flex;
118+
align-items: center;
119+
justify-content: center;
120+
}
121+
122+
.basemap-modal {
123+
background: #fff;
124+
border-radius: 10px;
125+
padding: 20px;
126+
min-width: 220px;
127+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
128+
display: flex;
129+
flex-direction: column;
130+
gap: 10px;
131+
}
132+
133+
.basemap-modal-title {
134+
font-size: 14px;
135+
font-weight: 600;
136+
color: #555;
137+
text-align: center;
138+
margin-bottom: 4px;
139+
}
140+
141+
.basemap-modal-option {
142+
padding: 12px 16px;
143+
border: 1px solid #ddd;
144+
border-radius: 6px;
145+
background: #f9f9f9;
146+
font-size: 14px;
147+
cursor: pointer;
148+
text-align: left;
149+
}
150+
151+
.basemap-modal-option.active {
152+
background: #2563eb;
153+
color: #fff;
154+
border-color: #2563eb;
155+
}
156+
157+
/* ≤ 560px: shorten button labels */
158+
@media (max-width: 560px) {
159+
.overture-suffix { display: none; }
160+
.osm-long { display: none; }
161+
}
162+
163+
.osm-short { display: none; }
164+
@media (max-width: 560px) {
165+
.osm-short { display: inline; }
166+
}
167+
168+
/* ≤ 480px: switch buttons → dropdown */
169+
@media (max-width: 480px) {
170+
.source-toggle { display: none; }
171+
.source-select { display: block; }
172+
}
173+
77174
/* Search bar */
78175
.search-container {
79176
position: relative;
80177
flex: 1;
81-
min-width: 200px;
178+
min-width: 120px;
82179
max-width: 400px;
83180
}
84181

@@ -253,6 +350,7 @@ body,
253350
cursor: pointer;
254351
display: flex;
255352
justify-content: space-between;
353+
gap: 6px;
256354
align-items: center;
257355
border-bottom: 1px solid #eee;
258356
user-select: none;

site/src/components/MapContainer.vue

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,35 @@
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 {
4562
import Map from 'ol/Map'
4663
import View from 'ol/View'
4764
import Overlay from 'ol/Overlay'
48-
import TileLayer from 'ol/layer/Tile'
49-
import XYZ from 'ol/source/XYZ'
5065
import { fromLonLat, transformExtent } from 'ol/proj'
5166
import { apply } from 'ol-mapbox-style'
5267
import PoiPopup from './PoiPopup.vue'
@@ -95,6 +110,7 @@ const popupOverlay = shallowRef(null)
95110
const selectedFeature = shallowRef(null)
96111
const loading = ref(false)
97112
const selectedStyle = ref('positron')
113+
const basemapModalOpen = ref(false)
98114
const currentZoom = ref(INITIAL_ZOOM)
99115
const 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
226242
async 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>

site/src/components/SourceToggle.vue

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
<template>
2+
<!-- Dropdown for narrow screens -->
3+
<select
4+
class="source-select"
5+
:value="activeSource"
6+
@change="$emit('update:source', $event.target.value)"
7+
>
8+
<option value="osm">OpenStreetMap</option>
9+
<option value="overture">Overture Maps</option>
10+
<option value="conflated">Conflated</option>
11+
</select>
12+
13+
<!-- Buttons for wider screens -->
214
<div class="source-toggle">
315
<button
416
:class="{ active: activeSource === 'osm' }"
517
@click="$emit('update:source', 'osm')"
618
>
7-
OpenStreetMap
19+
<span class="osm-short">OSM</span>
20+
<span class="osm-long">OpenStreetMap</span>
821
</button>
922
<button
1023
:class="{ active: activeSource === 'overture' }"
1124
@click="$emit('update:source', 'overture')"
1225
>
13-
Overture Maps
26+
Overture<span class="overture-suffix"> Maps</span>
1427
</button>
1528
<button
1629
:class="{ active: activeSource === 'conflated' }"

site/src/constants.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,6 @@ export const CLUSTER_MAX_ZOOM = 12
167167
export const STADIA_GEOCODING_URL =
168168
'https://api.stadiamaps.com/geocoding/v1/search'
169169

170-
// Initial map view — New York City (fallback if geolocation is denied)
171-
export const INITIAL_CENTER = [-74.006, 40.7128]
172-
export const INITIAL_ZOOM = 14
170+
// Initial map view — Times Square (fallback if geolocation is denied)
171+
export const INITIAL_CENTER = [-73.9855, 40.758]
172+
export const INITIAL_ZOOM = 18

0 commit comments

Comments
 (0)