From dbeabc1baefe0b1165125dc6fa3092a797907c61 Mon Sep 17 00:00:00 2001 From: rouggy Date: Tue, 8 Sep 2026 21:52:38 +0200 Subject: [PATCH] feat: my rig and my antenna as dropdowns, and a ceiling on the decodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rigs and antennas are already declared once, in Settings ▸ Operating conditions — a station per rig with its antennas hanging off it — and then typed again into every contact. That is work, and it is a source of spellings that do not match: "IC-7610", "IC 7610" and "ic7610" are three different rigs to an award, to a filter, and to anyone reading the log later. Both fields now offer that list, in the entry form and in the QSO editor, and the antenna field offers the antennas of the rig that was picked because that is the structure the tree already has. It falls back to all of them for a rig the tree does not know, so an operator typing a borrowed rig is still offered their own antennas rather than nothing. Free text stays allowed throughout — a contact made from somebody else's station, or imported from another logger, carries a rig that was never in this tree and must still be loggable. Same rule the satellite-name field follows. And the decodes list gains a ceiling of two thousand rows. The rolling half hour was never a limit on a crowded evening — three decoders put several thousand rows inside it — and the panel slows down long before the age cut removes any of them, because each row is a layout, a status lookup and a distance. Past the ceiling the oldest go: what has already been scrolled past, rather than the period being read. Co-Authored-By: Claude Opus 5 (1M context) --- changelog.json | 8 ++- frontend/src/App.tsx | 13 ++++- frontend/src/components/DetailsPanel.tsx | 16 +++++- frontend/src/components/QSOEditModal.tsx | 18 ++++++- frontend/src/lib/operatingLists.ts | 66 ++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 frontend/src/lib/operatingLists.ts diff --git a/changelog.json b/changelog.json index 689d975..369a018 100644 --- a/changelog.json +++ b/changelog.json @@ -7,14 +7,18 @@ "A UDP row set to multicast on an address that is not one now listens anyway. 127.0.0.1 in the group box is the common mistake — it is the address every other field in every other program wants — but a multicast group runs 224.0.0.0 to 239.255.255.255, and joining anything else failed on every interface with a Windows error naming nothing the operator had typed. The row simply did not run. It now listens on unicast, which is what such an address means, and says so in the log.", "The confirmation defaults added for the newest services were blank. HAMLOG.online arrived after most profiles were set up, so it had no default at all — and blank is not a status anybody chose. Every service now starts the same way: the sent side at R (waiting to go out), the received side at N. A blank left by a service that did not exist when you last saved is filled in; a status you chose yourself is untouched.", "OmniRig: a setting for rig files whose CW is the reverse one. OmniRig has two CW modes and nothing says which one a rig file calls plain CW — some Icom files map PM_CW_U to CW, others to CW-R — so clicking a CW spot on an IC-7610 landed the radio in CW-R, and the only way out was to edit the rig file. Settings → CAT → OmniRig now has a tick box for it, applied at once without dropping the link. (If your VFOs read the wrong way round on the same rig, the VFO override beside it is the answer: rig files disagree there too.)", - "Choosing a radio now switches CAT on. The master switch sits above the radio dropdown, and leaving it off while you pick your brand, type the address and run the detector — which finds your radio and prints its name — is a trap: a Flex 6700 owner did exactly that, saved six times, and got no link and no error. Picking a radio, or clicking one the detector found, ticks it. The panel also says so plainly while it is off, and the log line that used to announce \"link unchanged, staying connected\" when nothing was connected now says CAT is switched off." + "Choosing a radio now switches CAT on. The master switch sits above the radio dropdown, and leaving it off while you pick your brand, type the address and run the detector — which finds your radio and prints its name — is a trap: a Flex 6700 owner did exactly that, saved six times, and got no link and no error. Picking a radio, or clicking one the detector found, ticks it. The panel also says so plainly while it is off, and the log line that used to announce \"link unchanged, staying connected\" when nothing was connected now says CAT is switched off.", + "My rig and my antenna are dropdowns now, in the entry form and in the QSO editor, offering what you declared in Settings → Operating conditions — and the antennas of the rig you picked, since that is what they hang off. Typing them again on every contact was both work and a source of spellings that do not match: \"IC-7610\", \"IC 7610\" and \"ic7610\" are three different rigs to an award and to a filter. Free text still works, for a QSO made from somebody else's station.", + "The FT decodes list is capped at 2000 rows. The rolling half hour is not a limit on a crowded evening — three decoders fill it with several thousand — and the panel slowed down long before anything aged out, since every row is a layout, a status and a distance. Past two thousand the oldest go, which is what has already been scrolled past." ], "fr": [ "Après une mise à jour, OpsLog redémarre. Le correctif qui a fait cesser la détection en cheval de Troie a supprimé l'assistant qui attendait la mort de l'ancien processus, et rien n'a repris ce travail : la nouvelle instance patientait vingt secondes sur le verrou d'instance unique alors que l'ancienne dispose de trente pour se fermer — elle referme un journal distant, une session CAT, parfois une sauvegarde. Quand cela durait, le nouveau processus abandonnait en silence : pas de fenêtre, et un OpsLog restant dans le gestionnaire des tâches. Il attend désormais l'ancien processus lui-même, et repart à l'instant où celui-ci s'arrête ; et s'il n'est vraiment pas parti, il le dit au lieu d'annoncer qu'OpsLog tourne déjà.", "Une ligne UDP réglée en multicast sur une adresse qui n'en est pas une écoute désormais quand même. 127.0.0.1 dans le champ groupe est l'erreur classique — c'est l'adresse que réclame tout autre champ de tout autre programme — mais un groupe multicast va de 224.0.0.0 à 239.255.255.255, et rejoindre autre chose échouait sur toutes les interfaces avec une erreur Windows ne nommant rien de ce que l'opérateur avait saisi. La ligne ne tournait tout simplement pas. Elle écoute maintenant en unicast, ce que veut dire une telle adresse, et le dit dans le journal.", "Les statuts par défaut des services les plus récents étaient vides. HAMLOG.online est arrivé après la configuration de la plupart des profils : il n'avait donc aucun défaut — et vide n'est pas un statut que quelqu'un a choisi. Chaque service démarre désormais pareil : côté envoi R (en attente de départ), côté réception N. Un vide laissé par un service qui n'existait pas lors de votre dernier enregistrement est comblé ; un statut que vous avez choisi n'est pas touché.", "OmniRig : un réglage pour les fichiers de rig dont la CW est l'inverse. OmniRig a deux modes CW et rien ne dit lequel un fichier appelle CW tout court — certains fichiers Icom associent PM_CW_U à CW, d'autres à CW-R — si bien qu'un clic sur un spot CW mettait un IC-7610 en CW-R, sans autre issue que de modifier le fichier de rig. Réglages → CAT → OmniRig a désormais une case pour cela, appliquée aussitôt sans couper la liaison. (Si vos VFO sont inversés sur la même radio, le sélecteur de VFO juste à côté est la réponse : les fichiers de rig divergent là aussi.)", - "Choisir une radio active désormais le CAT. L'interrupteur principal est au-dessus de la liste des radios, et le laisser éteint pendant qu'on choisit sa marque, saisit l'adresse et lance la détection — qui trouve la radio et affiche son nom — est un piège : un possesseur de Flex 6700 a fait exactement cela, enregistré six fois, sans liaison ni erreur. Choisir une radio, ou cliquer sur celle que la détection a trouvée, coche la case. Le panneau le dit aussi clairement tant qu'elle est décochée, et la ligne de journal qui annonçait « liaison inchangée, toujours connecté » alors que rien n'était connecté dit maintenant que le CAT est désactivé." + "Choisir une radio active désormais le CAT. L'interrupteur principal est au-dessus de la liste des radios, et le laisser éteint pendant qu'on choisit sa marque, saisit l'adresse et lance la détection — qui trouve la radio et affiche son nom — est un piège : un possesseur de Flex 6700 a fait exactement cela, enregistré six fois, sans liaison ni erreur. Choisir une radio, ou cliquer sur celle que la détection a trouvée, coche la case. Le panneau le dit aussi clairement tant qu'elle est décochée, et la ligne de journal qui annonçait « liaison inchangée, toujours connecté » alors que rien n'était connecté dit maintenant que le CAT est désactivé.", + "Mon équipement et mon antenne sont désormais des listes déroulantes, dans la saisie comme dans l'éditeur de QSO, proposant ce que vous avez déclaré dans Réglages → Conditions de trafic — et les antennes du poste choisi, puisque c'est à lui qu'elles sont rattachées. Les retaper à chaque contact était à la fois du travail et une source d'orthographes divergentes : « IC-7610 », « IC 7610 » et « ic7610 » sont trois équipements différents pour un diplôme et pour un filtre. La saisie libre reste possible, pour un QSO fait depuis la station de quelqu'un d'autre.", + "La liste des décodages FT est plafonnée à 2000 lignes. La demi-heure glissante n'est pas une limite un soir chargé — trois décodeurs la remplissent de plusieurs milliers — et le panneau ralentissait bien avant que quoi que ce soit n'expire, chaque ligne étant une mise en page, un statut et une distance. Au-delà de deux mille, les plus anciennes partent : celles qu'on a déjà dépassées en défilant." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5cb8052..65b3952 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2594,6 +2594,15 @@ export default function App() { // half hour — long enough to hold a whole opening, short enough that a night // of FT8 on 20 m does not turn the list into something no filter can rescue. const DECODE_KEEP_MS = 30 * 60 * 1000; + // And a hard ceiling on the count, because the half hour is not one on a + // crowded band. + // + // Three decoders on an open evening put several thousand rows in that window, + // and the panel slows down long before the age limit removes any of them: + // every one is a row to lay out, a status to resolve and a distance to work + // out. Two thousand is more than a screen can hold many times over, and past + // it the oldest go — the newest period is what an operator is reading. + const DECODE_MAX = 2000; const [decodes, setDecodes] = useState([]); const [txMsgs, setTxMsgs] = useState([]); // The LIVE transmit state, replaced on every Status — what is going out now @@ -3887,7 +3896,9 @@ export default function App() { return !b2 || (d.band ?? '').toLowerCase() === b2; }); const next = [...kept, ...fresh].filter((d) => Date.parse(d.at) >= cutoff); - return next; + // Oldest first in this list, so the ceiling is applied from the front: + // what goes is what was already scrolled past. + return next.length > DECODE_MAX ? next.slice(next.length - DECODE_MAX) : next; }); }; flushDecodesRef.current = () => { void flushDecodes(); }; diff --git a/frontend/src/components/DetailsPanel.tsx b/frontend/src/components/DetailsPanel.tsx index d103730..35352cc 100644 --- a/frontend/src/components/DetailsPanel.tsx +++ b/frontend/src/components/DetailsPanel.tsx @@ -9,6 +9,7 @@ import { import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; import { Combobox } from '@/components/ui/combobox'; +import { useOperatingLists } from '@/lib/operatingLists'; import { pathBetween, pathBetweenLatLon, gridToLatLon } from '@/lib/maidenhead'; import { BandSlotGrid } from '@/components/BandSlotGrid'; import { AwardRefSelector } from '@/components/AwardRefSelector'; @@ -158,6 +159,7 @@ 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, modes, satellites = [], slotCall, slotBand, slotMode, slotWb, slotWbBusy, tab, onTab, keyerActive, onEditQso }: Props) { const { t } = useI18n(); + const oper = useOperatingLists(tab); const [internalOpen, setInternalOpen] = useState('stats'); const open = tab ?? internalOpen; // controlled when `tab` is provided @@ -476,11 +478,21 @@ export function DetailsPanel({ callsign, prefix, operatorGrid, remoteGrid, qth, + {/* The rigs and antennas already declared in Settings ▸ Operating + conditions. Typing them again on every contact is both work and a + source of spellings that do not match — "IC-7610", "IC 7610" and + "ic7610" are three different rigs to an award and to a filter. + Free text stays allowed: a QSO made from somebody else's station + carries a rig that was never in this tree. */} - onChange({ my_rig: e.target.value })} /> + onChange({ my_rig: v })} /> - onChange({ my_antenna: e.target.value })} /> + {/* The antennas of the chosen rig, since that is what they hang off + — and all of them when the rig is one this tree does not know. */} + onChange({ my_antenna: v })} /> {satelliteMode && ( <> diff --git a/frontend/src/components/QSOEditModal.tsx b/frontend/src/components/QSOEditModal.tsx index b0cd2dc..fe24c08 100644 --- a/frontend/src/components/QSOEditModal.tsx +++ b/frontend/src/components/QSOEditModal.tsx @@ -19,6 +19,7 @@ import { } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; import { Combobox } from '@/components/ui/combobox'; +import { useOperatingLists } from '@/lib/operatingLists'; import { cn } from '@/lib/utils'; import { flagURL } from '@/lib/flags'; import { useI18n } from '@/lib/i18n'; @@ -288,6 +289,9 @@ function QslViaSelect({ value, onChange }: { value?: string; onChange: (v: strin export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], bands, modes }: Props) { const { t } = useI18n(); + // Read once per opening of the editor: rigs and antennas do not change while + // a contact is being corrected. + const oper = useOperatingLists(); // Use the operator's configured band/mode lists (incl. custom ones like 13cm); // fall back to the built-in sets. Always include the QSO's own band/mode so an // imported/legacy value is never silently dropped from the dropdown. @@ -982,8 +986,18 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b set('my_street', e.target.value)} /> set('my_city', e.target.value)} /> set('my_postal_code', e.target.value)} /> - set('my_rig', e.target.value)} /> - set('my_antenna', e.target.value)} /> + {/* The station's own rigs and antennas (Settings ▸ Operating + conditions), so a correction here spells them the same way + the log already does. Free text stays: an imported contact + carries whatever the other logger wrote. */} + + set('my_rig', v)} /> + + + set('my_antenna', v)} /> + diff --git a/frontend/src/lib/operatingLists.ts b/frontend/src/lib/operatingLists.ts new file mode 100644 index 0000000..5887a45 --- /dev/null +++ b/frontend/src/lib/operatingLists.ts @@ -0,0 +1,66 @@ +// The station's own rigs and antennas, for the MY_RIG and MY_ANTENNA fields. +// +// They are already defined once, in Settings ▸ Operating conditions — a station +// per rig, with the antennas hanging off it. Typing them again into every +// contact is both work and a source of spellings that do not match: "IC-7610", +// "IC 7610" and "ic7610" are three different rigs to an award, a filter and to +// anyone reading the log later. +// +// So the two fields offer what the operator has already declared. FREE TEXT +// stays allowed: a QSO made from somebody else's station, or imported from +// another logger, carries a rig that was never in this tree and must still be +// loggable — the same rule the satellite-name field follows. +import { useEffect, useState } from 'react'; +import { ListOperatingTree } from '../../wailsjs/go/main/App'; + +export type OperatingLists = { + rigs: string[]; + // Every antenna in the profile, whichever rig it belongs to. + antennas: string[]; + // The antennas of ONE rig. Falls back to all of them for a rig that is not in + // the tree — an operator typing a borrowed rig's name should still be offered + // their own antennas rather than nothing. + antennasFor: (rig: string) => string[]; +}; + +const EMPTY: OperatingLists = { rigs: [], antennas: [], antennasFor: () => [] }; + +function build(stations: any[]): OperatingLists { + const rigs: string[] = []; + const byRig = new Map(); + const all = new Set(); + for (const st of stations ?? []) { + const name = String(st?.name ?? '').trim(); + const ants = ((st?.antennas ?? []) as any[]) + .map((a) => String(a?.name ?? '').trim()) + .filter(Boolean); + if (name) { + rigs.push(name); + byRig.set(name.toUpperCase(), ants); + } + for (const a of ants) all.add(a); + } + const antennas = [...all]; + return { + rigs, + antennas, + antennasFor: (rig: string) => byRig.get(String(rig ?? '').trim().toUpperCase()) ?? antennas, + }; +} + +// useOperatingLists reads the tree when the component mounts, and again whenever +// `reloadKey` changes — pass something that moves when Preferences close, so a +// rig added there is offered without a restart. +export function useOperatingLists(reloadKey?: unknown): OperatingLists { + const [lists, setLists] = useState(EMPTY); + useEffect(() => { + let live = true; + ListOperatingTree() + .then((st: any) => { if (live) setLists(build(st ?? [])); }) + // An empty list simply leaves both fields as free text, which is what they + // were before they had a list at all. + .catch(() => {}); + return () => { live = false; }; + }, [reloadKey]); + return lists; +}