feat: split map view into 50/50 map + bounds-filtered photo grid

On map view the body container now shows the Leaflet map on one half
and a photo grid on the other half. The grid renders only photos
whose GPS falls inside the map's current bounds, and re-renders on
moveend/zoomend so panning and zooming filters the visible photo set
live. Stacks vertically on narrow viewports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-17 12:49:25 -04:00
parent b4e801e17f
commit 96d9a6971c
1 changed files with 53 additions and 4 deletions

View File

@ -135,6 +135,29 @@
flex: 1; overflow-y: auto; padding: 0 20px 20px;
max-width: 1440px; width: 100%; margin: 0 auto;
}
#live-search-panel .ls-body.ls-split {
display: flex; flex-direction: row; gap: 12px;
overflow: hidden;
}
#live-search-panel .ls-body.ls-split > .ls-map-wrap {
flex: 0 0 50%; height: 100%; min-height: 0;
}
#live-search-panel .ls-body.ls-split > .ls-grid {
flex: 1 1 50%; overflow-y: auto; align-content: start;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
}
@media (max-width: 768px) {
#live-search-panel .ls-body.ls-split {
flex-direction: column;
}
#live-search-panel .ls-body.ls-split > .ls-map-wrap,
#live-search-panel .ls-body.ls-split > .ls-grid {
flex: 1 1 50%;
}
#live-search-panel .ls-body.ls-split > .ls-grid {
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
}
}
#live-search-panel .ls-grid {
display: grid;
@ -235,14 +258,17 @@
const bodyEl = panel.querySelector('.ls-body');
if (view === 'grid') {
bodyEl.classList.remove('ls-split');
gridEl.style.display = '';
mapWrap.style.display = 'none';
bodyEl.style.overflowY = 'auto';
renderGridItems(currentItems);
} else {
gridEl.style.display = 'none';
// Map view = 50/50 split: map on one side, bounds-filtered photos on the other
bodyEl.classList.add('ls-split');
gridEl.style.display = '';
mapWrap.style.display = '';
bodyEl.style.overflowY = 'hidden';
bodyEl.style.overflowY = '';
showMap(currentItems);
}
}
@ -267,9 +293,10 @@
const mapWrap = panel ? panel.querySelector('.ls-map-wrap') : null;
const bodyEl = panel ? panel.querySelector('.ls-body') : null;
if (gridEl && mapWrap && bodyEl) {
gridEl.style.display = 'none';
bodyEl.classList.add('ls-split');
gridEl.style.display = '';
mapWrap.style.display = '';
bodyEl.style.overflowY = 'hidden';
bodyEl.style.overflowY = '';
}
}
@ -545,6 +572,28 @@
// Fix map sizing (Leaflet needs this after dynamic show)
setTimeout(() => mapInstance.invalidateSize(), 100);
// Bounds-filtered grid on the other half of the split. Updates live
// as the user pans/zooms so "photos taken on that part of the map"
// stay in sync with the visible viewport.
const updateGridFromBounds = () => {
if (currentView !== 'map') return;
const b = mapInstance.getBounds();
const visible = geoItems.filter(i =>
b.contains([i.exifInfo.latitude, i.exifInfo.longitude])
);
renderGridItems(visible);
const statusEl = panel.querySelector('.ls-status');
if (statusEl) {
const totalGeo = geoItems.length;
statusEl.textContent =
visible.length + ' of ' + totalGeo + ' shown in view';
}
};
mapInstance.on('moveend', updateGridFromBounds);
mapInstance.on('zoomend', updateGridFromBounds);
// Initial population once bounds settle
setTimeout(updateGridFromBounds, 150);
// Add custom label style
if (!document.getElementById('ls-marker-styles')) {
const s = document.createElement('style');