diff --git a/.storybook/main.ts b/.storybook/main.ts index 3bb79035..9be41676 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,3 +1,10 @@ +/* +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 { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { diff --git a/.storybook/manager.ts b/.storybook/manager.ts index 2aa8e054..1177be2f 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,3 +1,10 @@ +/* +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 { create } from "storybook/theming"; import { addons } from "storybook/manager-api"; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 74df1899..757c1f8a 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,3 +1,10 @@ +/* +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 { Preview } from "@storybook/react-vite"; import { TooltipProvider } from "@vector-im/compound-web"; import i18n from "i18next"; diff --git a/src/UrlParams.ts b/src/UrlParams.ts index 31101197..339dd9f1 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -14,11 +14,13 @@ import { type RTCNotificationType, } from "matrix-js-sdk/lib/matrixrtc"; import { pickBy } from "lodash-es"; +import { BehaviorSubject } from "rxjs"; import { Config } from "./config/Config"; import { type EncryptionSystem } from "./e2ee/sharedKeyManagement"; import { E2eeType } from "./e2ee/e2eeType"; import { platform } from "./Platform"; +import { type ObservableScope } from "./state/ObservableScope"; interface RoomIdentifier { roomAlias: string | null; @@ -607,6 +609,24 @@ export const useRoomIdentifier = (): RoomIdentifier => { ); }; +let urlParams$ = undefined as BehaviorSubject | undefined; +export const observerUrlParams$ = ( + scope: ObservableScope, +): BehaviorSubject => { + if (urlParams$ !== undefined) return urlParams$; + function updateUrlParams(): void { + console.log("[observerUrlParams$] update urlParams$"); + urlParams$!.next(getUrlParams()); + } + + urlParams$ = new BehaviorSubject(getUrlParams()); + window.addEventListener("hashchange", updateUrlParams); + scope.onEnd(() => { + window.removeEventListener("hashchange", updateUrlParams); + }); + return urlParams$; +}; + export function generateUrlSearchParams( roomId: string, encryptionSystem: EncryptionSystem, diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index dfd11ff3..9ef46fc7 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -93,7 +93,6 @@ interface Props { confineToRoom: boolean; preload: UrlParams["preload"]; skipLobby: UrlParams["skipLobby"]; - header: HeaderStyle; rtcSession: MatrixRTCSession; joined: boolean; setJoined: (value: boolean) => void; @@ -107,7 +106,6 @@ export const GroupCallView: FC = ({ confineToRoom, preload, skipLobby, - header, rtcSession, joined, setJoined, @@ -182,6 +180,7 @@ export const GroupCallView: FC = ({ perParticipantE2EE, returnToLobby, password: passwordFromUrl, + header, } = useUrlParams(); const e2eeSystem = useRoomEncryptionSystem(room.roomId); @@ -463,7 +462,6 @@ export const GroupCallView: FC = ({ rtcSession={rtcSession as MatrixRTCSession} matrixRoom={room} onLeft={onLeft} - header={header} muteStates={muteStates} e2eeSystem={e2eeSystem} //otelGroupCallMembership={otelGroupCallMembership} diff --git a/src/room/InCallView.test.tsx b/src/room/InCallView.test.tsx index 2f797c65..e4b7d012 100644 --- a/src/room/InCallView.test.tsx +++ b/src/room/InCallView.test.tsx @@ -39,7 +39,6 @@ import { ReactionsSenderProvider } from "../reactions/useReactionsSender"; import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement"; import { LivekitRoomAudioRenderer } from "../livekit/MatrixAudioRenderer"; import { MediaDevicesContext } from "../MediaDevicesContext"; -import { HeaderStyle } from "../UrlParams"; import { type MediaDevices as ECMediaDevices } from "../state/MediaDevices"; import { initializeWidget } from "../widget"; @@ -131,7 +130,6 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & { void) | null; } @@ -181,8 +180,6 @@ export const InCallView: FC = ({ matrixInfo, matrixRoom, muteStates, - - header: headerStyle, onShareClick, }) => { const { t } = useTranslation(); @@ -206,7 +203,7 @@ export const InCallView: FC = ({ // Merge the refs so they can attach to the same element const containerRef = useMergedRefs(containerRef1, containerRef2); - const { showControls } = useUrlParams(); + const { showControls, header: headerStyle } = useUrlParams(); const muteAllAudio = useBehavior(muteAllAudio$); @@ -565,14 +562,11 @@ export const InCallView: FC = ({ , ); - const footerNotNeeded = - showControls === false && headerStyle === HeaderStyle.None; const footer = ( { confineToRoom={confineToRoom} preload={preload} skipLobby={skipLobby || wasInWaitForInviteState.current} - header={header} muteStates={muteStates} /> ) diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 6dca08dc..37196308 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -51,6 +51,7 @@ import { v4 as uuidv4 } from "uuid"; import { type IMembershipManager } from "matrix-js-sdk/lib/matrixrtc/IMembershipManager"; import { + and$, createToggle$, filterBehavior, generateItem, @@ -82,7 +83,7 @@ import { constant, type Behavior } from "../Behavior"; import { E2eeType } from "../../e2ee/e2eeType"; import { MatrixKeyProvider } from "../../e2ee/matrixKeyProvider"; import { type MuteStates } from "../MuteStates"; -import { getUrlParams } from "../../UrlParams"; +import { getUrlParams, HeaderStyle, observerUrlParams$ } from "../../UrlParams"; import { type ProcessorState } from "../../livekit/TrackProcessorContext"; import { ElementWidgetActions, widget } from "../../widget"; import { @@ -1316,7 +1317,15 @@ export function createCallViewModel$( windowMode$.pipe(map((mode) => mode !== "pip" && mode !== "flat")), ); - const showFooter$ = scope.behavior( + const urlParams$ = observerUrlParams$(scope); + const showFooterUrlParams$ = urlParams$.pipe( + map( + ({ header, showControls }) => + // with no header and no controls we always set showFooter to false. + !(header === HeaderStyle.None && showControls === false), + ), + ); + const showFooterLayout$ = scope.behavior( windowMode$.pipe( switchMap((mode) => { switch (mode) { @@ -1370,7 +1379,9 @@ export function createCallViewModel$( }), ), ); - + const showFooter$ = scope.behavior( + and$(showFooterLayout$, showFooterUrlParams$), + ); /** * Whether audio is currently being output through the earpiece. */