Корректировка ошибок. Стили, прокрутка, шрифты
This commit is contained in:
@@ -24,8 +24,7 @@ import (
|
||||
)
|
||||
|
||||
// ===== EMBED WEB =====
|
||||
//
|
||||
//go:embed web/dist web/css web/*.html web/*.png
|
||||
//go:embed web/dist web/css web/fonts web/*.html web/*.png
|
||||
var webFiles embed.FS
|
||||
|
||||
type API struct {
|
||||
|
||||
@@ -94,9 +94,10 @@
|
||||
padding-right: 4px;
|
||||
margin: 0;
|
||||
letter-spacing: 0.02em;
|
||||
overscroll-behavior: contain;
|
||||
overscroll-behavior: contain; /* ← ДОБАВИТЬ ЭТО */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
#history::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
@@ -109,6 +110,15 @@
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ===== LAST BYTE ===== */
|
||||
#last-byte {
|
||||
font-family: var(--font-tech);
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: #00ff88;
|
||||
text-shadow: 0 0 20px rgba(0, 255, 136, 0.2);
|
||||
}
|
||||
|
||||
/* ===== КАНАЛЫ ===== */
|
||||
.channels-header {
|
||||
display: flex;
|
||||
@@ -181,6 +191,10 @@
|
||||
border-color: rgba(0, 255, 136, 0.25);
|
||||
background: rgba(0, 255, 136, 0.06);
|
||||
}
|
||||
.channel-item.channel-active {
|
||||
border-color: rgba(0, 255, 136, 0.4);
|
||||
background: rgba(0, 255, 136, 0.08);
|
||||
}
|
||||
.channel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -230,16 +244,34 @@
|
||||
}
|
||||
.channel-progress .progress {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
height: 12px;
|
||||
padding: 2px;
|
||||
border-width: 2px;
|
||||
background: #222;
|
||||
border: 2px solid #444;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* ===== ЯРКИЙ КОНТРАСТНЫЙ ПРОГРЕСС-БАР ===== */
|
||||
.channel-progress .progress .fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #0a0, #0f0, #ff0, #f80);
|
||||
background: #e6852d;
|
||||
border-radius: 2px;
|
||||
transition: width 0.15s ease;
|
||||
width: 0%;
|
||||
box-shadow:
|
||||
inset 0 1px 2px rgba(255,255,255,0.3),
|
||||
0 0 10px rgba(230,133,45,0.5);
|
||||
}
|
||||
|
||||
/* Активный канал - более яркий */
|
||||
.channel-item.channel-active .fill {
|
||||
box-shadow:
|
||||
inset 0 1px 2px rgba(255,255,255,0.4),
|
||||
0 0 15px rgba(230,133,45,0.6);
|
||||
}
|
||||
|
||||
.channel-stats {
|
||||
font-size: var(--font-size-xs);
|
||||
color: #666;
|
||||
@@ -248,17 +280,117 @@
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ===== ЗВУК И ВЫСОТА ===== */
|
||||
.card:has(#bar2) .fill,
|
||||
.card:has(#bar3) .fill {
|
||||
background: linear-gradient(90deg, #0a0, #0f0, #ff0, #f80);
|
||||
transition: width 0.15s ease;
|
||||
/* ===== ОСНОВНЫЕ ПРОГРЕСС-БАРЫ (bar1, bar2, bar3) ===== */
|
||||
.progress-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: 6px;
|
||||
color: #ffffff;
|
||||
opacity: 0.9;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
padding: 4px;
|
||||
background: #222;
|
||||
border: 2px solid #444;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.progress-with-signal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.progress-90 {
|
||||
flex: 0 0 86%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* ЯРКИЙ ЗАПОЛНЕННЫЙ БАР */
|
||||
.fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: #e6852d;
|
||||
border-radius: 2px;
|
||||
box-shadow:
|
||||
inset 0 1px 2px rgba(255,255,255,0.3),
|
||||
0 0 12px rgba(230,133,45,0.5);
|
||||
transition: width 0.25s linear;
|
||||
}
|
||||
|
||||
.fill.no-connection {
|
||||
background: #555555 !important;
|
||||
box-shadow: none !important;
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
/* ===== ЗВУК И ВЫСОТА ===== */
|
||||
.card:has(#bar2) .progress,
|
||||
.card:has(#bar3) .progress {
|
||||
height: 34px;
|
||||
padding: 6px;
|
||||
border-width: 3px;
|
||||
background: #222;
|
||||
border: 2px solid #444;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.card:has(#bar2) .fill,
|
||||
.card:has(#bar3) .fill {
|
||||
background: #e6852d;
|
||||
box-shadow:
|
||||
inset 0 1px 2px rgba(255,255,255,0.3),
|
||||
0 0 12px rgba(230,133,45,0.5);
|
||||
}
|
||||
|
||||
/* ===== ИНДИКАТОРЫ СИГНАЛА ===== */
|
||||
.signal-icon {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sig-bar {
|
||||
flex: 1;
|
||||
border-radius: 2px;
|
||||
min-width: 5px;
|
||||
border: 2px solid #e6852d;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
.sig-bar-1 { height: 33%; }
|
||||
.sig-bar-2 { height: 66%; }
|
||||
.sig-bar-3 { height: 100%; }
|
||||
|
||||
.sig-bar.dim {
|
||||
background-color: transparent;
|
||||
border-color: #1a3a1a;
|
||||
}
|
||||
|
||||
.sig-bar.active-1,
|
||||
.sig-bar.active-2,
|
||||
.sig-bar.active-3 {
|
||||
background-color: #e6852d;
|
||||
border-color: #c46b1f;
|
||||
}
|
||||
|
||||
.sig-bar.no-connection {
|
||||
background-color: #555555 !important;
|
||||
border-color: #555555 !important;
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
/* ===== АДАПТИВ ===== */
|
||||
@@ -268,7 +400,7 @@
|
||||
gap: 4px;
|
||||
max-height: none;
|
||||
overflow-y: visible;
|
||||
overscroll-behavior: auto;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
.channels-header {
|
||||
flex-direction: column;
|
||||
@@ -285,8 +417,9 @@
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
.channel-progress .progress {
|
||||
height: 6px;
|
||||
height: 10px;
|
||||
padding: 1px;
|
||||
border-width: 2px;
|
||||
}
|
||||
#history {
|
||||
max-height: 300px;
|
||||
@@ -297,6 +430,11 @@
|
||||
padding: 4px;
|
||||
border-width: 2px;
|
||||
}
|
||||
.progress {
|
||||
height: 28px;
|
||||
padding: 4px;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -322,6 +460,11 @@
|
||||
padding: 3px;
|
||||
border-width: 2px;
|
||||
}
|
||||
.progress {
|
||||
height: 24px;
|
||||
padding: 3px;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== АНИМАЦИЯ ===== */
|
||||
|
||||
@@ -3,58 +3,56 @@
|
||||
/* JetBrains Mono */
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
src: url('/fonts/JetBrainsMono/ttf/JetBrainsMono-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold.ttf') format('truetype');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
src: url('/fonts/JetBrainsMono-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
src: url('/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Orbitron - для заголовков */
|
||||
/* Orbitron - используем существующий файл */
|
||||
@font-face {
|
||||
font-family: 'Orbitron';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/Orbitron/static/Orbitron-Regular.ttf') format('truetype');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Orbitron';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('/fonts/Orbitron/static/Orbitron-SemiBold.ttf') format('truetype');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Orbitron';
|
||||
font-style: normal;
|
||||
src: url('/fonts/orbitron-bold-webfont.woff') format('woff');
|
||||
font-weight: 700;
|
||||
src: url('/fonts/Orbitron/static/Orbitron-Bold.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Share Tech Mono - для статусов и значений */
|
||||
/* Share Tech Mono */
|
||||
@font-face {
|
||||
font-family: 'Share Tech Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/ShareTechMono-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* ===== ПЕРЕМЕННЫЕ СО ШРИФТАМИ ===== */
|
||||
:root {
|
||||
--font-mono: 'JetBrains Mono', 'Share Tech Mono', 'Courier New', monospace;
|
||||
--font-display: 'Orbitron', 'Share Tech Mono', 'Arial Black', sans-serif;
|
||||
--font-tech: 'Share Tech Mono', 'JetBrains Mono', 'Courier New', monospace;
|
||||
|
||||
/* Размеры шрифтов */
|
||||
--font-size-xs: 11px;
|
||||
--font-size-sm: 13px;
|
||||
--font-size-base: 15px;
|
||||
--font-size-lg: 18px;
|
||||
--font-size-xl: 22px;
|
||||
--font-size-2xl: 28px;
|
||||
--font-size-3xl: 36px;
|
||||
|
||||
/* Веса шрифтов */
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
.left {
|
||||
flex: 1 1 auto;
|
||||
min-width: 300px;
|
||||
padding: 10px;
|
||||
padding: 10px 10%; /* ← X% от ширины экрана */
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
@@ -66,6 +66,8 @@
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ===== МОБИЛЬНАЯ ВЕРСИЯ ===== */
|
||||
@@ -76,6 +78,10 @@
|
||||
min-height: 100vh;
|
||||
gap: 8px;
|
||||
overflow: visible !important;
|
||||
padding: 0 8px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.panel {
|
||||
height: auto !important;
|
||||
@@ -86,12 +92,18 @@
|
||||
.left {
|
||||
flex: 1 1 auto !important;
|
||||
min-width: 0 !important;
|
||||
padding: 0;
|
||||
padding: 10px 16px !important;
|
||||
width: 100% !important;
|
||||
overflow-y: visible !important;
|
||||
height: auto !important;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 0 0 auto !important;
|
||||
min-width: 100% !important;
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
<a href="/logs.html" class="header-link">Журнал</a>
|
||||
<div id="server-time" class="header-time">--:--:--</div>
|
||||
<div id="audio-indicator" class="audio-indicator">🔇</div>
|
||||
<button id="camToggle" class="cam-btn">КАМЕРА</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +44,7 @@
|
||||
<!-- Карточка каналов (8 штук) -->
|
||||
<div class="card wide channels-card">
|
||||
<div class="channels-header">
|
||||
<h3>КАНАЛЫ СИГНАЛОВ (8)</h3>
|
||||
<h3>КАНАЛЫ СИГНАЛОВ</h3>
|
||||
<div class="channels-controls">
|
||||
<button class="channels-toggle-all" id="expandAllBtn">▼</button>
|
||||
<button class="channels-toggle-all" id="collapseAllBtn">▶</button>
|
||||
@@ -56,7 +55,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== ВОЗВРАЩАЕМ ЗВУК И ВЫСОТУ ===== -->
|
||||
<!-- Карточка со звуком -->
|
||||
<div class="card wide">
|
||||
<h3>🔊 ЗВУК</h3>
|
||||
|
||||
@@ -19,13 +19,13 @@ import {
|
||||
setBars,
|
||||
setSignal,
|
||||
setAudioIndicator,
|
||||
DOM
|
||||
DOM,
|
||||
updateAllChannels
|
||||
} from "./ui.js";
|
||||
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;
|
||||
@@ -56,24 +56,20 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||
if (signalLevel !== lastSignalLevel) {
|
||||
lastSignalLevel = signalLevel;
|
||||
audioEngine.updateLevel(signalLevel);
|
||||
|
||||
// Обновляем индикатор звука
|
||||
const isActive = signalLevel > 0;
|
||||
setAudioIndicator(isActive, signalLevel);
|
||||
setAudioIndicator(signalLevel > 0, signalLevel);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Обработчик для кнопки закрытия камеры
|
||||
const closeBtn = document.getElementById('camCloseBtn');
|
||||
const toggleBtn = document.getElementById('camToggle');
|
||||
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', () => {
|
||||
if (toggleBtn) {
|
||||
toggleBtn.click(); // Эмулируем нажатие кнопки переключения
|
||||
toggleBtn.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -104,17 +100,13 @@ document.addEventListener("keydown", (e: KeyboardEvent) => {
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
// Toggle звука
|
||||
if (lastSignalLevel > 0) {
|
||||
// Временно отключаем
|
||||
audioEngine.updateLevel(0);
|
||||
setAudioIndicator(false);
|
||||
console.log('[Audio] Звук отключён (M)');
|
||||
// Сохраняем уровень для восстановления
|
||||
window._mutedLevel = lastSignalLevel;
|
||||
lastSignalLevel = 0;
|
||||
} else {
|
||||
// Восстанавливаем уровень
|
||||
const level = window._mutedLevel || state.peakSignalLevel;
|
||||
if (level > 0) {
|
||||
audioEngine.updateLevel(level);
|
||||
@@ -164,23 +156,33 @@ async function update(): Promise<void> {
|
||||
const arr = Array.isArray(history.bytes) ? history.bytes : [];
|
||||
const raw = arr[arr.length - 1] ?? 0;
|
||||
|
||||
// Обновляем историю сигналов в state
|
||||
if (arr.length > 0 && isOnline) {
|
||||
const bufferSize = health.stats?.filled ?? 0;
|
||||
addSignals(arr, bufferSize);
|
||||
updateAllChannels(arr);
|
||||
}
|
||||
|
||||
const value = raw & 0x3F;
|
||||
const level = (raw >> 6) & 0x3;
|
||||
const percent = (value / 63) * 100;
|
||||
// ===== ПАРСИНГ ДАННЫХ =====
|
||||
// Биты 0-5: значение (0-63)
|
||||
// Биты 6-7: уровень сигнала (0-3)
|
||||
const value = raw & 0x3F; // 0-63
|
||||
const level = (raw >> 6) & 0x3; // 0-3
|
||||
const percent = (value / 63) * 100; // 0-100%
|
||||
|
||||
// ===== ОБНОВЛЯЕМ КАНАЛЫ =====
|
||||
// Передаем сырые данные для отображения в каналах
|
||||
updateAllChannels(arr);
|
||||
|
||||
if (isOnline) {
|
||||
// ===== Обновляем Peak Holder (проценты) ====
|
||||
const displayValue = updatePeakHolder(percent);
|
||||
setBars(displayValue);
|
||||
|
||||
// Обновляем уровень сигнала с Peak Holder
|
||||
const displaySignalLevel = updateSignalPeakHolder(level);
|
||||
setSignal(displaySignalLevel);
|
||||
|
||||
// Обновляем звук
|
||||
const currentSignalLevel = state.peakSignalLevel;
|
||||
if (currentSignalLevel !== lastSignalLevel) {
|
||||
lastSignalLevel = currentSignalLevel;
|
||||
@@ -239,8 +241,6 @@ async function update(): Promise<void> {
|
||||
|
||||
if (consecutiveErrors >= MAX_ERRORS && pollTimer) {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = window.setInterval(update, 2000);
|
||||
console.log("[app] Замедлили опрос до 2с");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +253,6 @@ function startPolling(): void {
|
||||
|
||||
window.addEventListener("focus", () => {
|
||||
if (consecutiveErrors >= MAX_ERRORS) {
|
||||
console.log("[app] Окно в фокусе, ускоряем опрос");
|
||||
consecutiveErrors = 0;
|
||||
startPolling();
|
||||
}
|
||||
@@ -270,4 +269,4 @@ declare global {
|
||||
}
|
||||
|
||||
startPolling();
|
||||
update();
|
||||
update();
|
||||
|
||||
@@ -237,7 +237,6 @@ function scheduleSignalPeakRelease(): void {
|
||||
state.peakSignalLevel = state.currentSignalLevel;
|
||||
state.signalPeakTimer = null;
|
||||
state.hasPeak = false;
|
||||
console.log(`[SignalPeak] Сброс пика до: ${state.peakSignalLevel}`);
|
||||
notifyPeakHolderChange();
|
||||
}, state.signalPeakHoldTime);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
// ui.ts (DOM слой)
|
||||
// ui.ts - ИСПРАВЛЕННАЯ ВЕРСИЯ
|
||||
// Канал 1 - реальные данные, каналы 2-8 - неактивны
|
||||
|
||||
export interface DOMElements {
|
||||
serverTime: HTMLElement | null;
|
||||
status: HTMLElement | null;
|
||||
@@ -81,6 +83,7 @@ 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;
|
||||
@@ -90,10 +93,16 @@ export function setBars(p: number, noConnection: boolean = false): void {
|
||||
el.classList.add("no-connection");
|
||||
} else {
|
||||
el.classList.remove("no-connection");
|
||||
el.style.width = `${Math.max(0, Math.min(100, p))}%`;
|
||||
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;
|
||||
@@ -187,6 +196,11 @@ export function initChannels() {
|
||||
item.className = 'channel-item';
|
||||
item.dataset.channel = String(i);
|
||||
|
||||
// Только канал 1 активен по умолчанию
|
||||
if (i === 0) {
|
||||
item.classList.add('channel-active');
|
||||
}
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="channel-header" data-channel="${i}">
|
||||
<span class="channel-label">Канал ${i + 1}</span>
|
||||
@@ -198,7 +212,7 @@ export function initChannels() {
|
||||
<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 class="fill" id="ch-bar-${i}" style="width:0%; background: #e6852d;"></div>
|
||||
</div>
|
||||
<div class="signal-icon" id="ch-signal-${i}">
|
||||
<div class="sig-bar sig-bar-1"></div>
|
||||
@@ -250,14 +264,28 @@ export function setChannelValue(index: number, value: number, strength: number)
|
||||
|
||||
const ch = channels[index];
|
||||
const el = channelElements[index];
|
||||
if (!el) return;
|
||||
if (!el) {
|
||||
console.warn(`[UI] Channel ${index} element not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
ch.value = value;
|
||||
ch.strength = strength;
|
||||
|
||||
const percent = (value / 31) * 100;
|
||||
el.bar.style.width = `${percent}%`;
|
||||
el.value.textContent = value.toString();
|
||||
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) {
|
||||
@@ -268,22 +296,67 @@ export function setChannelValue(index: number, value: number, strength: number)
|
||||
}
|
||||
|
||||
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);
|
||||
if (el.stats) {
|
||||
el.stats.textContent = `${value}/${maxValue} | ${strengthNames[strength] || '---'}`;
|
||||
}
|
||||
|
||||
if (el.item) {
|
||||
el.item.classList.remove('no-data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ОБНОВЛЕНИЕ ВСЕХ КАНАЛОВ
|
||||
* Канал 1 - реальные данные из эмулятора
|
||||
* Каналы 2-8 - неактивны (0)
|
||||
*/
|
||||
export function updateAllChannels(rawData?: number[]) {
|
||||
// Сначала сбрасываем все каналы в 0
|
||||
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);
|
||||
setChannelValue(i, 0, 0);
|
||||
if (channelElements[i]) {
|
||||
channelElements[i].item.classList.remove('channel-active');
|
||||
}
|
||||
}
|
||||
|
||||
// Если есть данные - обновляем КАНАЛ 1
|
||||
if (rawData && rawData.length > 0) {
|
||||
// Берем последний байт (самый свежий)
|
||||
const lastByte = rawData[rawData.length - 1];
|
||||
|
||||
// ПРАВИЛЬНЫЙ ПАРСИНГ:
|
||||
// Биты 0-5: значение (количество сигналов) 0-63
|
||||
// Биты 6-7: уровень сигнала (амплитуда/сила) 0-3
|
||||
const value = lastByte & 0x3F; // маска 0b00111111 → 0-63
|
||||
const strength = (lastByte >> 6) & 0x3; // маска 0b00000011 → 0-3
|
||||
|
||||
// Обновляем КАНАЛ 1 с реальными данными
|
||||
setChannelValue(0, value, strength);
|
||||
if (channelElements[0]) {
|
||||
channelElements[0].item.classList.add('channel-active');
|
||||
}
|
||||
|
||||
// Обновляем last-byte
|
||||
if (DOM.lastByte) {
|
||||
DOM.lastByte.textContent = value.toString();
|
||||
}
|
||||
|
||||
// Обновляем основной прогресс-бар (для синхронизации)
|
||||
const percent = (value / 63) * 100;
|
||||
if (DOM.barFill) {
|
||||
DOM.barFill.style.width = `${percent}%`;
|
||||
DOM.barFill.style.background = '#e6852d';
|
||||
DOM.barFill.style.backgroundColor = '#e6852d';
|
||||
DOM.barFill.style.backgroundImage = 'none';
|
||||
}
|
||||
} else {
|
||||
// Если данных нет - канал 1 тоже неактивен
|
||||
if (DOM.lastByte) {
|
||||
DOM.lastByte.textContent = '--';
|
||||
}
|
||||
if (DOM.barFill) {
|
||||
DOM.barFill.style.width = '0%';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +364,9 @@ export function updateAllChannels(rawData: number[]) {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initChannels();
|
||||
|
||||
// Принудительно обновляем каналы при загрузке
|
||||
updateAllChannels();
|
||||
|
||||
const expandAll = document.getElementById('expandAllBtn');
|
||||
const collapseAll = document.getElementById('collapseAllBtn');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user