From f612c1bb5047081337e1f0ed5fb888e4e3aa7660 Mon Sep 17 00:00:00 2001 From: byGalax Date: Tue, 12 May 2026 18:43:55 +0200 Subject: [PATCH] feat(call): introduce StageLayout discriminated union Co-Authored-By: Claude Sonnet 4.6 --- apps/desktop/src/components/InCallPanel.tsx | 46 ++++++++++++++------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/components/InCallPanel.tsx b/apps/desktop/src/components/InCallPanel.tsx index 94942a9..6db3f14 100644 --- a/apps/desktop/src/components/InCallPanel.tsx +++ b/apps/desktop/src/components/InCallPanel.tsx @@ -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 = ( {micError && ( @@ -391,7 +407,7 @@ export function InCallPanel({ conversation }: Props) { )}