feat(call): introduce StageLayout discriminated union
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -294,12 +294,35 @@ export function InCallPanel({ conversation }: Props) {
|
||||
? t('app:call.waiting_for_peers', { defaultValue: 'Warte auf andere…' })
|
||||
: t('app:call.connected');
|
||||
|
||||
// Screen shares no longer auto-promote — the user opts in by clicking the
|
||||
// "Bildschirm anschauen" overlay, which also toggles whether the audio
|
||||
// plays. Focus falls back to the first tile so focus-mode always has
|
||||
// something to show when no tile was explicitly picked.
|
||||
const effectiveFocusedId = focusedId ?? tiles[0]?.id ?? null;
|
||||
const speaker = tiles.find((p) => p.id === effectiveFocusedId) ?? tiles[0];
|
||||
// Discord-style precedence:
|
||||
// 1. focusedId set → 'focus', that tile is the stage.
|
||||
// 2. ≥2 shares, no pin → 'bento', shares fill the stage, webcams strip.
|
||||
// 3. exactly 1 share, no pin → 'focus' (auto-promote share).
|
||||
// 4. no shares, no pin → 'equal-grid'.
|
||||
type StageLayout =
|
||||
| { kind: 'equal-grid' }
|
||||
| { kind: 'focus'; bigTileId: string }
|
||||
| { kind: 'bento'; shareIds: string[] };
|
||||
|
||||
const shareIds = tiles.filter((t) => t.kind === 'screen').map((t) => t.id);
|
||||
const stageLayout: StageLayout = (() => {
|
||||
if (focusedId !== null && tiles.some((t) => t.id === focusedId)) {
|
||||
return { kind: 'focus', bigTileId: focusedId };
|
||||
}
|
||||
if (shareIds.length >= 2) return { kind: 'bento', shareIds };
|
||||
if (shareIds.length === 1 && shareIds[0]) {
|
||||
return { kind: 'focus', bigTileId: shareIds[0] };
|
||||
}
|
||||
return { kind: 'equal-grid' };
|
||||
})();
|
||||
|
||||
// Tile that owns the big stage when layout is 'focus'. Resolved lazily by
|
||||
// callers below — kept here just so the speaker prop on CallStage/Fullscreen
|
||||
// stays consistent with the layout decision.
|
||||
const bigTile =
|
||||
stageLayout.kind === 'focus'
|
||||
? tiles.find((t) => t.id === stageLayout.bigTileId)
|
||||
: undefined;
|
||||
|
||||
const controls = (
|
||||
<CallControls
|
||||
@@ -369,13 +392,6 @@ export function InCallPanel({ conversation }: Props) {
|
||||
}));
|
||||
|
||||
if (callMode === 'fullscreen') {
|
||||
// Discord-style: without an explicit pin, fullscreen stays as a stable
|
||||
// equal-size grid. Focus only kicks in when the user pins a tile
|
||||
// (focusedId !== null). Auto-promoting the latest speaker caused the
|
||||
// whole layout to flip on every utterance, which is not what users
|
||||
// expect from a "cinema" view.
|
||||
const hasFocus = focusedId !== null;
|
||||
const effectiveSpeaker = hasFocus ? speaker : undefined;
|
||||
return (
|
||||
<>
|
||||
{micError && (
|
||||
@@ -391,7 +407,7 @@ export function InCallPanel({ conversation }: Props) {
|
||||
)}
|
||||
<FullscreenCall
|
||||
tiles={tiles}
|
||||
speaker={effectiveSpeaker}
|
||||
speaker={bigTile}
|
||||
remoteScreenShares={remoteScreenShares}
|
||||
conversationMembers={conversation.members}
|
||||
activeSpeakers={activeSpeakers}
|
||||
@@ -540,7 +556,7 @@ export function InCallPanel({ conversation }: Props) {
|
||||
|
||||
<CallStage
|
||||
tiles={tiles}
|
||||
speaker={speaker}
|
||||
speaker={bigTile}
|
||||
mode={callMode}
|
||||
activeSpeakers={activeSpeakers}
|
||||
e2ee={isE2EEActive}
|
||||
|
||||
Reference in New Issue
Block a user