feat: Cluster alert implemented
This commit is contained in:
@@ -67,6 +67,7 @@ import { ClusterGrid } from '@/components/ClusterGrid';
|
||||
import { cleanSpotter, inferSpotMode, spotModeCategory, spotStatusKey } from '@/lib/spot';
|
||||
import { WorkedBeforeGrid } from '@/components/WorkedBeforeGrid';
|
||||
import { NetControlPanel } from '@/components/NetControlPanel';
|
||||
import { AlertsModal } from '@/components/AlertsModal';
|
||||
import { BulkEditModal } from '@/components/BulkEditModal';
|
||||
import { ChatPanel, type ChatMsg, type ChatPresence } from '@/components/ChatPopover';
|
||||
import { DetailsPanel, type DetailsState } from '@/components/DetailsPanel';
|
||||
@@ -730,6 +731,8 @@ export default function App() {
|
||||
}
|
||||
const chatShown = chatOpen && chatAvailable;
|
||||
|
||||
const [alertsOpen, setAlertsOpen] = useState(false); // Alert management modal
|
||||
|
||||
// NET Control tab — enabled from Tools (persisted; once on it's a tab like Cluster).
|
||||
const [netEnabled, setNetEnabled] = useState(() => localStorage.getItem('opslog.netEnabled') === '1');
|
||||
useEffect(() => { localStorage.setItem('opslog.netEnabled', netEnabled ? '1' : '0'); }, [netEnabled]);
|
||||
@@ -1100,6 +1103,35 @@ export default function App() {
|
||||
return () => { off(); };
|
||||
}, [showToast]);
|
||||
|
||||
// DX-cluster spot alerts: a matched rule fires here. Play a beep (WebAudio, no
|
||||
// asset needed — CSP-safe) and/or show a toast, per the rule's chosen actions.
|
||||
useEffect(() => {
|
||||
const off = EventsOn('alert:fired', (p: any) => {
|
||||
if (p?.sound) {
|
||||
try {
|
||||
const AC = (window as any).AudioContext || (window as any).webkitAudioContext;
|
||||
const ctx = new AC();
|
||||
const beep = (freq: number, at: number, dur: number) => {
|
||||
const o = ctx.createOscillator(); const g = ctx.createGain();
|
||||
o.type = 'sine'; o.frequency.value = freq;
|
||||
o.connect(g); g.connect(ctx.destination);
|
||||
g.gain.setValueAtTime(0.0001, ctx.currentTime + at);
|
||||
g.gain.exponentialRampToValueAtTime(0.25, ctx.currentTime + at + 0.01);
|
||||
g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + at + dur);
|
||||
o.start(ctx.currentTime + at); o.stop(ctx.currentTime + at + dur + 0.02);
|
||||
};
|
||||
beep(880, 0, 0.14); beep(1320, 0.16, 0.18); // two-tone chirp
|
||||
window.setTimeout(() => ctx.close().catch(() => {}), 600);
|
||||
} catch { /* audio blocked — ignore */ }
|
||||
}
|
||||
if (p?.visual) {
|
||||
const call = String(p?.call ?? ''); const band = String(p?.band ?? ''); const rule = String(p?.rule ?? 'alert');
|
||||
showToast(`🔔 ${rule}: ${call}${band ? ` on ${band}` : ''}${p?.country ? ` — ${p.country}` : ''}`);
|
||||
}
|
||||
});
|
||||
return () => { off(); };
|
||||
}, [showToast]);
|
||||
|
||||
// Poll PstRotator for the live antenna heading (status bar). Cheap when the
|
||||
// rotator is disabled (the backend just reads settings and returns).
|
||||
useEffect(() => {
|
||||
@@ -2209,6 +2241,7 @@ export default function App() {
|
||||
{ type: 'item', label: cwEnabled ? '✓ CW decoder (RX audio)' : 'CW decoder (RX audio)', action: 'tools.cwdecoder' },
|
||||
{ type: 'separator' },
|
||||
{ type: 'item', label: netEnabled ? '✓ NET Control' : 'NET Control', action: 'tools.net' },
|
||||
{ type: 'item', label: 'Alert management…', action: 'tools.alerts' },
|
||||
{ type: 'separator' },
|
||||
// Maintenance — bumped here while we only have one entry. Will move
|
||||
// to a Tools → Maintenance submenu once Clublog + LoTW refresh land.
|
||||
@@ -2237,6 +2270,7 @@ export default function App() {
|
||||
case 'tools.dvk': setDvkEnabled((v) => !v); break;
|
||||
case 'tools.cwdecoder': toggleCwDecoder(); break;
|
||||
case 'tools.net': setNetEnabled((v) => { const nv = !v; if (nv) setActiveTab('net'); return nv; }); break;
|
||||
case 'tools.alerts': setAlertsOpen(true); break;
|
||||
case 'tools.refreshCty': refreshCtyDat(); break;
|
||||
case 'tools.downloadRefs': downloadRefs(); break;
|
||||
case 'help.about': setShowAbout(true); break;
|
||||
@@ -4101,6 +4135,10 @@ export default function App() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{alertsOpen && (
|
||||
<AlertsModal onClose={() => setAlertsOpen(false)} bands={bands} modes={modes} countries={countries} />
|
||||
)}
|
||||
|
||||
<AutoEQSL
|
||||
onSent={(call) => showToast(`OpsLog QSL sent to ${call}`)}
|
||||
onError={(msg) => showToast(msg)}
|
||||
|
||||
Reference in New Issue
Block a user