fix(bandmap): call all hooks before the unknown-band early return (React #300)
The Ctrl+↑/↓ keyboard-nav useEffect sat AFTER `if (!range) return`, so on a band with no range — e.g. a spurious 33 cm frequency briefly reported by an Icom over CAT — the component returned before that hook and the hook count changed between renders, crashing the whole window with React #300. Moved the effect above the early return; lo/hi already fall back to [0,1] when there's no range.
This commit is contained in:
+4
-2
@@ -18,7 +18,8 @@
|
||||
"DX Cluster: new option \"Already worked only on the same slot\" (Settings → DX Cluster). With it on, a spot reads \"worked\" only when you worked that callsign on the SAME band and mode — not just anywhere. It respects the digital-mode grouping (Settings → General): grouped, a 20m FT8 contact also marks a 20m FT4 spot as worked; ungrouped, FT8 and FT4 are separate slots.",
|
||||
"DX Cluster: added a WORKED status-filter chip, so you can show — or, with the other chips off, isolate — already-worked spots, not only hide them with the \"Hide worked\" checkbox.",
|
||||
"DX Cluster: spots that represent nothing — entity/band/mode already worked, the callsign not in your log, no POTA/county/prefix novelty — are now dimmed, so your eye skips them and the spots worth working stand out. What counts as \"nothing\" follows the \"same slot\" option and the digital-mode grouping.",
|
||||
"Ultrabeam: an older controller (seen over an RS232-to-Ethernet bridge) answers with an 11-byte status frame instead of 12. OpsLog rejected it as \"too short\" and reconnect-looped; it now accepts a shorter checksum-valid frame and defaults the missing tail field."
|
||||
"Ultrabeam: an older controller (seen over an RS232-to-Ethernet bridge) answers with an 11-byte status frame instead of 12. OpsLog rejected it as \"too short\" and reconnect-looped; it now accepts a shorter checksum-valid frame and defaults the missing tail field.",
|
||||
"Fixed a crash (React #300) that could take down the whole window when the rig briefly reported an unusual band — for example a spurious 33 cm reading from an Icom. The Band Map had a keyboard-navigation hook after its early return for unknown bands, so the number of hooks changed between renders. The hook now always runs."
|
||||
],
|
||||
"fr": [
|
||||
"Visionneuse de log : la fenêtre conserve deux fois plus d'historique (512 Ko au lieu de 256 Ko, ~3200 lignes). Lors d'une trace chargée, les plus vieilles lignes défilaient hors du buffer pendant qu'on les lisait encore ; la fenêtre agrandie les garde.",
|
||||
@@ -36,7 +37,8 @@
|
||||
"DX Cluster : nouvelle option « Déjà contacté seulement sur le même slot » (Réglages → DX Cluster). Activée, un spot n'affiche « contacté » que si vous avez contacté cet indicatif sur la MÊME bande et le MÊME mode — pas juste n'importe où. Elle respecte le groupage des modes numériques (Réglages → Général) : groupé, un contact 20m FT8 marque aussi un spot 20m FT4 comme contacté ; dégroupé, FT8 et FT4 sont des slots distincts.",
|
||||
"DX Cluster : ajout d'un filtre de statut WORKED, pour afficher — ou, en désactivant les autres, isoler — les spots déjà contactés, pas seulement les masquer avec la case « Masquer les contactés ».",
|
||||
"DX Cluster : les spots qui ne représentent rien — entité/bande/mode déjà faite, indicatif absent du journal, aucune nouveauté POTA/comté/préfixe — sont désormais atténués, pour que l'œil les ignore et que les spots à travailler ressortent. Ce qui compte comme « rien » suit l'option « même slot » et le groupage des modes numériques.",
|
||||
"Ultrabeam : un contrôleur plus ancien (vu derrière un pont RS232-Ethernet) répond avec une trame de statut de 11 octets au lieu de 12. OpsLog la rejetait comme « trop courte » et bouclait en reconnexion ; il accepte désormais une trame valide plus courte et met par défaut le champ de fin manquant."
|
||||
"Ultrabeam : un contrôleur plus ancien (vu derrière un pont RS232-Ethernet) répond avec une trame de statut de 11 octets au lieu de 12. OpsLog la rejetait comme « trop courte » et bouclait en reconnexion ; il accepte désormais une trame valide plus courte et met par défaut le champ de fin manquant.",
|
||||
"Correction d'un plantage (React #300) qui pouvait faire tomber toute la fenêtre quand la radio rapportait brièvement une bande inhabituelle — par exemple une lecture 33 cm parasite d'un Icom. La Band Map avait un hook de navigation clavier après son retour anticipé pour les bandes inconnues, changeant le nombre de hooks entre deux rendus. Le hook est désormais toujours exécuté."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -358,6 +358,43 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
return () => el.removeEventListener('wheel', onWheel);
|
||||
}, [range]);
|
||||
|
||||
// Ctrl+↑ / Ctrl+↓ hop to the next spot above / below the rig frequency and tune
|
||||
// to it. Higher freq is UP on the map (see freqToY), so ↑ = next higher spot.
|
||||
// Only active on the docked Main-view map (keyNav) and ignored while typing.
|
||||
// MUST stay ABOVE the `if (!range)` early return below: on an unknown band
|
||||
// (e.g. a spurious 33 cm CAT reading from an Icom) that return skipped this
|
||||
// hook, so the hook count changed between renders and React crashed with #300
|
||||
// ("rendered fewer hooks than expected"). lo/hi are always defined (they fall
|
||||
// back to [0,1] when there's no range), so it's safe to run here.
|
||||
useEffect(() => {
|
||||
if (!keyNav) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (!e.ctrlKey || e.altKey || e.metaKey) return;
|
||||
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
|
||||
const ae = document.activeElement as HTMLElement | null;
|
||||
const tag = (ae?.tagName || '').toLowerCase();
|
||||
if (tag === 'input' || tag === 'textarea' || tag === 'select' || ae?.isContentEditable) return;
|
||||
const list = spots
|
||||
.filter((s) => (s.band ?? '') === band && s.freq_hz > 0)
|
||||
.slice()
|
||||
.sort((a, b) => a.freq_hz - b.freq_hz);
|
||||
if (!list.length) return;
|
||||
const cur = currentFreqHz || (lo + hi) * 500; // mid-band kHz→Hz when no rig freq
|
||||
const EPS = 50; // Hz, so we don't re-pick the spot we're already sitting on
|
||||
let target: Spot | undefined;
|
||||
if (e.key === 'ArrowUp') {
|
||||
target = list.find((s) => s.freq_hz > cur + EPS);
|
||||
} else {
|
||||
for (let i = list.length - 1; i >= 0; i--) { if (list[i].freq_hz < cur - EPS) { target = list[i]; break; } }
|
||||
}
|
||||
if (!target) return;
|
||||
e.preventDefault();
|
||||
onSpotClick(target);
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [keyNav, spots, band, currentFreqHz, lo, hi, onSpotClick]);
|
||||
|
||||
if (!range) {
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col items-center justify-center text-xs text-muted-foreground p-3 bg-muted/20">
|
||||
@@ -388,38 +425,6 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o
|
||||
scrollerRef.current.scrollTop = Math.max(0, y - containerH / 2);
|
||||
}
|
||||
|
||||
// Ctrl+↑ / Ctrl+↓ hop to the next spot above / below the rig frequency and tune
|
||||
// to it. Higher freq is UP on the map (see freqToY), so ↑ = next higher spot.
|
||||
// Only active on the docked Main-view map (keyNav) and ignored while typing.
|
||||
useEffect(() => {
|
||||
if (!keyNav) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (!e.ctrlKey || e.altKey || e.metaKey) return;
|
||||
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
|
||||
const ae = document.activeElement as HTMLElement | null;
|
||||
const tag = (ae?.tagName || '').toLowerCase();
|
||||
if (tag === 'input' || tag === 'textarea' || tag === 'select' || ae?.isContentEditable) return;
|
||||
const list = spots
|
||||
.filter((s) => (s.band ?? '') === band && s.freq_hz > 0)
|
||||
.slice()
|
||||
.sort((a, b) => a.freq_hz - b.freq_hz);
|
||||
if (!list.length) return;
|
||||
const cur = currentFreqHz || (lo + hi) * 500; // mid-band kHz→Hz when no rig freq
|
||||
const EPS = 50; // Hz, so we don't re-pick the spot we're already sitting on
|
||||
let target: Spot | undefined;
|
||||
if (e.key === 'ArrowUp') {
|
||||
target = list.find((s) => s.freq_hz > cur + EPS);
|
||||
} else {
|
||||
for (let i = list.length - 1; i >= 0; i--) { if (list[i].freq_hz < cur - EPS) { target = list[i]; break; } }
|
||||
}
|
||||
if (!target) return;
|
||||
e.preventDefault();
|
||||
onSpotClick(target);
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [keyNav, spots, band, currentFreqHz, lo, hi, onSpotClick]);
|
||||
|
||||
const currentKHz = currentFreqHz ? currentFreqHz / 1000 : 0;
|
||||
const showRigPointer = currentKHz >= lo && currentKHz <= hi;
|
||||
const rigY = freqToY(currentKHz);
|
||||
|
||||
Reference in New Issue
Block a user