diff --git a/.env.example b/.env.example index ad63e0b..c9fefd5 100644 --- a/.env.example +++ b/.env.example @@ -231,3 +231,10 @@ VITE_PSTROTATOR_BASE_URL=/pstrotator # Optional: HTTP endpoint for PstRotatorAz web interface (for proxy) # Set this to the machine running PstRotatorAz (default shown below) VITE_PSTROTATOR_TARGET=http://192.168.1.43:50004 +# =========================================== +# N3FJP QSO RETENTION +# =========================================== + +# How long the server keeps logged QSOs in memory (minutes) +# 1440 = 24 hours +N3FJP_QSO_RETENTION_MINUTES=1440 \ No newline at end of file diff --git a/server.js b/server.js index 13ce5a5..d268fa5 100644 --- a/server.js +++ b/server.js @@ -8820,7 +8820,7 @@ function handleWSJTXMessage(msg, state) { } // ---- N3FJP Logged QSO relay (in-memory) ---- -const N3FJP_QSO_RETENTION_MINUTES = parseInt(process.env.N3FJP_QSO_RETENTION_MINUTES || "15", 10); +const N3FJP_QSO_RETENTION_MINUTES = parseInt(process.env.N3FJP_QSO_RETENTION_MINUTES || "1440", 10); let n3fjpQsos = []; function pruneN3fjpQsos() { diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index 1fad6de..bf87df3 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -18,6 +18,8 @@ import { importProfile } from '../utils/profiles.js'; +import useLocalInstall from '../hooks/app/useLocalInstall.js'; + export const SettingsPanel = ({ isOpen, onClose, config, onSave, onResetLayout, satellites, satelliteFilters, onSatelliteFiltersChange, mapLayers, onToggleDXNews }) => { const [callsign, setCallsign] = useState(config?.callsign || ''); const [headerSize, setheaderSize] = useState(config?.headerSize || 1.0); @@ -39,6 +41,40 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave, onResetLayout, const [rigPort, setRigPort] = useState(config?.rigControl?.port || 5555); const [tuneEnabled, setTuneEnabled] = useState(config?.rigControl?.tuneEnabled || false); const [satelliteSearch, setSatelliteSearch] = useState(''); + const isLocalInstall = useLocalInstall(); + const [rotatorEnabled, setRotatorEnabled] = useState(() => { + try { + return localStorage.getItem('ohc_rotator_enabled') === '1'; + } catch { + return false; + } + }); + + // Local-only integration flags + const [n3fjpEnabled, setN3fjpEnabled] = useState(() => { + try { + return localStorage.getItem('ohc_n3fjp_enabled') === '1'; + } catch { + return false; + } + }); + + // N3FJP UI settings (persisted) + const [n3fjpDisplayMinutes, setN3fjpDisplayMinutes] = useState(() => { + try { + const v = parseInt(localStorage.getItem('n3fjp_display_minutes') || '15', 10); + return Number.isFinite(v) ? v : 15; + } catch { + return 15; + } + }); + const [n3fjpLineColor, setN3fjpLineColor] = useState(() => { + try { + return localStorage.getItem('n3fjp_line_color') || '#3388ff'; + } catch { + return '#3388ff'; + } + }); const { t, i18n } = useTranslation(); // Layer controls @@ -94,6 +130,31 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave, onResetLayout, } }, [config, isOpen]); + // Keep rotator toggle in sync with localStorage when opening settings + useEffect(() => { + if (!isOpen) return; + try { + setRotatorEnabled(localStorage.getItem('ohc_rotator_enabled') === '1'); + } catch { + setRotatorEnabled(false); + } + }, [isOpen]); + + // Keep N3FJP toggle/settings in sync with localStorage when opening settings + useEffect(() => { + if (!isOpen) return; + try { + setN3fjpEnabled(localStorage.getItem('ohc_n3fjp_enabled') === '1'); + const v = parseInt(localStorage.getItem('n3fjp_display_minutes') || '15', 10); + setN3fjpDisplayMinutes(Number.isFinite(v) ? v : 15); + setN3fjpLineColor(localStorage.getItem('n3fjp_line_color') || '#3388ff'); + } catch { + setN3fjpEnabled(false); + setN3fjpDisplayMinutes(15); + setN3fjpLineColor('#3388ff'); + } + }, [isOpen]); + // Load layers when panel opens useEffect(() => { if (isOpen && window.hamclockLayerControls) { @@ -370,6 +431,25 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave, onResetLayout, > {t('station.settings.tab1.title')} + + +