From e2025126a8d4a6c933346703237172cfdcfea024 Mon Sep 17 00:00:00 2001 From: Shawn Date: Thu, 21 May 2026 22:53:14 -0400 Subject: [PATCH] v3: GPS tap-to-enable + server-side EXIF bypass for manual creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPS: - initGPS() now shows tappable GPS badge instead of failing silently - Badge pulses amber with '📍 Tap for GPS' — click triggers permission prompt - fillGpsParking() auto-triggers badge click if GPS not yet available - requestLocation() extracted as reusable Promise EXIF bypass: - submitManualAsset() now uses server-extracted GPS from raw upload bytes - Server returns exif_gps in /api/upload/photo response (was already working) - Client was ignoring it — now fills parking + map link fields automatically - Bypasses Android content provider EXIF stripping since server reads raw bytes Admin separation: - admin.canteen.ourpad.casa now served by systemd service (canteen-admin.service) - Main server also systemd (canteen-main.service) — both auto-restart on crash/boot --- static/index.html | 85 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/static/index.html b/static/index.html index ac76173..6867fce 100644 --- a/static/index.html +++ b/static/index.html @@ -89,11 +89,18 @@ .gps-badge { display: inline-flex; align-items: center; gap: 3px; font-size: 10px; font-weight: 600; padding: 4px 8px; border-radius: 20px; - white-space: nowrap; + white-space: nowrap; transition: 0.2s; } .gps-badge.ok { background: var(--green-bg); color: var(--green); } .gps-badge.waiting{ background: var(--amber-bg); color: var(--amber); } .gps-badge.err { background: var(--red-bg); color: var(--red); } + .gps-badge.waiting:hover, + .gps-badge.err:hover { filter: brightness(1.15); } + .gps-badge.waiting { animation: pulse-gps 2s ease-in-out infinite; } + @keyframes pulse-gps { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.65; } + } .user-badge { width: 30px; height: 30px; border-radius: 50%; background: var(--accent-bg); color: var(--accent2); @@ -755,7 +762,7 @@

📦 Canteen Assets

- 📍 GPS... + 📍 Tap GPS ⬇️ 0
?
@@ -1463,27 +1470,55 @@ // ========================================================================= // GPS // ========================================================================= + function requestLocation() { + return new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition( + pos => { + AppState.gpsLat = pos.coords.latitude; + AppState.gpsLng = pos.coords.longitude; + AppState.gpsAcc = pos.coords.accuracy; + const badge = document.getElementById('gpsBadge'); + badge.className = 'gps-badge ok'; + badge.textContent = `📍 ${AppState.gpsLat.toFixed(4)}, ${AppState.gpsLng.toFixed(4)}`; + resolve(pos); + }, + err => { + const badge = document.getElementById('gpsBadge'); + badge.className = 'gps-badge err'; + if (err.code === 1) badge.textContent = '📍 Tap for GPS'; + else badge.textContent = '📍 ' + err.message; + reject(err); + }, + { enableHighAccuracy: true, timeout: 15000, maximumAge: 300000 } + ); + }); + } + function initGPS() { const badge = document.getElementById('gpsBadge'); if (!navigator.geolocation) { badge.className = 'gps-badge err'; badge.textContent = '📍 Unavailable'; + badge.style.cursor = 'default'; return; } - navigator.geolocation.getCurrentPosition( - pos => { - AppState.gpsLat = pos.coords.latitude; - AppState.gpsLng = pos.coords.longitude; - AppState.gpsAcc = pos.coords.accuracy; - badge.className = 'gps-badge ok'; - badge.textContent = `📍 ${AppState.gpsLat.toFixed(4)}, ${AppState.gpsLng.toFixed(4)}`; - }, - err => { - badge.className = 'gps-badge err'; - badge.textContent = '📍 ' + err.message; - }, - { enableHighAccuracy: true, timeout: 15000, maximumAge: 300000 } - ); + // Show a tappable hint so user knows to interact + badge.className = 'gps-badge waiting'; + badge.textContent = '📍 Tap for GPS'; + badge.style.cursor = 'pointer'; + badge.onclick = function() { + badge.textContent = '📍 Locating...'; + badge.onclick = null; // one-shot + requestLocation().catch(() => { + badge.onclick = function() { requestLocation().catch(() => {}); }; + badge.style.cursor = 'pointer'; + badge.textContent = '📍 Tap to retry'; + }); + }; + // Also try silently once in case browser allows it without gesture + requestLocation().catch(() => { + // Silent fail is expected on mobile — badge click handler covers it + }); } // ========================================================================= @@ -2637,6 +2672,17 @@ fd.append('file', manualPhotoFile, manualPhotoFile.name || 'photo.jpg'); const upData = await api('/api/upload/photo', { method: 'POST', body: fd }); photoPath = upData.path; + // Use server-extracted GPS from raw bytes (bypasses client-side EXIF stripping) + if (upData.exif_gps) { + const coords = upData.exif_gps.lat.toFixed(6) + ', ' + upData.exif_gps.lng.toFixed(6); + if (!document.getElementById('manParkingLocation').value.trim()) { + document.getElementById('manParkingLocation').value = coords; + } + if (!document.getElementById('manMapLink').value.trim()) { + document.getElementById('manMapLink').value = `https://www.openstreetmap.org/?mlat=${upData.exif_gps.lat}&mlon=${upData.exif_gps.lng}&zoom=17`; + } + showToast('📍 GPS extracted from photo!'); + } } catch (e) { /* photo upload failed, continue without */ } } @@ -2764,11 +2810,14 @@ function fillGpsParking(inputId) { if (!AppState.gpsLat) { - showToast('GPS not available — allow location access', true); + showToast('📍 Tap the GPS badge in the header to enable location', true); + // Also trigger the GPS badge click if it exists + const badge = document.getElementById('gpsBadge'); + if (badge && badge.onclick) badge.onclick(); return; } document.getElementById(inputId).value = AppState.gpsLat.toFixed(6) + ', ' + AppState.gpsLng.toFixed(6); - showToast('GPS coords filled!'); + showToast('📍 GPS coords filled!'); } // Settings are loaded after auth (in doLogin / initAuth)