feat: replace Place dropdowns with search input + forced map view

Hijacks #location-selection in Immich's search filter modal: hides
country/state/city comboboxes, injects a text input that opens the
live-search overlay directly in map view for pan/zoom exploration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-17 12:14:47 -04:00
parent 38bdcf25dc
commit 40dd22e3ad
1 changed files with 93 additions and 1 deletions

View File

@ -255,11 +255,24 @@
}
// --- Search ---
async function doSearch(query) {
async function doSearch(query, opts) {
if (abortCtrl) abortCtrl.abort();
abortCtrl = new AbortController();
currentQuery = query;
if (opts && opts.forceMap) {
currentView = 'map';
updateToggleButtons();
const gridEl = panel ? panel.querySelector('.ls-grid') : null;
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';
mapWrap.style.display = '';
bodyEl.style.overflowY = 'hidden';
}
}
const statusEl = getOrCreatePanel().querySelector('.ls-status');
const gridEl = getOrCreatePanel().querySelector('.ls-grid');
gridEl.innerHTML = '';
@ -625,6 +638,7 @@
// Initial scan
scanForSearchInputs();
hijackLocationSection();
// Watch for dynamically added inputs (SPA navigation)
const observer = new MutationObserver((mutations) => {
@ -638,9 +652,87 @@
}
}
}
// Always re-check for the Place section (modal mounts/unmounts)
hijackLocationSection();
});
observer.observe(document.body, { childList: true, subtree: true });
// --- Replace the search filter modal's Place section (Country/State/City
// dropdowns) with a single text input that opens the live-search overlay
// directly in map view.
function hijackLocationSection() {
const section = document.getElementById('location-selection');
if (!section || section.dataset.lsHijacked === '1') return;
section.dataset.lsHijacked = '1';
// Hide every original child (heading + combobox grid)
for (const child of Array.from(section.children)) {
child.style.display = 'none';
}
const wrap = document.createElement('div');
wrap.className = 'ls-place-hijack';
wrap.innerHTML = `
<label class="ls-place-label">Location search</label>
<div class="ls-place-row">
<input type="search" class="ls-place-input"
placeholder="Search a place (city, country, landmark)…"
autocomplete="off" />
<button type="button" class="ls-place-go">Map it</button>
</div>
<div class="ls-place-hint">Results open on a map pan and zoom to explore nearby spots.</div>
`;
section.appendChild(wrap);
if (!document.getElementById('ls-place-styles')) {
const s = document.createElement('style');
s.id = 'ls-place-styles';
s.textContent = `
.ls-place-hijack { display: flex; flex-direction: column; gap: 6px; }
.ls-place-label { font-weight: 600; font-size: 14px; }
.ls-place-row { display: flex; gap: 8px; }
.ls-place-input {
flex: 1; padding: 10px 12px; border-radius: 8px;
border: 1px solid var(--immich-ui-gray-400, #bbb);
background: var(--immich-bg, transparent); color: inherit;
font-size: 14px;
}
.ls-place-input:focus { outline: 2px solid #e94560; outline-offset: 1px; }
.ls-place-go {
padding: 0 14px; border-radius: 8px; border: 1px solid #e94560;
background: #e94560; color: #fff; font-weight: 600; cursor: pointer;
}
.ls-place-go:hover { filter: brightness(1.1); }
.ls-place-hint { font-size: 12px; opacity: 0.7; }
`;
document.head.appendChild(s);
}
const input = wrap.querySelector('.ls-place-input');
const btn = wrap.querySelector('.ls-place-go');
const go = () => {
const q = input.value.trim();
if (q.length < MIN_CHARS) return;
doSearch(q, { forceMap: true });
};
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
go();
}
});
btn.addEventListener('click', (e) => {
e.preventDefault();
go();
});
// Autofocus once visible
setTimeout(() => { try { input.focus(); } catch {} }, 50);
}
// Global Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && panel?.classList.contains('active')) {