// ── RadioLock — blokada radia (jeden operator naraz) ──────────────────────────
// Wydzielone z index.html (2026-08-24) do osobnego pliku, żeby telefon mógł
// reużyć DOKŁADNIE tę samą logikę (lista operatorów online, blokada TRX,
// prośby o radio) zamiast duplikować ją — ten sam wzorzec co wsjtx.js/cw.js/
// radiofunctions.js/qsolog.js/rotormini.js gdzie indziej w tym projekcie.
// Treść bez zmian względem oryginału inline w index.html — czysta ekstrakcja.
window.RadioLock = (function() {
'use strict';
let _state = { locked: false, user_id: null, username: null, callsign: null,
timeout_min: 20, requests: [], online: [] };
const S = window.AppState;
// Callsign/username/uid come from other users' profiles (or WS messages they
// trigger) and land here via innerHTML - escape before interpolating, same
// fix already applied once in qsolog.js for the same reason.
function _esc(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
// ── Pobierz/przetwórz stan z WS lub API ──────────────────────────────────────
function handleWS(msg) {
if (msg.locked !== undefined) _state.locked = msg.locked;
if (msg.user_id !== undefined) _state.user_id = msg.user_id;
if (msg.username !== undefined) _state.username = msg.username;
if (msg.callsign !== undefined) _state.callsign = msg.callsign;
if (msg.timeout_min !== undefined) _state.timeout_min = msg.timeout_min;
if (msg.requests) _state.requests = msg.requests || [];
if (msg.online) _state.online = msg.online || [];
_render();
}
// ── Powiadomienie o prośbie o radio ──────────────────────────────────────────
function handleRequest(msg) {
const myUid = window.AppState?.my_uid || window.CurrentUser?.id;
const isHolder = _state.locked && _state.user_id === myUid;
if (!isHolder) return;
const callsign = msg.from_cs || msg.from_uid || '?';
// Pokaż nieblokowalny toast z przyciskami zamiast confirm()
_showRequestToast(callsign, msg.from_uid);
}
function _showRequestToast(callsign, fromUid) {
// Usuń poprzednie powiadomienie jeśli istnieje
document.getElementById('radio-request-toast')?.remove();
const toast = document.createElement('div');
toast.id = 'radio-request-toast';
toast.style.cssText = `
position:fixed; top:70px; right:20px; z-index:9999;
background:var(--panel); border:1px solid var(--amber);
border-radius:6px; padding:16px 20px; min-width:280px;
font-family:var(--mono); box-shadow:0 4px 20px rgba(0,0,0,0.5);
`;
toast.innerHTML = `
📻 PROŚBA O RADIO
${_esc(callsign)} prosi o dostęp do TRX
`;
document.body.appendChild(toast);
// Auto-zamknij po 30s
setTimeout(() => toast.remove(), 30000);
}
// ── Akcje ─────────────────────────────────────────────────────────────────────
async function lockRadio() {
try {
const r = await _api('/api/radio/lock', 'POST', {});
if (!r.ok) { window.UI?.showToast('✗ ' + r.error, 'error'); }
} catch(e) { window.UI?.showToast('✗ Błąd blokady radia', 'error'); }
}
async function releaseRadio() {
try {
const r = await _api('/api/radio/release', 'POST', {});
if (!r.ok) { window.UI?.showToast('✗ ' + r.error, 'error'); }
} catch(e) { window.UI?.showToast(I18n.t('toast_release_error'), 'error'); }
}
async function requestRadio() {
const btn = document.getElementById('op-btn-request');
if (btn) { btn.disabled = true; btn.textContent = I18n.t('request_sending'); }
try {
const r = await _api('/api/radio/request', 'POST', {});
if (r.granted) {
window.UI?.showToast(I18n.t('toast_trx_granted'));
} else {
window.UI?.showToast(I18n.t('toast_request_sent'));
// Po 10s odblokuj przycisk — user może wysłać ponownie jeśli odrzucono
setTimeout(() => {
if (btn) { btn.disabled = false; btn.textContent = I18n.t('request_trx_again_btn'); }
}, 10000);
}
} catch(e) {
window.UI?.showToast(I18n.t('toast_request_error'), 'error');
if (btn) { btn.disabled = false; btn.textContent = I18n.t('request_trx_btn'); }
}
}
async function rejectRequest(fromUid) {
try {
const r = await _api('/api/radio/reject-request', 'POST', { uid: fromUid });
if (!r.ok) window.UI?.showToast('✗ ' + (r.error || 'Błąd'), 'error');
} catch(e) { window.UI?.showToast(I18n.t('toast_reject_error'), 'error'); }
}
// Powiadomienie DLA PROSZĄCEGO gdy jego prośba zostanie odrzucona — bez tego
// przycisk "POPROŚ O TRX" milczy i user nie wie czy ktoś w ogóle odpowiedział.
function handleRejected(msg) {
const myUid = window.AppState?.my_uid || window.CurrentUser?.id;
if (String(msg.to_uid) !== String(myUid)) return;
window.UI?.showToast(I18n.t('toast_request_rejected').replace('{by}', msg.by || '?'), 'error');
const btn = document.getElementById('op-btn-request');
if (btn) { btn.disabled = false; btn.textContent = I18n.t('request_trx_again_btn'); }
}
async function forceRelease() {
if (!await window.UI?.confirmModal('Wymusić zwolnienie radia? Aktywny operator straci dostęp.', { danger: true, okLabel: 'ZWOLNIJ' })) return;
await _api('/api/radio/force-release', 'POST', {});
}
async function setTimeout_(minutes) {
await _api('/api/radio/timeout', 'POST', { minutes });
}
// ── Render UI ─────────────────────────────────────────────────────────────────
function _render() {
_renderOpPanel();
}
function _renderOpPanel() {
const myUid = window.AppState?.my_uid || window.CurrentUser?.id;
const isAdmin = window.CurrentUser?.role === 'admin';
const isHolder = _state.locked && _state.user_id === myUid;
const isFree = !_state.locked;
const hasReq = _state.requests?.some(r => r.user_id === myUid);
// Lista operatorów online
const list = document.getElementById('op-list');
const count = document.getElementById('op-online-count');
if (list) {
list.innerHTML = _state.online.map(u => {
const isMe = u.user_id === myUid;
const isActive = u.user_id === _state.user_id;
return `