Rename one-on-one layouts to be orientation-agnostic

This commit is contained in:
Robin
2026-07-14 16:47:03 +02:00
parent 062e0a00fb
commit 0b21cff020
11 changed files with 85 additions and 85 deletions

View File

@@ -10,28 +10,28 @@ import { type ReactNode, useCallback, useMemo } from "react";
import { useObservableEagerState } from "observable-hooks";
import classNames from "classnames";
import { type OneOnOneLandscapeLayout as OneOnOneLandscapeLayoutModel } from "../state/layout-types.ts";
import { type OneOnOneDesktopLayout as OneOnOneDesktopLayoutModel } from "../state/layout-types.ts";
import { type CallLayout, arrangeTiles } from "./CallLayout";
import styles from "./OneOnOneLandscapeLayout.module.css";
import styles from "./OneOnOneDesktopLayout.module.css";
import { type DragCallback, useUpdateLayout } from "./Grid";
import { useBehavior } from "../useBehavior";
/**
* An implementation of the "one-on-one" layout for landscape screens, in which
* An implementation of the "one-on-one" layout for desktop platforms, in which
* the remote participant is shown at maximum size, overlaid by a small view of
* the local participant.
*/
export const makeOneOnOneLandscapeLayout: CallLayout<
OneOnOneLandscapeLayoutModel
export const makeOneOnOneDesktopLayout: CallLayout<
OneOnOneDesktopLayoutModel
> = ({ minBounds$ }) => ({
foreground: "fixed",
fixed: function OneOnOneLandscapeLayoutFixed({ ref }): ReactNode {
fixed: function OneOnOneDesktopLayoutFixed({ ref }): ReactNode {
useUpdateLayout();
return <div ref={ref} />;
},
scrolling: function OneOnOneLandscapeLayoutScrolling({
scrolling: function OneOnOneDesktopLayoutScrolling({
ref,
model,
Slot,

View File

@@ -9,23 +9,23 @@ Please see LICENSE in the repository root for full details.
import { type ReactNode, useCallback } from "react";
import classNames from "classnames";
import { type OneOnOnePortraitLayout as OneOnOnePortraitLayoutModel } from "../state/layout-types.ts";
import { type OneOnOneMobileLayout as OneOnOneMobileLayoutModel } from "../state/layout-types.ts";
import { type CallLayout } from "./CallLayout";
import styles from "./OneOnOnePortraitLayout.module.css";
import styles from "./OneOnOneMobileLayout.module.css";
import { type DragCallback, useUpdateLayout } from "./Grid";
import { useBehavior } from "../useBehavior";
/**
* An implementation of the "one-on-one" layout for portrait screens, in which
* An implementation of the "one-on-one" layout for mobile platforms, in which
* the remote participant is shown at maximum size, overlaid by a small view of
* the local participant.
*/
export const makeOneOnOnePortraitLayout: CallLayout<
OneOnOnePortraitLayoutModel
export const makeOneOnOneMobileLayout: CallLayout<
OneOnOneMobileLayoutModel
> = () => ({
foreground: "scrolling",
fixed: function OneOnOnePortraitLayoutFixed({ ref, model, Slot }): ReactNode {
fixed: function OneOnOneMobileLayoutFixed({ ref, model, Slot }): ReactNode {
useUpdateLayout();
return (
<div ref={ref} className={styles.layer}>
@@ -38,7 +38,7 @@ export const makeOneOnOnePortraitLayout: CallLayout<
);
},
scrolling: function OneOnOnePortraitLayoutScrolling({
scrolling: function OneOnOneMobileLayoutScrolling({
ref,
model,
Slot,

View File

@@ -49,8 +49,8 @@ import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
import { E2eeType } from "../e2ee/e2eeType";
import { makeGridLayout } from "../grid/GridLayout";
import { type CallLayoutOutputs } from "../grid/CallLayout";
import { makeOneOnOneLandscapeLayout } from "../grid/OneOnOneLandscapeLayout";
import { makeOneOnOnePortraitLayout } from "../grid/OneOnOnePortraitLayout";
import { makeOneOnOneDesktopLayout } from "../grid/OneOnOneDesktopLayout";
import { makeOneOnOneMobileLayout } from "../grid/OneOnOneMobileLayout";
import { makeSpotlightExpandedLayout } from "../grid/SpotlightExpandedLayout";
import { makeSpotlightLandscapeLayout } from "../grid/SpotlightLandscapeLayout";
import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout";
@@ -483,8 +483,8 @@ export const InCallView: FC<InCallViewProps> = ({
"spotlight-landscape": makeSpotlightLandscapeLayout(inputs),
"spotlight-portrait": makeSpotlightPortraitLayout(inputs),
"spotlight-expanded": makeSpotlightExpandedLayout(inputs),
"one-on-one-landscape": makeOneOnOneLandscapeLayout(inputs),
"one-on-one-portrait": makeOneOnOnePortraitLayout(inputs),
"one-on-one-desktop": makeOneOnOneDesktopLayout(inputs),
"one-on-one-mobile": makeOneOnOneMobileLayout(inputs),
};
}, [gridBoundsObservable$]);

View File

@@ -87,14 +87,14 @@ exports[`InCallView > rendering > renders 1`] = `
class="_scrollingGrid_4e7ff8 _grid_b0d1cd"
>
<div
class="_layer_b00c5f"
class="_layer_185815"
>
<div
class="_container_b00c5f _slot_b0d1cd"
class="_container_185815 _slot_b0d1cd"
data-id="1"
>
<div
class="_slot_b00c5f _local_b00c5f _slot_b0d1cd"
class="_slot_185815 _local_185815 _slot_b0d1cd"
data-block-alignment="start"
data-id="0"
data-inline-alignment="end"

View File

@@ -144,13 +144,13 @@ export interface SpotlightExpandedLayoutSummary {
}
export interface OneOnOneLandscapeLayoutSummary {
type: "one-on-one-landscape";
type: "one-on-one-desktop";
spotlight: string;
pip: string;
}
export interface OneOnOnePortraitLayoutSummary {
type: "one-on-one-portrait";
type: "one-on-one-mobile";
spotlight: string[];
pip?: string;
pipSize: "sm" | "lg";
@@ -205,7 +205,7 @@ function summarizeLayout$(l$: Observable<Layout>): Observable<LayoutSummary> {
pip: pip?.id,
}),
);
case "one-on-one-landscape":
case "one-on-one-desktop":
return combineLatest(
[l.spotlight.media$, l.pip.media$],
(spotlight, pip) => ({
@@ -214,7 +214,7 @@ function summarizeLayout$(l$: Observable<Layout>): Observable<LayoutSummary> {
pip: pip.id,
}),
);
case "one-on-one-portrait":
case "one-on-one-mobile":
return combineLatest(
[
l.spotlight.media$,
@@ -437,7 +437,7 @@ describe.each([
expectedLayoutMarbles,
{
a: {
type: "one-on-one-landscape",
type: "one-on-one-desktop",
pip: `${localId}:0`,
spotlight: `${aliceId}:0`,
},
@@ -453,7 +453,7 @@ describe.each([
});
});
test("one-on-one portrait layout shows local tile when video is enabled", () => {
test("one-on-one mobile layout shows local tile when video is enabled", () => {
withTestScheduler(({ behavior, schedule, expectObservable }) => {
// Local participant enables their video, then disables it
const videoInputMarbles = " ny--n";
@@ -480,19 +480,19 @@ describe.each([
expectedLayoutMarbles,
{
a: {
type: "one-on-one-portrait",
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: undefined,
pipSize: "lg",
},
b: {
type: "one-on-one-portrait",
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: `${localId}:0`,
pipSize: "lg",
},
c: {
type: "one-on-one-portrait",
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: `${localId}:0`,
pipSize: "sm",
@@ -504,7 +504,7 @@ describe.each([
});
});
test("one-on-one portrait layout shows name tags in room with 3 members", () => {
test("one-on-one mobile layout shows name tags in room with 3 members", () => {
withTestScheduler(({ expectObservable }) => {
withCallViewModel(
{
@@ -512,13 +512,13 @@ describe.each([
// Both Alice and Bob are with us in the room
roomMembers: [local, alice, bob],
rtcMembers$: constant([localRtcMember, aliceRtcMember]),
windowSize$: constant({ width: 380, height: 700 }), // Mobile phone in portrait
windowSize$: constant({ width: 380, height: 700 }), // Mobile phone
},
(vm) => {
// Uses one-on-one portrait layout
// Uses one-on-one mobile layout
expectObservable(summarizeLayout$(vm.layout$)).toBe("a", {
a: {
type: "one-on-one-portrait",
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: undefined,
pipSize: "lg",
@@ -706,14 +706,14 @@ describe.each([
{
a: {
// This is the expected one-on-one layout for a narrow window
type: "one-on-one-portrait",
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: undefined,
pipSize: "lg",
},
b: {
// In a larger window, expect the normal one-on-one layout
type: "one-on-one-landscape",
// In a larger window, expect the one-on-one desktop layout
type: "one-on-one-desktop",
pip: `${localId}:0`,
spotlight: `${aliceId}:0`,
},
@@ -1187,7 +1187,7 @@ describe.each([
grid: [`${localId}:0`],
},
b: {
type: "one-on-one-landscape",
type: "one-on-one-desktop",
pip: `${localId}:0`,
spotlight: `${aliceId}:0`,
},
@@ -1230,7 +1230,7 @@ describe.each([
grid: [`${localId}:0`],
},
b: {
type: "one-on-one-landscape",
type: "one-on-one-desktop",
pip: `${localId}:0`,
spotlight: `${aliceId}:0`,
},
@@ -1240,7 +1240,7 @@ describe.each([
grid: [`${localId}:0`, `${aliceId}:0`, `${daveId}:0`],
},
d: {
type: "one-on-one-landscape",
type: "one-on-one-desktop",
pip: `${localId}:0`,
spotlight: `${daveId}:0`,
},
@@ -1464,7 +1464,7 @@ describe.each([
// ringing the entire time (even once timed out)
expectObservable(summarizeLayout$(vm.layout$)).toBe("a", {
a: {
type: "one-on-one-landscape",
type: "one-on-one-desktop",
spotlight: `${localId}:0`,
pip: `ringing:${aliceUserId}`,
},
@@ -1510,12 +1510,12 @@ describe.each([
// ringing the entire time
expectObservable(summarizeLayout$(vm.layout$)).toBe("a 20ms b", {
a: {
type: "one-on-one-landscape",
type: "one-on-one-desktop",
spotlight: `${localId}:0`,
pip: `ringing:${aliceUserId}`,
},
b: {
type: "one-on-one-landscape",
type: "one-on-one-desktop",
spotlight: `${aliceId}:0`,
pip: `${localId}:0`,
},

View File

@@ -70,8 +70,8 @@ import { setPipEnabled$ } from "../../controls";
import { TileStore } from "../TileStore";
import { gridLikeLayout } from "../GridLikeLayout";
import { spotlightExpandedLayout } from "../SpotlightExpandedLayout";
import { oneOnOneLandscapeLayout } from "../OneOnOneLandscapeLayout";
import { oneOnOnePortraitLayout } from "../OneOnOnePortraitLayout";
import { oneOnOneDesktopLayout } from "../OneOnOneDesktopLayout";
import { oneOnOneMobileLayout } from "../OneOnOneMobileLayout";
import { pipLayout } from "../PipLayout";
import { type EncryptionSystem } from "../../e2ee/sharedKeyManagement";
import {
@@ -93,8 +93,8 @@ import {
type GridLayoutMedia,
type Layout,
type LayoutMedia,
type OneOnOneLandscapeLayoutMedia,
type OneOnOnePortraitLayoutMedia,
type OneOnOneDesktopLayoutMedia,
type OneOnOneMobileLayoutMedia,
type SpotlightExpandedLayoutMedia,
type SpotlightLandscapeLayoutMedia,
type SpotlightPortraitLayoutMedia,
@@ -1145,19 +1145,19 @@ export function createCallViewModel$(
}),
);
const oneOnOneLandscapeLayoutMedia$: Observable<OneOnOneLandscapeLayoutMedia | null> =
const oneOnOneDesktopLayoutMedia$: Observable<OneOnOneDesktopLayoutMedia | null> =
oneOnOneLayoutMedia$.pipe(
map((media) => {
if (media === null) return null;
return media.remote.type === "ringing"
? {
type: "one-on-one-landscape" as const,
type: "one-on-one-desktop" as const,
edgeToEdge: false,
spotlight: media.local,
pip: media.remote,
}
: {
type: "one-on-one-landscape" as const,
type: "one-on-one-desktop" as const,
edgeToEdge: false,
spotlight: media.remote,
pip: media.local,
@@ -1165,13 +1165,13 @@ export function createCallViewModel$(
}),
);
const oneOnOnePortraitLayoutMedia$: Observable<OneOnOnePortraitLayoutMedia | null> =
const oneOnOneMobileLayoutMedia$: Observable<OneOnOneMobileLayoutMedia | null> =
oneOnOneLayoutMedia$.pipe(
switchMap((media) => {
if (media === null) return of(null);
return media.local.videoEnabled$.pipe(
map((videoEnabled) => ({
type: "one-on-one-portrait" as const,
type: "one-on-one-mobile" as const,
edgeToEdge: true as const,
spotlight: media.remote,
pip: videoEnabled ? media.local : undefined,
@@ -1227,7 +1227,7 @@ export function createCallViewModel$(
switchMap((gridMode) => {
switch (gridMode) {
case "grid":
return oneOnOneLandscapeLayoutMedia$.pipe(
return oneOnOneDesktopLayoutMedia$.pipe(
switchMap((oneOnOne) =>
oneOnOne === null ? gridLayoutMedia$ : of(oneOnOne),
),
@@ -1244,7 +1244,7 @@ export function createCallViewModel$(
}),
);
case "narrow":
return oneOnOnePortraitLayoutMedia$.pipe(
return oneOnOneMobileLayoutMedia$.pipe(
switchMap((oneOnOne) =>
oneOnOne === null
? combineLatest([grid$, spotlight$], (grid, spotlight) =>
@@ -1295,8 +1295,8 @@ export function createCallViewModel$(
// indicators. And in one-on-one layout there's no question as to who is
// speaking.
case "spotlight-expanded":
case "one-on-one-landscape":
case "one-on-one-portrait":
case "one-on-one-desktop":
case "one-on-one-mobile":
return false;
default:
return true;
@@ -1308,7 +1308,7 @@ export function createCallViewModel$(
const showNameTags$ = scope.behavior<boolean>(
layoutMedia$.pipe(
switchMap((l) =>
l.type === "pip" || l.type === "one-on-one-portrait"
l.type === "pip" || l.type === "one-on-one-mobile"
? matrixRoomMembers$.pipe(
map(
(members) =>
@@ -1513,16 +1513,16 @@ export function createCallViewModel$(
prevTiles,
);
break;
case "one-on-one-landscape":
[layout, newTiles] = oneOnOneLandscapeLayout(
case "one-on-one-desktop":
[layout, newTiles] = oneOnOneDesktopLayout(
media,
landscapePipAlignment$,
prevTiles,
);
pip = layout.pip;
break;
case "one-on-one-portrait":
[layout, newTiles] = oneOnOnePortraitLayout(
case "one-on-one-mobile":
[layout, newTiles] = oneOnOneMobileLayout(
media,
portraitPipSize$,
portraitPipAlignment$,

View File

@@ -10,19 +10,19 @@ import { type BehaviorSubject } from "rxjs";
import {
type Alignment,
type OneOnOneLandscapeLayout,
type OneOnOneLandscapeLayoutMedia,
type OneOnOneDesktopLayout,
type OneOnOneDesktopLayoutMedia,
} from "./layout-types";
import { type TileStore } from "./TileStore";
/**
* Produces a one-on-one landscape layout with the given media.
* Produces a one-on-one desktop layout with the given media.
*/
export function oneOnOneLandscapeLayout(
media: OneOnOneLandscapeLayoutMedia,
export function oneOnOneDesktopLayout(
media: OneOnOneDesktopLayoutMedia,
pipAlignment$: BehaviorSubject<Alignment>,
prevTiles: TileStore,
): [OneOnOneLandscapeLayout, TileStore] {
): [OneOnOneDesktopLayout, TileStore] {
const update = prevTiles.from(2);
update.registerGridTile(media.pip);
update.registerGridTile(media.spotlight);

View File

@@ -10,21 +10,21 @@ import { type BehaviorSubject } from "rxjs";
import {
type Alignment,
type OneOnOnePortraitLayout,
type OneOnOnePortraitLayoutMedia,
type OneOnOneMobileLayout,
type OneOnOneMobileLayoutMedia,
} from "./layout-types";
import { type TileStore } from "./TileStore";
import { type Behavior } from "./Behavior";
/**
* Produces a one-on-one portrait layout with the given media.
* Produces a one-on-one mobile layout with the given media.
*/
export function oneOnOnePortraitLayout(
media: OneOnOnePortraitLayoutMedia,
export function oneOnOneMobileLayout(
media: OneOnOneMobileLayoutMedia,
pipSize$: Behavior<"sm" | "lg">,
pipAlignment$: BehaviorSubject<Alignment>,
prevTiles: TileStore,
): [OneOnOnePortraitLayout, TileStore] {
): [OneOnOneMobileLayout, TileStore] {
const update = prevTiles.from(media.pip === undefined ? 0 : 1);
update.registerSpotlight([media.spotlight], true, "transparent");
if (media.pip !== undefined) update.registerGridTile(media.pip);

View File

@@ -45,15 +45,15 @@ export interface SpotlightExpandedLayoutMedia {
pip?: UserMediaViewModel;
}
export interface OneOnOneLandscapeLayoutMedia {
type: "one-on-one-landscape";
export interface OneOnOneDesktopLayoutMedia {
type: "one-on-one-desktop";
edgeToEdge: false;
spotlight: UserMediaViewModel;
pip: LocalUserMediaViewModel | RingingMediaViewModel;
}
export interface OneOnOnePortraitLayoutMedia {
type: "one-on-one-portrait";
export interface OneOnOneMobileLayoutMedia {
type: "one-on-one-mobile";
edgeToEdge: true;
spotlight: UserMediaViewModel | RingingMediaViewModel;
pip?: LocalUserMediaViewModel;
@@ -70,8 +70,8 @@ export type LayoutMedia =
| SpotlightLandscapeLayoutMedia
| SpotlightPortraitLayoutMedia
| SpotlightExpandedLayoutMedia
| OneOnOneLandscapeLayoutMedia
| OneOnOnePortraitLayoutMedia
| OneOnOneDesktopLayoutMedia
| OneOnOneMobileLayoutMedia
| PipLayoutMedia;
export interface Alignment {
@@ -108,15 +108,15 @@ export interface SpotlightExpandedLayout {
pipAlignment$: BehaviorSubject<Alignment>;
}
export interface OneOnOneLandscapeLayout {
type: "one-on-one-landscape";
export interface OneOnOneDesktopLayout {
type: "one-on-one-desktop";
spotlight: GridTileViewModel;
pip: GridTileViewModel;
pipAlignment$: BehaviorSubject<Alignment>;
}
export interface OneOnOnePortraitLayout {
type: "one-on-one-portrait";
export interface OneOnOneMobileLayout {
type: "one-on-one-mobile";
spotlight: SpotlightTileViewModel;
pip?: GridTileViewModel;
pipSize$: Behavior<"sm" | "lg">;
@@ -137,6 +137,6 @@ export type Layout =
| SpotlightLandscapeLayout
| SpotlightPortraitLayout
| SpotlightExpandedLayout
| OneOnOneLandscapeLayout
| OneOnOnePortraitLayout
| OneOnOneDesktopLayout
| OneOnOneMobileLayout
| PipLayout;