feat: Repeaters card with per-step tune status
This commit is contained in:
+52
@@ -151,6 +151,16 @@ input[type=range]::-moz-range-thumb{width:22px;height:26px;border-radius:5px;
|
|||||||
text-transform:uppercase}
|
text-transform:uppercase}
|
||||||
.reveal{animation:rise .7s both}
|
.reveal{animation:rise .7s both}
|
||||||
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
|
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
|
||||||
|
.rblist{display:flex;flex-direction:column;gap:6px;margin-top:10px;max-height:300px;overflow-y:auto}
|
||||||
|
.rbrow{display:flex;flex-direction:column;gap:3px;
|
||||||
|
background:#0c0b08;border:1px solid #3a352b;border-radius:8px;padding:9px 11px;cursor:pointer}
|
||||||
|
.rbrow:active{transform:translateY(1px)}
|
||||||
|
.rbrow .cs{font-family:'Saira Condensed',sans-serif;font-weight:800;color:var(--ink);letter-spacing:.06em}
|
||||||
|
.rbrow .dv{color:var(--accent-hi);border:1px solid var(--accent);border-radius:10px;
|
||||||
|
padding:1px 6px;font-size:9px;letter-spacing:.1em;margin-left:6px}
|
||||||
|
.rbrow .meta{color:var(--ink-dim);font-size:11px}
|
||||||
|
.rbrow .steps{font-family:'Chivo Mono';font-size:10px;letter-spacing:.04em;color:var(--ink-dim);min-height:12px}
|
||||||
|
.rbrow .steps .ok{color:var(--led-on)} .rbrow .steps .no{color:var(--red)}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -198,6 +208,13 @@ input[type=range]::-moz-range-thumb{width:22px;height:26px;border-radius:5px;
|
|||||||
<div class="fader"><input id="sql" type="range" min="0" max="255"><span id="sqlv" class="val">--</span></div>
|
<div class="fader"><input id="sql" type="range" min="0" max="255"><span id="sqlv" class="val">--</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card reveal" style="animation-delay:.40s">
|
||||||
|
<div class="label">Repeaters · nearest first</div>
|
||||||
|
<div class="freqset">
|
||||||
|
<input id="rbq" type="text" inputmode="search" placeholder="search callsign / city">
|
||||||
|
</div>
|
||||||
|
<div id="rblist" class="rblist"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="err" class="err"></div>
|
<div id="err" class="err"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,6 +289,41 @@ wireFader('sql','/api/sql','#sqlv');
|
|||||||
$('#fset').onclick=()=>{const v=parseFloat($('#finp').value);if(!isNaN(v))post('/api/freq',{mhz:v});};
|
$('#fset').onclick=()=>{const v=parseFloat($('#finp').value);if(!isNaN(v))post('/api/freq',{mhz:v});};
|
||||||
$('#finp').addEventListener('keydown',e=>{if(e.key==='Enter')$('#fset').click();});
|
$('#finp').addEventListener('keydown',e=>{if(e.key==='Enter')$('#fset').click();});
|
||||||
|
|
||||||
|
const rbq=$('#rbq'), rblist=$('#rblist');
|
||||||
|
function stepHTML(steps){
|
||||||
|
if(!steps) return '';
|
||||||
|
return Object.entries(steps).map(([k,v])=>
|
||||||
|
`<span class="${v?'ok':'no'}">${k} ${v?'✓':'✗'}</span>`).join(' ');
|
||||||
|
}
|
||||||
|
async function loadRepeaters(){
|
||||||
|
try{
|
||||||
|
const r=await fetch('/api/repeaters?limit=40&q='+encodeURIComponent(rbq.value||''));
|
||||||
|
const j=await r.json(); if(!j.ok) return;
|
||||||
|
rblist.innerHTML='';
|
||||||
|
j.repeaters.forEach(rep=>{
|
||||||
|
const row=document.createElement('div'); row.className='rbrow';
|
||||||
|
const dup=rep.duplex==='simplex'?'':(' '+rep.duplex);
|
||||||
|
const tone=rep.tone_hz?(' · '+rep.tone_hz):'';
|
||||||
|
row.innerHTML=
|
||||||
|
`<div><span class="cs">${rep.callsign||'—'}</span>`+
|
||||||
|
`${rep.mode==='DV'?'<span class="dv">DV</span>':''}</div>`+
|
||||||
|
`<div class="meta">${rep.location||''} · ${(rep.output_mhz||0).toFixed(3)}${dup}${tone}</div>`+
|
||||||
|
`<div class="steps"></div>`;
|
||||||
|
row.onclick=()=>tuneRepeater(rep,row);
|
||||||
|
rblist.appendChild(row);
|
||||||
|
});
|
||||||
|
}catch(e){/* offline: leave list as-is */}
|
||||||
|
}
|
||||||
|
async function tuneRepeater(rep,row){
|
||||||
|
const s=row.querySelector('.steps'); s.textContent='tuning…';
|
||||||
|
try{
|
||||||
|
const r=await fetch('/api/tune-repeater',{method:'POST',
|
||||||
|
headers:{'Content-Type':'application/json'},body:JSON.stringify(rep)});
|
||||||
|
const j=await r.json(); s.innerHTML=stepHTML(j.steps); refresh();
|
||||||
|
}catch(e){ s.innerHTML='<span class="no">send failed</span>'; }
|
||||||
|
}
|
||||||
|
let rbt; rbq.addEventListener('input',()=>{clearTimeout(rbt);rbt=setTimeout(loadRepeaters,250);});
|
||||||
|
loadRepeaters();
|
||||||
refresh();
|
refresh();
|
||||||
setInterval(refresh,900);
|
setInterval(refresh,900);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user