diff --git a/app_sat.go b/app_sat.go index d7eab28..436175e 100644 --- a/app_sat.go +++ b/app_sat.go @@ -11,6 +11,7 @@ package main import ( "context" + "encoding/json" "fmt" "os" "path/filepath" @@ -590,6 +591,47 @@ func (a *App) GetSatelliteBirds() []SatBird { return out } +// GetSatelliteNames is the list behind the entry form's SAT_NAME box. +// +// One list, not two. It used to be a text box in Settings ▸ Lists that an +// operator typed their birds into by hand, which then had nothing to do with +// the satellites the tracker knew — the same station kept two lists of the same +// satellites and they drifted apart. This is the followed set (or every +// satellite with a frequency plan, when none is followed), plus anything the +// old hand-kept list still holds so nobody's typing is thrown away. +// +// SAT_NAME is compared character for character by the awards and by LoTW, so +// offering the spelling already used beats inventing a new one every pass. +func (a *App) GetSatelliteNames() []string { + seen := map[string]bool{} + var out []string + add := func(n string) { + n = strings.ToUpper(strings.TrimSpace(n)) + if n == "" || seen[n] { + return + } + seen[n] = true + out = append(out, n) + } + for _, n := range a.satNames(nil) { + add(n) + } + // The legacy list. Read, never written: the panel that edited it is gone, + // and what it holds is somebody's past work. + if a.settings != nil { + if raw, _ := a.settings.Get(a.ctx, keyListsSatellites); raw != "" { + var legacy []string + if json.Unmarshal([]byte(raw), &legacy) == nil { + for _, n := range legacy { + add(n) + } + } + } + } + sort.Strings(out) + return out +} + // satElement finds the elements for a bird, trying its aliases. // // The feed's name and the operator's name for the same satellite are routinely diff --git a/changelog.json b/changelog.json index 7ae3617..767929d 100644 --- a/changelog.json +++ b/changelog.json @@ -7,14 +7,16 @@ "Doppler tracking drives the radio. Track puts the rig on the satellite and keeps it there, once a second: an IC-9700 or IC-9100 in its own satellite mode, a FlexRadio on two slices (A the downlink, B the uplink, created if they are missing, full duplex on) — and any other radio on the downlink, which it says plainly rather than half-doing the job. Tune the receiver where you like: the tracker reads the dial, takes it as the station you have chosen, and moves the transmitter to match. QSOs made while tracking are logged with the NOMINAL frequencies, SAT_NAME, SAT_MODE and PROP_MODE=SAT — the transponder's own numbers, which both stations can agree on, rather than where one radio happened to be.", "The antenna follows too. An EasyComm II rotator — what SatPC32, Gpredict and Hamlib speak, so most az/el controllers — is pointed at the satellite while you track, over serial or over the network (Settings → Satellites). It is a separate machine from your HF rotator, so a station with both keeps both. A 450° rotator is used as one: a pass crossing north continues past 360 instead of unwinding through the whole scale with the antenna sweeping the ground. The panel shows where the antenna actually is beside where the satellite is — and says plainly when a controller only accepts commands without reporting back, which many do.", "Settings → Satellites is where the satellite work is set up, and the tab is what you use during a pass. Choose the satellites you follow the way you choose awards — two columns, search, the ones with a frequency plan first — and the tab and the pass list show those and nothing else. The orbital elements move there too: age, fetch, and paste-your-own. None of it is something to be doing while a bird is going over.", - "The satellite panel says what a pass actually is. A countdown to AOS, or to LOS once it is up, with a bar showing where in the pass you are; rise, peak and set with their compass directions; distance, altitude and footprint; and whether it is approaching or receding, which is why the frequencies move the way they do. The frequencies keep the corrected figure large and the Doppler shift beside it." + "The satellite panel says what a pass actually is. A countdown to AOS, or to LOS once it is up, with a bar showing where in the pass you are; rise, peak and set with their compass directions; distance, altitude and footprint; and whether it is approaching or receding, which is why the frequencies move the way they do. The frequencies keep the corrected figure large and the Doppler shift beside it.", + "One satellite list, in one place. Settings → Lists → Satellites is gone: the birds it held by hand had nothing to do with the ones the tracker knew, and the same station kept two lists that drifted apart. The SAT_NAME box on the entry form now offers the satellites you follow — plus anything the old list still held, so nobody's typing is lost. Satellites also moved out of Hardware, where it never belonged, and sits with Operating." ], "fr": [ "[NOUVEAU] Satellites. Un nouvel onglet (Outils → Satellites) suit les satellites amateurs : une carte avec l'empreinte de chacun et la trace au sol de celui qui est sélectionné, les prochains passages avec leur élévation maximale, et — pour le satellite en cours — l'azimut, l'élévation et les fréquences de descente et de montée corrigées de l'effet Doppler. Les éléments orbitaux viennent de Celestrak (avec un miroir derrière) et sont conservés sur disque : l'onglet est rempli dès son ouverture, même sans internet. Les éléments d'un satellite qu'aucun flux ne diffuse encore peuvent être collés à la main et survivent à chaque mise à jour. La liste de fréquences fournie couvre les satellites FM et linéaires ainsi que QO-100, dans un fichier que vous pouvez corriger vous-même quand un transpondeur change de mode.", "Le suivi Doppler pilote la radio. « Suivre » met le poste sur le satellite et l'y maintient, chaque seconde : un IC-9700 ou IC-9100 dans son propre mode satellite, un FlexRadio sur deux slices (A la descente, B la montée, créées si elles manquent, full duplex activé) — et n'importe quelle autre radio sur la descente seule, ce qu'elle annonce clairement plutôt que de faire le travail à moitié. Accordez le récepteur où vous voulez : le suivi lit le VFO, y voit la station que vous avez choisie, et déplace l'émetteur en conséquence. Les QSO faits pendant le suivi sont enregistrés avec les fréquences NOMINALES, SAT_NAME, SAT_MODE et PROP_MODE=SAT — les chiffres du transpondeur, sur lesquels les deux stations peuvent s'accorder, plutôt que l'endroit où une radio se trouvait.", "L'antenne suit aussi. Un rotor EasyComm II — le langage de SatPC32, Gpredict et Hamlib, donc la plupart des contrôleurs az/él — est pointé vers le satellite pendant le suivi, en série ou en réseau (Réglages → Satellites). C'est une machine distincte du rotor HF : une station qui a les deux garde les deux. Un rotor 450° est utilisé comme tel : un passage qui traverse le nord continue au-delà de 360 au lieu de se dérouler sur toute la course, antenne balayant le sol. Le panneau montre où l'antenne se trouve réellement à côté de la position du satellite — et dit clairement quand un contrôleur se contente d'accepter les commandes sans répondre, ce que beaucoup font.", "Réglages → Satellites, c'est là qu'on configure ; l'onglet, c'est ce qu'on utilise pendant un passage. On choisit les satellites suivis comme on choisit les diplômes — deux colonnes, recherche, ceux qui ont un plan de fréquences d'abord — et l'onglet comme la liste des passages n'affichent que ceux-là. Les éléments orbitaux y passent aussi : âge, récupération, et collage des siens. Rien de tout cela ne se fait pendant qu'un satellite passe.", - "Le panneau satellite dit enfin ce qu'est un passage. Un compte à rebours jusqu'à l'AOS, ou jusqu'au LOS une fois qu'il est levé, avec une barre montrant où l'on en est ; lever, culmination et coucher avec leurs directions à la boussole ; distance, altitude et empreinte ; et s'il se rapproche ou s'éloigne, ce qui explique le sens du décalage. Les fréquences gardent la valeur corrigée en grand et le Doppler à côté." + "Le panneau satellite dit enfin ce qu'est un passage. Un compte à rebours jusqu'à l'AOS, ou jusqu'au LOS une fois qu'il est levé, avec une barre montrant où l'on en est ; lever, culmination et coucher avec leurs directions à la boussole ; distance, altitude et empreinte ; et s'il se rapproche ou s'éloigne, ce qui explique le sens du décalage. Les fréquences gardent la valeur corrigée en grand et le Doppler à côté.", + "Une seule liste de satellites, à un seul endroit. Réglages → Listes → Satellites disparaît : les satellites qu'on y saisissait à la main n'avaient aucun rapport avec ceux que connaissait le suivi, et une même station entretenait deux listes qui divergeaient. Le champ SAT_NAME de la saisie propose désormais les satellites que vous suivez — plus ce que l'ancienne liste contenait encore, pour ne rien perdre. Satellites quitte aussi Matériel, où il n'avait rien à faire, pour rejoindre Opération." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e1811c1..2d1ff6a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,7 +12,7 @@ import { ContestDupe, GetQSO, UpdateQSO, DeleteQSO, DeleteQSOs, DeleteAllQSO, UpdateQSOsFromCty, UpdateQSOsFromQRZ, UpdateQSOsFromClublog, UpdateQSOsCountyFromULS, ULSStatus, UploadQSOsManual, SendQSORecordingEmail, - LookupCallsign, GetStationSettings, GetListsSettings, + LookupCallsign, GetStationSettings, GetListsSettings, GetSatelliteNames, GetStartupStatus, CheckForUpdate, DownloadAndApplyUpdate, GetLiveStations, GetWhatsNew, GetChangelog, SMTPConfigured, SendLogToDeveloper, WorkedBefore, @@ -3332,7 +3332,11 @@ export default function App() { const l: ListsSettings = await GetListsSettings(); setRstLists({ phone: (l as any).rst_phone ?? [], cw: (l as any).rst_cw ?? [], digital: (l as any).rst_digital ?? [] }); if (l.bands && l.bands.length) setBands(l.bands); - setSatellites([...(((l as any).satellites ?? []) as string[])].filter(Boolean).sort()); + // The satellites come from the satellite side now, not from a list typed + // by hand in Settings: one station kept two lists of the same birds and + // they drifted apart. Go merges the followed set with whatever the old + // hand-kept list still holds, so nobody's typing is lost. + GetSatelliteNames().then((s) => setSatellites((s ?? []) as string[])).catch(() => {}); if (l.modes && l.modes.length) { setModePresets(l.modes); const names = l.modes.map((m) => m.name); diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index a9e8b45..6ea7e0e 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -214,7 +214,6 @@ type SectionId = | 'lookup' | 'lists-bands' | 'lists-modes' - | 'lists-satellites' | 'cluster' | 'dxhunter' | 'backup' @@ -297,7 +296,6 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[] const hardware: TreeNode[] = [ { kind: 'item', label: t('sec.cat'), id: 'cat' }, { kind: 'item', label: t('sec.rotator'), id: 'rotator' }, - { kind: 'item', label: t('sec.satellite'), id: 'satellite' }, { kind: 'item', label: t('sec.winkeyer'), id: 'winkeyer' }, { kind: 'item', label: t('sec.antenna'), id: 'antenna' }, { kind: 'item', label: t('sec.antgenius'), id: 'antgenius', vendor: 'o3a' }, @@ -314,6 +312,10 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[] { kind: 'item', label: t('sec.station'), id: 'station' }, { kind: 'item', label: t('sec.profiles'), id: 'profiles' }, { kind: 'item', label: t('sec.operating'), id: 'operating' }, + // Not hardware, whatever the rotator block inside it suggests: which + // satellites you chase, where your antenna stands and how old your + // elements are is operating, and it sits with the rest of it. + { kind: 'item', label: t('sec.satellite'), id: 'satellite' }, { kind: 'item', label: t('sec.confirmations'), id: 'confirmations' }, { kind: 'item', label: t('sec.awards'), id: 'awards' }, { kind: 'item', label: t('sec.external'), id: 'external-services' }, @@ -328,7 +330,6 @@ 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.dxhunter'), id: 'dxhunter' }, @@ -359,7 +360,7 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[] // Map section id → i18n key (breadcrumb / placeholders). const SECTION_KEY: Partial> = { 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', 'lists-satellites': 'sec.satellites', + 'external-services': 'sec.external', appearance: 'sec.appearance', lookup: 'sec.lookup', 'lists-bands': 'sec.bands', 'lists-modes': 'sec.modes', cluster: 'sec.cluster', backup: 'sec.backup', database: 'sec.database', autostart: 'sec.autostart', udp: 'sec.udp', adifmon: 'sec.adifmon', foldersync: 'sec.foldersync', @@ -381,7 +382,6 @@ const SECTION_LABELS: Partial> = { lookup: 'Callsign Lookup', 'lists-bands': 'Bands', 'lists-modes': 'Modes & default RST', - 'lists-satellites': 'Satellites', cluster: 'DX Cluster', backup: 'Database backup', database: 'Database', @@ -3277,39 +3277,6 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged ); } - function SatellitesPanel() { - const sats = lists.satellites ?? []; - return ( - <> - -
-
- - {/* 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. */} -