// ui.ts (DOM слой) export interface DOMElements { serverTime: HTMLElement | null; status: HTMLElement | null; meta: HTMLElement | null; latest: HTMLElement | null; lastByte: HTMLElement | null; history: HTMLElement | null; canvas: HTMLCanvasElement | null; bars: NodeListOf; barFill: HTMLElement | null; bar2: HTMLElement | null; bar3: HTMLElement | null; soundLevel: HTMLElement | null; heightLevel: HTMLElement | null; historyHeader: HTMLElement | null; historyBody: HTMLElement | null; audioIndicator: HTMLElement | null; } export const DOM: DOMElements = { serverTime: null, status: null, meta: null, latest: null, lastByte: null, history: null, canvas: null, bars: document.querySelectorAll(".sig-bar"), barFill: null, bar2: null, bar3: null, soundLevel: null, heightLevel: null, historyHeader: null, historyBody: null, audioIndicator: null, }; export function initDOM(): void { DOM.serverTime = document.getElementById("server-time"); DOM.status = document.getElementById("status"); DOM.meta = document.getElementById("meta"); DOM.latest = document.getElementById("latest"); DOM.lastByte = document.getElementById("last-byte"); DOM.history = document.getElementById("history"); DOM.canvas = document.getElementById("signalChart") as HTMLCanvasElement; DOM.barFill = document.getElementById("bar1"); DOM.bar2 = document.getElementById("bar2"); DOM.bar3 = document.getElementById("bar3"); DOM.soundLevel = document.getElementById("sound-level"); DOM.heightLevel = document.getElementById("height-level"); DOM.historyHeader = document.getElementById("history-header"); DOM.historyBody = document.getElementById("history-body"); DOM.bars = document.querySelectorAll(".sig-bar"); DOM.audioIndicator = document.getElementById("audio-indicator"); } export function setServerTime(v: string): void { if (DOM.serverTime) DOM.serverTime.textContent = v; } export function setStatus(v: string, isError: boolean = false): void { if (!DOM.status) return; DOM.status.textContent = v; DOM.status.classList.remove("status-ok", "status-error", "status-lost"); if (isError) { if (v === "Нет связи") { DOM.status.classList.add("status-lost"); } else { DOM.status.classList.add("status-error"); } } else { DOM.status.classList.add("status-ok"); } } export function setMeta(v: string): void { if (DOM.meta) DOM.meta.textContent = v; } export function setBars(p: number, noConnection: boolean = false): void { const el = DOM.barFill; if (!el) return; if (noConnection) { el.style.width = "0%"; el.classList.add("no-connection"); } else { el.classList.remove("no-connection"); el.style.width = `${Math.max(0, Math.min(100, p))}%`; } } export function setSignal(level: number, noConnection: boolean = false): void { const bars = DOM.bars; if (!bars || bars.length < 3) return; bars.forEach(bar => { bar.classList.remove("active-1", "active-2", "active-3", "dim", "no-connection"); if (noConnection) { bar.classList.add("no-connection"); } else { bar.classList.add("dim"); } }); if (noConnection) return; if (level >= 1) { bars[0].classList.add("active-1"); bars[0].classList.remove("dim"); } if (level >= 2) { bars[1].classList.add("active-2"); bars[1].classList.remove("dim"); } if (level >= 3) { bars[2].classList.add("active-3"); bars[2].classList.remove("dim"); } } export function setAudioIndicator(active: boolean, level: number = 0): void { const el = DOM.audioIndicator; if (!el) return; if (active && level > 0) { el.textContent = `🔊 ${level}`; el.style.color = '#00ff88'; el.style.opacity = '1'; el.style.animation = 'pulse 1s ease-in-out infinite'; } else { el.textContent = '🔇'; el.style.color = '#666'; el.style.opacity = '0.5'; el.style.animation = 'none'; } } export function setSoundLevel(level: number): void { if (DOM.bar2) { const percent = Math.max(0, Math.min(100, level)); DOM.bar2.style.width = `${percent}%`; } if (DOM.soundLevel) { DOM.soundLevel.textContent = Math.round(level).toString(); } } export function setHeightLevel(level: number): void { if (DOM.bar3) { const percent = Math.max(0, Math.min(100, level)); DOM.bar3.style.width = `${percent}%`; } if (DOM.heightLevel) { DOM.heightLevel.textContent = Math.round(level).toString(); } } // ===== 8 КАНАЛОВ ===== export interface ChannelState { value: number; strength: number; expanded: boolean; } export const channels: ChannelState[] = Array(8).fill(null).map(() => ({ value: 0, strength: 0, expanded: true })); let channelElements: any[] = []; export function initChannels() { const container = document.getElementById('channelsContainer'); if (!container) return; container.innerHTML = ''; channelElements = []; for (let i = 0; i < 8; i++) { const item = document.createElement('div'); item.className = 'channel-item'; item.dataset.channel = String(i); item.innerHTML = `
Канал ${i + 1} --
--
`; container.appendChild(item); channelElements[i] = { item: item, value: document.getElementById(`ch-val-${i}`), bar: document.getElementById(`ch-bar-${i}`), signal: document.getElementById(`ch-signal-${i}`), stats: document.getElementById(`ch-stats-${i}`), toggle: document.getElementById(`ch-toggle-${i}`), body: document.getElementById(`ch-body-${i}`) }; const header = item.querySelector('.channel-header'); header?.addEventListener('click', () => { toggleChannel(i); }); } } export function toggleChannel(index: number) { const ch = channels[index]; ch.expanded = !ch.expanded; const el = channelElements[index]; if (!el) return; if (ch.expanded) { el.body.classList.remove('hidden'); el.toggle.textContent = '▼'; } else { el.body.classList.add('hidden'); el.toggle.textContent = '▶'; } } export function setChannelValue(index: number, value: number, strength: number) { if (index < 0 || index > 7) return; const ch = channels[index]; const el = channelElements[index]; if (!el) return; ch.value = value; ch.strength = strength; const percent = (value / 31) * 100; el.bar.style.width = `${percent}%`; el.value.textContent = value.toString(); const bars = el.signal?.querySelectorAll('.sig-bar'); if (bars) { bars.forEach((bar: Element, i: number) => { const isActive = i < strength; (bar as HTMLElement).style.opacity = isActive ? '1' : '0.2'; }); } const strengthNames = ['Отсутствует', 'Низкая', 'Средняя', 'Высокая']; el.stats.textContent = `${value}/31 | ${strengthNames[strength] || '---'}`; el.item.classList.remove('no-data'); } export function updateAllChannels(rawData: number[]) { const latest = new Map(); for (const byte of rawData) { const channel = (byte >> 5) & 0x07; const value = byte & 0x1F; latest.set(channel, value); } for (let i = 0; i < 8; i++) { const value = latest.get(i) ?? 0; const strength = value >= 25 ? 3 : value >= 15 ? 2 : value >= 5 ? 1 : 0; setChannelValue(i, value, strength); } } // Инициализация при загрузке document.addEventListener('DOMContentLoaded', () => { initChannels(); const expandAll = document.getElementById('expandAllBtn'); const collapseAll = document.getElementById('collapseAllBtn'); if (expandAll) { expandAll.addEventListener('click', () => { channels.forEach((ch, i) => { if (!ch.expanded) toggleChannel(i); }); }); } if (collapseAll) { collapseAll.addEventListener('click', () => { channels.forEach((ch, i) => { if (ch.expanded) toggleChannel(i); }); }); } });