From ed6f9fff446f850ed9308c0b90532c081851fb56 Mon Sep 17 00:00:00 2001 From: Max Ronzhin Date: Tue, 4 Aug 2026 00:00:45 +0300 Subject: [PATCH] =?UTF-8?q?feat(room):=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE?= =?UTF-8?q?=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B6=D0=B8=D0=BC=20=D0=BD=D0=B0=20=D0=BC=D0=BE=D0=B1=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC=20=D1=81=20=D0=B2=D1=8B=D0=B5?= =?UTF-8?q?=D0=B7=D0=B6=D0=B0=D1=8E=D1=89=D0=B8=D0=BC=20=D1=82=D1=83=D0=BB?= =?UTF-8?q?=D0=B1=D0=B0=D1=80=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Вход — кнопка «Экран» в тулбаре, теперь видна и на мобильном (по аналогии с десктопом, а не спрятана в шторку настроек). В полноэкранном режиме топбар и тулбар выходят из потока и лежат оверлеем поверх сцены: тап/клик по сцене вне элементов управления показывает их, сами прячутся через несколько секунд бездействия; на десктопе дополнительно — наведение мыши в нижнюю полосу экрана. Работает одинаково на мобильном и десктопе. Кнопка настроек устройств в тулбаре на мобильном подписана «Настройки» вместо «Устройства». Раскладка кнопок мобильного тулбара, когда они не помещаются в один ряд (7+, обычный случай с «Экраном» и «Очередью» у организатора), стала равномерной сеткой на 4 колонки вместо переноса «как получится» через flex-wrap. --- frontend/src/components/room/RoomToolbar.tsx | 44 ++++++++-- frontend/src/components/room/RoomTopbar.tsx | 10 ++- frontend/src/pages/RoomPage.tsx | 85 +++++++++++++++++++- frontend/src/styles/room.css | 71 ++++++++++++++-- 4 files changed, 191 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/room/RoomToolbar.tsx b/frontend/src/components/room/RoomToolbar.tsx index 257b969..6dc3342 100644 --- a/frontend/src/components/room/RoomToolbar.tsx +++ b/frontend/src/components/room/RoomToolbar.tsx @@ -17,6 +17,7 @@ import { Track, type ScreenShareCaptureOptions } from 'livekit-client' import { DisconnectButton, useLocalParticipant, useTrackToggle } from '@livekit/components-react' import { useToast } from '@/components/ui/ToastProvider' import { useIsCompactViewport } from '@/hooks/useIsCompactViewport' +import { useIsOrganizer } from '@/hooks/useIsOrganizer' import type { HandQueueEntry } from '@/hooks/useChat' import { StageViewMenu, type StageViewProps } from '@/components/room/StageViewOptions' import { HandQueueMenu } from '@/components/room/HandQueueMenu' @@ -68,6 +69,8 @@ interface RoomToolbarProps extends StageViewProps { onLowerHand: () => void /** Опустить ЧУЖУЮ руку по identity — только организатору (панель очереди, `HandQueueMenu`). */ onLowerHandById: (identity: string) => void + /** Тулбар в оверлее полноэкранного режима — см. докстринг `RoomTopbar.overlayVisible`, тот же механизм. */ + overlayVisible?: boolean } /** @@ -77,16 +80,20 @@ interface RoomToolbarProps extends StageViewProps { * панели чата, стилизованные по design/mockups/room.html. * * Кнопка «Вид» (режимы показа и скрытие остальных) рендерится ТОЛЬКО на - * широком экране — условным рендерингом, а не скрытием через CSS: тулбар на - * мобильном и так ужат до пяти «безусловных» кнопок (демонстрация/ - * полноэкранный режим/мини-плеер скрыты на узком экране через CSS, см. - * `styles/room.css`), а те же настройки там доступны секцией «Вид» в шторке - * настроек (`DeviceSettingsDialog`). «Рука» — сознательное исключение из этой + * широком экране — условным рендерингом, а не скрытием через CSS: демонстрация + * и мини-плеер скрыты на узком экране через CSS (см. `styles/room.css`), а те + * же настройки показа сцены доступны секцией «Вид» в шторке настроек + * (`DeviceSettingsDialog`). «Рука» — сознательное исключение из этой * экономии: поднять руку посреди разговора — действие со временем жизни в * секунды, прятать его в шторку настроек означало бы делать его практически * недоступным с телефона. «Очередь» показывается только организатору — * встречается редко, но по той же причине оставлена в тулбаре, а не в * шторке: организатору с телефона тоже нужно видеть очередь сразу. + * Полноэкранный режим на мобильном ОСТАЁТСЯ в тулбаре (не спрятан в шторку, + * как демонстрация/мини-плеер) — по решению оператора вход в него должен + * быть по аналогии с десктопом; если из-за этого кнопки не помещаются в один + * ряд, `mobileButtonCount`/`.tb-wrap-grid` ниже раскладывают их равномерной + * сеткой, а не как получится через `flex-wrap`. */ export function RoomToolbar({ chatVisible, @@ -108,11 +115,29 @@ export function RoomToolbar({ onLayoutModeChange, hideOthers, onHideOthersChange, + overlayVisible, }: RoomToolbarProps) { const toast = useToast() const isCompact = useIsCompactViewport() + const isOrganizer = useIsOrganizer() const { localParticipant } = useLocalParticipant() const handRaised = handQueue.some((entry) => entry.identity === localParticipant.identity) + + // Сколько кнопок реально видно на мобильном (демонстрация/мини-окно там + // скрыты через CSS всегда, «Вид» не рендерится вовсе — см. докстринг) — + // считаем в JS, а не через CSS-селекторы вида `:nth-child`: у «Очереди» + // (только у организатора) в DOM есть своя обёртка `.tb-menu-wrap`, из-за + // которой позиция остальных кнопок «плывёт», и подсчёт по структуре DOM + // был бы хрупким. От порога зависит `.tb-wrap-grid` в room.css — ниже он + // переключает перенос на 2 строки с «как поместится» (flex-wrap) на + // равномерную сетку 4 колонки (7 → 4+3, 8 → 4+4). + const mobileButtonCount = + 3 /* микрофон, камера, рука */ + + (isOrganizer ? 1 : 0) /* очередь */ + + 1 /* настройки */ + + (fullscreenSupported ? 1 : 0) + + (chatVisible ? 1 : 0) + + 1 /* выйти */ const mic = useTrackToggle({ source: Track.Source.Microphone }) const camera = useTrackToggle({ source: Track.Source.Camera }) const screenShare = useTrackToggle({ @@ -129,7 +154,9 @@ export function RoomToolbar({ }) return ( -