diff --git a/CHANGELOG.md b/CHANGELOG.md index c5fbd8a4..ab7f8481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.0.4 server/web · 1.0.1 client — 2026-11-13 — статистика гостей / Guest statistics / 参与者统计 + +- RU: мини-статистика под карточкой гостя показывает измеренные битрейты приёма видео и микрофона и оценку сети. Маленькая стрелка открывает памятку с текущими числами и порогами; «Нет данных» тоже раскрывается. Обновления сохраняют видео и меню, одинаковые замеры не обновляют весь список. Обновите сервер и затем страницы вне эфира; Client 1.0.1 отображает интерфейс выбранного обновлённого сервера. [Примечания](distribution/RELEASE_NOTES_1_0_4.md). +- EN: guest cards show measured incoming camera/microphone bitrates and the network grade. A small arrow opens the current values and threshold guide, including No data. Updates preserve media and menus; identical samples preserve roster state. Update the server and reload pages off air; Client 1.0.1 displays its selected updated server's UI. [Release notes](distribution/docs/en/RELEASE_NOTES_1_0_4.md). +- 简体中文:参与者卡片显示摄像头和麦克风的实测接收码率及网络等级,小箭头可查看当前值和阈值,无数据时也可展开。更新保留视频和菜单,相同测量不更新整个列表。请在停播时更新服务器并刷新页面;Client 1.0.1 显示所选已更新服务器的界面。[发行说明](distribution/docs/zh-CN/RELEASE_NOTES_1_0_4.md)。 + ## Web — выравнивание карточек / Card alignment / 卡片对齐 - RU: видео и аудио остаются в двух ровных колонках, сеть — отдельной строкой с оценкой рядом с подписью. Выравнивание сохраняется на узких карточках; опубликованные установочные пакеты 1.0.3 остаются неизменными. diff --git a/apps/web/components/GuestMiniStats.tsx b/apps/web/components/GuestMiniStats.tsx index a60b979d..647af99e 100644 --- a/apps/web/components/GuestMiniStats.tsx +++ b/apps/web/components/GuestMiniStats.tsx @@ -1,10 +1,13 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useId, useRef, useState } from "react"; import { useI18n } from "./I18nProvider"; +import { StudioConsoleIcon } from "./StudioConsoleIcon"; +import { positionStudioConsolePopover } from "./studio-console-popover-position"; import styles from "./GuestVideoCard.module.css"; export type GuestMiniStatsData = { + name?: string; receiveVideoKbps?: number | null; receiveAudioKbps?: number | null; networkMeasured?: boolean; @@ -21,7 +24,17 @@ export type GuestMiniStatsData = { export function GuestMiniStats({ guest }: { guest: GuestMiniStatsData }) { const { t, locale } = useI18n(); const [now, setNow] = useState(Date.now); + const menuId = useId(); + const trigger = useRef(null), popup = useRef(null); + const [helpOpen, setHelpOpen] = useState(false); + const positionHelp = () => { if (popup.current && trigger.current) positionStudioConsolePopover(popup.current, trigger.current); }; useEffect(() => { const timer = setInterval(() => setNow(Date.now()), 2000); return () => clearInterval(timer); }, []); + useEffect(() => { + if (!helpOpen) return; + window.addEventListener("resize", positionHelp); + window.addEventListener("scroll", positionHelp, true); + return () => { window.removeEventListener("resize", positionHelp); window.removeEventListener("scroll", positionHelp, true); }; + }, [helpOpen]); const sampledAt = guest.statsSampledAtRef?.current ?? guest.statsSampledAt; const fresh = sampledAt != null && sampledAt > 0 && now - sampledAt < 8000; const measured = fresh && guest.networkMeasured; @@ -31,9 +44,35 @@ export function GuestMiniStats({ guest }: { guest: GuestMiniStatsData }) { const detail = measured ? t("Потери: {loss} пак. за всё время трека; джиттер: {jitter} мс; RTT: {rtt}", { loss: guest.packetLoss, jitter: guest.jitterMs, rtt: guest.rttMs > 0 ? `${guest.rttMs} ${t("мс")}` : "—", }) : t("Нет свежего замера соединения"); - return
+ return <>
{t("Приём видео")}
{bitrate(guest.receiveVideoKbps, guest.camera)}
{t("Приём аудио")}
{bitrate(guest.receiveAudioKbps, guest.microphone)}
-
{t("Сеть")}