diff --git a/changelog.json b/changelog.json index dc48919..81e06d0 100644 --- a/changelog.json +++ b/changelog.json @@ -6,14 +6,18 @@ "Every block of the readout column now has a heading and shuts on its own: the pass, the position, what to tune, the pass list. Each one remembers whether it is open, because an operator working FM birds never looks at the linear passband and one watching a schedule never looks at the range rate. A shut block keeps the one thing worth knowing in its heading — the countdown, the elevation, the mode.", "The frequency to tune to is the CENTRE of the passband, not the Doppler-corrected figure. The corrected one moves every second, differs for every operator, and is not what the frequency plan or anybody on the air calls the satellite's frequency; it belongs beside Tracking, where the radio is, and that is where it now lives. The correction stays here as the offset that explains the difference between the two.", "More colour where it decides something: the band of each frequency, the Doppler offset, an inverting transponder, the passband width and the peak elevation of a pass are badges in the app's own status colours rather than rows of grey words.", - "The ground track is visible on every basemap. It was drawn in a CSS variable, which is not a colour a canvas can use, so the browser kept whatever it had and the track came out a pale near-white that disappeared over the imagery and the deserts alike. It is now a proper colour, drawn over a dark casing the way a road is on a map — one line cannot hold up over both a pale sea and a dark continent, but a line with an outline can." + "The ground track is visible on every basemap. It was drawn in a CSS variable, which is not a colour a canvas can use, so the browser kept whatever it had and the track came out a pale near-white that disappeared over the imagery and the deserts alike. It is now a proper colour, drawn over a dark casing the way a road is on a map — one line cannot hold up over both a pale sea and a dark continent, but a line with an outline can.", + "The sky plot and the position now share the left column, and it opens and shuts with the same kind of control as the readout column on the right — it used to be a radar icon, from when it toggled a plot inside that column. The plot and the numbers are the same answer at different precisions, azimuth and elevation drawn and then written out, and having them on opposite sides of the window meant reading a bearing off one and finding it on the other.", + "The decode panel's band-drift warning stops firing on a second slice. It compared the decoder's band against the radio's TRANSMIT band, so a station with slice A on 20 m, slice B on 40 m and transmit focus on B was told its 20 m decoder disagreed with a radio that had been on 20 m all along — on the slice that decoder listens to. The comparison is now against every band the radio has a receiver on, which is what the warning was always about: a decoder announcing a band nothing on the radio is on, the way a lost CAT link looks." ], "fr": [ "L’onglet Satellites est réagencé. Le tracé du ciel passe dans sa propre colonne à GAUCHE de la carte, où un graphique qui veut être carré ne dispute plus la largeur aux chiffres — il occupait la colonne de droite et repoussait le tableau des passes hors de l’écran, alors que la marge de l’autre côté de la carte restait vide. Sa largeur se règle à la souris, et un double-clic sur la poignée la réinitialise.", "Chaque bloc de la colonne de droite a désormais un titre et se replie indépendamment : la passe, la position, ce qu’il faut accorder, la liste des passes. Chacun retient s’il est ouvert, car qui travaille les satellites FM ne regarde jamais la bande passante linéaire, et qui surveille un calendrier ne regarde jamais la vitesse radiale. Un bloc replié conserve dans son titre la seule chose utile : le décompte, l’élévation, le mode.", "La fréquence à accorder est le CENTRE de la bande passante, pas la valeur corrigée du Doppler. Celle-ci bouge chaque seconde, diffère pour chaque opérateur, et n’est pas ce que le plan de fréquences ou n’importe qui sur l’air appelle la fréquence du satellite ; elle a sa place à côté de Tracking, là où se trouve la radio, et c’est là qu’elle est désormais. La correction reste ici, comme l’écart qui explique la différence entre les deux.", "Plus de couleur là où elle décide de quelque chose : la bande de chaque fréquence, l’écart Doppler, un transpondeur inverseur, la largeur de bande passante et le pic d’élévation d’une passe sont des pastilles aux couleurs d’état de l’application, plutôt que des rangées de mots gris.", - "Le tracé au sol est visible sur tous les fonds de carte. Il était dessiné avec une variable CSS, qui n’est pas une couleur exploitable par un canvas : le navigateur conservait ce qu’il avait et le tracé sortait dans un blanc pâle qui disparaissait aussi bien sur l’imagerie que sur les déserts. C’est maintenant une vraie couleur, tracée sur un liseré sombre comme on dessine une route sur une carte — une seule ligne ne peut pas tenir à la fois sur une mer claire et un continent sombre, une ligne avec un contour si." + "Le tracé au sol est visible sur tous les fonds de carte. Il était dessiné avec une variable CSS, qui n’est pas une couleur exploitable par un canvas : le navigateur conservait ce qu’il avait et le tracé sortait dans un blanc pâle qui disparaissait aussi bien sur l’imagerie que sur les déserts. C’est maintenant une vraie couleur, tracée sur un liseré sombre comme on dessine une route sur une carte — une seule ligne ne peut pas tenir à la fois sur une mer claire et un continent sombre, une ligne avec un contour si.", + "Le tracé du ciel et la position partagent désormais la colonne de gauche, qui s’ouvre et se ferme avec le même type de bouton que le panneau de droite — c’était une icône radar, héritée de l’époque où elle basculait un graphique à l’intérieur de ce panneau. Le tracé et les chiffres sont la même réponse à deux précisions près, l’azimut et l’élévation dessinés puis écrits, et les avoir aux deux extrémités de la fenêtre obligeait à lire un relèvement d’un côté pour le retrouver de l’autre.", + "L’avertissement de dérive de bande du panneau de décodages ne se déclenche plus sur une seconde tranche. Il comparait la bande du décodeur à la bande d’ÉMISSION de la radio : une station avec la tranche A sur 20 m, la B sur 40 m et le focus d’émission sur B se voyait annoncer que son décodeur 20 m contredisait une radio qui était sur 20 m depuis le début — sur la tranche que ce décodeur écoute. La comparaison porte désormais sur toutes les bandes où la radio a un récepteur, ce qui a toujours été le sujet de cet avertissement : un décodeur qui annonce une bande sur laquelle rien de la radio ne se trouve, comme le fait une liaison CAT perdue." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 07a6c80..40422cc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6644,6 +6644,9 @@ export default function App() { // Only while CAT is actually connected: an empty band means "nothing to // compare with", never "the rig is on no band". rigBand={catState.connected ? (catState.band || '') : ''} + // Every band the radio is listening on, so a second slice on a second + // band does not read as a decoder that has lost CAT. + rigBands={catState.connected ? (catState.rx_bands ?? []) : []} myCall={station.callsign} myGrid={station.my_grid} // A DOUBLE click answers the station: it hands the decode back to diff --git a/frontend/src/components/DecodesPanel.tsx b/frontend/src/components/DecodesPanel.tsx index e0be688..6b10cf4 100644 --- a/frontend/src/components/DecodesPanel.tsx +++ b/frontend/src/components/DecodesPanel.tsx @@ -102,6 +102,9 @@ interface Props { // The band the RIG is on, when CAT is connected. Only ever compared with what // the decoder announces — see the drift warning. rigBand?: string; + // Every band the radio has a receiver on. A Flex running two slices has + // two, and a decoder on either of them is not drifting. + rigBands?: string[]; onCall: (d: Decode) => void; // A single click: take the station without transmitting — fill the entry, and // point the panels at it. Absent, a click falls back to onCall. @@ -641,7 +644,7 @@ function buildPeriods(filtered: Decode[], txMsgs: TxMsg[]) { })); } -export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, rigBand, onCall, onSelect, myCall, myGrid, onClear, onHalt, autoCallOn, onToggleAutoCall, autoCall, autoCallOnly, onSetAutoCallOnly, watchlist }: Props) { +export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, rigBand, rigBands, onCall, onSelect, myCall, myGrid, onClear, onHalt, autoCallOn, onToggleAutoCall, autoCall, autoCallOnly, onSetAutoCallOnly, watchlist }: Props) { const { t } = useI18n(); // Column widths, dragged in the header and shared by every row. Persisted // through writeUiPref (not raw localStorage) so the layout travels with data/ @@ -778,9 +781,19 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, r // Said, not decided. Using the rig's band instead would be wrong for anyone // decoding a second receiver on another band, and a warning costs that setup // nothing but a line it can read past. + // + // Compared against every band the radio is RECEIVING on, not the transmit + // band. Two slices on two bands with a decoder on each is a normal setup, + // and it made this warning lie: with slice A on 20 m, slice B on 40 m and + // transmit focus on B, the 20 m decoder was told the rig was on 40 m while + // the slice it listens to was on 20 m all along. The warning is for a + // decoder announcing a band NOTHING on the radio is on, which is what a + // lost CAT link actually looks like. const decoderBand = decodes.length ? (decodes[decodes.length - 1].band ?? '') : ''; - const bandDrift = !!rigBand && !!decoderBand - && rigBand.toLowerCase() !== decoderBand.toLowerCase(); + const onAir = (rigBands && rigBands.length ? rigBands : (rigBand ? [rigBand] : [])) + .map((b) => b.toLowerCase()); + const bandDrift = onAir.length > 0 && !!decoderBand + && !onAir.includes(decoderBand.toLowerCase()); const driftInstance = decodes.length ? (decodes[decodes.length - 1].instance ?? '') : ''; const liveMode = decodes.length ? decodes[decodes.length - 1].mode : txState?.mode; diff --git a/frontend/src/components/SatellitePanel.tsx b/frontend/src/components/SatellitePanel.tsx index cbcd95f..a81db0e 100644 --- a/frontend/src/components/SatellitePanel.tsx +++ b/frontend/src/components/SatellitePanel.tsx @@ -1,7 +1,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; -import { Satellite as SatIcon, Radio, ArrowUp, ArrowDown, PanelRightClose, PanelRightOpen, Radar, Compass, +import { Satellite as SatIcon, Radio, ArrowUp, ArrowDown, PanelRightClose, PanelRightOpen, + PanelLeftClose, PanelLeftOpen, Radar, Compass, ChevronDown, Clock, Crosshair, SlidersHorizontal, ListOrdered } from 'lucide-react'; import { GetSatelliteBirds, GetSatellitePositions, GetSatellitePasses, GetSatelliteTuning, @@ -857,13 +858,18 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) { > {Object.entries(BASEMAPS).map(([k, v]) => )} + {/* The left column, with the same kind of control as the right one. + + It was a Radar icon, from when this toggled a plot inside the + readout column. It now opens and shuts a column of the window, so + it says so the way the other one does — the two are the same + gesture and an operator should not have to learn them twice. */} {/* Put the whole window on the map. On a laptop the readout takes a third of the screen, and there are moments — watching a footprint @@ -880,17 +886,23 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) { {err &&
{err}
}
- {/* The sky, seen from underneath it: the centre is straight up, the - rim is the horizon, north is at the top. One glance says whether - the pass comes over the roof or along the treeline. + {/* WHERE IT IS: the sky plot and the position, in a column of their + own to the left of the map. - To the LEFT of the map, in a column of its own. It was stacked in - the readout column, where a plot that wants to be square competed - for width with the numbers and pushed the pass table off the - bottom of the screen — while the margin on this side of the map - sat empty the whole time. */} + The plot is the sky seen from underneath — the centre is + straight up, the rim is the horizon, north is at the top — and + one glance says whether the pass comes over the roof or along + the treeline. The numbers below it are the same answer to the + digit: azimuth, elevation, distance, height. They belong + together, and having them apart meant reading a bearing off one + side of the window and finding it on the other. + + Both were in the readout column, where a plot that wants to be + square competed for width with everything else and pushed the + pass table off the bottom of the screen — while the margin on + this side of the map sat empty the whole time. */} {skyShown && ( -
+
+ + {/* Where it is, right now. Its elevation goes in the heading: above + or below the horizon is the one thing worth knowing with the + block shut. */} +
toggleSec('where')} + right={tuning && !secOpen.where ? ( + + {fmtDeg(tuning.el)} {tuning.visible ? t('sat.up') : t('sat.below')} + + ) : undefined} + > +
+ + +
+ +
+ + + +
+ + {/* Approaching or receding, which is the sign of the whole Doppler + correction and the one number that explains why the frequencies + are moving the way they are. */} + {!!tuning && !bird?.geostationary && ( +
+ {tuning.range_rate < 0 + ? + : } + {tuning.range_rate < 0 ? t('sat.approaching') : t('sat.receding')} + {Math.abs(tuning.range_rate).toFixed(2)} km/s +
+ )} + + {/* Where the antenna is, beside where the satellite is. The two + differing is a rotator still slewing; the two differing for a + long time is a rotator that is stuck, and that is worth being + able to see without walking outside. */} + {tracking?.rot_on && ( +
+ {t('sat.antenna')} + {/* No elevation when none is being driven: an undriven zero + draws an antenna lying on the horizon, which is a bearing + and not the absence of one. */} + + {tracking.rot_az_only + ? fmtDeg(tracking.rot_az) + : `${fmtDeg(tracking.rot_az)} / ${fmtDeg(tracking.rot_el)}`} + + {tracking.rot_az_only && {t('sat.rotAzOnly')}} + {!tracking.rot_live && {t('sat.rotCommanded')}} +
+ )} +
)} {skyShown && ( @@ -1004,68 +1078,6 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) { )} - {/* Where it is, right now. Its elevation goes in the heading: above - or below the horizon is the one thing worth knowing with the - block shut. */} -
toggleSec('where')} - right={tuning && !secOpen.where ? ( - - {fmtDeg(tuning.el)} {tuning.visible ? t('sat.up') : t('sat.below')} - - ) : undefined} - > -
- - -
- -
- - - -
- - {/* Approaching or receding, which is the sign of the whole Doppler - correction and the one number that explains why the frequencies - are moving the way they are. */} - {!!tuning && !bird?.geostationary && ( -
- {tuning.range_rate < 0 - ? - : } - {tuning.range_rate < 0 ? t('sat.approaching') : t('sat.receding')} - {Math.abs(tuning.range_rate).toFixed(2)} km/s -
- )} - - {/* Where the antenna is, beside where the satellite is. The two - differing is a rotator still slewing; the two differing for a - long time is a rotator that is stuck, and that is worth being - able to see without walking outside. */} - {tracking?.rot_on && ( -
- {t('sat.antenna')} - {/* No elevation when none is being driven: an undriven zero - draws an antenna lying on the horizon, which is a bearing - and not the absence of one. */} - - {tracking.rot_az_only - ? fmtDeg(tracking.rot_az) - : `${fmtDeg(tracking.rot_az)} / ${fmtDeg(tracking.rot_el)}`} - - {tracking.rot_az_only && {t('sat.rotAzOnly')}} - {!tracking.rot_live && {t('sat.rotCommanded')}} -
- )} -
- {/* What to tune. */}