diff --git a/static/css/modes/sstv.css b/static/css/modes/sstv.css index f433d6b..9102b6d 100644 --- a/static/css/modes/sstv.css +++ b/static/css/modes/sstv.css @@ -164,8 +164,7 @@ display: flex; flex-direction: column; flex: 1; - min-width: 340px; - max-width: 400px; + min-width: 300px; } .sstv-live-header { @@ -290,8 +289,8 @@ overflow: hidden; display: flex; flex-direction: column; - flex: 2; - min-width: 0; + flex: 1.5; + min-width: 300px; } .sstv-gallery-header { diff --git a/static/js/core/settings-manager.js b/static/js/core/settings-manager.js index 290d1e5..b67c834 100644 --- a/static/js/core/settings-manager.js +++ b/static/js/core/settings-manager.js @@ -540,6 +540,133 @@ document.addEventListener('DOMContentLoaded', () => { Settings.init(); }); +// ============================================================================= +// Location Settings Functions +// ============================================================================= + +/** + * Load and display current observer location + */ +function loadObserverLocation() { + const lat = localStorage.getItem('observerLat'); + const lon = localStorage.getItem('observerLon'); + + const latInput = document.getElementById('observerLatInput'); + const lonInput = document.getElementById('observerLonInput'); + const currentLatDisplay = document.getElementById('currentLatDisplay'); + const currentLonDisplay = document.getElementById('currentLonDisplay'); + + if (latInput && lat) latInput.value = lat; + if (lonInput && lon) lonInput.value = lon; + + if (currentLatDisplay) { + currentLatDisplay.textContent = lat ? parseFloat(lat).toFixed(4) + '°' : 'Not set'; + } + if (currentLonDisplay) { + currentLonDisplay.textContent = lon ? parseFloat(lon).toFixed(4) + '°' : 'Not set'; + } +} + +/** + * Detect location using browser GPS + */ +function detectLocationGPS() { + const latInput = document.getElementById('observerLatInput'); + const lonInput = document.getElementById('observerLonInput'); + + if (!navigator.geolocation) { + if (typeof showNotification === 'function') { + showNotification('Location', 'GPS not available in this browser'); + } else { + alert('GPS not available in this browser'); + } + return; + } + + // Show loading state + const btn = event.target.closest('button'); + const originalText = btn.innerHTML; + btn.innerHTML = 'Detecting...'; + btn.disabled = true; + + navigator.geolocation.getCurrentPosition( + (pos) => { + if (latInput) latInput.value = pos.coords.latitude.toFixed(4); + if (lonInput) lonInput.value = pos.coords.longitude.toFixed(4); + + btn.innerHTML = originalText; + btn.disabled = false; + + if (typeof showNotification === 'function') { + showNotification('Location', 'GPS coordinates detected'); + } + }, + (err) => { + btn.innerHTML = originalText; + btn.disabled = false; + + let message = 'Failed to get location'; + if (err.code === 1) message = 'Location access denied'; + else if (err.code === 2) message = 'Location unavailable'; + else if (err.code === 3) message = 'Location request timed out'; + + if (typeof showNotification === 'function') { + showNotification('Location', message); + } else { + alert(message); + } + }, + { enableHighAccuracy: true, timeout: 10000 } + ); +} + +/** + * Save observer location to localStorage + */ +function saveObserverLocation() { + const latInput = document.getElementById('observerLatInput'); + const lonInput = document.getElementById('observerLonInput'); + + const lat = parseFloat(latInput?.value); + const lon = parseFloat(lonInput?.value); + + if (isNaN(lat) || lat < -90 || lat > 90) { + if (typeof showNotification === 'function') { + showNotification('Location', 'Invalid latitude (must be -90 to 90)'); + } else { + alert('Invalid latitude (must be -90 to 90)'); + } + return; + } + + if (isNaN(lon) || lon < -180 || lon > 180) { + if (typeof showNotification === 'function') { + showNotification('Location', 'Invalid longitude (must be -180 to 180)'); + } else { + alert('Invalid longitude (must be -180 to 180)'); + } + return; + } + + localStorage.setItem('observerLat', lat.toString()); + localStorage.setItem('observerLon', lon.toString()); + + // Update display + const currentLatDisplay = document.getElementById('currentLatDisplay'); + const currentLonDisplay = document.getElementById('currentLonDisplay'); + if (currentLatDisplay) currentLatDisplay.textContent = lat.toFixed(4) + '°'; + if (currentLonDisplay) currentLonDisplay.textContent = lon.toFixed(4) + '°'; + + if (typeof showNotification === 'function') { + showNotification('Location', 'Observer location saved'); + } + + // Refresh SSTV ISS schedule if available + if (typeof SSTV !== 'undefined' && typeof SSTV.loadIssSchedule === 'function') { + SSTV.loadIssSchedule(); + } +} + // ============================================================================= // Update Settings Functions // ============================================================================= @@ -709,5 +836,7 @@ function switchSettingsTab(tabName) { loadSettingsTools(); } else if (tabName === 'updates') { loadUpdateStatus(); + } else if (tabName === 'location') { + loadObserverLocation(); } } diff --git a/static/js/modes/sstv.js b/static/js/modes/sstv.js index 856c581..cec3fcb 100644 --- a/static/js/modes/sstv.js +++ b/static/js/modes/sstv.js @@ -312,33 +312,45 @@ const SSTV = (function() { * Load ISS pass schedule */ async function loadIssSchedule() { - // Try to get user's location - const lat = localStorage.getItem('observerLat') || 51.5074; - const lon = localStorage.getItem('observerLon') || -0.1278; + // Try to get user's location from settings + const storedLat = localStorage.getItem('observerLat'); + const storedLon = localStorage.getItem('observerLon'); + + // Check if location is actually set + const hasLocation = storedLat !== null && storedLon !== null; + const lat = storedLat || 51.5074; + const lon = storedLon || -0.1278; try { const response = await fetch(`/sstv/iss-schedule?latitude=${lat}&longitude=${lon}&hours=48`); const data = await response.json(); if (data.status === 'ok' && data.passes && data.passes.length > 0) { - renderIssInfo(data.passes[0]); + renderIssInfo(data.passes[0], hasLocation); } else { - renderIssInfo(null); + renderIssInfo(null, hasLocation); } } catch (err) { console.error('Failed to load ISS schedule:', err); - renderIssInfo(null); + renderIssInfo(null, hasLocation); } } /** * Render ISS pass info */ - function renderIssInfo(nextPass) { + function renderIssInfo(nextPass, hasLocation = true) { const container = document.getElementById('sstvIssInfo'); if (!container) return; if (!nextPass) { + const locationMsg = hasLocation + ? 'No passes in next 48 hours' + : 'Set location in Settings > Location tab'; + const noteMsg = hasLocation + ? 'Check ARISS.org for SSTV event schedules' + : 'Click the gear icon to open Settings'; + container.innerHTML = `
@@ -348,8 +360,8 @@ const SSTV = (function() {
Next ISS Pass
-
Unknown - Set location in settings
-
Check ARISS.org for SSTV event schedules
+
${locationMsg}
+
${noteMsg}
`; @@ -443,6 +455,7 @@ const SSTV = (function() { start, stop, loadImages, + loadIssSchedule, showImage, closeImage }; diff --git a/templates/partials/settings-modal.html b/templates/partials/settings-modal.html index a3d66db..5692f80 100644 --- a/templates/partials/settings-modal.html +++ b/templates/partials/settings-modal.html @@ -11,6 +11,7 @@
+ @@ -119,6 +120,72 @@
+ +
+
+
Observer Location
+

+ Set your geographic coordinates for satellite pass predictions and ISS tracking. +

+ +
+
+ Latitude + Decimal degrees (-90 to 90) +
+ +
+ +
+
+ Longitude + Decimal degrees (-180 to 180) +
+ +
+ +
+ + +
+
+ +
+
Current Location
+
+
+ Latitude + Not set +
+
+ Longitude + Not set +
+
+
+ +
+ Note: Location is used for ISS pass predictions in SSTV mode and satellite tracking. + Your location is stored locally and never sent to external servers. +
+
+
diff --git a/utils/updater.py b/utils/updater.py index 7791bc2..0b9b902 100644 --- a/utils/updater.py +++ b/utils/updater.py @@ -174,6 +174,7 @@ def check_for_updates(force: bool = False) -> dict[str, Any]: return { 'success': True, + 'checked': True, 'update_available': update_available, 'show_notification': show_notification, 'current_version': current_version, @@ -196,6 +197,7 @@ def check_for_updates(force: bool = False) -> dict[str, Any]: update_available = _compare_versions(current_version, cached_version) < 0 return { 'success': True, + 'checked': True, 'update_available': update_available, 'current_version': current_version, 'latest_version': cached_version, @@ -223,6 +225,7 @@ def check_for_updates(force: bool = False) -> dict[str, Any]: return { 'success': True, + 'checked': True, 'update_available': update_available, 'show_notification': show_notification, 'current_version': current_version, @@ -231,7 +234,8 @@ def check_for_updates(force: bool = False) -> dict[str, Any]: 'release_notes': release['body'] or '', 'release_name': release['name'] or f'v{latest_version}', 'published_at': release['published_at'], - 'cached': False + 'cached': False, + 'last_check': datetime.now().isoformat() }