From 3a9dda13c4ca74582a28aa92f597cf7873306924 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 25 Jul 2026 11:22:56 +0200 Subject: [PATCH] feat: collapsible Flex/amp/tuner cards, matched meter sizes, faster + distinct-icon TGXL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses three points of feedback on the Tuner Genius work: - Meter sizes: the amplifier meters were rendered `compact` (smaller than the FlexRadio meters). Dropped compact so the amp, tuner and Flex meters are all the same size. - Collapsible cards: the FlexRadio panel Card, AmpCard and TunerCard now fold from a chevron in the header, state persisted per card (opslog.cardOpen.*). The amplifier cards share the "amplifier" collapse key across their SPE/ACOM/ PGXL variants so folding sticks regardless of the shown model. - TGXL responsiveness: the tuner's device poll dropped 1500ms→400ms and the three UI pollers 1500ms→500ms, so the SWR/power meters track TX without the 2–3s lag behind the amplifier the user saw. - Icon: the Tuner Genius top-bar toggle used Zap, same as the CW keyer — changed the tuner's icon (top bar + widget + card) to Gauge so the two are distinct. --- changelog.json | 2 ++ frontend/src/App.tsx | 8 ++++-- frontend/src/components/AmpCard.tsx | 28 +++++++++++-------- frontend/src/components/FlexPanel.tsx | 27 +++++++++++------- .../src/components/StationControlPanel.tsx | 2 +- frontend/src/components/TunerCard.tsx | 19 +++++++++---- frontend/src/components/TunerGeniusPanel.tsx | 4 +-- internal/tunergenius/tunergenius.go | 9 ++++-- 8 files changed, 63 insertions(+), 36 deletions(-) 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 ( - +