This commit is contained in:
Robin
2026-05-06 17:41:00 +02:00
parent 4a853618f4
commit 1c8b20e8ad
11 changed files with 78 additions and 77 deletions

View File

@@ -1,5 +1,21 @@
.bar {
flex-shrink: 0;
position: relative;
}
/* Pseudo-element for the gradient background */
.bar::before {
content: "";
position: absolute;
inset-inline: 0;
/* Extend the gradient beyond the bottom of the header for readability */
inset-block: -24px;
z-index: var(--call-view-header-footer-layer);
background: linear-gradient(
0deg,
rgba(0, 0, 0, 0) 0%,
var(--cpd-color-bg-canvas-default) 100%
);
}
.bar > header {

View File

@@ -31,7 +31,7 @@ export const makeOneOnOnePortraitLayout: CallLayout<
<div ref={ref} className={styles.layer}>
<Slot
className={styles.spotlight}
id={model.spotlight.id}
id="spotlight"
model={model.spotlight}
/>
</div>

View File

@@ -240,6 +240,7 @@ export const InCallView: FC<InCallViewProps> = ({
const edgeToEdge = useBehavior(vm.edgeToEdge$);
const tileStoreGeneration = useBehavior(vm.tileStoreGeneration$);
const [debugTileLayout] = useSetting(debugTileLayoutSetting);
const showNameTags = useBehavior(vm.showNameTags$);
const gridMode = useBehavior(vm.gridMode$);
const showHeader = useBehavior(vm.showHeader$);
const showFooter = useBehavior(vm.showFooter$);
@@ -432,12 +433,11 @@ export const InCallView: FC<InCallViewProps> = ({
}: TileProps<TileViewModel, HTMLDivElement>): ReactNode {
const spotlightExpanded = useBehavior(vm.spotlightExpanded$);
const onToggleExpanded = useBehavior(vm.toggleSpotlightExpanded$);
const showSpeakingIndicatorsValue = useBehavior(
vm.showSpeakingIndicators$,
);
const showSpotlightIndicatorsValue = useBehavior(
const showSpotlightIndicators = useBehavior(
vm.showSpotlightIndicators$,
);
const showSpeakingIndicators = useBehavior(vm.showSpeakingIndicators$);
const showNameTags = useBehavior(vm.showNameTags$);
return model instanceof GridTileViewModel ? (
<GridTile
@@ -448,7 +448,8 @@ export const InCallView: FC<InCallViewProps> = ({
targetHeight={targetHeight}
className={classNames(className, styles.tile)}
style={style}
showSpeakingIndicators={showSpeakingIndicatorsValue}
showSpeakingIndicators={showSpeakingIndicators}
showNameTags={showNameTags}
focusable={!contentObscured}
/>
) : (
@@ -459,7 +460,8 @@ export const InCallView: FC<InCallViewProps> = ({
onToggleExpanded={onToggleExpanded}
targetWidth={targetWidth}
targetHeight={targetHeight}
showIndicators={showSpotlightIndicatorsValue}
showIndicators={showSpotlightIndicators}
showNameTags={showNameTags}
focusable={!contentObscured}
className={classNames(className, styles.tile)}
style={style}
@@ -492,6 +494,7 @@ export const InCallView: FC<InCallViewProps> = ({
targetWidth={gridBounds.width}
targetHeight={gridBounds.height}
showIndicators={false}
showNameTags={showNameTags}
focusable={!contentObscured}
aria-hidden={contentObscured}
/>
@@ -505,10 +508,8 @@ export const InCallView: FC<InCallViewProps> = ({
className={styles.fixedGrid}
style={{
insetBlockStart:
edgeToEdge || headerBounds.height === 0
? bounds.top
: headerBounds.bottom,
height: gridBounds.height,
edgeToEdge || headerBounds.height === 0 ? 0 : headerBounds.bottom,
height: edgeToEdge ? "100%" : gridBounds.height,
}}
model={layout}
Layout={layers.fixed}

View File

@@ -341,6 +341,7 @@ export interface CallViewModel {
tileStoreGeneration$: Behavior<number>;
showSpotlightIndicators$: Behavior<boolean>;
showSpeakingIndicators$: Behavior<boolean>;
showNameTags$: Behavior<boolean>;
spotlightExpanded$: Behavior<boolean>;
toggleSpotlightExpanded$: Behavior<(() => void) | null>;
gridMode$: Behavior<GridMode>;
@@ -1273,6 +1274,31 @@ export function createCallViewModel$(
),
);
const showNameTags$ = scope.behavior<boolean>(
layoutMedia$.pipe(
switchMap((l) =>
l.type === "pip" || l.type === "one-on-one-portrait"
? matrixRoomMembers$.pipe(
map(
(members) =>
// Hide name tags by default in these layouts. For safety we
// still need to show them in case it wouldn't be clear who
// the spotlight media belongs to.
// TODO: Respect io.element.functional_members
// TODO: Only hide name tags in DMs, not group chats that just
// happen to have only 2 users
members.size > 2 ||
!(l.type === "pip"
? l.spotlight.length <= 1 &&
members.has(l.spotlight[0].userId)
: members.has(l.spotlight.userId)),
),
)
: of(true),
),
),
);
const toggleSpotlightExpanded$ = scope.behavior<(() => void) | null>(
windowMode$.pipe(
switchMap((mode) =>
@@ -1716,6 +1742,7 @@ export function createCallViewModel$(
tileStoreGeneration$: tileStoreGeneration$,
showSpotlightIndicators$: showSpotlightIndicators$,
showSpeakingIndicators$: showSpeakingIndicators$,
showNameTags$,
showHeader$: showHeader$,
showFooter$: showFooter$,
edgeToEdge$,

View File

@@ -25,16 +25,16 @@ export function oneOnOnePortraitLayout(
pipAlignment$: BehaviorSubject<Alignment>,
prevTiles: TileStore,
): [OneOnOnePortraitLayout, TileStore] {
const update = prevTiles.from(media.pip === undefined ? 1 : 2);
if (media.pip !== undefined) update.registerGridTile(media.pip, false);
update.registerGridTile(media.spotlight, false, true);
const update = prevTiles.from(media.pip === undefined ? 0 : 1);
update.registerSpotlight([media.spotlight], true);
if (media.pip !== undefined) update.registerGridTile(media.pip);
const tiles = update.build();
return [
{
type: media.type,
foreground: "fixed",
spotlight: tiles.gridTilesByMedia.get(media.spotlight)!,
spotlight: tiles.spotlightTile!,
pip: media.pip && tiles.gridTilesByMedia.get(media.pip),
pipSize$,
pipAlignment$,

View File

@@ -59,37 +59,11 @@ class GridTileData {
this.media$.next(value);
}
private readonly showNameTag$: BehaviorSubject<boolean>;
public get showNameTag(): boolean {
return this.showNameTag$.value;
}
public set showNameTag(value: boolean) {
this.showNameTag$.next(value);
}
private readonly edgeToEdge$: BehaviorSubject<boolean>;
public get edgeToEdge(): boolean {
return this.edgeToEdge$.value;
}
public set edgeToEdge(value: boolean) {
this.edgeToEdge$.next(value);
}
public readonly vm: GridTileViewModel;
public constructor(
media: UserMediaViewModel | RingingMediaViewModel,
showNameTag: boolean,
edgeToEdge: boolean,
) {
public constructor(media: UserMediaViewModel | RingingMediaViewModel) {
this.media$ = new BehaviorSubject(media);
this.showNameTag$ = new BehaviorSubject(showNameTag);
this.edgeToEdge$ = new BehaviorSubject(edgeToEdge);
this.vm = new GridTileViewModel(
this.media$,
this.showNameTag$,
this.edgeToEdge$,
);
this.vm = new GridTileViewModel(this.media$);
}
}
@@ -209,8 +183,6 @@ export class TileStoreBuilder {
*/
public registerGridTile(
media: UserMediaViewModel | RingingMediaViewModel,
showNameTag = true,
edgeToEdge = false,
): void {
if (DEBUG_ENABLED)
logger.debug(
@@ -248,8 +220,6 @@ export class TileStoreBuilder {
this.stationaryGridEntries[prevIndex] = entry;
// Do the media swap
entry.media = media;
entry.showNameTag = showNameTag;
entry.edgeToEdge = edgeToEdge;
this.prevGridByMedia.delete(this.spotlight.media[0]);
this.prevGridByMedia.set(media, prev);
} else {
@@ -258,7 +228,7 @@ export class TileStoreBuilder {
(nowVisible
? this.visibleGridEntries
: this.invisibleGridEntries
).push(new GridTileData(media, showNameTag, edgeToEdge));
).push(new GridTileData(media));
}
this.numGridEntries++;
@@ -274,12 +244,10 @@ export class TileStoreBuilder {
(this.numGridEntries < this.visibleTiles
? this.visibleGridEntries
: this.invisibleGridEntries
).push(new GridTileData(media, showNameTag, edgeToEdge));
).push(new GridTileData(media));
} else {
// Reuse the existing tile
const [entry, prevIndex] = prev;
entry.showNameTag = showNameTag;
entry.edgeToEdge = edgeToEdge;
const previouslyVisible = prevIndex < this.visibleTiles;
const nowVisible = this.numGridEntries < this.visibleTiles;
// If it doesn't need to move between the visible/invisible sections of
@@ -302,7 +270,7 @@ export class TileStoreBuilder {
* method will more eagerly try to reuse an existing tile, replacing its
* media, than registerGridTile would.
*/
public registerPipTile(media: UserMediaViewModel, showNameTag = true): void {
public registerPipTile(media: UserMediaViewModel): void {
if (DEBUG_ENABLED)
logger.debug(
`[TileStore, ${this.generation}] register PiP tile: ${media.displayName$.value}`,
@@ -314,12 +282,10 @@ export class TileStoreBuilder {
this.stationaryGridEntries[0] = entry;
// Do the media swap
entry.media = media;
entry.showNameTag = showNameTag;
entry.edgeToEdge = false;
this.prevGridByMedia.delete(entry.media);
this.prevGridByMedia.set(media, [entry, 0]);
} else {
this.visibleGridEntries.push(new GridTileData(media, showNameTag, false));
this.visibleGridEntries.push(new GridTileData(media));
}
this.numGridEntries++;

View File

@@ -22,8 +22,6 @@ export class GridTileViewModel {
public readonly media$: Behavior<
UserMediaViewModel | RingingMediaViewModel
>,
public readonly showNameTag$: Behavior<boolean>,
public readonly edgeToEdge$: Behavior<boolean>,
) {}
}

View File

@@ -123,7 +123,7 @@ export interface OneOnOneLandscapeLayout {
export interface OneOnOnePortraitLayout {
type: "one-on-one-portrait";
foreground: "fixed";
spotlight: GridTileViewModel;
spotlight: SpotlightTileViewModel;
pip?: GridTileViewModel;
pipSize$: Behavior<"sm" | "lg">;
pipAlignment$: BehaviorSubject<Alignment>;

View File

@@ -62,7 +62,7 @@ interface TileProps {
targetHeight: number;
displayName: string;
mxcAvatarUrl: string | undefined;
showNameTag: boolean;
showNameTags: boolean;
focusable: boolean;
}
@@ -399,6 +399,7 @@ interface GridTileProps {
className?: string;
style?: ComponentProps<typeof animated.div>["style"];
showSpeakingIndicators: boolean;
showNameTags: boolean;
focusable: boolean;
}
@@ -407,7 +408,6 @@ export const GridTile: FC<GridTileProps> = ({
vm,
showSpeakingIndicators,
onOpenProfile,
className,
...props
}) => {
const ourRef = useRef<HTMLDivElement | null>(null);
@@ -415,20 +415,14 @@ export const GridTile: FC<GridTileProps> = ({
const media = useBehavior(vm.media$);
const displayName = useBehavior(media.displayName$);
const mxcAvatarUrl = useBehavior(media.mxcAvatarUrl$);
const showNameTag = useBehavior(vm.showNameTag$);
const edgeToEdge = useBehavior(vm.edgeToEdge$);
const classes = classNames(className, { [styles.edgeToEdge]: edgeToEdge });
if (media.type === "ringing") {
return (
<RingingMediaTile
ref={ref}
className={classes}
vm={media}
displayName={displayName}
mxcAvatarUrl={mxcAvatarUrl}
showNameTag={showNameTag}
{...props}
/>
);
@@ -436,13 +430,11 @@ export const GridTile: FC<GridTileProps> = ({
return (
<LocalUserMediaTile
ref={ref}
className={classes}
vm={media}
showSpeakingIndicators={showSpeakingIndicators}
onOpenProfile={onOpenProfile}
displayName={displayName}
mxcAvatarUrl={mxcAvatarUrl}
showNameTag={showNameTag}
{...props}
/>
);
@@ -450,12 +442,10 @@ export const GridTile: FC<GridTileProps> = ({
return (
<RemoteUserMediaTile
ref={ref}
className={classes}
vm={media}
showSpeakingIndicators={showSpeakingIndicators}
displayName={displayName}
mxcAvatarUrl={mxcAvatarUrl}
showNameTag={showNameTag}
{...props}
/>
);

View File

@@ -44,11 +44,7 @@ interface Props extends ComponentProps<typeof animated.div> {
videoEnabled: boolean;
unencryptedWarning: boolean;
status?: { text: string; Icon: ComponentType<SVGAttributes<SVGElement>> };
/**
* Whether to show the participant's name tag.
* @default true
*/
showNameTag?: boolean;
showNameTags: boolean;
nameTagLeadingIcon?: ReactNode;
displayName: string;
mxcAvatarUrl: string | undefined;
@@ -77,7 +73,7 @@ export const MediaView: FC<Props> = ({
userId,
videoEnabled,
unencryptedWarning,
showNameTag = true,
showNameTags,
nameTagLeadingIcon,
displayName,
mxcAvatarUrl,
@@ -207,7 +203,7 @@ export const MediaView: FC<Props> = ({
</Text>
</div>
)*/}
{showNameTag ? (
{showNameTags && targetWidth >= 100 ? (
<div className={styles.nameTag}>
{nameTagLeadingIcon}
<Text

View File

@@ -66,6 +66,7 @@ interface SpotlightItemBaseProps {
userId: string;
displayName: string;
mxcAvatarUrl: string | undefined;
showNameTags: boolean;
focusable: boolean;
"aria-hidden"?: boolean;
}
@@ -244,6 +245,7 @@ interface SpotlightItemProps {
* The height this tile will have once its animations have settled.
*/
targetHeight: number;
showNameTags: boolean;
focusable: boolean;
intersectionObserver$: Observable<IntersectionObserver>;
/**
@@ -258,6 +260,7 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
vm,
targetWidth,
targetHeight,
showNameTags,
focusable,
intersectionObserver$,
snap,
@@ -293,6 +296,7 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
userId: vm.userId,
displayName,
mxcAvatarUrl,
showNameTags,
focusable,
"aria-hidden": ariaHidden,
};
@@ -381,6 +385,7 @@ interface Props {
targetWidth: number;
targetHeight: number;
showIndicators: boolean;
showNameTags: boolean;
focusable: boolean;
className?: string;
style?: ComponentProps<typeof animated.div>["style"];
@@ -394,6 +399,7 @@ export const SpotlightTile: FC<Props> = ({
targetWidth,
targetHeight,
showIndicators,
showNameTags,
focusable = true,
className,
style,
@@ -504,6 +510,7 @@ export const SpotlightTile: FC<Props> = ({
vm={vm}
targetWidth={targetWidth}
targetHeight={targetHeight}
showNameTags={showNameTags}
focusable={focusable}
intersectionObserver$={intersectionObserver$}
// This is how we get the container to scroll to the right media