mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
feat: Add location settings for ISS pass predictions
- Add Location tab to settings modal with lat/lon inputs - Add GPS detection button for auto-location - Update SSTV to use saved location for ISS pass predictions - Fix SSTV panels to use full screen width (remove max-width constraint) - Improve ISS pass messages to guide users to location settings - Add checked/last_check fields to update status response Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 = '<span style="opacity: 0.7;">Detecting...</span>';
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = `
|
||||
<div class="sstv-iss-info">
|
||||
<svg class="sstv-iss-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -348,8 +360,8 @@ const SSTV = (function() {
|
||||
</svg>
|
||||
<div class="sstv-iss-details">
|
||||
<div class="sstv-iss-label">Next ISS Pass</div>
|
||||
<div class="sstv-iss-value">Unknown - Set location in settings</div>
|
||||
<div class="sstv-iss-note">Check ARISS.org for SSTV event schedules</div>
|
||||
<div class="sstv-iss-value">${locationMsg}</div>
|
||||
<div class="sstv-iss-note">${noteMsg}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -443,6 +455,7 @@ const SSTV = (function() {
|
||||
start,
|
||||
stop,
|
||||
loadImages,
|
||||
loadIssSchedule,
|
||||
showImage,
|
||||
closeImage
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
<div class="settings-tabs">
|
||||
<button class="settings-tab active" data-tab="offline" onclick="switchSettingsTab('offline')">Offline</button>
|
||||
<button class="settings-tab" data-tab="location" onclick="switchSettingsTab('location')">Location</button>
|
||||
<button class="settings-tab" data-tab="display" onclick="switchSettingsTab('display')">Display</button>
|
||||
<button class="settings-tab" data-tab="updates" onclick="switchSettingsTab('updates')">Updates</button>
|
||||
<button class="settings-tab" data-tab="tools" onclick="switchSettingsTab('tools')">Tools</button>
|
||||
@@ -119,6 +120,72 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location Section -->
|
||||
<div id="settings-location" class="settings-section">
|
||||
<div class="settings-group">
|
||||
<div class="settings-group-title">Observer Location</div>
|
||||
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
|
||||
Set your geographic coordinates for satellite pass predictions and ISS tracking.
|
||||
</p>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Latitude</span>
|
||||
<span class="settings-label-desc">Decimal degrees (-90 to 90)</span>
|
||||
</div>
|
||||
<input type="number" id="observerLatInput" class="settings-input"
|
||||
step="0.0001" min="-90" max="90" placeholder="51.5074"
|
||||
style="width: 120px; text-align: right;">
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Longitude</span>
|
||||
<span class="settings-label-desc">Decimal degrees (-180 to 180)</span>
|
||||
</div>
|
||||
<input type="number" id="observerLonInput" class="settings-input"
|
||||
step="0.0001" min="-180" max="180" placeholder="-0.1278"
|
||||
style="width: 120px; text-align: right;">
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px; margin-top: 15px;">
|
||||
<button class="check-assets-btn" onclick="detectLocationGPS()" style="flex: 1;">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px; vertical-align: -2px; margin-right: 5px;">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<line x1="12" y1="2" x2="12" y2="6"/>
|
||||
<line x1="12" y1="18" x2="12" y2="22"/>
|
||||
<line x1="2" y1="12" x2="6" y2="12"/>
|
||||
<line x1="18" y1="12" x2="22" y2="12"/>
|
||||
</svg>
|
||||
Use GPS
|
||||
</button>
|
||||
<button class="check-assets-btn" onclick="saveObserverLocation()" style="flex: 1; background: var(--accent-cyan); color: #000;">
|
||||
Save Location
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="settings-group-title">Current Location</div>
|
||||
<div id="currentLocationDisplay" style="padding: 12px; background: var(--bg-tertiary); border-radius: 6px; font-family: 'JetBrains Mono', monospace; font-size: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
|
||||
<span style="color: var(--text-dim);">Latitude</span>
|
||||
<span id="currentLatDisplay" style="color: var(--accent-cyan);">Not set</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span style="color: var(--text-dim);">Longitude</span>
|
||||
<span id="currentLonDisplay" style="color: var(--accent-cyan);">Not set</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-info">
|
||||
<strong>Note:</strong> Location is used for ISS pass predictions in SSTV mode and satellite tracking.
|
||||
Your location is stored locally and never sent to external servers.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Display Section -->
|
||||
<div id="settings-display" class="settings-section">
|
||||
<div class="settings-group">
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user