This commit is contained in:
2026-03-11 22:14:30 +03:00
parent 4407511d56
commit a34d25bdba
+26 -40
View File
@@ -171,6 +171,7 @@ begin
'const stEl=document.getElementById("stEl"),latEl=document.getElementById("latEl");' +
'let cur={},vfoA=14200000,vfoB=14200000,activeVfo="A";' +
'let localCenter=14200000,localSpan=192000;' + // локальные копии — обновляются мгновенно при действии пользователя
'let dragging=false,dragX0=0,dragCen0=0,dragVfo0=0,dragMs=0;' +
'let hoverX=-1,wTarget="",markerOn=false,markerX=0;' +
'let specSm=[],wfAvg=[],wfHi=-50,wfLo=-120;' +
@@ -353,33 +354,27 @@ begin
'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"};' +
'function applyState(msg){' +
// Мержим только поля которые не pending
'Object.keys(msg).forEach(function(k){' +
'var pk=PEND_MAP[k];' +
'if(!pk||!isPending(pk))cur[k]=msg[k];' +
'});' +
// VFO A
'Object.keys(msg).forEach(function(k){var pk=PEND_MAP[k];if(!pk||!isPending(pk))cur[k]=msg[k];});' +
// Обновляем localCenter/localSpan из сервера только когда пользователь не тянет
'if(!dragging){' +
'if(msg.center_hz!=null)localCenter=msg.center_hz;' +
'if(msg.span_hz!=null)localSpan=msg.span_hz||192000;' +
'}' +
'if(!isPending("freq")){vfoA=cur.vfo_a_hz||vfoA;}' +
// VFO B (always sync - no separate pending for B)
'if(msg.vfo_b_hz!=null)vfoB=msg.vfo_b_hz;' +
// Active VFO from server (authoritative)
'if(msg.active_vfo!=null&&!isPending("freq")){' +
'const sv=msg.active_vfo===0?"A":"B";' +
'if(sv!==activeVfo){' +
'activeVfo=sv;' +
'if(sv!==activeVfo){activeVfo=sv;' +
'q("vBtnA").classList.toggle("active",activeVfo==="A");' +
'q("vBtnB").classList.toggle("active",activeVfo==="B");' +
'q("vDispA").classList.toggle("dim",activeVfo!=="A");' +
'q("vDispB").classList.toggle("dim",activeVfo!=="B");' +
'}}' +
'q("vDispB").classList.toggle("dim",activeVfo!=="B");}}' +
'renderVfos();' +
'const as=q("agcSel");if(as&&cur.agc_mode!=null&&!isPending("agc"))as.value=cur.agc_mode;' +
'const vs=q("volSl");if(vs&&!vs.matches(":active")){vs.value=cur.volume||70;var vv=q("volVal");if(vv)vv.textContent=cur.volume||70;}' +
'const rs=q("rfSl");if(rs&&!rs.matches(":active")){rs.value=cur.agc_top||90;var rv=q("rfVal");if(rv)rv.textContent=(cur.agc_top||90)+"dB";}' +
'if(!isPending("nr"))updNrBtn();' +
'if(!isPending("nb"))updNbBtn();' +
'if(!isPending("snb"))updSnbBtn();' +
'if(!isPending("anf"))updAnfBtn();' +
'if(!isPending("nr"))updNrBtn();if(!isPending("nb"))updNbBtn();' +
'if(!isPending("snb"))updSnbBtn();if(!isPending("anf"))updAnfBtn();' +
'if(!isPending("run"))updRunBtn(!!cur.running);' +
'if(!isPending("mode")){syncModGrp(cur.mode||0);}' +
'updBand(activeVfo==="A"?(cur.vfo_a_hz||0):(vfoB||0));updSpan(cur.span_hz||192000);' +
@@ -389,15 +384,15 @@ begin
'function pal(db){const v=Math.round(cl((db-wfLo)/Math.max(1,wfHi-wfLo),0,1)*255);const c=PAL[v]|0;return[(c>>16)&255,(c>>8)&255,c&255];}' +
'function fltLH(){const bw=cur.filter_bw||2700;const m=cur.mode|0;if(m===0)return[-bw,-100];if(m===1)return[100,bw];return[-bw/2,bw/2];}' +
'function xToHz(x,w){return(cur.center_hz||0)+((x/Math.max(1,w))-.5)*(cur.span_hz||0);}' +
'function hzToX(f,w){return((f-(cur.center_hz||0))/(cur.span_hz||1)+.5)*w;}' +
'function xToHz(x,w){return localCenter+((x/Math.max(1,w))-.5)*localSpan;}' +
'function hzToX(f,w){return((f-localCenter)/localSpan+.5)*w;}' +
'function drawMk(ctx,w,h){if(!markerOn)return;ctx.save();ctx.strokeStyle="rgba(255,80,80,.85)";ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(markerX,0);ctx.lineTo(markerX,h);ctx.stroke();' +
'const lbl=((xToHz(markerX,w))/1e6).toFixed(4)+" M";ctx.fillStyle="rgba(35,5,5,.82)";ctx.fillRect(cl(markerX+5,0,w-88),h-16,84,13);ctx.fillStyle="#f88";ctx.font="10px monospace";ctx.fillText(lbl,cl(markerX+8,2,w-86),h-6);ctx.restore();}' +
'function drawRuler(){const w=rulerCv.clientWidth,h=rulerCv.clientHeight;rctx.fillStyle="#0c1210";rctx.fillRect(0,0,w,h);' +
'const st=(cur.center_hz||0)-(cur.span_hz||0)/2;' +
'for(let i=0;i<=8;i++){const x=i*w/8,f=st+i*(cur.span_hz||0)/8;rctx.strokeStyle="#2a3a2e";rctx.lineWidth=1;rctx.beginPath();rctx.moveTo(x+.5,0);rctx.lineTo(x+.5,7);rctx.stroke();rctx.fillStyle="#7a9a80";rctx.font="10px monospace";const t=(f/1e6).toFixed(3);rctx.fillText(t,x-14,21);}' +
'const st=localCenter-localSpan/2;' +
'for(let i=0;i<=8;i++){const x=i*w/8,f=st+i*localSpan/8;rctx.strokeStyle="#2a3a2e";rctx.lineWidth=1;rctx.beginPath();rctx.moveTo(x+.5,0);rctx.lineTo(x+.5,7);rctx.stroke();rctx.fillStyle="#7a9a80";rctx.font="10px monospace";const t=(f/1e6).toFixed(3);rctx.fillText(t,x-14,21);}' +
'const vxr=hzToX(activeVfoHz(),w);rctx.strokeStyle="#60d070";rctx.lineWidth=1;rctx.beginPath();rctx.moveTo(vxr+.5,0);rctx.lineTo(vxr+.5,h);rctx.stroke();}' +
'function drawSpec(bins){const w=specCv.clientWidth,h=specCv.clientHeight;sctx.fillStyle="#050a05";sctx.fillRect(0,0,w,h);' +
@@ -442,7 +437,7 @@ begin
'function sendActiveFreq(f){if(activeVfo==="A")ws2({cmd:"freq",hz:f});else ws2({cmd:"freq",hz:f});}' +
'function doWheel(e){e.preventDefault();e.stopPropagation();audioKick();const dir=e.deltaY<0?1:-1,st=tuneStep(e),base=Math.floor(activeVfoHz()/st)*st;' +
'const nf=cl(dir>0?base+st:(Math.round(activeVfoHz())===base?base-st:base),30000,60000000);' +
'setActiveVfoHz(nf);' +
'setActiveVfoHz(nf);localCenter=nf;' +
'if(activeVfo==="A"){cur.vfo_a_hz=nf;}else{cur.vfo_b_hz=nf;}' +
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});}' +
'function bindCv(cv){let wT=0;const wh=e=>{const n=performance.now();if(n-wT<20)return;wT=n;wTarget=cv.id;doWheel(e);};' +
@@ -454,32 +449,23 @@ begin
'if(e.button===2){markerOn=Math.abs(markerX-e.offsetX)>=8||!markerOn;markerX=e.offsetX;return;}' +
'if(e.button!==0)return;' +
'dragging=true;dragX0=e.offsetX;' +
// CTUN ON: drag scrolls the spectrum window (center moves, VFO stays)
// CTUN OFF: drag moves active VFO (and center follows it)
'dragCen0=cur.center_hz||activeVfoHz();' +
'dragCen0=localCenter;' +
'dragVfo0=activeVfoHz();' +
'dragMs=0;' +
'cv.style.cursor="ew-resize";' +
'dragMs=0;cv.style.cursor="ew-resize";' +
'});' +
'cv.addEventListener("mousemove",e=>{' +
'wTarget=cv.id;hoverX=e.offsetX;' +
'if(markerOn&&!dragging)markerX=e.offsetX;' +
'if(!dragging)return;' +
'const n=Date.now();if(n-dragMs<16)return;dragMs=n;' +
'const span=cur.span_hz||192000;' +
'const dHz=(e.offsetX-dragX0)/Math.max(1,cv.clientWidth)*span;' +
'const dHz=(e.offsetX-dragX0)/Math.max(1,cv.clientWidth)*localSpan;' +
'if(cur.ctun){' +
// CTUN ON: move DDC center window, VFO marker moves on screen
'const newCen=Math.round(dragCen0-dHz);' +
'cur.center_hz=newCen;' +
'ws2({cmd:"set_center",hz:newCen});'
+ '} else {' +
// CTUN OFF: move active VFO, center follows → update center_hz locally
'localCenter=Math.round(dragCen0-dHz);' +
'ws2({cmd:"set_center",hz:localCenter});' +
'}else{' +
'const nf=cl(Math.round(dragVfo0-dHz),30000,60000000);' +
'setActiveVfoHz(nf);' +
// Optimistically update center so ruler/filter band stay in sync while dragging
'cur.center_hz=nf;' +
'if(activeVfo==="A"){cur.vfo_a_hz=nf;}else{cur.vfo_b_hz=nf;}' +
'setActiveVfoHz(nf);localCenter=nf;' +
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +
'}' +
'});' +
@@ -487,9 +473,9 @@ begin
'cv.style.cursor="";' +
'if(e.button===2)return;' +
'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' +
// Click (no drag): tune active VFO to clicked frequency
// клик — настраиваем на кликнутую частоту мгновенно
'const nf=cl(Math.round(xToHz(e.offsetX,cv.clientWidth)/100)*100,30000,60000000);' +
'setActiveVfoHz(nf);cur.center_hz=nf;' +
'setActiveVfoHz(nf);localCenter=nf;' +
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +
'}' +