feat(lists): a satellite list, offered on the entry form

SAT_NAME is compared character for character by the awards and by LoTW:
AO-91 and AO91 are two different satellites to everything downstream, and
typing it afresh on every pass is how one of them ends up in a log. So
the station keeps its own list (Preferences → Lists → Satellites) and the
field offers it, alphabetically — while still accepting anything typed,
because a bird worked once and never added to the list must not be
impossible to log.

Not seeded with a shipped list of two dozen birds: an empty list means
this station does not work satellites, and filling the dropdown with
names nobody here has heard makes the field harder to use, not easier.

Also fixes the RDA district comparison: its conflict list was capped at
about six visible rows of a list holding up to two hundred, in a panel
that would not scroll to show the rest, and nothing in it could be acted
on. Taller, scrolling, and a callsign opens the contact.
This commit is contained in:
2026-08-25 19:30:29 +02:00
parent 12b0a861e0
commit 1e87081058
7 changed files with 145 additions and 32 deletions
+41 -19
View File
@@ -106,6 +106,7 @@ const (
keyListsRSTPhone = "lists.rst_phone"
keyListsRSTCW = "lists.rst_cw"
keyListsRSTDigital = "lists.rst_digital"
keyListsSatellites = "lists.satellites" // the satellites this station works (SAT_NAME dropdown)
keyCATEnabled = "cat.enabled"
keyCATBackend = "cat.backend" // "omnirig" | "flex"
@@ -139,8 +140,8 @@ const (
// choice from whether a field happened to be filled in is how an operator
// ends up with a host typed in and a radio that never answers.
keyCATKenwoodLink = "cat.kenwood.link"
keyCATKenwoodPort = "cat.kenwood.port" // Kenwood CAT serial port (TS-590/890/2000, Elecraft)
keyCATKenwoodBaud = "cat.kenwood.baud" // Kenwood CAT baud (TS-590 default 9600, TS-890 115200)
keyCATKenwoodPort = "cat.kenwood.port" // Kenwood CAT serial port (TS-590/890/2000, Elecraft)
keyCATKenwoodBaud = "cat.kenwood.baud" // Kenwood CAT baud (TS-590 default 9600, TS-890 115200)
// One key PER BACKEND, deliberately not shared. A Xiegu fix that reached
// into the Yaesu and Kenwood backends is what broke them in 0.22.7/0.22.8;
// the rule since is that nothing a backend does may be steered by another
@@ -526,6 +527,12 @@ type ListsSettings struct {
RSTPhone []string `json:"rst_phone"` // RS reports for phone modes
RSTCW []string `json:"rst_cw"` // RST reports for CW/RTTY/PSK
RSTDigital []string `json:"rst_digital"` // dB reports for FT8/FT4/JT…
// Satellites the station works, offered as a dropdown on the satellite
// fields. A list rather than free text because SAT_NAME is matched
// character for character by the awards and by LoTW: "AO-91" and "AO91"
// are two different satellites to everything downstream, and typing it
// afresh on every pass is how one of them appears in a log.
Satellites []string `json:"satellites"`
}
var defaultBands = []string{
@@ -8059,25 +8066,25 @@ func (a *App) GetCATSettings() (CATSettings, error) {
return CATSettings{}, err
}
out := CATSettings{
Enabled: m[keyCATEnabled] == "1",
Backend: m[keyCATBackend],
OmniRigNum: 1,
FlexHost: m[keyCATFlexHost],
FlexPort: 4992,
FlexSpots: m[keyCATFlexSpots] == "1",
FlexDVKDax: m[keyCATFlexDVKDax] == "1",
FlexDecodeSpots: m[keyCATFlexDecodeSpots] == "1",
FlexDecodeSecs: 120,
XieguPort: m[keyCATXieguPort],
XieguBaud: 19200,
XieguAddr: cat.XieguDefaultAddr,
YaesuPort: m[keyCATYaesuPort],
YaesuBaud: 38400,
KenwoodPort: m[keyCATKenwoodPort],
KenwoodHost: m[keyCATKenwoodHost],
Enabled: m[keyCATEnabled] == "1",
Backend: m[keyCATBackend],
OmniRigNum: 1,
FlexHost: m[keyCATFlexHost],
FlexPort: 4992,
FlexSpots: m[keyCATFlexSpots] == "1",
FlexDVKDax: m[keyCATFlexDVKDax] == "1",
FlexDecodeSpots: m[keyCATFlexDecodeSpots] == "1",
FlexDecodeSecs: 120,
XieguPort: m[keyCATXieguPort],
XieguBaud: 19200,
XieguAddr: cat.XieguDefaultAddr,
YaesuPort: m[keyCATYaesuPort],
YaesuBaud: 38400,
KenwoodPort: m[keyCATKenwoodPort],
KenwoodHost: m[keyCATKenwoodHost],
// An install that predates the setting is read from what it has: a host
// filled in meant the bridge, since that is what the old code preferred.
KenwoodLink: kenwoodLinkOr(m[keyCATKenwoodLink], m[keyCATKenwoodHost]),
KenwoodLink: kenwoodLinkOr(m[keyCATKenwoodLink], m[keyCATKenwoodHost]),
KenwoodBaud: 9600,
YaesuLowLines: m[keyCATYaesuLowLines] == "1",
KenwoodLowLines: m[keyCATKenwoodLowLines] == "1",
@@ -15405,6 +15412,9 @@ func (a *App) GetListsSettings() (ListsSettings, error) {
if raw, _ := a.settings.Get(a.ctx, keyListsRSTDigital); raw != "" {
_ = json.Unmarshal([]byte(raw), &out.RSTDigital)
}
if raw, _ := a.settings.Get(a.ctx, keyListsSatellites); raw != "" {
_ = json.Unmarshal([]byte(raw), &out.Satellites)
}
if len(out.Bands) == 0 {
out.Bands = append([]string(nil), defaultBands...)
}
@@ -15420,6 +15430,10 @@ func (a *App) GetListsSettings() (ListsSettings, error) {
if len(out.RSTDigital) == 0 {
out.RSTDigital = append([]string(nil), defaultRSTDigital...)
}
// Satellites are NOT defaulted to a shipped list. An empty list means the
// station does not work satellites, and filling it with two dozen birds
// nobody here has heard would make the field harder to use, not easier —
// the operator adds the ones they actually work.
return out, nil
}
@@ -15459,6 +15473,14 @@ func (a *App) SaveListsSettings(l ListsSettings) error {
// cache, so a band ticked here has to reach that cache now — otherwise it
// takes effect at the next restart, which looks like the option not working.
a.refreshChaseBands()
sat, err := json.Marshal(l.Satellites)
if err != nil {
return err
}
if err := a.settings.Set(a.ctx, keyListsSatellites, string(sat)); err != nil {
return err
}
return nil
}
+6 -2
View File
@@ -10,7 +10,9 @@
"PowerGenius XL: the amplifier's real state is read at startup. Its status frame carries no 'operate' field — the state is in 'state' — so on the direct GSCP link the flag was never read at all and OpsLog opened claiming STANDBY on an amp that was in line, with the first press of the button then commanding the state it was already in. IDLE means in line, not keyed.",
"Antenna Genius: an option to write the SELECTED antenna into MY_ANTENNA, under the name it carries on the switch, ahead of the band default from Operating conditions. Which of the two ports counts is decided by the antenna jack the radio is transmitting on (ANT1/ANT2 on a Flex), with the jack-to-port wiring set once in Preferences — neither device can report it. When the port cannot be told, the log keeps the band default rather than naming an antenna at random.",
"The awards tab beside the entry form (F3) now offers only the awards this station follows, and drops the ones switched off — the same list the Awards tab reads. It offered every award that existed, so a station chasing three of them picked references out of a list of twenty.",
"CAT settings ask two questions instead of one: WHICH RADIO, then HOW IT IS CONNECTED — and the second only appears where there is a choice to make. OmniRig leads the list, the brands follow alphabetically, and each offers only what it has: USB for a Yaesu or a Xiegu, USB or an RS-232-to-Ethernet bridge for a Kenwood or an Elecraft, USB or its own network protocol for an Icom, and nothing to choose for a FlexRadio or a SunSDR, which are reached one way each. The old list mixed the two questions — 'Icom (USB)' and 'Icom (network)' were separate entries while Kenwood and Elecraft hid the same choice in a field further down — and the example network address named port 4532, which is Hamlib rigctld and the one OpsLog itself serves under Share CAT."
"CAT settings ask two questions instead of one: WHICH RADIO, then HOW IT IS CONNECTED — and the second only appears where there is a choice to make. OmniRig leads the list, the brands follow alphabetically, and each offers only what it has: USB for a Yaesu or a Xiegu, USB or an RS-232-to-Ethernet bridge for a Kenwood or an Elecraft, USB or its own network protocol for an Icom, and nothing to choose for a FlexRadio or a SunSDR, which are reached one way each. The old list mixed the two questions — 'Icom (USB)' and 'Icom (network)' were separate entries while Kenwood and Elecraft hid the same choice in a field further down — and the example network address named port 4532, which is Hamlib rigctld and the one OpsLog itself serves under Share CAT.",
"An editable list of satellites (Preferences → Lists → Satellites). The satellite-name field on the entry form offers them as a dropdown, alphabetically, and still accepts anything typed. SAT_NAME is compared character for character by the awards and by LoTW — AO-91 and AO91 are two different satellites to everything downstream — so a remembered spelling beats one retyped on every pass.",
"Awards, RDA district comparison: the conflict list is taller and scrolls, and a callsign in it opens the contact. It was capped at about six visible rows of a list that holds up to two hundred, inside a panel that would not scroll to show the rest — and nothing in it could be acted on."
],
"fr": [
"Console Elecraft : les mesures d'émission sont lues dès que la RADIO se déclare en émission, et plus seulement quand OpsLog l'a mise en émission. Passer en émission par le PTT de façade, une pédale ou le bouton du micro laissait le panneau croire à une réception — et comme les barres de puissance et de ROS ne sont lues qu'en émission, elles ne l'étaient jamais pour qui manipule à la main.",
@@ -20,7 +22,9 @@
"Power Genius XL : l'état réel de l'amplificateur est lu au démarrage. Sa trame d'état ne contient pas de champ « operate » — l'état est dans « state » — si bien que sur la liaison GSCP directe l'indicateur n'était jamais lu : OpsLog s'ouvrait en annonçant STANDBY sur un ampli en ligne, et le premier appui commandait l'état dans lequel il se trouvait déjà. IDLE veut dire en ligne, pas en émission.",
"Antenna Genius : une option pour inscrire l'antenne SÉLECTIONNÉE dans MY_ANTENNA, sous le nom qu'elle porte sur le switch, avant l'antenne par défaut des conditions de trafic. C'est la prise d'antenne sur laquelle la radio émet (ANT1/ANT2 sur un Flex) qui décide du port retenu, le câblage prise→port se règlant une fois dans les préférences — aucun des deux appareils ne peut le dire. Quand le port ne peut pas être déterminé, le journal conserve l'antenne par défaut plutôt que d'en nommer une au hasard.",
"L'onglet des diplômes à côté de la saisie (F3) ne propose plus que les diplômes suivis par la station, et écarte ceux qui sont désactivés — la même liste que l'onglet Diplômes. Il proposait tous les diplômes existants : une station qui en chasse trois choisissait ses références dans une liste de vingt.",
"Les réglages CAT posent deux questions au lieu d'une : QUELLE RADIO, puis COMMENT ELLE EST RELIÉE — la seconde n'apparaissant que là où il y a un choix. OmniRig ouvre la liste, les marques suivent par ordre alphabétique, et chacune ne propose que ce qu'elle a : USB pour un Yaesu ou un Xiegu, USB ou un pont RS-232 vers Ethernet pour un Kenwood ou un Elecraft, USB ou son protocole réseau propre pour un Icom, et rien à choisir pour un FlexRadio ou un SunSDR, qui n'ont qu'une voie chacun. L'ancienne liste mélangeait les deux questions — « Icom (USB) » et « Icom (réseau) » étaient deux entrées tandis que Kenwood et Elecraft cachaient le même choix dans un champ plus bas — et l'exemple d'adresse réseau citait le port 4532, celui de Hamlib rigctld, que OpsLog propose lui-même sous « Partager le CAT »."
"Les réglages CAT posent deux questions au lieu d'une : QUELLE RADIO, puis COMMENT ELLE EST RELIÉE — la seconde n'apparaissant que là où il y a un choix. OmniRig ouvre la liste, les marques suivent par ordre alphabétique, et chacune ne propose que ce qu'elle a : USB pour un Yaesu ou un Xiegu, USB ou un pont RS-232 vers Ethernet pour un Kenwood ou un Elecraft, USB ou son protocole réseau propre pour un Icom, et rien à choisir pour un FlexRadio ou un SunSDR, qui n'ont qu'une voie chacun. L'ancienne liste mélangeait les deux questions — « Icom (USB) » et « Icom (réseau) » étaient deux entrées tandis que Kenwood et Elecraft cachaient le même choix dans un champ plus bas — et l'exemple d'adresse réseau citait le port 4532, celui de Hamlib rigctld, que OpsLog propose lui-même sous « Partager le CAT ».",
"Une liste de satellites éditable (Préférences → Listes → Satellites). Le champ du nom de satellite dans la saisie les propose en liste déroulante, par ordre alphabétique, et accepte toujours ce qu'on tape. SAT_NAME est comparé caractère par caractère par les diplômes et par LoTW — AO-91 et AO91 sont deux satellites différents pour tout ce qui suit — donc une orthographe mémorisée vaut mieux qu'une ressaisie à chaque passage.",
"Diplômes, comparaison des districts RDA : la liste des divergences est plus haute et défile, et un indicatif y ouvre le contact. Elle était limitée à six lignes visibles environ pour une liste pouvant en contenir deux cents, dans un panneau qui ne défilait pas pour montrer le reste — et rien n'y était actionnable."
]
},
{
+1
View File
@@ -8488,6 +8488,7 @@ export default function App() {
{showSettings && (
<SettingsModal
onEditQSO={openEdit}
initialSection={settingsSection}
onClose={() => { setShowSettings(false); setSettingsSection(undefined); refreshChaseNew(); }}
onSaved={() => {
+23 -2
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { ComputeQSOAwardRefs, IsNewUSCounty } from '../../wailsjs/go/main/App';
import { ComputeQSOAwardRefs, IsNewUSCounty, GetListsSettings } from '../../wailsjs/go/main/App';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
@@ -8,6 +8,7 @@ import {
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { Combobox } from '@/components/ui/combobox';
import { pathBetween, pathBetweenLatLon, gridToLatLon } from '@/lib/maidenhead';
import { BandSlotGrid } from '@/components/BandSlotGrid';
import { AwardRefSelector } from '@/components/AwardRefSelector';
@@ -153,6 +154,15 @@ function Field({ label, span = 1, className, children }: { label: string; span?:
export function DetailsPanel({ callsign, prefix, operatorGrid, remoteGrid, qth, name, country, comment, note, details, onChange, wb, wbBusy, band, mode, bands, slotCall, slotBand, slotMode, slotWb, slotWbBusy, tab, onTab, keyerActive, onEditQso }: Props) {
const { t } = useI18n();
const [internalOpen, setInternalOpen] = useState<TabName>('stats');
// The station's satellites, read once. Alphabetical because that is the only
// order a list of birds has, and an operator hunting AO-91 in a list sorted
// by when it was added is worse off than with a plain text box.
const [satellites, setSatellites] = useState<string[]>([]);
useEffect(() => {
GetListsSettings()
.then((l: any) => setSatellites([...((l?.satellites ?? []) as string[])].filter(Boolean).sort()))
.catch(() => {});
}, []);
const open = tab ?? internalOpen; // controlled when `tab` is provided
// Live award detection: run the SAME engine used at log time over the current
@@ -478,7 +488,18 @@ export function DetailsPanel({ callsign, prefix, operatorGrid, remoteGrid, qth,
{satelliteMode && (
<>
<Field label={t('detp.satName')} span={3}>
<Input value={details.sat_name} onChange={(e) => onChange({ sat_name: e.target.value })} />
{/* The station's own satellites, in alphabetical order, with the
box still open to anything typed: SAT_NAME is compared
character for character by the awards and by LoTW, so a
remembered spelling beats a fresh one every pass — but a bird
worked once and never added to the list must not be
impossible to log. */}
<Combobox
value={details.sat_name}
options={satellites}
placeholder={t('detp.satName')}
onChange={(v) => onChange({ sat_name: v })}
/>
</Field>
<Field label={t('detp.satelliteMode')} span={3}>
<Input value={details.sat_mode} onChange={(e) => onChange({ sat_mode: e.target.value })} />
+68 -5
View File
@@ -175,6 +175,11 @@ interface Props {
flexAvailable?: boolean; // CAT backend is FlexRadio → offer it as a Main pane
icomAvailable?: boolean; // CAT backend is Icom → offer the Icom console as a Main pane
yaesuAvailable?: boolean; // CAT backend is Yaesu → offer the Yaesu console as a Main pane
// Opens a QSO in the editor. Settings is not where a log is edited — but the
// RDA comparison lists contacts whose district is in dispute, and a list of
// things to fix that cannot be acted on is a list to write down and look up
// again later.
onEditQSO?: (id: number) => void;
}
// Pretty little card showing what OpsLog will stamp on each QSO based on
@@ -200,6 +205,7 @@ type SectionId =
| 'lookup'
| 'lists-bands'
| 'lists-modes'
| 'lists-satellites'
| 'cluster'
| 'backup'
| 'database'
@@ -310,6 +316,7 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[]
{ kind: 'group', label: t('nav.lists'), icon: Database, defaultOpen: true, children: [
{ kind: 'item', label: t('sec.bands'), id: 'lists-bands' },
{ kind: 'item', label: t('sec.modes'), id: 'lists-modes' },
{ kind: 'item', label: t('sec.satellites'), id: 'lists-satellites' },
]},
{ kind: 'item', label: t('sec.cluster'), id: 'cluster' },
{ kind: 'item', label: t('sec.udp'), id: 'udp' },
@@ -339,7 +346,7 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[]
// Map section id → i18n key (breadcrumb / placeholders).
const SECTION_KEY: Partial<Record<SectionId, string>> = {
station: 'sec.station', profiles: 'sec.profiles', operating: 'sec.operating', confirmations: 'sec.confirmations',
'external-services': 'sec.external', appearance: 'sec.appearance', lookup: 'sec.lookup', 'lists-bands': 'sec.bands', 'lists-modes': 'sec.modes',
'external-services': 'sec.external', appearance: 'sec.appearance', lookup: 'sec.lookup', 'lists-bands': 'sec.bands', 'lists-modes': 'sec.modes', 'lists-satellites': 'sec.satellites',
cluster: 'sec.cluster', backup: 'sec.backup', database: 'sec.database', autostart: 'sec.autostart', udp: 'sec.udp',
adifmon: 'sec.adifmon',
foldersync: 'sec.foldersync',
@@ -361,6 +368,7 @@ const SECTION_LABELS: Partial<Record<SectionId, string>> = {
lookup: 'Callsign Lookup',
'lists-bands': 'Bands',
'lists-modes': 'Modes & default RST',
'lists-satellites': 'Satellites',
cluster: 'DX Cluster',
backup: 'Database backup',
database: 'Database',
@@ -1516,7 +1524,7 @@ function brandOfBackend(backend: string, kenwoodLink?: string): { brand: string;
}
}
export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChanged, flexAvailable, icomAvailable, yaesuAvailable }: Props) {
export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChanged, flexAvailable, icomAvailable, yaesuAvailable, onEditQSO }: Props) {
const { t } = useI18n();
const [selected, setSelected] = useState<SectionId>((initialSection as SectionId) || 'station');
const [loading, setLoading] = useState(true);
@@ -1545,7 +1553,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
const [activeProfile, setActiveProfile] = useState<Profile | null>(null);
const updateActive = (patch: Partial<Profile>) =>
setActiveProfile((p) => (p ? { ...p, ...patch } : p));
const [lists, setLists] = useState<ListsSettings>({ bands: [], modes: [], rst_phone: [], rst_cw: [], rst_digital: [] });
const [lists, setLists] = useState<ListsSettings>({ bands: [], modes: [], rst_phone: [], rst_cw: [], rst_digital: [], satellites: [] });
// RST report lists edited as free text (one/space-separated values).
const [rstText, setRstText] = useState({ phone: '', cw: '', digital: '' });
// Custom band drafts (catalog covers ADIF spec but the user may have
@@ -2931,6 +2939,40 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
);
}
function SatellitesPanel() {
const sats = lists.satellites ?? [];
return (
<>
<SectionHeader title={t('sec.satellites')} hint={t('sat.hint')} />
<div className="space-y-3 max-w-xl">
<div className="space-y-1">
<Label>{t('sat.listLabel')}</Label>
{/* Raw text, one per line, parsed on change not a row-per-entry
editor with add and delete buttons. The list is short, edited
twice a year, and usually arrives pasted from a satellite
tracker; a textarea takes that paste in one gesture. */}
<textarea
className="w-full h-56 rounded-md border border-input bg-background p-2 font-mono text-xs"
value={sats.join('\n')}
placeholder={'AO-7\nAO-91\nRS-44\nSO-50'}
onChange={(e) => {
const next = e.target.value.split('\n').map((v) => v.trim());
setLists((s) => ({ ...s, satellites: next }));
}}
onBlur={() => setLists((s) => ({
// Tidied when the field is LEFT, never while typing: dropping an
// empty line as it is typed makes the Enter key look broken.
...s,
satellites: Array.from(new Set((s.satellites ?? []).map((v) => v.trim().toUpperCase()).filter(Boolean))).sort(),
}))}
/>
<p className="text-xs text-muted-foreground">{t('sat.listHint')}</p>
</div>
</div>
</>
);
}
function ModesPanel() {
const selected = lists.modes ?? [];
const selectedSet = new Set(selected.map((m) => (m.name ?? '').toUpperCase()));
@@ -7402,7 +7444,14 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
{/* The disagreements themselves. A count alone would say "there is a
problem" and leave the operator with no way to look at it. */}
{rdaCmp?.conflicts?.length > 0 && (
<div className="max-h-56 overflow-auto rounded border border-border">
<>
<p className="text-[11px] text-muted-foreground">
{t('rda.cmpListHint', { n: rdaCmp.conflicts.length, d: rdaCmp.disagree ?? 0 })}
</p>
{/* Tall enough to work through. It was capped at 224 px about six
rows of a list that can hold two hundred inside a panel that
does not scroll to reveal what the box could not show. */}
<div className="max-h-[28rem] overflow-y-auto overscroll-contain rounded border border-border">
<table className="w-full text-[11px]">
<thead className="sticky top-0 bg-card text-left text-muted-foreground border-b border-border">
<tr>
@@ -7416,7 +7465,19 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
<tbody>
{rdaCmp.conflicts.map((c: any) => (
<tr key={c.qso_id} className="border-b border-border/30">
<td className="py-1 px-2 font-mono font-semibold">{c.callsign}</td>
{/* The callsign opens the contact. A list of things to fix
that cannot be acted on is a list of things to write
down and look up again later. */}
<td className="py-1 px-2 font-mono font-semibold">
{onEditQSO ? (
<button type="button"
onClick={() => { onEditQSO(c.qso_id); onClose(); }}
title={t('rda.cmpOpen')}
className="underline decoration-dotted underline-offset-2 hover:text-primary">
{c.callsign}
</button>
) : c.callsign}
</td>
<td className="py-1 pr-2 font-mono">{c.date}</td>
<td className="py-1 pr-2 font-mono">{c.from_log}{c.confirmed ? ' ✓' : ''}</td>
<td className="py-1 pr-2 font-mono">{c.from_db}</td>
@@ -7428,6 +7489,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</tbody>
</table>
</div>
</>
)}
</div>
</div>
@@ -7447,6 +7509,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
lookup: LookupPanel,
'lists-bands': BandsPanel,
'lists-modes': ModesPanel,
'lists-satellites': SatellitesPanel,
cluster: ClusterPanel,
udp: UDPIntegrationsPanelWrapper,
// Module-scope components, wrapped so their props can be passed. The nested
+4 -4
View File
@@ -149,8 +149,8 @@ const en: Dict = {
'dec.emptyFiltered': 'No decode matches these filters.',
'sec.email': 'E-mail (SMTP)', 'sec.lookup': 'Callsign Lookup',
'sec.ftx': 'FTx decodes', 'ftx.hint': 'What OpsLog does on its own with the digital decode stream.', 'ftx.enable': 'Auto-call', 'ftx.enableHint': 'answer a decode without clicking it', 'ftx.callWhen': 'Call a station that is:', 'ftx.watch': 'Watch list', 'ftx.watchHint': 'One callsign per line, wildcards allowed (4S7*, */P). A watched station is answered ahead of the criteria above.', 'ftx.watchOnlyIf': 'but only if it is also:', 'ftx.cooldown': 'Ignore a callsign for', 'ftx.warn': 'This keys your transmitter without asking. It answers CQ only, never while you are already transmitting, one station at a time, and every call is written to the log file with its reason. Halt stops it.', 'ftx.c_dxcc': 'a new DXCC entity', 'ftx.c_bandmode': 'a new band AND a new mode for the entity', 'ftx.c_band': 'a new band for the entity', 'ftx.c_mode': 'a new mode for the entity', 'ftx.c_slot': 'a new slot (band+mode never worked together)', 'ftx.c_grid': 'a new grid square', 'ftx.c_county': 'a new US county', 'ftx.c_pota': 'a new POTA park', 'ftx.c_pfx': 'a new WPX prefix',
'sec.bands': 'Bands', 'sec.modes': 'Modes & default RST', 'sec.cluster': 'DX Cluster',
'sec.udp': 'Connections', 'sec.database': 'Database', 'sec.autostart': 'Autostart', 'sec.backup': 'Database backup', 'sec.uscounties': 'US Counties', 'nav.maintenance': 'Maintenance', 'sec.databases': 'Databases', 'db.hint': 'The reference data OpsLog keeps on disk. One line each, with what it holds and when it was last refreshed.', 'db.update': 'Update', 'db.never': 'never downloaded', 'db.cty': 'Country file (cty.dat)', 'db.ctyDetail': '{n} entities · file dated {d}', 'db.clublog': 'Club Log country exceptions', 'db.clublogDetail': '{n} exceptions · {d}', 'db.lotwUsers': 'LoTW users', 'db.lotwDetail': '{n} callsigns · {d}', 'db.scp': 'Super Check Partial (MASTER.SCP)', 'db.scpDetail': '{n} callsigns · {d}', 'db.uls': 'US counties (FCC ULS)', 'db.ulsDetail': '{n} callsigns · {d}', 'db.rda': 'Russian districts (RDA)', 'db.rdaDetail': '{n} callsigns, with their dated activity periods', 'db.rdaNote': 'built in', 'db.refLists': 'Award reference lists', 'db.refListsHint': 'Only the awards with an online source appear here; the others are shipped or edited by hand.', 'db.refDetail': '{n} references · {d}', 'db.refUpdated': '{code}: {n} references.', 'db.noRefLists': 'No award has an online reference list.', 'sec.rda': 'Russian districts (RDA)', 'rda.hint': 'The offline district database, and the one bulk operation it feeds.', 'rda.dbTitle': 'District database', 'rda.dbCount': '{n} Russian callsigns, each with the district it operates from and, where it moved, the dated periods it operated from each one. Built into OpsLog — nothing to download.', 'rda.backfillTitle': 'Fill the district on existing QSOs', 'rda.backfillIntro': 'Goes through every contact with a Russian entity and assigns its RDA reference, using the district the station was in ON THE DAY of the contact.', 'rda.useCurrent': 'Also use the current district for stations with no recorded history', 'rda.useCurrentHint': '(true for the great majority — the database records a history precisely for the callsigns that moved — but it is an assumption, not a dated fact)', 'rda.backfillRun': 'Fill districts', 'rda.backfillDone': '{s} Russian QSOs — {d} from a dated record, {c} from the current district, {u} unknown, {k} already had one.', 'rda.cmpTitle': 'Compare the two district sources', 'rda.cmpRun': 'Compare', 'rda.cmpDone': '{s} Russian QSOs — {a} identical, {d} differ, {l} only in the log, {b} only in the database', 'rda.cmpCall': 'Callsign', 'rda.cmpDate': 'Date', 'rda.cmpFromLog': 'Log (CNTY)', 'rda.cmpFromDb': 'RDA database', 'rda.cmpKind': 'Database', 'rda.cmpDated': 'dated', 'rda.cmpCurrent': 'current', 'rda.neverOverwrites': 'A reference you assigned by hand is never overwritten.',
'sec.bands': 'Bands', 'sec.satellites': 'Satellites', 'sat.hint': 'The satellites this station works. They are offered as a dropdown on the satellite fields, in alphabetical order.', 'sat.listLabel': 'One satellite per line', 'sat.listHint': 'Written into SAT_NAME exactly as spelled here, so use the name LoTW and the awards expect — AO-91, not AO91. Leaving the list empty simply keeps the field a plain text box.', 'sec.modes': 'Modes & default RST', 'sec.cluster': 'DX Cluster',
'sec.udp': 'Connections', 'sec.database': 'Database', 'sec.autostart': 'Autostart', 'sec.backup': 'Database backup', 'sec.uscounties': 'US Counties', 'nav.maintenance': 'Maintenance', 'sec.databases': 'Databases', 'db.hint': 'The reference data OpsLog keeps on disk. One line each, with what it holds and when it was last refreshed.', 'db.update': 'Update', 'db.never': 'never downloaded', 'db.cty': 'Country file (cty.dat)', 'db.ctyDetail': '{n} entities · file dated {d}', 'db.clublog': 'Club Log country exceptions', 'db.clublogDetail': '{n} exceptions · {d}', 'db.lotwUsers': 'LoTW users', 'db.lotwDetail': '{n} callsigns · {d}', 'db.scp': 'Super Check Partial (MASTER.SCP)', 'db.scpDetail': '{n} callsigns · {d}', 'db.uls': 'US counties (FCC ULS)', 'db.ulsDetail': '{n} callsigns · {d}', 'db.rda': 'Russian districts (RDA)', 'db.rdaDetail': '{n} callsigns, with their dated activity periods', 'db.rdaNote': 'built in', 'db.refLists': 'Award reference lists', 'db.refListsHint': 'Only the awards with an online source appear here; the others are shipped or edited by hand.', 'db.refDetail': '{n} references · {d}', 'db.refUpdated': '{code}: {n} references.', 'db.noRefLists': 'No award has an online reference list.', 'sec.rda': 'Russian districts (RDA)', 'rda.hint': 'The offline district database, and the one bulk operation it feeds.', 'rda.dbTitle': 'District database', 'rda.dbCount': '{n} Russian callsigns, each with the district it operates from and, where it moved, the dated periods it operated from each one. Built into OpsLog — nothing to download.', 'rda.backfillTitle': 'Fill the district on existing QSOs', 'rda.backfillIntro': 'Goes through every contact with a Russian entity and assigns its RDA reference, using the district the station was in ON THE DAY of the contact.', 'rda.useCurrent': 'Also use the current district for stations with no recorded history', 'rda.useCurrentHint': '(true for the great majority — the database records a history precisely for the callsigns that moved — but it is an assumption, not a dated fact)', 'rda.backfillRun': 'Fill districts', 'rda.backfillDone': '{s} Russian QSOs — {d} from a dated record, {c} from the current district, {u} unknown, {k} already had one.', 'rda.cmpTitle': 'Compare the two district sources', 'rda.cmpRun': 'Compare', 'rda.cmpDone': '{s} Russian QSOs — {a} identical, {d} differ, {l} only in the log, {b} only in the database', 'rda.cmpCall': 'Callsign', 'rda.cmpDate': 'Date', 'rda.cmpFromLog': 'Log (CNTY)', 'rda.cmpFromDb': 'RDA database', 'rda.cmpListHint': 'Showing {n} of {d} — click a callsign to open the contact.', 'rda.cmpOpen': 'Open this QSO', 'rda.cmpKind': 'Database', 'rda.cmpDated': 'dated', 'rda.cmpCurrent': 'current', 'rda.neverOverwrites': 'A reference you assigned by hand is never overwritten.',
'sec.webpublish': 'Web publishing', 'wpub.hint': 'Publishes your log as a file for a website: a standalone HTML page or a CSV, written locally and optionally uploaded by FTP. It is refreshed when you log a QSO and, if you set an interval, on a timer.', 'wpub.enable': 'Publish the log to a file', 'wpub.fileSection': 'The file', 'wpub.format': 'Format', 'wpub.formatHtml': 'HTML page', 'wpub.formatCsv': 'CSV', 'wpub.folder': 'Output folder', 'wpub.browse': 'Browse…', 'wpub.fileName': 'File name', 'wpub.title': 'Page title', 'wpub.titlePh': 'blank = your callsign', 'wpub.count': 'Last N QSOs', 'wpub.every': 'Refresh every', 'wpub.everyHint': 'minutes — 0 = only when a QSO is logged', 'wpub.columns': 'Columns', 'wpub.columnsCount': '{n} of {total} chosen', 'wpub.columnsPick': 'Choose columns…', 'wpub.columnsSearch': 'Search a field…', 'wpub.removeColumn': 'Click to remove', 'wpub.columnsHint': 'Click to add or remove. The order shown here is the order in the file.', 'wpub.ftpEnable': 'Upload by FTP', 'wpub.ftpHost': 'Server / port', 'wpub.ftpUser': 'User', 'wpub.ftpPassword': 'Password', 'wpub.ftpFolder': 'Remote folder', 'wpub.ftpFileName': 'Remote file name', 'wpub.ftpTls': 'Use TLS (FTPS)', 'wpub.publishNow': 'Publish now', 'wpub.testFtp': 'Test connection', 'wpub.lastRun': 'Last run:', 'sec.adifmon': 'ADIF monitor',
'sec.foldersync': 'Sync across PCs', 'sync.hint': 'Point every OpsLog at the SAME folder — one your PCs already synchronise (Seafile, OneDrive, Dropbox, a NAS share). Each machine writes what it logs there and reads the others; the databases themselves are never shared.', 'sync.enable': 'Keep my contacts in step across my PCs', 'sync.machine': 'This PC', 'sync.folder': 'Folder', 'sync.choose': 'Choose…', 'sync.state': 'State', 'sync.thisPc': 'This PC', 'sync.lastSync': 'Last check', 'sync.sent': 'Sent', 'sync.received': 'Received', 'sync.never': 'never', 'sync.noPeers': 'No other PC has written to this folder yet.', 'sync.behind': 'new contacts waiting', 'sync.now': 'Synchronise now', 'sync.applied': '{n} change(s) taken from the folder.', 'sync.saved': 'Saved.',
'adifmon.hint': 'Watch external ADIF files and import new QSOs automatically — e.g. fldigi logging RTTY, or N1MM/VarAC. Imported QSOs are enriched, de-duplicated and uploaded to your external services just like a QSO logged here.',
@@ -635,8 +635,8 @@ const fr: Dict = {
'dec.emptyFiltered': 'Aucun decode ne correspond a ces filtres.',
'sec.email': 'E-mail (SMTP)', 'sec.lookup': "Recherche d'indicatif",
'sec.ftx': 'Décodages FTx', 'ftx.hint': 'Ce quOpsLog fait de lui-même avec le flux de décodages numériques.', 'ftx.enable': 'Appel automatique', 'ftx.enableHint': 'répondre à un décodage sans cliquer', 'ftx.callWhen': 'Appeler une station qui est :', 'ftx.watch': 'Liste de surveillance', 'ftx.watchHint': 'Un indicatif par ligne, jokers acceptés (4S7*, */P). Une station surveillée est appelée avant les critères ci-dessus.', 'ftx.watchOnlyIf': 'mais seulement si elle est aussi :', 'ftx.cooldown': 'Ignorer un indicatif pendant', 'ftx.warn': 'Ceci met ton émetteur en marche sans te demander. Uniquement sur un CQ, jamais pendant que tu émets déjà, une station à la fois, et chaque appel est écrit dans le journal avec sa raison. Stop linterrompt.', 'ftx.c_dxcc': 'une nouvelle entité DXCC', 'ftx.c_bandmode': 'une nouvelle bande ET un nouveau mode pour lentité', 'ftx.c_band': 'une nouvelle bande pour lentité', 'ftx.c_mode': 'un nouveau mode pour lentité', 'ftx.c_slot': 'un nouveau slot (bande+mode jamais faits ensemble)', 'ftx.c_grid': 'un nouveau carré locator', 'ftx.c_county': 'un nouveau comté US', 'ftx.c_pota': 'un nouveau parc POTA', 'ftx.c_pfx': 'un nouveau préfixe WPX',
'sec.bands': 'Bandes', 'sec.modes': 'Modes & RST par défaut', 'sec.cluster': 'DX Cluster',
'sec.udp': 'Connexions', 'sec.database': 'Base de données', 'sec.autostart': 'Démarrage auto', 'sec.backup': 'Sauvegarde base', 'sec.uscounties': 'Comtés US', 'nav.maintenance': 'Maintenance', 'sec.databases': 'Bases de données', 'db.hint': 'Les données de référence quOpsLog garde sur disque. Une ligne chacune, avec ce quelle contient et sa dernière actualisation.', 'db.update': 'Mettre à jour', 'db.never': 'jamais téléchargée', 'db.cty': 'Fichier pays (cty.dat)', 'db.ctyDetail': '{n} entités · fichier daté du {d}', 'db.clublog': 'Exceptions pays Club Log', 'db.clublogDetail': '{n} exceptions · {d}', 'db.lotwUsers': 'Utilisateurs LoTW', 'db.lotwDetail': '{n} indicatifs · {d}', 'db.scp': 'Super Check Partial (MASTER.SCP)', 'db.scpDetail': '{n} indicatifs · {d}', 'db.uls': 'Comtés US (FCC ULS)', 'db.ulsDetail': '{n} indicatifs · {d}', 'db.rda': 'Districts russes (RDA)', 'db.rdaDetail': '{n} indicatifs, avec leurs périodes dactivité datées', 'db.rdaNote': 'intégrée', 'db.refLists': 'Listes de références des diplômes', 'db.refListsHint': 'Seuls les diplômes ayant une source en ligne apparaissent ici ; les autres sont livrés ou édités à la main.', 'db.refDetail': '{n} références · {d}', 'db.refUpdated': '{code} : {n} références.', 'db.noRefLists': 'Aucun diplôme na de liste de références en ligne.', 'sec.rda': 'Districts russes (RDA)', 'rda.hint': 'La base de districts hors ligne, et lunique opération de masse quelle alimente.', 'rda.dbTitle': 'Base des districts', 'rda.dbCount': '{n} indicatifs russes, chacun avec le district doù il émet et, pour ceux qui ont déménagé, les périodes datées passées dans chacun. Intégrée à OpsLog — rien à télécharger.', 'rda.backfillTitle': 'Renseigner le district sur les QSO existants', 'rda.backfillIntro': 'Parcourt tous les contacts avec une entité russe et attribue leur référence RDA, en utilisant le district où se trouvait la station LE JOUR du contact.', 'rda.useCurrent': 'Utiliser aussi le district actuel pour les stations sans historique connu', 'rda.useCurrentHint': '(vrai pour la grande majorité — la base enregistre un historique justement pour les indicatifs qui ont bougé — mais cest une supposition, pas un fait daté)', 'rda.backfillRun': 'Renseigner les districts', 'rda.backfillDone': '{s} QSO russes — {d} depuis une période datée, {c} depuis le district actuel, {u} inconnus, {k} en avaient déjà un.', 'rda.cmpTitle': 'Comparer les deux sources de district', 'rda.cmpRun': 'Comparer', 'rda.cmpDone': '{s} QSO russes — {a} identiques, {d} divergents, {l} seulement dans le log, {b} seulement dans la base', 'rda.cmpCall': 'Indicatif', 'rda.cmpDate': 'Date', 'rda.cmpFromLog': 'Log (CNTY)', 'rda.cmpFromDb': 'Base RDA', 'rda.cmpKind': 'Base', 'rda.cmpDated': 'daté', 'rda.cmpCurrent': 'courant', 'rda.neverOverwrites': 'Une référence attribuée à la main nest jamais écrasée.',
'sec.bands': 'Bandes', 'sec.satellites': 'Satellites', 'sat.hint': "Les satellites que cette station travaille. Ils sont proposés en liste déroulante sur les champs satellite, par ordre alphabétique.", 'sat.listLabel': 'Un satellite par ligne', 'sat.listHint': "Inscrit dans SAT_NAME exactement tel qu'écrit ici : utilise le nom attendu par LoTW et les diplômes — AO-91, pas AO91. Une liste vide laisse simplement le champ en saisie libre.", 'sec.modes': 'Modes & RST par défaut', 'sec.cluster': 'DX Cluster',
'sec.udp': 'Connexions', 'sec.database': 'Base de données', 'sec.autostart': 'Démarrage auto', 'sec.backup': 'Sauvegarde base', 'sec.uscounties': 'Comtés US', 'nav.maintenance': 'Maintenance', 'sec.databases': 'Bases de données', 'db.hint': 'Les données de référence quOpsLog garde sur disque. Une ligne chacune, avec ce quelle contient et sa dernière actualisation.', 'db.update': 'Mettre à jour', 'db.never': 'jamais téléchargée', 'db.cty': 'Fichier pays (cty.dat)', 'db.ctyDetail': '{n} entités · fichier daté du {d}', 'db.clublog': 'Exceptions pays Club Log', 'db.clublogDetail': '{n} exceptions · {d}', 'db.lotwUsers': 'Utilisateurs LoTW', 'db.lotwDetail': '{n} indicatifs · {d}', 'db.scp': 'Super Check Partial (MASTER.SCP)', 'db.scpDetail': '{n} indicatifs · {d}', 'db.uls': 'Comtés US (FCC ULS)', 'db.ulsDetail': '{n} indicatifs · {d}', 'db.rda': 'Districts russes (RDA)', 'db.rdaDetail': '{n} indicatifs, avec leurs périodes dactivité datées', 'db.rdaNote': 'intégrée', 'db.refLists': 'Listes de références des diplômes', 'db.refListsHint': 'Seuls les diplômes ayant une source en ligne apparaissent ici ; les autres sont livrés ou édités à la main.', 'db.refDetail': '{n} références · {d}', 'db.refUpdated': '{code} : {n} références.', 'db.noRefLists': 'Aucun diplôme na de liste de références en ligne.', 'sec.rda': 'Districts russes (RDA)', 'rda.hint': 'La base de districts hors ligne, et lunique opération de masse quelle alimente.', 'rda.dbTitle': 'Base des districts', 'rda.dbCount': '{n} indicatifs russes, chacun avec le district doù il émet et, pour ceux qui ont déménagé, les périodes datées passées dans chacun. Intégrée à OpsLog — rien à télécharger.', 'rda.backfillTitle': 'Renseigner le district sur les QSO existants', 'rda.backfillIntro': 'Parcourt tous les contacts avec une entité russe et attribue leur référence RDA, en utilisant le district où se trouvait la station LE JOUR du contact.', 'rda.useCurrent': 'Utiliser aussi le district actuel pour les stations sans historique connu', 'rda.useCurrentHint': '(vrai pour la grande majorité — la base enregistre un historique justement pour les indicatifs qui ont bougé — mais cest une supposition, pas un fait daté)', 'rda.backfillRun': 'Renseigner les districts', 'rda.backfillDone': '{s} QSO russes — {d} depuis une période datée, {c} depuis le district actuel, {u} inconnus, {k} en avaient déjà un.', 'rda.cmpTitle': 'Comparer les deux sources de district', 'rda.cmpRun': 'Comparer', 'rda.cmpDone': '{s} QSO russes — {a} identiques, {d} divergents, {l} seulement dans le log, {b} seulement dans la base', 'rda.cmpCall': 'Indicatif', 'rda.cmpDate': 'Date', 'rda.cmpFromLog': 'Log (CNTY)', 'rda.cmpFromDb': 'Base RDA', 'rda.cmpListHint': '{n} affichés sur {d} — cliquer un indicatif ouvre le contact.', 'rda.cmpOpen': 'Ouvrir ce QSO', 'rda.cmpKind': 'Base', 'rda.cmpDated': 'daté', 'rda.cmpCurrent': 'courant', 'rda.neverOverwrites': 'Une référence attribuée à la main nest jamais écrasée.',
'sec.webpublish': 'Publication web', 'wpub.hint': "Publie ton journal dans un fichier destiné à un site web : une page HTML autonome ou un CSV, écrit en local et envoyé par FTP si tu le souhaites. Il est rafraîchi à chaque QSO enregistré et, si tu règles un intervalle, périodiquement.", 'wpub.enable': 'Publier le journal dans un fichier', 'wpub.fileSection': 'Le fichier', 'wpub.format': 'Format', 'wpub.formatHtml': 'Page HTML', 'wpub.formatCsv': 'CSV', 'wpub.folder': 'Dossier de sortie', 'wpub.browse': 'Parcourir…', 'wpub.fileName': 'Nom du fichier', 'wpub.title': 'Titre de la page', 'wpub.titlePh': 'vide = ton indicatif', 'wpub.count': 'N derniers QSO', 'wpub.every': 'Rafraîchir toutes les', 'wpub.everyHint': 'minutes — 0 = seulement à chaque QSO', 'wpub.columns': 'Colonnes', 'wpub.columnsCount': '{n} sur {total} choisis', 'wpub.columnsPick': 'Choisir les colonnes…', 'wpub.columnsSearch': 'Chercher un champ…', 'wpub.removeColumn': 'Cliquer pour retirer', 'wpub.columnsHint': 'Clique pour ajouter ou retirer. L ordre affiché ici est celui du fichier.', 'wpub.ftpEnable': 'Envoyer par FTP', 'wpub.ftpHost': 'Serveur / port', 'wpub.ftpUser': 'Utilisateur', 'wpub.ftpPassword': 'Mot de passe', 'wpub.ftpFolder': 'Dossier distant', 'wpub.ftpFileName': 'Nom du fichier distant', 'wpub.ftpTls': 'Utiliser TLS (FTPS)', 'wpub.publishNow': 'Publier maintenant', 'wpub.testFtp': 'Tester la connexion', 'wpub.lastRun': 'Dernière exécution :', 'sec.adifmon': 'Moniteur ADIF',
'sec.foldersync': 'Synchro entre PC', 'sync.hint': 'Fais pointer chaque OpsLog vers le MÊME dossier — un dossier que tes PC synchronisent déjà (Seafile, OneDrive, Dropbox, un partage NAS). Chaque machine y écrit ce quelle enregistre et lit celui des autres ; les bases de données, elles, ne sont jamais partagées.', 'sync.enable': 'Garder mes contacts à jour sur tous mes PC', 'sync.machine': 'Ce PC', 'sync.folder': 'Dossier', 'sync.choose': 'Choisir…', 'sync.state': 'État', 'sync.thisPc': 'Ce PC', 'sync.lastSync': 'Dernière vérification', 'sync.sent': 'Envoyés', 'sync.received': 'Reçus', 'sync.never': 'jamais', 'sync.noPeers': 'Aucun autre PC na encore écrit dans ce dossier.', 'sync.behind': 'nouveaux contacts en attente', 'sync.now': 'Synchroniser maintenant', 'sync.applied': '{n} changement(s) repris du dossier.', 'sync.saved': 'Enregistré.',
'adifmon.hint': "Surveille des fichiers ADIF externes et importe les nouveaux QSO automatiquement — ex. fldigi en RTTY, ou N1MM/VarAC. Les QSO importés sont enrichis, dédoublonnés et envoyés à tes services externes comme un QSO loggé ici.",
+2
View File
@@ -2839,6 +2839,7 @@ export namespace main {
rst_phone: string[];
rst_cw: string[];
rst_digital: string[];
satellites: string[];
static createFrom(source: any = {}) {
return new ListsSettings(source);
@@ -2851,6 +2852,7 @@ export namespace main {
this.rst_phone = source["rst_phone"];
this.rst_cw = source["rst_cw"];
this.rst_digital = source["rst_digital"];
this.satellites = source["satellites"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {