mirror of
https://github.com/vector-im/element-call.git
synced 2026-05-07 10:14:36 +00:00
WIP
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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$,
|
||||
|
||||
@@ -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$,
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -22,8 +22,6 @@ export class GridTileViewModel {
|
||||
public readonly media$: Behavior<
|
||||
UserMediaViewModel | RingingMediaViewModel
|
||||
>,
|
||||
public readonly showNameTag$: Behavior<boolean>,
|
||||
public readonly edgeToEdge$: Behavior<boolean>,
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user