From 40dd22e3ad757725ed4aeec18b91f0435767cc38 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 17 Apr 2026 12:14:47 -0400 Subject: [PATCH] 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) --- search-app/live-search.js | 94 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/search-app/live-search.js b/search-app/live-search.js index c41344e..b0fe736 100644 --- a/search-app/live-search.js +++ b/search-app/live-search.js @@ -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 = ` + +
+ + +
+
Results open on a map — pan and zoom to explore nearby spots.
+ `; + 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')) {