From 1bfb018d907e2630eead5226ee5b9613552ad696 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 3 Sep 2026 12:10:04 +0200 Subject: [PATCH] Introduce ElementCallView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Being in a call and deciding to show one are different jobs, but RoomPage did both: routing, authentication, resolving room aliases and knocking, and then the call itself. Only the first set belongs to whatever is hosting Element Call. Add ElementCallView as the seam between them. It takes a client and a call to join, and owns whether the user has joined — which is the call's own business rather than its host's. RoomPage keeps everything about arriving at a call and renders this for the call itself. Nothing else moves yet. muteStates is still passed in, because the standalone shell shares one with the lobby it shows while waiting to be let into a room, and two instances would both report the user's mute state to the host. --- src/ElementCallView.tsx | 73 +++++++++++++++++++++++++++++++++++++++++ src/room/RoomPage.tsx | 7 ++-- 2 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 src/ElementCallView.tsx diff --git a/src/ElementCallView.tsx b/src/ElementCallView.tsx new file mode 100644 index 000000000..72cf9ddaf --- /dev/null +++ b/src/ElementCallView.tsx @@ -0,0 +1,73 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { type FC, type ReactNode, useState } from "react"; +import { type MatrixClient } from "matrix-js-sdk"; +import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc"; + +import { GroupCallView } from "./room/GroupCallView"; +import { type MuteStates } from "./state/MuteStates"; +import { type UrlParams } from "./UrlParams"; + +interface Props { + /** The client to place the call with. */ + client: MatrixClient; + /** The call to join. */ + rtcSession: MatrixRTCSession; + /** The audio and video mute state to start from, and keep in step with. */ + muteStates: MuteStates; + /** + * Whether the user is signed in as a guest, and so should be offered the + * chance to create an account when the call ends. + */ + isPasswordlessUser: boolean; + /** Whether to keep the user in this call rather than letting them navigate. */ + confineToRoom: boolean; + /** Whether to wait for the host to ask us to join. */ + preload: UrlParams["preload"]; + /** Whether to enter the call directly, without showing the lobby first. */ + skipLobby: UrlParams["skipLobby"]; +} + +/** + * A call, as a component. + * + * This owns being in a call, and nothing about how Element Call came to be + * showing one: no routing, no authentication, no resolving of room aliases. + * Those belong to whatever is hosting it — the standalone app's own shell, or + * an application embedding Element Call directly. + * + * TODO: `muteStates` is still passed in, because the standalone shell shares + * one with the lobby it shows while waiting to be let into a room. Ownership + * moves here once that lobby has its own. + */ +export const ElementCallView: FC = ({ + client, + rtcSession, + muteStates, + isPasswordlessUser, + confineToRoom, + preload, + skipLobby, +}): ReactNode => { + // Whether the user is in the call is the call's own business, not its host's. + const [joined, setJoined] = useState(false); + + return ( + + ); +}; diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx index 33eb80ddb..604840e5f 100644 --- a/src/room/RoomPage.tsx +++ b/src/room/RoomPage.tsx @@ -25,7 +25,7 @@ import { import { useClientLegacy } from "../ClientContext"; import { ErrorPage, FullScreenView, LoadingPage } from "../FullScreenView"; import { RoomAuthView } from "./RoomAuthView"; -import { GroupCallView } from "./GroupCallView"; +import { ElementCallView } from "../ElementCallView"; import { useRoomIdentifier, useUrlParams } from "../UrlParams"; import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser"; import { HomePage } from "../home/HomePage"; @@ -62,7 +62,6 @@ export const RoomPage: FC = (): ReactNode => { const { avatarUrl, displayName: userDisplayName } = useProfile(client); const groupCallState = useLoadGroupCall(client, roomIdOrAlias, viaServers); - const [joined, setJoined] = useState(false); const devices = useMediaDevices(); const [muteStates, setMuteStates] = useState(null); @@ -125,11 +124,9 @@ export const RoomPage: FC = (): ReactNode => { case "loaded": return ( muteStates && ( -