From da9c76e161c7cb16a9551bf99f02d1b43a2034fe Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Mon, 10 Aug 2026 09:07:08 +0200 Subject: [PATCH] feat(icom): offer the bands the connected radio actually has The band row was hardwired to 160-6 m, so an IC-9700 - the one radio in the range with no HF at all - showed ten dead buttons and none of 2 m, 70 cm or 23 cm. bandsFor(model) follows the model, exactly as attOptions already did for the attenuator steps. bandOfHz's labels had to move with it: it returned '70' where the button says '70cm', so the current-band highlight could never have matched, and it knew nothing of 23 cm. --- app.go | 9 ++++ changelog.json | 4 ++ frontend/src/components/IcomPanel.tsx | 32 ++++++++++-- frontend/src/components/SettingsModal.tsx | 6 +++ frontend/src/lib/i18n.tsx | 4 +- frontend/wailsjs/go/main/App.d.ts | 13 +++++ frontend/wailsjs/go/main/App.js | 24 +++++++++ frontend/wailsjs/go/models.ts | 61 +++++++++++++++++++++++ 8 files changed, 147 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index 803dab1..9c8523a 100644 --- a/app.go +++ b/app.go @@ -294,6 +294,11 @@ const ( keyScpEnabled = "scp.enabled" // Super Check Partial / N+1 suggestions on + // Web publishing: the whole config as one JSON blob. A single key rather than + // twenty: it is read and written as a unit by one settings panel, and the FTP + // password rides inside it — see isSensitiveSetting. + keyWebPublish = "webpublish.config" + // Worked-before: fold an operator's portable forms (X, X/3, X/P) together. // Stored inverted — "0" means OFF — so the feature is ON for an existing // install that has never seen the key, which is the behaviour operators asked @@ -690,6 +695,7 @@ type App struct { awardSnap []qso.QSO // light-scanned + enriched logbook snapshot reused across award computations awardSnapRev string // logbook revision the snapshot was built at ("" = none) awardSnapUsed time.Time // last read — the snapshot is dropped once it goes cold (see awardSnapshotJanitor) + webpub webPublisher // log-to-website publishing: debounce timer, periodic ticker, last result dataDir string // /data — holds config.json, logs, cty.dat // shuttingDown gates beforeClose re-entry: the first user attempt to @@ -1238,6 +1244,7 @@ func (a *App) startup(ctx context.Context) { a.clusterEvents = newClusterQueue() go a.clusterEventWorker() go a.awardSnapshotJanitor() // give the award snapshot's memory back once it goes cold + a.restartWebPublishTimer() // periodic log-to-website refresh, if configured a.cluster = cluster.NewManager( // onSpot / onLine run on the session's socket-read goroutine, so they must @@ -2655,6 +2662,7 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) { } a.maybeAutoSendEQSL(qc) a.maybeSelfSpot(qc) + a.publishSoon() // refresh the published web page, debounced if a.udp != nil { a.udp.EmitLoggedADIF(adif.SingleRecordADIF(qc)) } @@ -11704,6 +11712,7 @@ func (a *App) LogUDPLoggedADIF(adifText string) (int64, error) { } a.maybeAutoSendEQSL(qc) a.maybeSelfSpot(qc) + a.publishSoon() // refresh the published web page, debounced // Forward to the outbound UDP integrations, exactly like the manual log // path — otherwise a QSO logged FROM WSJT-X/JTDX/MSHV was never re-emitted // to the outbound ADIF listeners (Log4OM, N1MM, gridtracker…). diff --git a/changelog.json b/changelog.json index 84e658f..2612f35 100644 --- a/changelog.json +++ b/changelog.json @@ -3,6 +3,8 @@ "version": "0.24.1", "date": "", "en": [ + "Web publishing: OpsLog can now write your log to a file for a website — a standalone HTML page (sortable, dark/light, no external request of any kind) or a CSV. You pick the columns and how many QSOs, it refreshes when you log and, if you want, on a timer, and it can upload the result by FTP or FTPS. The file is always written locally first, so a network failure leaves a good file on disk instead of a truncated one on the server. Settings → Web publishing.", + "Icom console: an IC-9700 gets the bands it actually has. The band row was hardwired to 160–6 m, so the one radio in the range with no HF at all showed ten dead buttons and none of 2 m, 70 cm or 23 cm. The row now follows the model, as the attenuator steps already did — an IC-705 gains 2 m and 70 cm, an IC-9100 those plus 23 cm.", "Maps: the tiles are back. OpenStreetMap blocked OpsLog on its volunteer-run tile servers and every map filled with \"Access blocked\" squares. Their policy requires an application to identify itself with its own User-Agent on each request — something a program drawing maps inside a web view cannot do, the browser sets that header itself. So the two layers that still used those servers, the Street basemap and the locator map, have moved to services whose terms do cover a distributed application: Esri for Street, Carto for the locator map. OpenStreetMap is still credited — Carto's tiles are built from its data — and the locator map now throttles its tile requests like the world map already did.", "Band map: a station you have just worked stops showing as NEW. After each QSO the spot colours are refreshed, but only when the DX-cluster list was on screen — the band map was left out, so working a station with the usual layout (Recent QSOs on the left, band map on the right) left its pill orange until you happened to open the cluster tab. The band map, docked or in its own tab, now counts as on screen like the cluster does.", "Band map: new POTA, new county and already-worked callsign are shown at last. The band map only ever coloured the entity status — new DXCC, new band, new slot, worked — and ignored the other three markers, even though it was already receiving them: a new park on an entity you have worked looked like any other worked spot. They now appear on the pill's left strip, in the same colours the DX-cluster list uses, so a fact is not blue in one panel and green in the next. They stack rather than replace: a worked entity that is also a new park shows both, the way the cluster list already spells them out side by side. A new county is now violet instead of sharing green with a new park — in BOTH panels, since the two would otherwise disagree; the marker colours moved to one shared table, which is also what a per-marker colour setting will drive.", @@ -13,6 +15,8 @@ "Band map: the width can be dragged. Both the map docked beside the tables and the per-band cards in the Band map tab were locked at a fixed width, so an operator watching a busy band could not give the map more room — nor take it back for the log. Grab the edge to resize, double-click it to go back to the default. The width is remembered and travels with your data folder, like the other layout settings. In the tab, one width applies to every card: they sit side by side, and columns of different widths read as a mistake." ], "fr": [ + "Publication web : OpsLog peut désormais écrire ton journal dans un fichier destiné à un site — une page HTML autonome (triable, claire ou sombre, sans aucune requête externe) ou un CSV. Tu choisis les colonnes et le nombre de QSO, il se rafraîchit à chaque enregistrement et, si tu veux, périodiquement, et il peut envoyer le résultat par FTP ou FTPS. Le fichier est toujours écrit en local d abord : une panne réseau laisse donc un bon fichier sur le disque plutôt qu un fichier tronqué sur le serveur. Réglages → Publication web.", + "Console Icom : un IC-9700 reçoit enfin les bandes qu il possède. La rangée de bandes était figée sur 160–6 m, si bien que la seule radio de la gamme sans aucune HF affichait dix boutons morts et aucune de ses bandes 2 m, 70 cm et 23 cm. La rangée suit maintenant le modèle, comme le faisaient déjà les pas d atténuateur — un IC-705 gagne le 2 m et le 70 cm, un IC-9100 ceux-là plus le 23 cm.", "Cartes : les tuiles sont de retour. OpenStreetMap a bloqué OpsLog sur ses serveurs de tuiles bénévoles et toutes les cartes se sont remplies de carrés « Access blocked ». Leur politique exige qu'une application s'identifie par son propre User-Agent à chaque requête — ce qu'un programme qui dessine ses cartes dans une vue web ne peut pas faire, c'est le navigateur qui pose cet en-tête. Les deux couches qui utilisaient encore ces serveurs, le fond Street et la carte des locators, passent donc sur des services dont les conditions couvrent une application distribuée : Esri pour Street, Carto pour les locators. OpenStreetMap reste crédité — les tuiles Carto sont construites à partir de ses données — et la carte des locators limite désormais son débit de requêtes comme le faisait déjà la carte du monde.", "Band map : une station que tu viens de contacter cesse d'apparaître en NEW. Après chaque QSO, les couleurs des spots sont rafraîchies, mais seulement quand la liste du cluster DX était à l'écran — la band map avait été oubliée. Contacter une station dans la disposition habituelle (QSO récents à gauche, band map à droite) laissait donc sa pastille en orange jusqu'à ce qu'on ouvre l'onglet cluster. La band map, ancrée ou dans son onglet, compte désormais comme visible au même titre que le cluster.", "Band map : nouveau POTA, nouveau comté et indicatif déjà contacté sont enfin visibles. La band map ne colorait que le statut d'entité — nouveau DXCC, nouvelle bande, nouveau créneau, contacté — et ignorait les trois autres marqueurs alors qu'elle les recevait déjà : un nouveau parc sur une entité déjà travaillée ressemblait à n'importe quel spot contacté. Ils apparaissent désormais sur la bande latérale de la pastille, dans les couleurs de la liste du cluster DX, pour qu'un même fait ne soit pas bleu dans un panneau et vert dans l'autre. Ils s'empilent au lieu de se remplacer : une entité contactée qui est aussi un nouveau parc affiche les deux, comme la liste du cluster les énumère déjà côte à côte. Un nouveau comté passe en violet au lieu de partager le vert avec un nouveau parc — dans les DEUX panneaux, sinon ils se contrediraient ; les couleurs des marqueurs sont désormais dans une table unique, celle-là même que pilotera l'option de couleur à venir.", diff --git a/frontend/src/components/IcomPanel.tsx b/frontend/src/components/IcomPanel.tsx index 5dc9c7b..500b392 100644 --- a/frontend/src/components/IcomPanel.tsx +++ b/frontend/src/components/IcomPanel.tsx @@ -47,12 +47,33 @@ const ZERO: IcomState = { // Band buttons jump the VFO to a sensible default frequency (SSB/CW mix) using // the plain SetFrequency command — no band-stacking codes needed. Hz values. -const BANDS: { l: string; hz: number }[] = [ +type Band = { l: string; hz: number }; + +const HF_BANDS: Band[] = [ { l: '160', hz: 1_840_000 }, { l: '80', hz: 3_750_000 }, { l: '40', hz: 7_100_000 }, { l: '30', hz: 10_130_000 }, { l: '20', hz: 14_150_000 }, { l: '17', hz: 18_130_000 }, { l: '15', hz: 21_250_000 }, { l: '12', hz: 24_950_000 }, { l: '10', hz: 28_400_000 }, - { l: '6', hz: 50_150_000 }, ]; +const B6 = { l: '6', hz: 50_150_000 }; +const B2 = { l: '2', hz: 144_300_000 }; // SSB calling +const B70 = { l: '70cm', hz: 432_200_000 }; // SSB calling +const B23 = { l: '23cm', hz: 1_296_200_000 }; // SSB calling + +// Band buttons jump the VFO to a sensible default frequency (SSB/CW mix) using +// the plain SetFrequency command — no band-stacking codes needed. +// +// Which buttons to OFFER depends on the radio, exactly as the attenuator steps +// do below. An IC-9700 has no HF at all, yet the console was showing it 160 +// through 6 and none of the bands it actually covers: ten dead buttons, and no +// way to change band from here on the only rig where you would want to. +function bandsFor(model?: string): Band[] { + const m = (model ?? '').toUpperCase(); + if (m.includes('9700')) return [B2, B70, B23]; // VHF/UHF/SHF only + if (m.includes('705')) return [...HF_BANDS, B6, B2, B70]; + if (m.includes('9100')) return [...HF_BANDS, B6, B2, B70, B23]; + // 7300 / 7610 / 7100 / unknown: HF + 6 m, the historical list. + return [...HF_BANDS, B6]; +} // Mode buttons for the console (like RS-BA1's row). SetCATMode picks USB/LSB for // SSB by frequency and the rig's data variant for digital modes. @@ -85,7 +106,10 @@ function bandOfHz(hz?: number): string { ['160', 1.8, 2.0], ['80', 3.5, 4.0], ['60', 5.25, 5.45], ['40', 7.0, 7.3], ['30', 10.1, 10.15], ['20', 14.0, 14.35], ['17', 18.068, 18.168], ['15', 21.0, 21.45], ['12', 24.89, 24.99], ['10', 28.0, 29.7], - ['6', 50.0, 54.0], ['4', 70.0, 70.5], ['2', 144.0, 148.0], ['70', 430.0, 450.0], + // Labels must match the band buttons' exactly — this is only used to light + // the button for the band the rig is on, and '70' never matched '70cm'. + ['6', 50.0, 54.0], ['4', 70.0, 70.5], ['2', 144.0, 148.0], + ['70cm', 430.0, 450.0], ['23cm', 1240.0, 1300.0], ]; for (const [name, lo, hi] of bands) if (mhz >= lo && mhz <= hi) return name; return ''; @@ -749,7 +773,7 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r {/* Band buttons + antenna selection. */}
- {BANDS.map((b) => { + {bandsFor(st.model).map((b) => { const here = bandOfHz(mainHz) === b.l; return (