feat(room): выбор режима показа участников и скрытие остальных
Раскладка сцены больше не выбирается автоматически — пользователь выбирает
один из трёх режимов:
- «Стандарт» — как раньше: крупная плитка плюс карусель остальных сбоку,
кого показать крупно, по-прежнему решает pickStageFocus;
- «Плитки» — все участники равными плитками, без выделенного крупного;
- «Живые плитки» — сетка только из тех, у кого включена камера, остальные
в карусели сбоку; если камеру не включил никто, сетка была бы пустой —
режим вырождается в «Плитки».
Демонстрация экрана перебивает выбранный режим: пока в комнате есть активный
шэр, сцена ведёт себя как «Стандарт» (смысл плиточных режимов — равноправие
участников, а демонстрация неравноправна по определению). Режим при этом
живёт в состоянии RoomPage, поэтому по завершении шэра вид сам возвращается
к выбранному.
Скрытие остальных убирает карусель, основная область занимает сцену целиком.
Скрыть можно из меню «Вид», из шторки настроек на мобильном и кнопкой прямо
над колонкой миниатюр; вернуть — кнопкой «Показать остальных (N)» на сцене,
которая видна всё время, пока кто-то скрыт. В режиме «Плитки» скрывать
нечего, переключатель там заблокирован с пояснением.
Переключатель на широком экране — кнопка «Вид» с поповером; на мобильном её
нет (тулбар там и так ужат до пяти кнопок), те же настройки идут секцией в
шторке. Видимость решается условным рендерингом в React, а не новым
CSS-правилом поверх медиазапроса — в room.css за это уже была битва
специфичности.
Режим сохраняется между заходами в комнату (localStorage, своим модулем —
LocalUserChoices у LiveKit фиксированной структуры, поля под раскладку там
нет). Скрытие не сохраняется: разовое действие по ходу разговора, войти в
новую конференцию без половины участников — сюрприз, а не удобство.
Сетка собрана своим StageGrid поверх тех же публичных хуков, что использует
GridLayout: библиотечный компонент не пробрасывает gridLayouts, а её набор
раскладок требует 560px уже для 2x2 — на телефоне это две плитки на страницу.
Свой набор даёт 2x2 от 360px и портретную 2x3. Индикатор страниц тоже свой:
PaginationControl/PaginationIndicator из пакета не экспортируются.
This commit is contained in:
@@ -47,7 +47,9 @@
|
||||
.room-invite-chip span { font: var(--text-mono-sm); color: var(--color-room-text-secondary); }
|
||||
|
||||
.room-main { display: flex; flex: 1; min-height: 0; }
|
||||
.stage { flex: 1; display: flex; flex-direction: column; padding: var(--space-5) var(--space-6); gap: var(--space-4); min-width: 0; }
|
||||
/* `position: relative` — якорь для кнопки «Показать остальных» (`.stage-show-others`),
|
||||
которая висит оверлеем над сценой, см. её правило ниже. */
|
||||
.stage { flex: 1; display: flex; flex-direction: column; padding: var(--space-5) var(--space-6); gap: var(--space-4); min-width: 0; position: relative; }
|
||||
|
||||
/*
|
||||
* Демонстрация экрана: `object-fit: cover` (дефолт VideoTrack для
|
||||
@@ -115,6 +117,10 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
|
||||
@media (max-width: 600px) {
|
||||
.stage-tiles.lk-focus-layout { grid-template-columns: 1fr; }
|
||||
.stage { padding: var(--space-3) var(--space-4); gap: var(--space-3); }
|
||||
/* Их `.lk-carousel{order:1}` (ставит миниатюры ПОД спикера) больше не
|
||||
действует: прямой ребёнок грида теперь обёртка `.stage-side`, а не сама
|
||||
карусель — переносим порядок на неё, иначе миниатюры уезжают наверх. */
|
||||
.stage-side { order: 1; }
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -130,6 +136,123 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
|
||||
.room-single-tile { flex: 1; min-height: 0; display: flex; }
|
||||
.room-single-tile .lk-participant-tile { flex: 1; min-height: 220px; width: 100%; }
|
||||
|
||||
/*
|
||||
* ---------- Колонка миниатюр (кнопка «Скрыть» + карусель) ----------
|
||||
* Прямой ребёнок `.lk-focus-layout` — вместо самой карусели, потому что над
|
||||
* ней стоит кнопка «Скрыть» (см. `RoomStage`). Карусель занимает весь остаток
|
||||
* колонки: ей важны собственные размеры — `CarouselLayout` по ним и решает,
|
||||
* вертикальная она или горизонтальная (см. комментарий в `RoomStage`).
|
||||
* `min-height: 0` обязателен — иначе flex-элемент не даёт себя сжать и
|
||||
* колонка вылезает за высоту сцены.
|
||||
*/
|
||||
.stage-side { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; min-height: 0; }
|
||||
.stage-side .lk-carousel { flex: 1; min-height: 0; }
|
||||
.stage-side-hide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
padding: 6px 10px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-room-tile-border);
|
||||
background: var(--color-room-tile);
|
||||
color: var(--color-room-text-secondary);
|
||||
font: var(--text-caption);
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
.stage-side-hide:hover { background: var(--color-room-tile-hover); color: var(--color-room-text-primary); }
|
||||
.stage-side-hide svg { width: 14px; height: 14px; flex-shrink: 0; }
|
||||
|
||||
/*
|
||||
* ---------- Основная область без карусели ----------
|
||||
* `.stage-main-only` — сцена, где карусель не рендерится вовсе: плиточные
|
||||
* режимы («Плитки» всегда, «Живые плитки» когда камеры выключены не у кого),
|
||||
* «Стандарт» с единственным участником и любой режим при включённом «скрыть
|
||||
* остальных». Единственный ребёнок (плитка фокуса или сетка `.stage-grid`)
|
||||
* занимает всё место — тем же приёмом, что `.room-single-tile` выше, но без
|
||||
* `min-height` мини-плеера: в основном окне высота приходит от `.room-main`.
|
||||
*/
|
||||
.stage-main-only { display: flex; }
|
||||
.stage-main-only > * { flex: 1; min-width: 0; min-height: 0; }
|
||||
|
||||
/*
|
||||
* ---------- Равномерная сетка плиток (`StageGrid`) ----------
|
||||
* Класс `.lk-grid-layout` библиотеки на месте (оттуда вся геометрия сетки:
|
||||
* `--lk-col-count`/`--lk-row-count`, gap, padding), здесь — только то, чего у
|
||||
* неё нет: сброс минимальных размеров (сетка может жить внутри grid-ячейки
|
||||
* `.lk-focus-layout`, где без `min-*: 0` она распирала бы колонку) и якорь для
|
||||
* собственного переключателя страниц.
|
||||
*/
|
||||
.stage-grid { position: relative; min-width: 0; min-height: 0; }
|
||||
.stage-grid-pages {
|
||||
position: absolute;
|
||||
bottom: calc(var(--lk-grid-gap) / 2);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
border-radius: var(--radius-full);
|
||||
background: rgba(30, 30, 30, 0.85);
|
||||
border: 1px solid var(--color-room-tile-border);
|
||||
color: var(--color-room-text-primary);
|
||||
font: var(--text-caption);
|
||||
}
|
||||
.stage-grid-pages button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.stage-grid-pages button:hover:not(:disabled) { background: var(--color-room-tile-hover); }
|
||||
.stage-grid-pages button:disabled { opacity: 0.35; cursor: default; }
|
||||
.stage-grid-pages svg { width: 16px; height: 16px; }
|
||||
.stage-grid-pages span { padding: 0 4px; font-variant-numeric: tabular-nums; }
|
||||
|
||||
/*
|
||||
* ---------- Кнопка «Показать остальных» ----------
|
||||
* Единственный видимый способ вернуть скрытую карусель, поэтому висит прямо на
|
||||
* сцене, а не только в меню «Вид»: пользователь не должен «терять» участников
|
||||
* без понятного способа их вернуть. Внизу по центру — там у плиток пусто
|
||||
* (`.lk-participant-metadata` прижата к левому и правому углам).
|
||||
*/
|
||||
.stage-show-others {
|
||||
position: absolute;
|
||||
bottom: var(--space-5);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius-full);
|
||||
border: 1px solid var(--color-room-tile-border);
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
color: var(--color-room-text-primary);
|
||||
font: var(--text-body);
|
||||
font-weight: 600;
|
||||
/* Иначе на узком экране подпись ломается на две строки и кнопка вырастает
|
||||
в высоту вдвое (поймали на 390px). */
|
||||
white-space: nowrap;
|
||||
box-shadow: var(--shadow-md);
|
||||
cursor: pointer;
|
||||
}
|
||||
.stage-show-others:hover { background: var(--color-room-tile-hover); }
|
||||
.stage-show-others svg { width: 18px; height: 18px; flex-shrink: 0; }
|
||||
|
||||
/* Нижний тулбар: свои кнопки на хуках LiveKit (TrackToggle/DisconnectButton) */
|
||||
.room-toolbar {
|
||||
display: flex;
|
||||
@@ -200,6 +323,75 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/*
|
||||
* ---------- Кнопка «Вид» и её поповер (десктоп) ----------
|
||||
* Обёртка вокруг кнопки тулбара — якорь для всплывающей панели над ней
|
||||
* (`StageViewMenu`). На мобильном ни кнопки, ни поповера нет вовсе: там блок
|
||||
* настроек вида рендерится секцией внутри шторки (условный рендеринг в React,
|
||||
* а не скрытие через CSS, — см. `RoomToolbar`).
|
||||
*/
|
||||
.tb-menu-wrap { position: relative; display: flex; }
|
||||
.tb-menu {
|
||||
position: absolute;
|
||||
bottom: calc(100% + var(--space-2));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 50;
|
||||
width: 280px;
|
||||
padding: var(--space-4);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--color-room-tile-border);
|
||||
background: var(--color-room-surface-raised);
|
||||
box-shadow: var(--shadow-room-panel);
|
||||
}
|
||||
|
||||
/*
|
||||
* ---------- Блок «Вид сцены» (`StageViewOptions`) ----------
|
||||
* Общий для десктопного поповера и мобильной шторки — отсюда нейтральная
|
||||
* геометрия без привязки к контейнеру (ширину задаёт родитель).
|
||||
*/
|
||||
.stage-view-options { display: flex; flex-direction: column; gap: var(--space-3); }
|
||||
/* Секция «Вид» внутри шторки настроек — отбивка как у `.room-field` (свой
|
||||
класс, а не `.room-field`, см. комментарий в `DeviceSettingsDialog`). */
|
||||
.stage-view-section { margin-bottom: var(--space-5); }
|
||||
.stage-view-modes { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; border: none; }
|
||||
.stage-view-modes legend {
|
||||
padding: 0 0 var(--space-2);
|
||||
font: var(--text-caption);
|
||||
color: var(--color-room-text-tertiary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.stage-view-mode {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.stage-view-mode:hover { background: var(--color-room-tile); }
|
||||
.stage-view-mode.is-active { background: var(--color-room-tile); border-color: var(--color-room-speaker-ring); }
|
||||
.stage-view-mode input { margin-top: 3px; accent-color: var(--color-room-mic-on); flex-shrink: 0; }
|
||||
.stage-view-mode-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
||||
.stage-view-mode-title { font: var(--text-body); font-weight: 600; color: var(--color-room-text-primary); }
|
||||
.stage-view-mode-hint { font: var(--text-caption); text-transform: none; letter-spacing: normal; color: var(--color-room-text-secondary); }
|
||||
|
||||
.stage-view-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding-top: var(--space-3);
|
||||
border-top: 1px solid var(--color-room-tile-border);
|
||||
font: var(--text-body);
|
||||
color: var(--color-room-text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.stage-view-toggle input { accent-color: var(--color-room-mic-on); flex-shrink: 0; }
|
||||
.stage-view-toggle.is-disabled { color: var(--color-room-text-tertiary); cursor: default; }
|
||||
.stage-view-note { margin: 0; font: var(--text-caption); text-transform: none; letter-spacing: normal; color: var(--color-room-text-tertiary); }
|
||||
|
||||
/* ---------- Панель чата (см. design/mockups/room.html, .chat-panel) ---------- */
|
||||
.chat-panel {
|
||||
width: 320px;
|
||||
|
||||
Reference in New Issue
Block a user