mirror of
https://github.com/element-hq/element-call.git
synced 2026-05-25 18:34:17 +00:00
use spotlight view for 1:1s when showControls=false and hide more buttons
This commit is contained in:
+25
-9
@@ -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" && (
|
||||
<>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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$,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user