Phase 3 (slice): make Mute sync UI->web in the web client

The outbound channel already existed: PushLoop broadcasts the full
BuildStateJson (including "mute") to web clients every 200ms. The gap was
purely client-side — applyState never rendered the mute icon, so a
UI-initiated mute change was not reflected in the browser (the icon was
only updated on the client's own click).

WebPageHtml.pas:
- Add updMuteIc() helper; render it from applyState (guarded by
  isPending("mute")), so periodic state updates the icon.
- Click handler calls updMuteIc + pend("mute"); add "mute" to PEND_MAP so
  the optimistic local state isn't clobbered by an in-flight stale push.

Mute is now bidirectional. Other fields already render in applyState, so
UI->web works for them; this confirms the web outbound path is the
periodic full-state broadcast, not a per-field event push (the latter
will matter for the future serial/Arduino frontend).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-04 11:16:26 +03:00
co-authored by Claude Opus 4.8
parent b577a0d67a
commit a85f196c97
+4 -2
View File
@@ -377,7 +377,8 @@ begin
'function updRunBtn(r){var b=q("startStopBtn");if(!b)return;b.textContent=r?"STOP":"START";b.classList.toggle("rx-tx",!!r);b.classList.toggle("on",!r);}' +
'q("startStopBtn").onclick=function(){audioKick();cur.running=!cur.running;pend("run");updRunBtn(cur.running);cmd("set_run",cur.running);};' +
'q("muteIc").onclick=()=>{cur.mute=!cur.mute;q("muteIc").textContent=cur.mute?"\uD83D\uDD07":"\uD83D\uDD0A";cmd("set_mute",cur.mute);};' +
'function updMuteIc(){var mi=q("muteIc");if(mi)mi.textContent=cur.mute?"\uD83D\uDD07":"\uD83D\uDD0A";}' +
'q("muteIc").onclick=()=>{cur.mute=!cur.mute;updMuteIc();pend("mute");cmd("set_mute",cur.mute);};' +
'let stA=0,stAmem=100,stB=0,stBmem=100;' +
'let freqMhzDigits=3,freqMaxHz=999999999;' +
@@ -596,7 +597,7 @@ begin
'function isPending(field){return !!(pendingCmds[field]&&pendingCmds[field]>Date.now());}' +
// Маппинг: server JSON field → pend key
'var PEND_MAP={vfo_a_hz:"freq",vfo_b_hz:"freq",nr:"nr",nr_mode:"nr",nb:"nb",nb_mode:"nb",snb:"snb",anf:"anf",running:"run",mode:"mode",agc_mode:"agc",transmitting:"mox",drive:"drive",attn_idx:"attn",tuning:"tun",filter_bw:"filter"};' +
'var PEND_MAP={vfo_a_hz:"freq",vfo_b_hz:"freq",nr:"nr",nr_mode:"nr",nb:"nb",nb_mode:"nb",snb:"snb",anf:"anf",running:"run",mode:"mode",agc_mode:"agc",transmitting:"mox",drive:"drive",attn_idx:"attn",tuning:"tun",filter_bw:"filter",mute:"mute"};' +
'function applyState(msg){' +
'Object.keys(msg).forEach(function(k){var pk=PEND_MAP[k];if(!pk||!isPending(pk))cur[k]=msg[k];});' +
@@ -637,6 +638,7 @@ begin
'if(!isPending("run"))updRunBtn(!!cur.running);' +
'if(!isPending("mox"))updMoxBtn(!!cur.transmitting);' +
'if(!isPending("tun"))updToneBtn(!!cur.tuning);' +
'if(!isPending("mute"))updMuteIc();' +
'if(!isPending("drive")){const ts=q("txSl");if(ts&&!ts.matches(":active")){ts.value=cur.drive||0;var tv=q("txVal");if(tv)tv.textContent=cur.drive||0;}}' +
'if(!isPending("mode")){syncModGrp(cur.mode||0);syncStepSel();}' +
'if(!isPending("attn")){const at=q("attSel");if(at)at.value=cur.attn_idx||0;}' +