use spotlight view for 1:1s when showControls=false and hide more buttons

This commit is contained in:
Matthew Hodgson
2026-05-11 22:37:13 +00:00
parent 8d7ceeec93
commit 89b76eff23
4 changed files with 113 additions and 66 deletions
+25 -9
View File
@@ -67,6 +67,7 @@ import {
} from "../reactions/useReactionsSender";
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
import { ReactionsOverlay } from "./ReactionsOverlay";
import { CallClock } from "./CallClock";
import { CallEventAudioRenderer } from "./CallEventAudioRenderer";
import {
debugTileLayout as debugTileLayoutSetting,
@@ -364,13 +365,17 @@ export const InCallView: FC<InCallViewProps> = ({
}
case HeaderStyle.None:
// Cosmetic header to fill out space while still affecting the bounds of
// the grid
header = (
<div
className={classNames(styles.header, styles.filler)}
ref={headerRef}
/>
);
// the grid. In kiosk mode (showControls=false) we drop it entirely so
// the layout fills the window edge-to-edge — mirrors the existing
// suppression of the footer in this combo.
if (showControls) {
header = (
<div
className={classNames(styles.header, styles.filler)}
ref={headerRef}
/>
);
}
break;
case HeaderStyle.Standard:
header = (
@@ -433,6 +438,12 @@ export const InCallView: FC<InCallViewProps> = ({
// need to remove them from the accessibility tree and block focus.
const contentObscured = reconnecting || earpieceMode;
// In kiosk mode (showControls=false), suppress the per-tile fullscreen and
// zoom (expand/collapse) buttons when we're already rendering a single tile
// edge-to-edge in spotlight-expanded layout — they'd just be visual noise.
const suppressSpotlightTileButtons =
!showControls && layout.type === "spotlight-expanded";
const Tile = useMemo(
() =>
function Tile({
@@ -469,7 +480,10 @@ export const InCallView: FC<InCallViewProps> = ({
ref={ref}
vm={model}
expanded={spotlightExpanded}
onToggleExpanded={onToggleExpanded}
onToggleExpanded={
suppressSpotlightTileButtons ? null : onToggleExpanded
}
hideFullscreen={suppressSpotlightTileButtons}
targetWidth={targetWidth}
targetHeight={targetHeight}
showIndicators={showSpotlightIndicatorsValue}
@@ -479,7 +493,7 @@ export const InCallView: FC<InCallViewProps> = ({
/>
);
},
[vm, openProfile, contentObscured],
[vm, openProfile, contentObscured, suppressSpotlightTileButtons],
);
const layouts = useMemo(() => {
@@ -628,6 +642,8 @@ export const InCallView: FC<InCallViewProps> = ({
{reconnectingToast}
{earpieceOverlay}
<ReactionsOverlay vm={vm} />
{/* Clock hidden for now (retained for re-enabling later) */}
{false && <CallClock />}
{footer}
{layout.type !== "pip" && (
<>
+5
View File
@@ -11,6 +11,11 @@
.reaction {
font-size: 32pt;
/* WPEWebKit on Linux has no system colour-emoji font and its emoji-font
matching heuristic does not always honour an `@font-face` registration
buried late in the family stack. Putting "Twemoji" first guarantees the
bundled COLR font is picked for the emoji codepoint. */
font-family: "Twemoji", var(--cpd-font-family-sans);
/* Reactions are "active" for 3 seconds (as per REACTION_ACTIVE_TIME_MS), give a bit more time for it to fade out. */
animation-duration: 4s;
animation-name: reaction-up;
+80 -56
View File
@@ -1176,66 +1176,90 @@ export function createCallViewModel$(
map((spotlight) => ({ type: "pip", spotlight })),
);
const layoutMediaByWindowMode$: Observable<LayoutMedia> = windowMode$.pipe(
switchMap((windowMode) => {
switch (windowMode) {
case "normal":
return gridMode$.pipe(
switchMap((gridMode) => {
switch (gridMode) {
case "grid":
return oneOnOneLayoutMedia$.pipe(
switchMap((oneOnOne) =>
oneOnOne === null ? gridLayoutMedia$ : of(oneOnOne),
),
);
case "spotlight":
return spotlightExpanded$.pipe(
switchMap((expanded) =>
expanded
? spotlightExpandedLayoutMedia$
: spotlightLandscapeLayoutMedia$,
),
);
}
}),
);
case "narrow":
return oneOnOneLayoutMedia$.pipe(
switchMap((oneOnOne) =>
oneOnOne === null
? combineLatest([grid$, spotlight$], (grid, spotlight) =>
grid.length > smallMobileCallThreshold ||
spotlight.some((vm) => vm.type === "screen share")
? spotlightPortraitLayoutMedia$
: gridLayoutMedia$,
).pipe(switchAll())
: // The expanded spotlight layout makes for a better one-on-one
// experience in narrow windows
spotlightExpandedLayoutMedia$,
),
);
case "flat":
return gridMode$.pipe(
switchMap((gridMode) => {
switch (gridMode) {
case "grid":
// Yes, grid mode actually gets you a "spotlight" layout in
// this window mode.
return spotlightLandscapeLayoutMedia$;
case "spotlight":
return spotlightExpandedLayoutMedia$;
}
}),
);
case "pip":
return pipLayoutMedia$;
}
}),
);
// In kiosk mode (showControls=false) with a single user-media tile and
// no screen share, render that tile edge-to-edge as a full-window
// spotlight rather than as a floating PiP inside a one-on-one layout.
// This is what we'd produce on a small form-factor 1:1 anyway.
const soloKioskLayoutMedia$: Observable<SpotlightExpandedLayoutMedia | null> =
getUrlParams().showControls
? of(null)
: combineLatest([userMedia$, screenShares$]).pipe(
map(([userMedia, screenShares]) => {
if (screenShares.length > 0) return null;
if (userMedia.length !== 1) return null;
return {
type: "spotlight-expanded" as const,
spotlight: [userMedia[0]],
};
}),
);
/**
* The media to be used to produce a layout.
*/
const layoutMedia$ = scope.behavior<LayoutMedia>(
windowMode$.pipe(
switchMap((windowMode) => {
switch (windowMode) {
case "normal":
return gridMode$.pipe(
switchMap((gridMode) => {
switch (gridMode) {
case "grid":
return oneOnOneLayoutMedia$.pipe(
switchMap((oneOnOne) =>
oneOnOne === null ? gridLayoutMedia$ : of(oneOnOne),
),
);
case "spotlight":
return spotlightExpanded$.pipe(
switchMap((expanded) =>
expanded
? spotlightExpandedLayoutMedia$
: spotlightLandscapeLayoutMedia$,
),
);
}
}),
);
case "narrow":
return oneOnOneLayoutMedia$.pipe(
switchMap((oneOnOne) =>
oneOnOne === null
? combineLatest([grid$, spotlight$], (grid, spotlight) =>
grid.length > smallMobileCallThreshold ||
spotlight.some((vm) => vm.type === "screen share")
? spotlightPortraitLayoutMedia$
: gridLayoutMedia$,
).pipe(switchAll())
: // The expanded spotlight layout makes for a better one-on-one
// experience in narrow windows
spotlightExpandedLayoutMedia$,
),
);
case "flat":
return gridMode$.pipe(
switchMap((gridMode) => {
switch (gridMode) {
case "grid":
// Yes, grid mode actually gets you a "spotlight" layout in
// this window mode.
return spotlightLandscapeLayoutMedia$;
case "spotlight":
return spotlightExpandedLayoutMedia$;
}
}),
);
case "pip":
return pipLayoutMedia$;
}
}),
soloKioskLayoutMedia$.pipe(
switchMap((solo) =>
solo !== null ? of<LayoutMedia>(solo) : layoutMediaByWindowMode$,
),
),
);
+3 -1
View File
@@ -384,6 +384,7 @@ interface Props {
focusable: boolean;
className?: string;
style?: ComponentProps<typeof animated.div>["style"];
hideFullscreen?: boolean;
}
export const SpotlightTile: FC<Props> = ({
@@ -397,6 +398,7 @@ export const SpotlightTile: FC<Props> = ({
focusable = true,
className,
style,
hideFullscreen = false,
}) => {
const { t } = useTranslation();
const [ourRef, root$] = useObservableRef<HTMLDivElement | null>(null);
@@ -520,7 +522,7 @@ export const SpotlightTile: FC<Props> = ({
{visibleMedia?.type === "screen share" && !visibleMedia.local && (
<ScreenShareVolumeButton vm={visibleMedia} />
)}
{platform === "desktop" && (
{platform === "desktop" && !hideFullscreen && (
<button
className={classNames(styles.expand)}
aria-label={"maximise"}