diff --git a/static/js/core/app.js b/static/js/core/app.js index 8834146..7734f5f 100644 --- a/static/js/core/app.js +++ b/static/js/core/app.js @@ -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); diff --git a/static/js/core/utils.js b/static/js/core/utils.js index d09f7a1..9f4fcb5 100644 --- a/static/js/core/utils.js +++ b/static/js/core/utils.js @@ -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 diff --git a/static/js/modes/weather-satellite.js b/static/js/modes/weather-satellite.js index 44f47cf..4452403 100644 --- a/static/js/modes/weather-satellite.js +++ b/static/js/modes/weather-satellite.js @@ -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}`; diff --git a/templates/partials/modes/weather-satellite.html b/templates/partials/modes/weather-satellite.html index 702aa84..81dbfaa 100644 --- a/templates/partials/modes/weather-satellite.html +++ b/templates/partials/modes/weather-satellite.html @@ -84,7 +84,8 @@