mirror of
https://github.com/smittix/intercept.git
synced 2026-06-09 06:31:55 -07:00
Add global timezone/12h-24h setting and improve satellite selection
Global time preferences (Settings > Display > Time & Timezone): - InterceptTime utility in core/utils.js with timezone + 12h/24h support - Timezone options: UTC, Local, Eastern, Central, Mountain, Pacific - Time format: 12-hour (AM/PM) or 24-hour toggle - Defaults to US/Eastern + 12-hour - Header nav clock updates to use selected timezone and format - Weather satellite mode delegates to global InterceptTime - Settings persist via localStorage, change listeners notify all modes Weather satellite improvements: - Satellite dropdown defaults to "All Meteor Satellites" showing all passes - Can still filter to specific satellite (M2-3, M2-4, M2-4-80K) - Capture button on pass cards auto-selects the correct satellite Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-3
@@ -77,8 +77,23 @@ function declineDisclaimer() {
|
||||
|
||||
function updateHeaderClock() {
|
||||
const now = new Date();
|
||||
const utc = now.toISOString().substring(11, 19);
|
||||
document.getElementById('headerUtcTime').textContent = utc;
|
||||
const el = document.getElementById('headerUtcTime');
|
||||
const label = document.querySelector('.utc-label');
|
||||
if (typeof InterceptTime !== 'undefined') {
|
||||
if (el) el.textContent = InterceptTime.fullTime(now);
|
||||
if (label) label.textContent = InterceptTime.getLabel() || 'LOCAL';
|
||||
} else {
|
||||
if (el) el.textContent = now.toISOString().substring(11, 19);
|
||||
}
|
||||
}
|
||||
|
||||
function initTimeSettings() {
|
||||
const tzSelect = document.getElementById('globalTimezoneSelect');
|
||||
const fmtSelect = document.getElementById('globalTimeFormatSelect');
|
||||
if (typeof InterceptTime !== 'undefined') {
|
||||
if (tzSelect) tzSelect.value = InterceptTime.getTimezone();
|
||||
if (fmtSelect) fmtSelect.value = InterceptTime.getHour12() ? '12' : '24';
|
||||
}
|
||||
}
|
||||
|
||||
// ============== MODE SWITCHING ==============
|
||||
@@ -447,7 +462,8 @@ function initApp() {
|
||||
// Load theme
|
||||
loadTheme();
|
||||
|
||||
// Start clock
|
||||
// Start clock and init time settings
|
||||
initTimeSettings();
|
||||
updateHeaderClock();
|
||||
setInterval(updateHeaderClock, 1000);
|
||||
|
||||
|
||||
@@ -55,6 +55,114 @@ function isValidChannel(ch) {
|
||||
|
||||
// ============== TIME FORMATTING ==============
|
||||
|
||||
/**
|
||||
* Global time preferences — timezone and 12h/24h format.
|
||||
* Stored in localStorage, used by all modes.
|
||||
*/
|
||||
const InterceptTime = (function() {
|
||||
const TZ_MAP = {
|
||||
'UTC': 'UTC',
|
||||
'local': undefined,
|
||||
'US/Eastern': 'America/New_York',
|
||||
'US/Central': 'America/Chicago',
|
||||
'US/Mountain': 'America/Denver',
|
||||
'US/Pacific': 'America/Los_Angeles',
|
||||
};
|
||||
|
||||
const TZ_LABELS = {
|
||||
'UTC': 'UTC',
|
||||
'local': '',
|
||||
'US/Eastern': 'ET',
|
||||
'US/Central': 'CT',
|
||||
'US/Mountain': 'MT',
|
||||
'US/Pacific': 'PT',
|
||||
};
|
||||
|
||||
let _timezone = localStorage.getItem('interceptTimezone') || 'US/Eastern';
|
||||
let _hour12 = (localStorage.getItem('interceptHour12') || 'true') === 'true';
|
||||
const _listeners = [];
|
||||
|
||||
function getTimezone() { return _timezone; }
|
||||
function getHour12() { return _hour12; }
|
||||
function getIANA() { return TZ_MAP[_timezone]; }
|
||||
function getLabel() { return TZ_LABELS[_timezone] || ''; }
|
||||
|
||||
function setTimezone(tz) {
|
||||
if (!TZ_MAP.hasOwnProperty(tz)) return;
|
||||
_timezone = tz;
|
||||
localStorage.setItem('interceptTimezone', tz);
|
||||
// Migrate weather-sat specific key
|
||||
localStorage.setItem('wxsatTimezone', tz);
|
||||
_notify();
|
||||
}
|
||||
|
||||
function setHour12(val) {
|
||||
_hour12 = !!val;
|
||||
localStorage.setItem('interceptHour12', _hour12 ? 'true' : 'false');
|
||||
_notify();
|
||||
}
|
||||
|
||||
function onChange(fn) { _listeners.push(fn); }
|
||||
function _notify() { _listeners.forEach(fn => { try { fn(); } catch(e) { console.error(e); } }); }
|
||||
|
||||
/**
|
||||
* Format a Date or ISO string for the global timezone.
|
||||
* @param {Date|string} input - Date object or ISO string
|
||||
* @param {object} [extraOpts] - Additional Intl.DateTimeFormat options
|
||||
* @returns {string}
|
||||
*/
|
||||
function format(input, extraOpts) {
|
||||
if (!input) return '--';
|
||||
try {
|
||||
const date = typeof input === 'string' ? new Date(input) : input;
|
||||
if (isNaN(date.getTime())) return typeof input === 'string' ? input : '--';
|
||||
const opts = { hour12: _hour12, ...extraOpts };
|
||||
const iana = getIANA();
|
||||
if (iana) opts.timeZone = iana;
|
||||
return date.toLocaleString(undefined, opts);
|
||||
} catch { return typeof input === 'string' ? input : '--'; }
|
||||
}
|
||||
|
||||
/** HH:MM (or h:MM AM/PM) */
|
||||
function shortTime(input) {
|
||||
return format(input, { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
/** HH:MM:SS */
|
||||
function fullTime(input) {
|
||||
return format(input, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
}
|
||||
|
||||
/** Mon 25, 14:30 (or 2:30 PM) */
|
||||
function dateTime(input) {
|
||||
return format(input, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
/** Mon 25, 2026 */
|
||||
function dateOnly(input) {
|
||||
const iana = getIANA();
|
||||
const opts = { year: 'numeric', month: 'short', day: 'numeric' };
|
||||
if (iana) opts.timeZone = iana;
|
||||
try {
|
||||
const date = typeof input === 'string' ? new Date(input) : input;
|
||||
return date.toLocaleDateString(undefined, opts);
|
||||
} catch { return '--'; }
|
||||
}
|
||||
|
||||
/** Short label like " ET" or " UTC" for appending to times */
|
||||
function tzSuffix() {
|
||||
const l = getLabel();
|
||||
return l ? ' ' + l : '';
|
||||
}
|
||||
|
||||
return {
|
||||
getTimezone, getHour12, getIANA, getLabel,
|
||||
setTimezone, setHour12, onChange,
|
||||
format, shortTime, fullTime, dateTime, dateOnly, tzSuffix,
|
||||
TZ_MAP, TZ_LABELS,
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Get relative time string from timestamp
|
||||
* @param {string} timestamp - Time string in HH:MM:SS format
|
||||
|
||||
@@ -38,74 +38,23 @@ const WeatherSat = (function() {
|
||||
let lastDecodeSatellite = null;
|
||||
let consoleFilter = 'all';
|
||||
|
||||
// Timezone support
|
||||
const TZ_MAP = {
|
||||
'UTC': 'UTC',
|
||||
'local': null, // browser default
|
||||
'US/Eastern': 'America/New_York',
|
||||
'US/Central': 'America/Chicago',
|
||||
'US/Mountain': 'America/Denver',
|
||||
'US/Pacific': 'America/Los_Angeles',
|
||||
};
|
||||
let selectedTimezone = localStorage.getItem('wxsatTimezone') || 'UTC';
|
||||
|
||||
/**
|
||||
* Format an ISO date string for the selected timezone.
|
||||
* @param {string} isoString - ISO 8601 date string
|
||||
* @param {object} [opts] - Additional Intl.DateTimeFormat options
|
||||
* @returns {string} Formatted date/time string
|
||||
*/
|
||||
function formatTimeForTZ(isoString, opts = {}) {
|
||||
if (!isoString) return '--';
|
||||
try {
|
||||
const date = new Date(isoString);
|
||||
if (isNaN(date.getTime())) return isoString;
|
||||
const tz = TZ_MAP[selectedTimezone];
|
||||
const defaults = { hour: '2-digit', minute: '2-digit', hour12: false };
|
||||
const options = { ...defaults, ...opts };
|
||||
if (tz) options.timeZone = tz;
|
||||
return date.toLocaleString(undefined, options);
|
||||
} catch {
|
||||
return isoString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a short time (HH:MM) for the selected timezone.
|
||||
*/
|
||||
// Timezone — delegates to global InterceptTime utility
|
||||
function formatShortTime(isoString) {
|
||||
return formatTimeForTZ(isoString, { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
return typeof InterceptTime !== 'undefined' ? InterceptTime.shortTime(isoString) : (isoString || '--');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date + time for the selected timezone.
|
||||
*/
|
||||
function formatDateTime(isoString) {
|
||||
return formatTimeForTZ(isoString, {
|
||||
month: 'short', day: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit', hour12: false
|
||||
});
|
||||
return typeof InterceptTime !== 'undefined' ? InterceptTime.dateTime(isoString) : (isoString || '--');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a short timezone label for display.
|
||||
*/
|
||||
function getTZLabel() {
|
||||
if (selectedTimezone === 'local') return '';
|
||||
if (selectedTimezone === 'UTC') return ' UTC';
|
||||
const labels = { 'US/Eastern': ' ET', 'US/Central': ' CT', 'US/Mountain': ' MT', 'US/Pacific': ' PT' };
|
||||
return labels[selectedTimezone] || '';
|
||||
return typeof InterceptTime !== 'undefined' ? InterceptTime.tzSuffix() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timezone and refresh all displays.
|
||||
*/
|
||||
function setTimezone(tz) {
|
||||
selectedTimezone = tz;
|
||||
localStorage.setItem('wxsatTimezone', tz);
|
||||
if (typeof InterceptTime !== 'undefined') InterceptTime.setTimezone(tz);
|
||||
const sel = document.getElementById('wxsatTimezone');
|
||||
if (sel && sel.value !== tz) sel.value = tz;
|
||||
// Refresh all time-dependent displays
|
||||
applyPassFilter();
|
||||
renderGallery();
|
||||
updateTimelineLabels();
|
||||
@@ -137,9 +86,9 @@ const WeatherSat = (function() {
|
||||
* Initialize the Weather Satellite mode
|
||||
*/
|
||||
function init() {
|
||||
// Restore timezone selector
|
||||
// Sync timezone selector with global setting
|
||||
const tzSel = document.getElementById('wxsatTimezone');
|
||||
if (tzSel) tzSel.value = selectedTimezone;
|
||||
if (tzSel && typeof InterceptTime !== 'undefined') tzSel.value = InterceptTime.getTimezone();
|
||||
|
||||
if (initialized) {
|
||||
checkStatus();
|
||||
@@ -154,6 +103,17 @@ const WeatherSat = (function() {
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
// Listen for global timezone/format changes
|
||||
if (typeof InterceptTime !== 'undefined') {
|
||||
InterceptTime.onChange(() => {
|
||||
const sel = document.getElementById('wxsatTimezone');
|
||||
if (sel) sel.value = InterceptTime.getTimezone();
|
||||
applyPassFilter();
|
||||
renderGallery();
|
||||
updateTimelineLabels();
|
||||
});
|
||||
}
|
||||
|
||||
checkStatus();
|
||||
loadImages();
|
||||
loadLocationInputs();
|
||||
@@ -1870,15 +1830,14 @@ const WeatherSat = (function() {
|
||||
return new Date(b.timestamp || 0) - new Date(a.timestamp || 0);
|
||||
});
|
||||
|
||||
// Group by date (timezone-aware)
|
||||
// Group by date (timezone-aware via global InterceptTime)
|
||||
const groups = {};
|
||||
sorted.forEach(img => {
|
||||
let dateKey = 'Unknown Date';
|
||||
if (img.timestamp) {
|
||||
const tz = TZ_MAP[selectedTimezone];
|
||||
const opts = { year: 'numeric', month: 'short', day: 'numeric' };
|
||||
if (tz) opts.timeZone = tz;
|
||||
dateKey = new Date(img.timestamp).toLocaleDateString(undefined, opts);
|
||||
dateKey = typeof InterceptTime !== 'undefined'
|
||||
? InterceptTime.dateOnly(img.timestamp)
|
||||
: new Date(img.timestamp).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
}
|
||||
if (!groups[dateKey]) groups[dateKey] = [];
|
||||
groups[dateKey].push(img);
|
||||
@@ -2214,10 +2173,9 @@ const WeatherSat = (function() {
|
||||
|
||||
const type = logType || 'info';
|
||||
const now = new Date();
|
||||
const tz = TZ_MAP[selectedTimezone];
|
||||
const tsOpts = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||
if (tz) tsOpts.timeZone = tz;
|
||||
const ts = now.toLocaleTimeString(undefined, tsOpts);
|
||||
const ts = typeof InterceptTime !== 'undefined'
|
||||
? InterceptTime.fullTime(now)
|
||||
: now.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `wxsat-console-entry wxsat-log-${type}`;
|
||||
|
||||
@@ -84,7 +84,8 @@
|
||||
<div class="form-group">
|
||||
<label>Select Satellite</label>
|
||||
<select id="weatherSatSelect" class="mode-select">
|
||||
<option value="METEOR-M2-3" selected>Meteor-M2-3 (137.900 MHz LRPT)</option>
|
||||
<option value="" selected>All Meteor Satellites</option>
|
||||
<option value="METEOR-M2-3">Meteor-M2-3 (137.900 MHz LRPT)</option>
|
||||
<option value="METEOR-M2-4">Meteor-M2-4 (137.900 MHz LRPT)</option>
|
||||
<option value="METEOR-M2-4-80K">Meteor-M2-4 80k baud (fallback)</option>
|
||||
</select>
|
||||
|
||||
@@ -540,9 +540,14 @@
|
||||
window._navClockStarted = true;
|
||||
function updateNavUtcClock() {
|
||||
const now = new Date();
|
||||
const utc = now.toISOString().slice(11, 19);
|
||||
const el = document.getElementById('headerUtcTime');
|
||||
if (el) el.textContent = utc;
|
||||
const label = document.querySelector('.utc-label');
|
||||
if (typeof InterceptTime !== 'undefined') {
|
||||
if (el) el.textContent = InterceptTime.fullTime(now);
|
||||
if (label) label.textContent = InterceptTime.getLabel() || 'LOCAL';
|
||||
} else {
|
||||
if (el) el.textContent = now.toISOString().slice(11, 19);
|
||||
}
|
||||
}
|
||||
setInterval(updateNavUtcClock, 1000);
|
||||
updateNavUtcClock();
|
||||
|
||||
@@ -216,6 +216,36 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="settings-group-title">Time & Timezone</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Timezone</span>
|
||||
<span class="settings-label-desc">Applied across all modes</span>
|
||||
</div>
|
||||
<select id="globalTimezoneSelect" class="settings-select" onchange="InterceptTime.setTimezone(this.value)">
|
||||
<option value="UTC">UTC</option>
|
||||
<option value="local">Local (browser)</option>
|
||||
<option value="US/Eastern">Eastern (ET)</option>
|
||||
<option value="US/Central">Central (CT)</option>
|
||||
<option value="US/Mountain">Mountain (MT)</option>
|
||||
<option value="US/Pacific">Pacific (PT)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-label">
|
||||
<span class="settings-label-text">Time Format</span>
|
||||
<span class="settings-label-desc">12-hour (2:30 PM) or 24-hour (14:30)</span>
|
||||
</div>
|
||||
<select id="globalTimeFormatSelect" class="settings-select" onchange="InterceptTime.setHour12(this.value === '12')">
|
||||
<option value="12">12-hour (AM/PM)</option>
|
||||
<option value="24">24-hour</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Updates Section -->
|
||||
|
||||
Reference in New Issue
Block a user