Добавление 8 каналов в ui

This commit is contained in:
Maxim
2026-06-19 15:56:30 +03:00
parent 311d09f0b3
commit 5d15f215be
8 changed files with 737 additions and 563 deletions

View File

@@ -25,6 +25,7 @@ import { initAccordion } from "./accordion.js";
import { drawChart } from "./chart.js";
import { initResize } from "./layout.js";
import { initCamera } from "./camera.js";
import { updateAllChannels } from './ui.js';
import { audioEngine } from "./audio.js";
let lastChart: number = 0;
@@ -166,6 +167,7 @@ async function update(): Promise<void> {
if (arr.length > 0 && isOnline) {
const bufferSize = health.stats?.filled ?? 0;
addSignals(arr, bufferSize);
updateAllChannels(arr);
}
const value = raw & 0x3F;

View File

@@ -9,6 +9,10 @@ export interface DOMElements {
canvas: HTMLCanvasElement | null;
bars: NodeListOf<HTMLElement>;
barFill: HTMLElement | null;
bar2: HTMLElement | null;
bar3: HTMLElement | null;
soundLevel: HTMLElement | null;
heightLevel: HTMLElement | null;
historyHeader: HTMLElement | null;
historyBody: HTMLElement | null;
audioIndicator: HTMLElement | null;
@@ -24,6 +28,10 @@ export const DOM: DOMElements = {
canvas: null,
bars: document.querySelectorAll(".sig-bar"),
barFill: null,
bar2: null,
bar3: null,
soundLevel: null,
heightLevel: null,
historyHeader: null,
historyBody: null,
audioIndicator: null,
@@ -38,6 +46,10 @@ export function initDOM(): void {
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");
@@ -51,9 +63,9 @@ export function setServerTime(v: string): void {
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");
@@ -72,7 +84,7 @@ export function setMeta(v: string): void {
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");
@@ -85,7 +97,7 @@ export function setBars(p: number, noConnection: boolean = false): void {
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) {
@@ -94,9 +106,9 @@ export function setSignal(level: number, noConnection: boolean = false): void {
bar.classList.add("dim");
}
});
if (noConnection) return;
if (level >= 1) {
bars[0].classList.add("active-1");
bars[0].classList.remove("dim");
@@ -111,7 +123,6 @@ export function setSignal(level: number, noConnection: boolean = false): void {
}
}
// ФУНКЦИЮ ДЛЯ ИНДИКАТОРА ЗВУКА
export function setAudioIndicator(active: boolean, level: number = 0): void {
const el = DOM.audioIndicator;
if (!el) return;
@@ -120,7 +131,6 @@ export function setAudioIndicator(active: boolean, level: number = 0): void {
el.textContent = `🔊 ${level}`;
el.style.color = '#00ff88';
el.style.opacity = '1';
// Анимация пульсации
el.style.animation = 'pulse 1s ease-in-out infinite';
} else {
el.textContent = '🔇';
@@ -128,4 +138,175 @@ export function setAudioIndicator(active: boolean, level: number = 0): void {
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 = `
<div class="channel-header" data-channel="${i}">
<span class="channel-label">Канал ${i + 1}</span>
<span style="display:flex;align-items:center;gap:6px;">
<span class="channel-value" id="ch-val-${i}">--</span>
<span class="channel-toggle" id="ch-toggle-${i}">▼</span>
</span>
</div>
<div class="channel-body" id="ch-body-${i}">
<div class="channel-progress">
<div class="progress">
<div class="fill" id="ch-bar-${i}" style="width:0%"></div>
</div>
<div class="signal-icon" id="ch-signal-${i}">
<div class="sig-bar sig-bar-1"></div>
<div class="sig-bar sig-bar-2"></div>
<div class="sig-bar sig-bar-3"></div>
</div>
</div>
<div class="channel-stats" id="ch-stats-${i}">--</div>
</div>
`;
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<number, number>();
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);
});
});
}
});