diff --git a/changelog.json b/changelog.json index 7796eb5..a906da4 100644 --- a/changelog.json +++ b/changelog.json @@ -10,7 +10,9 @@ "The diagnostic log no longer carries the FlexRadio meter readings, which drowned everything else.", "OmniRig: a new \"VFO to read\" setting. OmniRig reports the active VFO from the rig file, and some files get it wrong — an IC-7610 file declared VFO B while the operator was on the main VFO, so OpsLog wrote to one VFO and read the other, and the frequency looked frozen. Force VFO A or B when that happens.", "Bulk edit: fixed \"field is not bulk-editable\" on the QRZ.com received status and the confirmation dates added in 0.21.6 — they were declared in the wrong place and refused at Apply. Bulk-editing State and County, offered since the start, was refused the same way and now works.", - "QRZ.com confirmation download: the whole logbook was requested every time, which QRZ cuts short on a large log — the read stopped two thirds of the way through and the rest was never seen. Only the window is requested now. And the last-download date is no longer moved when the download was incomplete: it used to advance over records that had never been read, which skipped them for good." + "QRZ.com confirmation download: the whole logbook was requested every time, which QRZ cuts short on a large log — the read stopped two thirds of the way through and the rest was never seen. Only the window is requested now. And the last-download date is no longer moved when the download was incomplete: it used to advance over records that had never been read, which skipped them for good.", + "The callsign in the top bar is now the station switcher: click it and pick a profile to activate it. Managing profiles is still there, at the bottom of the list.", + "The UTC clock moved from the top bar to the status bar at the bottom, beside the database." ], "fr": [ "Le constructeur de filtres reprend les mêmes noms de champs de confirmation que l'édition en lot, gagne le statut de réception QRZ.com manquant, et permet de filtrer sur les dates d'envoi et de réception (avant / après).", @@ -20,7 +22,9 @@ "Le journal de diagnostic ne contient plus les mesures des instruments FlexRadio, qui noyaient tout le reste.", "OmniRig : nouveau réglage « VFO à lire ». OmniRig indique le VFO actif d'après le fichier radio, et certains fichiers se trompent — un fichier IC-7610 déclarait le VFO B alors que l'opérateur était sur le VFO principal, si bien qu'OpsLog écrivait dans l'un et lisait l'autre, et la fréquence semblait figée. Forcez le VFO A ou B le cas échéant.", "Édition en lot : correction du refus « field is not bulk-editable » sur le statut de réception QRZ.com et les dates de confirmation ajoutées en 0.21.6 — ils étaient déclarés au mauvais endroit et rejetés au moment d'appliquer. L'édition en lot de State et County, proposée depuis toujours, était refusée de la même façon et fonctionne désormais.", - "Téléchargement des confirmations QRZ.com : le carnet entier était demandé à chaque fois, ce que QRZ tronque sur un grand log — la lecture s'arrêtait aux deux tiers et le reste n'était jamais vu. Seule la fenêtre est désormais demandée. Et la date du dernier téléchargement n'avance plus lorsque le téléchargement est incomplet : elle passait par-dessus des enregistrements jamais lus, qui étaient alors ignorés définitivement." + "Téléchargement des confirmations QRZ.com : le carnet entier était demandé à chaque fois, ce que QRZ tronque sur un grand log — la lecture s'arrêtait aux deux tiers et le reste n'était jamais vu. Seule la fenêtre est désormais demandée. Et la date du dernier téléchargement n'avance plus lorsque le téléchargement est incomplet : elle passait par-dessus des enregistrements jamais lus, qui étaient alors ignorés définitivement.", + "L'indicatif en haut de la fenêtre est désormais le sélecteur de station : un clic, on choisit un profil et il devient actif. La gestion des profils reste accessible, en bas de la liste.", + "L'horloge UTC est passée de la barre du haut à la barre d'état en bas, à côté de la base de données." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e7c69cd..2b3785c 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, Gauge, Hash, Loader2, Lock, + Activity, AlertCircle, Antenna, Bell, Check, CheckCircle2, ChevronDown, Clock, CloudOff, Compass, Database, Ear, Eraser, Gauge, Hash, Loader2, Lock, Maximize2, Minimize2, Mic, MessageSquare, Pencil, Radio, RadioTower, RefreshCw, Satellite, Send, Settings, SlidersHorizontal, SpellCheck, Square, Terminal, Trash2, Unlock, X, Zap, } from 'lucide-react'; @@ -45,7 +45,7 @@ import { ChatAvailable, GetChatHistory, SendChatMessage, GetOnlineOperators, QSOAudioBegin, QSOAudioCancel, QSOAudioRestart, QSOAudioResetClock, GetAwardDefs, - GetUIPref, GetActiveProfile, QuitApp, + GetUIPref, GetActiveProfile, ListProfiles, ActivateProfile, QuitApp, ReportLiveActivity, LiveLastQSOAgeSec, GetAmpStatuses, AmpOperate, GetFlexState, FlexAmpOperate, @@ -57,6 +57,9 @@ import type { adif as adifModels, lookup as lookupModels, cat as catModels } fro import type { QSOForm, WorkedBeforeView, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types'; import { Menubar, type Menu } from '@/components/Menubar'; +import { + DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, +} from '@/components/ui/dropdown-menu'; import { APP_VERSION, APP_AUTHOR } from '@/version'; import { QSLManagerPanel } from '@/components/QSLManagerModal'; import { QslDesignerModal } from '@/components/qsl/QslDesignerModal'; @@ -2398,6 +2401,18 @@ export default function App() { // every profile switch; the grids take it as their React key so they remount // and re-read the now-correct per-profile layout. const [activeProfileId, setActiveProfileId] = useState(null); + // The station switcher in the header needs the whole list, not just the active + // one. Reloaded on every profile change so a profile renamed or added in the + // settings shows up without a restart. + const [profileList, setProfileList] = useState<{ id: number; callsign: string; name: string }[]>([]); + const loadProfileList = useCallback(() => { + ListProfiles().then((ps: any) => { + setProfileList((Array.isArray(ps) ? ps : []).map((p: any) => ({ + id: p.id, callsign: p.callsign ?? '', name: p.name ?? '', + }))); + }).catch(() => {}); + }, []); + useEffect(() => { loadProfileList(); }, [loadProfileList]); useEffect(() => { GetActiveProfile().then((p: any) => { if (p && p.id != null) { setGridPrefsProfile(p.id); setActiveProfileId(p.id); } @@ -2420,7 +2435,7 @@ export default function App() { // Re-scope the grid column layout BEFORE the grids remount (key change). setGridPrefsProfile(id ?? null); setActiveProfileId(typeof id === 'number' ? id : null); - loadStation(); loadLists(); loadCATCfg(); reloadWk(); loadMainPanes(); + loadStation(); loadLists(); loadCATCfg(); reloadWk(); loadMainPanes(); loadProfileList(); // The chat is per shared logbook — clear the previous profile's messages // and reload for the new logbook (or hide if it isn't a MySQL log). setChatMsgs([]); chatSeen.current.clear(); setChatOnline([]); setChatUnread(0); @@ -2428,7 +2443,7 @@ export default function App() { setChatEpoch((e) => e + 1); }); return () => { off(); }; - }, [loadStation, loadLists, loadCATCfg, reloadWk, loadMainPanes]); + }, [loadStation, loadLists, loadCATCfg, reloadWk, loadMainPanes, loadProfileList]); useEffect(() => { (async () => { await reloadWk(); @@ -4650,24 +4665,48 @@ export default function App() { ) : } -
- - {utcNow}UTC -
-
{station.callsign ? ( - + // Switching station is the frequent act; editing a profile is the + // rare one. The callsign used to open the settings, so changing + // station took four clicks and a scroll — it now IS the switcher, + // with the settings kept at the bottom of the list. + + + + + + {profileList.map((p) => ( + { if (p.id !== activeProfileId) ActivateProfile(p.id).catch((e: any) => setError(String(e?.message ?? e))); }} + > + + {p.id === activeProfileId + ? + : } + {p.callsign || '—'} + {p.name && {p.name}} + + + ))} + + { setSettingsSection('profiles'); setShowSettings(true); }}> + {t('prof.manage')} + + + ) : (
+ {/* UTC clock — moved out of the header, where it competed with the + frequency for the eye. It belongs with the other passive + indicators. */} + + + {utcNow}Z + {dbConn && (