feat(room): полноэкранный режим на мобильном с выезжающим тулбаром
Вход — кнопка «Экран» в тулбаре, теперь видна и на мобильном (по аналогии с десктопом, а не спрятана в шторку настроек). В полноэкранном режиме топбар и тулбар выходят из потока и лежат оверлеем поверх сцены: тап/клик по сцене вне элементов управления показывает их, сами прячутся через несколько секунд бездействия; на десктопе дополнительно — наведение мыши в нижнюю полосу экрана. Работает одинаково на мобильном и десктопе. Кнопка настроек устройств в тулбаре на мобильном подписана «Настройки» вместо «Устройства». Раскладка кнопок мобильного тулбара, когда они не помещаются в один ряд (7+, обычный случай с «Экраном» и «Очередью» у организатора), стала равномерной сеткой на 4 колонки вместо переноса «как получится» через flex-wrap.
This commit is contained in:
@@ -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 (
|
||||
<footer className="room-toolbar">
|
||||
<footer
|
||||
className={`room-toolbar${overlayVisible ? ' is-visible' : ''}${mobileButtonCount >= 7 ? ' tb-wrap-grid' : ''}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
{...mic.buttonProps}
|
||||
@@ -214,7 +241,10 @@ export function RoomToolbar({
|
||||
<span className="icon-shell">
|
||||
<Settings className="lucide" aria-hidden="true" />
|
||||
</span>
|
||||
<span className="label">Устройства</span>
|
||||
{/* На мобильном подпись шире по смыслу («Настройки»): там же в шторке
|
||||
секция «Вид», а отдельной кнопки под неё в тулбаре нет. На десктопе
|
||||
панель — только настройки устройств, подпись это отражает. */}
|
||||
<span className="label">{isCompact ? 'Настройки' : 'Устройства'}</span>
|
||||
</button>
|
||||
|
||||
{fullscreenSupported && (
|
||||
|
||||
Reference in New Issue
Block a user