diff --git a/changelog.json b/changelog.json index e998a75..e617b14 100644 --- a/changelog.json +++ b/changelog.json @@ -4,6 +4,7 @@ "date": "2026-07-24", "en": [ "New: 4O3A Tuner Genius XL control. Enable it in Settings → Tuner Genius (IP only; port is fixed at 9010). Live SWR and forward power with Tune, Bypass and Operate/Standby, the two channels A/B (source, frequency and antenna) shown and click-selectable. Available as a docked widget, a card in the FlexRadio panel (like the PowerGenius) and a card in Station Control. Controlled directly over TCP, so it uses just one of the box's connection slots.", + "The FlexRadio, amplifier and tuner cards can now be collapsed with the chevron in their header, and the amplifier meters are the same size as the others for a tidier layout.", "Fixed the colour theme sometimes reverting to the default when reopening OpsLog — it's now restored reliably.", "ADIF export field picker: the per-group All / None buttons work again.", "Station Control: cards now pack tightly into balanced columns instead of leaving big gaps under shorter panels — a cleaner, more even dashboard.", @@ -13,6 +14,7 @@ ], "fr": [ "Nouveau : contrôle du 4O3A Tuner Genius XL. Active-le dans Réglages → Tuner Genius (IP seulement ; port fixé à 9010). ROS et puissance directe en direct avec Accord, Bypass et Operate/Standby, et les deux canaux A/B (source, fréquence et antenne) affichés et sélectionnables d'un clic. Disponible en widget ancré, en carte dans le panneau FlexRadio (comme le PowerGenius) et en carte dans Station Control. Piloté directement en TCP, il n'utilise qu'une des connexions de la boîte.", + "Les cartes FlexRadio, amplificateur et tuner peuvent maintenant être repliées via le chevron de leur en-tête, et les meters de l'amplificateur ont la même taille que les autres pour une mise en page plus nette.", "Correction du thème qui revenait parfois au défaut à la réouverture d'OpsLog — il est maintenant restauré de façon fiable.", "Sélecteur de champs à l'export ADIF : les boutons Tout / Aucun par groupe refonctionnent.", "Station Control : les cartes se rangent en colonnes équilibrées et se tassent au lieu de laisser de gros trous sous les panneaux plus courts — tableau de bord plus propre et régulier.", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index efcba58..1b5c6a5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { - Activity, AlertCircle, Antenna, Bell, CheckCircle2, Clock, CloudOff, Compass, Database, Ear, Eraser, Hash, Loader2, Lock, + Activity, AlertCircle, Antenna, Bell, CheckCircle2, Clock, CloudOff, Compass, Database, Ear, Eraser, Gauge, Hash, Loader2, Lock, Maximize2, Minimize2, Mic, MessageSquare, Pencil, Radio, RadioTower, RefreshCw, Satellite, Send, Settings, SlidersHorizontal, Square, Terminal, Trash2, Unlock, X, Zap, } from 'lucide-react'; @@ -1687,7 +1687,9 @@ export default function App() { } catch {} }; tick(); - const id = window.setInterval(tick, 1500); + // Fast poll so the SWR/power meters track TX responsively (backend polls the + // device at ~400ms; 500ms here keeps the UI close behind). + const id = window.setInterval(tick, 500); return () => { alive = false; window.clearInterval(id); }; }, []); const tgTune = () => { @@ -4143,7 +4145,7 @@ export default function App() { : 'border-border text-muted-foreground hover:bg-muted', )} > - + {showTuner && tgStatus.connected && } )} diff --git a/frontend/src/components/AmpCard.tsx b/frontend/src/components/AmpCard.tsx index 630d7b8..59af7b0 100644 --- a/frontend/src/components/AmpCard.tsx +++ b/frontend/src/components/AmpCard.tsx @@ -1,5 +1,5 @@ -import { useRef } from 'react'; -import { Flame } from 'lucide-react'; +import { useRef, useState } from 'react'; +import { Flame, ChevronDown } from 'lucide-react'; import { cn } from '@/lib/utils'; import { AmpOperate, AmpPower, AmpPowerLevel, AmpFanMode, FlexAmpOperate } from '../../wailsjs/go/main/App'; @@ -49,14 +49,20 @@ function MeterBar({ label, value, unit, lo, hi, accent = '#16a34a', display, seg ); } -function Card({ icon: Icon, title, accent, children }: { icon: any; title: string; accent?: string; children: React.ReactNode }) { +function Card({ icon: Icon, title, accent, children, ckey }: { icon: any; title: string; accent?: string; children: React.ReactNode; ckey?: string }) { + // Collapsible with persisted state — same behaviour as the FlexRadio panel's Card. + const storeKey = 'opslog.cardOpen.' + (ckey || title); + const [open, setOpen] = useState(() => localStorage.getItem(storeKey) !== '0'); + const toggle = () => setOpen((o) => { const n = !o; localStorage.setItem(storeKey, n ? '1' : '0'); return n; }); return (
-
+
-
{children}
+ + + {open &&
{children}
}
); } @@ -95,7 +101,7 @@ export function AmpCard({ amp, flex, t }: { amp: Amp; flex: any; t: (k: string, if (isSPE) { const spe = amp.spe; return ( - +
-
{children}
+ + + {open &&
{children}
} ); } @@ -367,7 +374,7 @@ export function FlexPanel({ onCWSpeed, onReportRST }: { onCWSpeed?: (wpm: number try { const s: any = await GetTunerGeniusStatus(); if (alive && s) setTg(s as TGStatus); } catch {} }; tick(); - const id = window.setInterval(tick, 1500); + const id = window.setInterval(tick, 500); // fast so meters track TX (see App.tsx) return () => { alive = false; window.clearInterval(id); }; }, []); @@ -927,7 +934,7 @@ export function FlexPanel({ onCWSpeed, onReportRST }: { onCWSpeed?: (wpm: number The Flex doesn't report SPE amps, so this card is driven by OpsLog's own SPE link rather than the Flex amplifier object. */} {isSPE && ( - +
{ampPicker}
); diff --git a/frontend/src/components/StationControlPanel.tsx b/frontend/src/components/StationControlPanel.tsx index c8a83ce..a86c6df 100644 --- a/frontend/src/components/StationControlPanel.tsx +++ b/frontend/src/components/StationControlPanel.tsx @@ -298,7 +298,7 @@ export function StationControlPanel({ centerLat, centerLon, bearing }: RotatorPr try { const s: any = await GetTunerGeniusStatus(); if (alive && s) setTg(s as TGStatus); } catch {} }; load(); - const id = window.setInterval(load, 1500); + const id = window.setInterval(load, 500); // fast so meters track TX (see App.tsx) return () => { alive = false; window.clearInterval(id); }; }, []); diff --git a/frontend/src/components/TunerCard.tsx b/frontend/src/components/TunerCard.tsx index 854b558..d334820 100644 --- a/frontend/src/components/TunerCard.tsx +++ b/frontend/src/components/TunerCard.tsx @@ -1,4 +1,5 @@ -import { Zap, Radio } from 'lucide-react'; +import { useState } from 'react'; +import { Gauge, Radio, ChevronDown } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { TGStatus, TGChannel } from '@/components/TunerGeniusPanel'; import { TunerGeniusAutotune, TunerGeniusSetBypass, TunerGeniusSetOperate, TunerGeniusActivate } from '../../wailsjs/go/main/App'; @@ -46,14 +47,20 @@ function MeterBar({ label, value, unit, lo, hi, accent = '#16a34a', display, seg ); } -function Card({ icon: Icon, title, accent, children }: { icon: any; title: string; accent?: string; children: React.ReactNode }) { +function Card({ icon: Icon, title, accent, children, ckey }: { icon: any; title: string; accent?: string; children: React.ReactNode; ckey?: string }) { + // Collapsible with persisted state — same behaviour as the FlexRadio panel's Card. + const storeKey = 'opslog.cardOpen.' + (ckey || title); + const [open, setOpen] = useState(() => localStorage.getItem(storeKey) !== '0'); + const toggle = () => setOpen((o) => { const n = !o; localStorage.setItem(storeKey, n ? '1' : '0'); return n; }); return (
-
+
-
{children}
+ + + {open &&
{children}
}
); } @@ -102,7 +109,7 @@ export function TunerCard({ status, t }: { status: TGStatus; t: (k: string, v?: const title = `${t('tgp.title')}${status.host ? ' · ' + status.host : ''}`; return ( - +