mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-05 19:59:20 +00:00
use spotlight view for 1:1s when showControls=false and hide more buttons
This commit is contained in:
@@ -67,6 +67,7 @@ import {
|
|||||||
} from "../reactions/useReactionsSender";
|
} from "../reactions/useReactionsSender";
|
||||||
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
||||||
import { ReactionsOverlay } from "./ReactionsOverlay";
|
import { ReactionsOverlay } from "./ReactionsOverlay";
|
||||||
|
import { CallClock } from "./CallClock";
|
||||||
import { CallEventAudioRenderer } from "./CallEventAudioRenderer";
|
import { CallEventAudioRenderer } from "./CallEventAudioRenderer";
|
||||||
import {
|
import {
|
||||||
debugTileLayout as debugTileLayoutSetting,
|
debugTileLayout as debugTileLayoutSetting,
|
||||||
@@ -364,13 +365,17 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
}
|
}
|
||||||
case HeaderStyle.None:
|
case HeaderStyle.None:
|
||||||
// Cosmetic header to fill out space while still affecting the bounds of
|
// Cosmetic header to fill out space while still affecting the bounds of
|
||||||
// the grid
|
// the grid. In kiosk mode (showControls=false) we drop it entirely so
|
||||||
header = (
|
// the layout fills the window edge-to-edge — mirrors the existing
|
||||||
<div
|
// suppression of the footer in this combo.
|
||||||
className={classNames(styles.header, styles.filler)}
|
if (showControls) {
|
||||||
ref={headerRef}
|
header = (
|
||||||
/>
|
<div
|
||||||
);
|
className={classNames(styles.header, styles.filler)}
|
||||||
|
ref={headerRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case HeaderStyle.Standard:
|
case HeaderStyle.Standard:
|
||||||
header = (
|
header = (
|
||||||
@@ -433,6 +438,12 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
// need to remove them from the accessibility tree and block focus.
|
// need to remove them from the accessibility tree and block focus.
|
||||||
const contentObscured = reconnecting || earpieceMode;
|
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(
|
const Tile = useMemo(
|
||||||
() =>
|
() =>
|
||||||
function Tile({
|
function Tile({
|
||||||
@@ -469,7 +480,10 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
vm={model}
|
vm={model}
|
||||||
expanded={spotlightExpanded}
|
expanded={spotlightExpanded}
|
||||||
onToggleExpanded={onToggleExpanded}
|
onToggleExpanded={
|
||||||
|
suppressSpotlightTileButtons ? null : onToggleExpanded
|
||||||
|
}
|
||||||
|
hideFullscreen={suppressSpotlightTileButtons}
|
||||||
targetWidth={targetWidth}
|
targetWidth={targetWidth}
|
||||||
targetHeight={targetHeight}
|
targetHeight={targetHeight}
|
||||||
showIndicators={showSpotlightIndicatorsValue}
|
showIndicators={showSpotlightIndicatorsValue}
|
||||||
@@ -479,7 +493,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[vm, openProfile, contentObscured],
|
[vm, openProfile, contentObscured, suppressSpotlightTileButtons],
|
||||||
);
|
);
|
||||||
|
|
||||||
const layouts = useMemo(() => {
|
const layouts = useMemo(() => {
|
||||||
@@ -628,6 +642,8 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
{reconnectingToast}
|
{reconnectingToast}
|
||||||
{earpieceOverlay}
|
{earpieceOverlay}
|
||||||
<ReactionsOverlay vm={vm} />
|
<ReactionsOverlay vm={vm} />
|
||||||
|
{/* Clock hidden for now (retained for re-enabling later) */}
|
||||||
|
{false && <CallClock />}
|
||||||
{footer}
|
{footer}
|
||||||
{layout.type !== "pip" && (
|
{layout.type !== "pip" && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
|
|
||||||
.reaction {
|
.reaction {
|
||||||
font-size: 32pt;
|
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. */
|
/* 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-duration: 4s;
|
||||||
animation-name: reaction-up;
|
animation-name: reaction-up;
|
||||||
|
|||||||
@@ -1176,66 +1176,90 @@ export function createCallViewModel$(
|
|||||||
map((spotlight) => ({ type: "pip", spotlight })),
|
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.
|
* The media to be used to produce a layout.
|
||||||
*/
|
*/
|
||||||
const layoutMedia$ = scope.behavior<LayoutMedia>(
|
const layoutMedia$ = scope.behavior<LayoutMedia>(
|
||||||
windowMode$.pipe(
|
soloKioskLayoutMedia$.pipe(
|
||||||
switchMap((windowMode) => {
|
switchMap((solo) =>
|
||||||
switch (windowMode) {
|
solo !== null ? of<LayoutMedia>(solo) : layoutMediaByWindowMode$,
|
||||||
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$;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ interface Props {
|
|||||||
focusable: boolean;
|
focusable: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: ComponentProps<typeof animated.div>["style"];
|
style?: ComponentProps<typeof animated.div>["style"];
|
||||||
|
hideFullscreen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpotlightTile: FC<Props> = ({
|
export const SpotlightTile: FC<Props> = ({
|
||||||
@@ -397,6 +398,7 @@ export const SpotlightTile: FC<Props> = ({
|
|||||||
focusable = true,
|
focusable = true,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
|
hideFullscreen = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [ourRef, root$] = useObservableRef<HTMLDivElement | null>(null);
|
const [ourRef, root$] = useObservableRef<HTMLDivElement | null>(null);
|
||||||
@@ -520,7 +522,7 @@ export const SpotlightTile: FC<Props> = ({
|
|||||||
{visibleMedia?.type === "screen share" && !visibleMedia.local && (
|
{visibleMedia?.type === "screen share" && !visibleMedia.local && (
|
||||||
<ScreenShareVolumeButton vm={visibleMedia} />
|
<ScreenShareVolumeButton vm={visibleMedia} />
|
||||||
)}
|
)}
|
||||||
{platform === "desktop" && (
|
{platform === "desktop" && !hideFullscreen && (
|
||||||
<button
|
<button
|
||||||
className={classNames(styles.expand)}
|
className={classNames(styles.expand)}
|
||||||
aria-label={"maximise"}
|
aria-label={"maximise"}
|
||||||
|
|||||||
Reference in New Issue
Block a user