feat(watchlist): the DXHunter watchlist as an OpsLog tab
The concept, transplanted: a list of callsigns or prefixes being hunted, matched against the live spot stream, one card per entry with the spots underneath and the two questions that matter answered on every line — is this slot still needed, and what is it worth (the cluster's own NEW badges, read from the same status index). The file is DXHunter's own watchlist.json, field for field, ClubLog block included though phase 2 will fill it — a file that round-trips unchanged is the whole of 'same format', and a test pins it with a real DXHunter entry. Global (dataDir), not per profile. CONTEST is per entry, not the global mode DXHunter has: a contest entry is judged against the current UTC day — the boundary lives in the query's date bound, so midnight needs no timer and resets nothing. Normal entries read the same in-memory worked index the alerts use. Prefix matching is why RI0SP catches RI0SP/MM, pinned by test. Notify goes through the existing alert:fired event — the frontend already toasts and sounds it — throttled to one alert per entry per two minutes, because a DXpedition lights every skimmer on the planet. Tab wired like NET Control: opt-in from Tools, persisted, closable. Single click fills the callsign, double click works the spot — the cluster's own gesture, kept.
This commit is contained in:
@@ -38,6 +38,20 @@ const en: Dict = {
|
||||
'cwd.tipOnCw': 'CW decoder — on (decoding) · click to disable',
|
||||
'cwd.tipOnIdle': 'CW decoder — on, idle until CW mode · click to disable',
|
||||
'cwd.tipOff': 'CW decoder · click to enable (decodes RX audio in CW mode)',
|
||||
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
|
||||
'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest',
|
||||
'wl.contestHint': 'Contest station: worked/needed is judged against the current UTC day — at 00:00 UTC every slot can be worked again.',
|
||||
'wl.searchPh': 'Search…', 'wl.famAll': 'All', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||
'wl.activeOnly': 'Active only', 'wl.neededOnly': 'Needed only',
|
||||
'wl.empty': 'Add the callsigns or prefixes you are hunting — a prefix catches the portable forms too (RI0SP matches RI0SP/MM). Spots from the cluster appear under each entry with what they are worth.',
|
||||
'wl.noneMatch': 'Nothing matches the current filters.',
|
||||
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpedition',
|
||||
'wl.nNeeded': '{n} needed', 'wl.nToday': '{n} today', 'wl.allWorked': 'All worked', 'wl.workedToday': 'Worked today',
|
||||
'wl.totalSpots': '{n} spots', 'wl.toggleContest': 'Contest entry: judged per UTC day',
|
||||
'wl.notifyOn': 'Alert me when this station is spotted', 'wl.notifyOff': 'Stop alerting for this station',
|
||||
'wl.remove': 'Remove (click twice)', 'wl.noSpots': 'No live spots',
|
||||
'wl.newDxcc': 'NEW DXCC', 'wl.worked': 'Worked', 'wl.needed': 'Needed!', 'wl.todayOk': 'Today ✓', 'wl.workToday': 'Work today!',
|
||||
'wl.spotTip': 'Click: fill the callsign · double-click: tune and work',
|
||||
'tools.net': 'NET Control', 'tools.alerts': 'Alert management…', 'tools.contest': 'Contest mode',
|
||||
'alert.tuneHint': 'Click to tune the rig to this spot (freq + mode) and fill the call', 'alert.dismiss': 'Dismiss', 'alert.pending': '{n} recent spot alert(s) — click to view', 'alert.noneShort': 'No recent alerts', 'alert.recent': 'Recent alerts', 'alert.clear': 'Clear',
|
||||
'menu.help': 'Help', 'prof.switchTitle': 'Switch station profile', 'prof.manage': 'Manage profiles…', 'logview.title': "Diagnostic log", 'logview.empty': "(log is empty)", 'logview.searchPh': "Filter lines — e.g. cluster, acom, cat", 'logview.matches': "{n} of {total} lines", 'logview.autoScroll': "Follow the end", 'logview.updated': "refreshed {time}", 'logview.copy': "Copy", 'logview.toEnd': "Jump to end", 'help.about': 'About OpsLog', 'help.donate': '♥ Support OpsLog (donate)', 'tools.duplicates': 'Find duplicates…',
|
||||
@@ -548,6 +562,20 @@ const fr: Dict = {
|
||||
'cwd.tipOnCw': 'Décodeur CW — actif (décode) · clic pour désactiver',
|
||||
'cwd.tipOnIdle': 'Décodeur CW — actif, en veille hors mode CW · clic pour désactiver',
|
||||
'cwd.tipOff': 'Décodeur CW · clic pour activer (décode l’audio RX en mode CW)',
|
||||
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
|
||||
'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest',
|
||||
'wl.contestHint': "Station contest : contacté/manquant est jugé sur la journée UTC courante — à 00:00 UTC chaque créneau redevient à faire.",
|
||||
'wl.searchPh': 'Chercher…', 'wl.famAll': 'Tous', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||
'wl.activeOnly': 'Actifs seulement', 'wl.neededOnly': 'Manquants seulement',
|
||||
'wl.empty': "Ajoutez les indicatifs ou préfixes que vous chassez — un préfixe attrape aussi les formes portables (RI0SP matche RI0SP/MM). Les spots du cluster apparaissent sous chaque entrée avec ce qu'ils valent.",
|
||||
'wl.noneMatch': 'Rien ne correspond aux filtres actuels.',
|
||||
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpédition',
|
||||
'wl.nNeeded': '{n} manquants', 'wl.nToday': "{n} aujourd'hui", 'wl.allWorked': 'Tout contacté', 'wl.workedToday': "Contacté aujourd'hui",
|
||||
'wl.totalSpots': '{n} spots', 'wl.toggleContest': 'Entrée contest : jugée par jour UTC',
|
||||
'wl.notifyOn': "M'alerter quand cette station est spottée", 'wl.notifyOff': "Ne plus alerter pour cette station",
|
||||
'wl.remove': 'Supprimer (cliquer deux fois)', 'wl.noSpots': 'Aucun spot en cours',
|
||||
'wl.newDxcc': 'NOUV DXCC', 'wl.worked': 'Contacté', 'wl.needed': 'Manquant !', 'wl.todayOk': "Auj. ✓", 'wl.workToday': "À faire auj. !",
|
||||
'wl.spotTip': "Clic : remplir l'indicatif · double-clic : régler la radio et travailler",
|
||||
'tools.net': 'Contrôle de NET', 'tools.alerts': 'Gestion des alertes…', 'tools.contest': 'Mode contest',
|
||||
'alert.tuneHint': 'Cliquer pour accorder la radio sur ce spot (fréq + mode) et remplir l\'indicatif', 'alert.dismiss': 'Fermer', 'alert.pending': '{n} alerte(s) de spot récente(s) — cliquer pour voir', 'alert.noneShort': 'Aucune alerte récente', 'alert.recent': 'Alertes récentes', 'alert.clear': 'Effacer',
|
||||
'menu.help': 'Aide', 'prof.switchTitle': 'Changer de profil de station', 'prof.manage': 'Gérer les profils…', 'logview.title': "Journal de diagnostic", 'logview.empty': "(journal vide)", 'logview.searchPh': "Filtrer les lignes — ex. cluster, acom, cat", 'logview.matches': "{n} lignes sur {total}", 'logview.autoScroll': "Suivre la fin", 'logview.updated': "actualisé {time}", 'logview.copy': "Copier", 'logview.toEnd': "Aller à la fin", 'help.about': 'À propos d\'OpsLog', 'help.donate': '♥ Soutenir OpsLog (don)', 'tools.duplicates': 'Trouver les doublons…',
|
||||
|
||||
Reference in New Issue
Block a user