// ui.ts - Управление UI элементами 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"); const percent = Math.max(0, Math.min(100, p)); el.style.width = `${percent}%`; el.style.background = '#e6852d'; el.style.backgroundColor = '#e6852d'; el.style.backgroundImage = 'none'; } } // ===== ИНДИКАТОРЫ УРОВНЯ СИГНАЛА ===== 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 })); export 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); if (i === 0) { item.classList.add('channel-active'); } 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 getChannelElement(index: number) { return channelElements[index] || null; } 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 maxValue = 63; const percent = (value / maxValue) * 100; if (el.bar) { el.bar.style.width = `${Math.min(100, percent)}%`; el.bar.style.background = '#e6852d'; el.bar.style.backgroundColor = '#e6852d'; el.bar.style.backgroundImage = 'none'; } if (el.value) { 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 = ['Отсутствует', 'Низкая', 'Средняя', 'Высокая']; if (el.stats) { el.stats.textContent = `${value}/${maxValue} | ${strengthNames[strength] || '---'}`; } if (el.item) { el.item.classList.remove('no-data'); } } export function updateAllChannels(rawData?: number[]) { for (let i = 0; i < 8; i++) { setChannelValue(i, 0, 0); if (channelElements[i]) { channelElements[i].item.classList.remove('channel-active'); } } if (rawData && rawData.length > 0) { const lastByte = rawData[rawData.length - 1]; const value = lastByte & 0x3F; const strength = (lastByte >> 6) & 0x3; setChannelValue(0, value, strength); if (channelElements[0]) { channelElements[0].item.classList.add('channel-active'); } if (DOM.lastByte) { DOM.lastByte.textContent = value.toString(); } } else { if (DOM.lastByte) { DOM.lastByte.textContent = '--'; } } } document.addEventListener('DOMContentLoaded', () => { initChannels(); updateAllChannels(); 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); }); }); } });