Merge pull request #4054 from element-hq/johannes/pip-border

Add PiP border in 1on1 layouts
This commit is contained in:
Johannes Marbach
2026-06-23 12:38:24 +02:00
committed by GitHub
6 changed files with 36 additions and 0 deletions

View File

@@ -74,6 +74,7 @@ import {
useAppBarSubtitle,
} from "../AppBar.tsx";
import { useBehavior } from "../useBehavior.ts";
import { constant } from "../state/Behavior.ts";
import { Toast } from "../Toast.tsx";
import overlayStyles from "../Overlay.module.css";
import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.tsx";
@@ -433,6 +434,11 @@ export const InCallView: FC<InCallViewProps> = ({
const showSpeakingIndicators = useBehavior(vm.showSpeakingIndicators$);
const showNameTags = useBehavior(vm.showNameTags$);
const showRingingStatus = vm.ringingStatusLocation === "tile";
const showOutline = useBehavior(
model instanceof GridTileViewModel
? model.showOutline$
: constant(false),
);
return model instanceof GridTileViewModel ? (
<GridTile
@@ -446,6 +452,7 @@ export const InCallView: FC<InCallViewProps> = ({
showSpeakingIndicators={showSpeakingIndicators}
showNameTags={showNameTags}
showRingingStatus={showRingingStatus}
showOutline={showOutline}
focusable={!contentObscured}
/>
) : (

View File

@@ -155,6 +155,7 @@ import {
createRingingMedia,
type RingingMediaViewModel,
} from "../media/RingingMediaViewModel.ts";
import { type GridTileViewModel } from "../TileViewModel.ts";
const logger = rootLogger.getChild("[CallViewModel]");
//TODO
@@ -1482,6 +1483,7 @@ export function createCallViewModel$(
({ tiles: prevTiles }, [media, visibleTiles]) => {
let layout: Layout;
let newTiles: TileStore;
let pip: GridTileViewModel | undefined;
switch (media.type) {
case "grid":
case "spotlight-landscape":
@@ -1507,6 +1509,7 @@ export function createCallViewModel$(
landscapePipAlignment$,
prevTiles,
);
pip = layout.pip;
break;
case "one-on-one-portrait":
[layout, newTiles] = oneOnOnePortraitLayout(
@@ -1515,12 +1518,17 @@ export function createCallViewModel$(
portraitPipAlignment$,
prevTiles,
);
pip = layout.pip;
break;
case "pip":
[layout, newTiles] = pipLayout(media, prevTiles);
break;
}
for (const tile of newTiles.gridTiles) {
tile.setShowOutline(tile === pip);
}
return { layout, tiles: newTiles };
},
{ layout: null, tiles: TileStore.empty() },

View File

@@ -5,6 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { BehaviorSubject } from "rxjs";
import { type Behavior } from "./Behavior";
import { type MediaViewModel } from "./media/MediaViewModel";
import { type RingingMediaViewModel } from "./media/RingingMediaViewModel";
@@ -17,12 +19,18 @@ function createId(): string {
export class GridTileViewModel {
public readonly id = createId();
private readonly _showOutline$ = new BehaviorSubject(false);
public readonly showOutline$: Behavior<boolean> = this._showOutline$;
public constructor(
public readonly media$: Behavior<
UserMediaViewModel | RingingMediaViewModel
>,
) {}
public setShowOutline(value: boolean): void {
this._showOutline$.next(value);
}
}
export class SpotlightTileViewModel {

View File

@@ -66,6 +66,11 @@ borders don't support gradients */
opacity: 1;
}
.tile.outline {
outline: var(--cpd-border-width-1) solid
var(--cpd-color-border-interactive-secondary);
}
@media (hover: hover) {
.tile:hover {
outline: var(--cpd-border-width-2) solid

View File

@@ -78,6 +78,7 @@ test("GridTile is accessible", async () => {
showSpeakingIndicators
showNameTags
showRingingStatus
showOutline
focusable
/>
</ReactionsSenderProvider>,
@@ -110,6 +111,7 @@ test("GridTile displays ringing media", async () => {
showSpeakingIndicators
showNameTags
showRingingStatus
showOutline
focusable
/>
</ReactionsSenderProvider>,

View File

@@ -398,6 +398,7 @@ interface GridTileProps {
showSpeakingIndicators: boolean;
showNameTags: boolean;
showRingingStatus: boolean;
showOutline: boolean;
focusable: boolean;
}
@@ -406,7 +407,9 @@ export const GridTile: FC<GridTileProps> = ({
vm,
showSpeakingIndicators,
showRingingStatus,
showOutline,
onOpenProfile,
className,
...props
}) => {
const ourRef = useRef<HTMLDivElement | null>(null);
@@ -423,6 +426,7 @@ export const GridTile: FC<GridTileProps> = ({
displayName={displayName}
mxcAvatarUrl={mxcAvatarUrl}
showStatus={showRingingStatus}
className={classNames(className, { [styles.outline]: showOutline })}
{...props}
/>
);
@@ -435,6 +439,7 @@ export const GridTile: FC<GridTileProps> = ({
onOpenProfile={onOpenProfile}
displayName={displayName}
mxcAvatarUrl={mxcAvatarUrl}
className={classNames(className, { [styles.outline]: showOutline })}
{...props}
/>
);
@@ -446,6 +451,7 @@ export const GridTile: FC<GridTileProps> = ({
showSpeakingIndicators={showSpeakingIndicators}
displayName={displayName}
mxcAvatarUrl={mxcAvatarUrl}
className={classNames(className, { [styles.outline]: showOutline })}
{...props}
/>
);