mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
review. Dont pass full vm to developer settings
This commit is contained in:
@@ -122,12 +122,13 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
|||||||
remoteParticipants$: of([remoteParticipant]),
|
remoteParticipants$: of([remoteParticipant]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const { vm, footerVm, rtcSession } = getBasicCallViewModelEnvironment(
|
const { vm, footerVm, developerSettingsVm, rtcSession } =
|
||||||
[local, alice],
|
getBasicCallViewModelEnvironment(
|
||||||
undefined,
|
[local, alice],
|
||||||
mediaDevices,
|
undefined,
|
||||||
args.callViewModelOptions,
|
mediaDevices,
|
||||||
);
|
args.callViewModelOptions,
|
||||||
|
);
|
||||||
|
|
||||||
rtcSession.joined = true;
|
rtcSession.joined = true;
|
||||||
const room = rtcSession.room;
|
const room = rtcSession.room;
|
||||||
@@ -140,6 +141,7 @@ function createInCallView(args: CreateInCallViewArgs = {}): RenderResult & {
|
|||||||
muteStates={muteState}
|
muteStates={muteState}
|
||||||
vm={vm}
|
vm={vm}
|
||||||
footerVm={footerVm}
|
footerVm={footerVm}
|
||||||
|
developerSettingsVm={developerSettingsVm}
|
||||||
matrixInfo={{
|
matrixInfo={{
|
||||||
userId: "",
|
userId: "",
|
||||||
displayName: "",
|
displayName: "",
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ import { ObservableScope } from "../state/ObservableScope.ts";
|
|||||||
import { CallFooter, type FooterSnapshot } from "../components/CallFooter.tsx";
|
import { CallFooter, type FooterSnapshot } from "../components/CallFooter.tsx";
|
||||||
import { SettingsIconButton } from "../button/Button.tsx";
|
import { SettingsIconButton } from "../button/Button.tsx";
|
||||||
import { createCallFooterViewModel } from "../components/CallFooterViewModel.tsx";
|
import { createCallFooterViewModel } from "../components/CallFooterViewModel.tsx";
|
||||||
|
import { createDeveloperSettingsTabViewModel } from "../settings/DeveloperSettingsTabViewModel.ts";
|
||||||
|
import { type DeveloperSettingsSnapshot } from "../settings/DeveloperSettingsTab.tsx";
|
||||||
import { type ViewModel } from "../state/ViewModel.ts";
|
import { type ViewModel } from "../state/ViewModel.ts";
|
||||||
import { RingingStatus } from "../tile/RingingStatus.tsx";
|
import { RingingStatus } from "../tile/RingingStatus.tsx";
|
||||||
import { RingingAudioRenderer } from "./RingingAudioRenderer.tsx";
|
import { RingingAudioRenderer } from "./RingingAudioRenderer.tsx";
|
||||||
@@ -96,7 +98,7 @@ declare module "react" {
|
|||||||
|
|
||||||
export interface ActiveCallProps extends Omit<
|
export interface ActiveCallProps extends Omit<
|
||||||
InCallViewProps,
|
InCallViewProps,
|
||||||
"vm" | "livekitRoom" | "connState" | "footerVm"
|
"vm" | "livekitRoom" | "connState" | "footerVm" | "developerSettingsVm"
|
||||||
> {
|
> {
|
||||||
e2eeSystem: EncryptionSystem;
|
e2eeSystem: EncryptionSystem;
|
||||||
// TODO refactor those reasons into an enum
|
// TODO refactor those reasons into an enum
|
||||||
@@ -110,6 +112,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
const [footerVm, setFooterVm] = useState<ViewModel<FooterSnapshot> | null>(
|
const [footerVm, setFooterVm] = useState<ViewModel<FooterSnapshot> | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const [developerSettingsVm, setDeveloperSettingsVm] =
|
||||||
|
useState<ViewModel<DeveloperSettingsSnapshot> | null>(null);
|
||||||
|
|
||||||
const urlParams = useUrlParams();
|
const urlParams = useUrlParams();
|
||||||
const mediaDevices = useMediaDevices();
|
const mediaDevices = useMediaDevices();
|
||||||
const trackProcessorState$ = useTrackProcessorObservable$();
|
const trackProcessorState$ = useTrackProcessorObservable$();
|
||||||
@@ -168,7 +173,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
mediaDevices,
|
mediaDevices,
|
||||||
`${props.client.getUserId()}:${props.client.getDeviceId()}`,
|
`${props.client.getUserId()}:${props.client.getDeviceId()}`,
|
||||||
);
|
);
|
||||||
|
const developerSettingsVm = createDeveloperSettingsTabViewModel(scope, vm);
|
||||||
setFooterVm(footerVm);
|
setFooterVm(footerVm);
|
||||||
|
setDeveloperSettingsVm(developerSettingsVm);
|
||||||
|
|
||||||
return (): void => {
|
return (): void => {
|
||||||
scope.end();
|
scope.end();
|
||||||
@@ -188,10 +195,16 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
|
|
||||||
if (vm === null) return null;
|
if (vm === null) return null;
|
||||||
if (footerVm === null) return null;
|
if (footerVm === null) return null;
|
||||||
|
if (developerSettingsVm === null) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}>
|
<ReactionsSenderProvider vm={vm} rtcSession={props.rtcSession}>
|
||||||
<InCallView {...props} vm={vm} footerVm={footerVm} />
|
<InCallView
|
||||||
|
{...props}
|
||||||
|
vm={vm}
|
||||||
|
footerVm={footerVm}
|
||||||
|
developerSettingsVm={developerSettingsVm}
|
||||||
|
/>
|
||||||
</ReactionsSenderProvider>
|
</ReactionsSenderProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -200,6 +213,7 @@ export interface InCallViewProps {
|
|||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
vm: CallViewModel;
|
vm: CallViewModel;
|
||||||
footerVm: ViewModel<FooterSnapshot>;
|
footerVm: ViewModel<FooterSnapshot>;
|
||||||
|
developerSettingsVm: ViewModel<DeveloperSettingsSnapshot>;
|
||||||
matrixInfo: MatrixInfo;
|
matrixInfo: MatrixInfo;
|
||||||
rtcSession: MatrixRTCSession;
|
rtcSession: MatrixRTCSession;
|
||||||
matrixRoom: MatrixRoom;
|
matrixRoom: MatrixRoom;
|
||||||
@@ -211,6 +225,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
client,
|
client,
|
||||||
vm,
|
vm,
|
||||||
footerVm,
|
footerVm,
|
||||||
|
developerSettingsVm,
|
||||||
matrixInfo,
|
matrixInfo,
|
||||||
matrixRoom,
|
matrixRoom,
|
||||||
muteStates,
|
muteStates,
|
||||||
@@ -632,7 +647,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
onDismiss={(): void => setSettingsOpen(false)}
|
onDismiss={(): void => setSettingsOpen(false)}
|
||||||
tab={settingsTab}
|
tab={settingsTab}
|
||||||
onTabChange={setSettingsTab}
|
onTabChange={setSettingsTab}
|
||||||
vm={vm}
|
developerSettingsVm={developerSettingsVm}
|
||||||
livekitRooms={allConnections
|
livekitRooms={allConnections
|
||||||
.getConnections()
|
.getConnections()
|
||||||
.map((connectionItem) => ({
|
.map((connectionItem) => ({
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ import { afterEach, describe, expect, it, type Mock, vi } from "vitest";
|
|||||||
import { render, waitFor, screen } from "@testing-library/react";
|
import { render, waitFor, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { BehaviorSubject } from "rxjs";
|
|
||||||
|
|
||||||
import type { MatrixClient } from "matrix-js-sdk";
|
import type { MatrixClient } from "matrix-js-sdk";
|
||||||
import type { Room as LivekitRoom } from "livekit-client";
|
import type { Room as LivekitRoom } from "livekit-client";
|
||||||
import { DeveloperSettingsTab } from "./DeveloperSettingsTab";
|
import {
|
||||||
|
DeveloperSettingsTab,
|
||||||
|
type DeveloperSettingsSnapshot,
|
||||||
|
} from "./DeveloperSettingsTab";
|
||||||
|
import { outOfCallDeveloperSettingsTabViewModel } from "./DeveloperSettingsTabViewModel";
|
||||||
|
import { createStaticViewModel } from "../state/ViewModel";
|
||||||
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
||||||
import {
|
import {
|
||||||
customLivekitUrl as customLivekitUrlSetting,
|
customLivekitUrl as customLivekitUrlSetting,
|
||||||
@@ -108,6 +112,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
roomId={"#room:example.org"}
|
roomId={"#room:example.org"}
|
||||||
livekitRooms={livekitRooms}
|
livekitRooms={livekitRooms}
|
||||||
env={{ MY_MOCK_ENV: 10, ENV: "test" } as unknown as ImportMetaEnv}
|
env={{ MY_MOCK_ENV: 10, ENV: "test" } as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -137,6 +142,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -160,6 +166,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
client={client}
|
client={client}
|
||||||
roomId="#testRoom"
|
roomId="#testRoom"
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -182,6 +189,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
client={client}
|
client={client}
|
||||||
roomId="#testRoom"
|
roomId="#testRoom"
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -207,6 +215,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
client={client}
|
client={client}
|
||||||
roomId="#testRoom"
|
roomId="#testRoom"
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -237,6 +246,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
client={client}
|
client={client}
|
||||||
roomId="#testRoom"
|
roomId="#testRoom"
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -274,6 +284,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -306,6 +317,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -350,6 +362,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -389,6 +402,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -417,17 +431,16 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
describe("KeyRotationStatus", () => {
|
describe("KeyRotationStatus", () => {
|
||||||
it("displays active status when key rotation is not suppressed", async () => {
|
it("displays active status when key rotation is not suppressed", async () => {
|
||||||
const client = createMockMatrixClient();
|
const client = createMockMatrixClient();
|
||||||
const mockVm = {
|
const vm = createStaticViewModel<DeveloperSettingsSnapshot>({
|
||||||
keyRotationSuppressed$: new BehaviorSubject(false),
|
keyRotation: { suppressed: false, participantCount: 5 },
|
||||||
participantCount$: new BehaviorSubject(5),
|
});
|
||||||
} as unknown as any;
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
vm={mockVm}
|
vm={vm}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -443,17 +456,16 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
|
|
||||||
it("displays suppressed status when key rotation is suppressed", async () => {
|
it("displays suppressed status when key rotation is suppressed", async () => {
|
||||||
const client = createMockMatrixClient();
|
const client = createMockMatrixClient();
|
||||||
const mockVm = {
|
const vm = createStaticViewModel<DeveloperSettingsSnapshot>({
|
||||||
keyRotationSuppressed$: new BehaviorSubject(true),
|
keyRotation: { suppressed: true, participantCount: 50 },
|
||||||
participantCount$: new BehaviorSubject(50),
|
});
|
||||||
} as unknown as any;
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
vm={mockVm}
|
vm={vm}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
@@ -469,7 +481,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render KeyRotationStatus when vm is not provided", async () => {
|
it("does not render KeyRotationStatus when not in a call", async () => {
|
||||||
const client = createMockMatrixClient();
|
const client = createMockMatrixClient();
|
||||||
|
|
||||||
render(
|
render(
|
||||||
@@ -477,6 +489,7 @@ describe("DeveloperSettingsTab", () => {
|
|||||||
<DeveloperSettingsTab
|
<DeveloperSettingsTab
|
||||||
client={client}
|
client={client}
|
||||||
env={{} as unknown as ImportMetaEnv}
|
env={{} as unknown as ImportMetaEnv}
|
||||||
|
vm={outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
</TooltipProvider>,
|
</TooltipProvider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,25 +67,40 @@ import styles from "./DeveloperSettingsTab.module.css";
|
|||||||
import settingsStyles from "./SettingsModal.module.css";
|
import settingsStyles from "./SettingsModal.module.css";
|
||||||
import { Slider } from "../Slider";
|
import { Slider } from "../Slider";
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel.ts";
|
|
||||||
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
import { getSFUConfigWithOpenID } from "../livekit/openIDSFU";
|
||||||
import { useBehavior } from "../useBehavior";
|
import { useBehavior } from "../useBehavior";
|
||||||
|
import { type ViewModel } from "../state/ViewModel.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of MatrixRTC's media key rotation.
|
||||||
|
*/
|
||||||
|
export interface KeyRotationInfo {
|
||||||
|
/** Whether the call is large enough that MatrixRTC has stopped rotating the media key. */
|
||||||
|
suppressed: boolean;
|
||||||
|
participantCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Snapshot combines all fields the developer settings tab needs from the
|
||||||
|
* surrounding call. Everything else in this tab is read from the settings store
|
||||||
|
* or the environment directly.
|
||||||
|
*/
|
||||||
|
export interface DeveloperSettingsSnapshot {
|
||||||
|
/** The media key rotation state, or `null` when we are not in a call. */
|
||||||
|
keyRotation: KeyRotationInfo | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows whether the call is large enough that MatrixRTC has stopped rotating the media key.
|
* Shows whether the call is large enough that MatrixRTC has stopped rotating the media key.
|
||||||
*/
|
*/
|
||||||
const KeyRotationStatus: FC<{ vm: CallViewModel }> = ({ vm }) => {
|
const KeyRotationStatus: FC<{ info: KeyRotationInfo }> = ({ info }) => (
|
||||||
const suppressed = useBehavior(vm.keyRotationSuppressed$);
|
<p>
|
||||||
const participantCount = useBehavior(vm.participantCount$);
|
Media key rotation:{" "}
|
||||||
return (
|
{info.suppressed
|
||||||
<p>
|
? `suppressed, participant limit reached (${info.participantCount} participants)`
|
||||||
Media key rotation:{" "}
|
: `active (${info.participantCount} participants)`}
|
||||||
{suppressed
|
</p>
|
||||||
? `suppressed, participant limit reached (${participantCount} participants)`
|
);
|
||||||
: `active (${participantCount} participants)`}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
@@ -97,8 +112,7 @@ interface Props {
|
|||||||
livekitAlias?: string;
|
livekitAlias?: string;
|
||||||
}[];
|
}[];
|
||||||
env: ImportMetaEnv;
|
env: ImportMetaEnv;
|
||||||
/** Only available while in a call. */
|
vm: ViewModel<DeveloperSettingsSnapshot>;
|
||||||
vm?: CallViewModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeveloperSettingsTab: FC<Props> = ({
|
export const DeveloperSettingsTab: FC<Props> = ({
|
||||||
@@ -109,6 +123,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
vm,
|
vm,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const keyRotation = useBehavior(vm.keyRotation$);
|
||||||
const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting);
|
const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting);
|
||||||
const [debugTileLayout, setDebugTileLayout] = useSetting(
|
const [debugTileLayout, setDebugTileLayout] = useSetting(
|
||||||
debugTileLayoutSetting,
|
debugTileLayoutSetting,
|
||||||
@@ -385,7 +400,7 @@ export const DeveloperSettingsTab: FC<Props> = ({
|
|||||||
id: client.getDeviceId() || "unknown",
|
id: client.getDeviceId() || "unknown",
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
{vm && <KeyRotationStatus vm={vm} />}
|
{keyRotation !== null && <KeyRotationStatus info={keyRotation} />}
|
||||||
<Separator />
|
<Separator />
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<InputField
|
<InputField
|
||||||
|
|||||||
48
src/settings/DeveloperSettingsTabViewModel.test.ts
Normal file
48
src/settings/DeveloperSettingsTabViewModel.test.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
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 { describe, expect, it } from "vitest";
|
||||||
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
|
import { testScope } from "../utils/test";
|
||||||
|
import { type CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||||
|
import {
|
||||||
|
createDeveloperSettingsTabViewModel,
|
||||||
|
outOfCallDeveloperSettingsTabViewModel,
|
||||||
|
} from "./DeveloperSettingsTabViewModel";
|
||||||
|
|
||||||
|
describe("createDeveloperSettingsTabViewModel", () => {
|
||||||
|
it("projects the key rotation state of the call", () => {
|
||||||
|
const keyRotationSuppressed$ = new BehaviorSubject(false);
|
||||||
|
const participantCount$ = new BehaviorSubject(5);
|
||||||
|
const vm = createDeveloperSettingsTabViewModel(testScope(), {
|
||||||
|
keyRotationSuppressed$,
|
||||||
|
participantCount$,
|
||||||
|
} as unknown as CallViewModel);
|
||||||
|
|
||||||
|
expect(vm.keyRotation$.value).toEqual({
|
||||||
|
suppressed: false,
|
||||||
|
participantCount: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
participantCount$.next(50);
|
||||||
|
keyRotationSuppressed$.next(true);
|
||||||
|
|
||||||
|
expect(vm.keyRotation$.value).toEqual({
|
||||||
|
suppressed: true,
|
||||||
|
participantCount: 50,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("outOfCallDeveloperSettingsTabViewModel", () => {
|
||||||
|
it("has no key rotation state", () => {
|
||||||
|
expect(outOfCallDeveloperSettingsTabViewModel.keyRotation$.value).toBe(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
43
src/settings/DeveloperSettingsTabViewModel.ts
Normal file
43
src/settings/DeveloperSettingsTabViewModel.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
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 { combineLatest } from "rxjs";
|
||||||
|
|
||||||
|
import { type CallViewModel } from "../state/CallViewModel/CallViewModel.ts";
|
||||||
|
import { type ObservableScope } from "../state/ObservableScope.ts";
|
||||||
|
import { createStaticViewModel, type ViewModel } from "../state/ViewModel.ts";
|
||||||
|
import { type DeveloperSettingsSnapshot } from "./DeveloperSettingsTab.tsx";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the ViewModel for the developer settings tab while in a call.
|
||||||
|
*
|
||||||
|
* Only the call state the tab actually renders is projected here, so that the
|
||||||
|
* tab does not need to know about the CallViewModel.
|
||||||
|
*
|
||||||
|
* @param scope - ObservableScope that bounds the lifetime of derived behaviors.
|
||||||
|
* @param callModel - The root CallViewModel; provides the key rotation state.
|
||||||
|
*/
|
||||||
|
export function createDeveloperSettingsTabViewModel(
|
||||||
|
scope: ObservableScope,
|
||||||
|
callModel: CallViewModel,
|
||||||
|
): ViewModel<DeveloperSettingsSnapshot> {
|
||||||
|
return {
|
||||||
|
keyRotation$: scope.behavior(
|
||||||
|
combineLatest(
|
||||||
|
[callModel.keyRotationSuppressed$, callModel.participantCount$],
|
||||||
|
(suppressed, participantCount) => ({ suppressed, participantCount }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ViewModel for the developer settings tab outside of a call (lobby, user
|
||||||
|
* menu), where no call state exists. All call specific fields are `null`.
|
||||||
|
*/
|
||||||
|
export const outOfCallDeveloperSettingsTabViewModel: ViewModel<DeveloperSettingsSnapshot> =
|
||||||
|
createStaticViewModel<DeveloperSettingsSnapshot>({ keyRotation: null });
|
||||||
@@ -29,12 +29,16 @@ import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
|
|||||||
import { Slider } from "../Slider";
|
import { Slider } from "../Slider";
|
||||||
import { DeviceSelection } from "./DeviceSelection";
|
import { DeviceSelection } from "./DeviceSelection";
|
||||||
import { useTrackProcessor } from "../livekit/TrackProcessorContext";
|
import { useTrackProcessor } from "../livekit/TrackProcessorContext";
|
||||||
import { DeveloperSettingsTab } from "./DeveloperSettingsTab";
|
import {
|
||||||
|
DeveloperSettingsTab,
|
||||||
|
type DeveloperSettingsSnapshot,
|
||||||
|
} from "./DeveloperSettingsTab";
|
||||||
import { FieldRow, InputField } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import { useSubmitRageshake } from "./submit-rageshake";
|
import { useSubmitRageshake } from "./submit-rageshake";
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
import { useBehavior } from "../useBehavior";
|
import { useBehavior } from "../useBehavior";
|
||||||
import { type CallViewModel } from "../state/CallViewModel/CallViewModel.ts";
|
import { type ViewModel } from "../state/ViewModel.ts";
|
||||||
|
import { outOfCallDeveloperSettingsTabViewModel } from "./DeveloperSettingsTabViewModel";
|
||||||
|
|
||||||
type SettingsTab =
|
type SettingsTab =
|
||||||
| "audio"
|
| "audio"
|
||||||
@@ -58,7 +62,7 @@ interface Props {
|
|||||||
isLocal?: boolean;
|
isLocal?: boolean;
|
||||||
}[];
|
}[];
|
||||||
/** Only available while in a call. Used by the developer tab. */
|
/** Only available while in a call. Used by the developer tab. */
|
||||||
vm?: CallViewModel;
|
developerSettingsVm?: ViewModel<DeveloperSettingsSnapshot>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultSettingsTab: SettingsTab = "audio";
|
export const defaultSettingsTab: SettingsTab = "audio";
|
||||||
@@ -71,7 +75,7 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
client,
|
client,
|
||||||
roomId,
|
roomId,
|
||||||
livekitRooms,
|
livekitRooms,
|
||||||
vm,
|
developerSettingsVm,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -224,7 +228,7 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
client={client}
|
client={client}
|
||||||
livekitRooms={livekitRooms}
|
livekitRooms={livekitRooms}
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
vm={vm}
|
vm={developerSettingsVm ?? outOfCallDeveloperSettingsTabViewModel}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import { MatrixRTCMode } from "../config/ConfigOptions";
|
|||||||
import { createCallFooterViewModel } from "../components/CallFooterViewModel";
|
import { createCallFooterViewModel } from "../components/CallFooterViewModel";
|
||||||
import { type FooterSnapshot } from "../components/CallFooter";
|
import { type FooterSnapshot } from "../components/CallFooter";
|
||||||
import { type ViewModel } from "../state/ViewModel";
|
import { type ViewModel } from "../state/ViewModel";
|
||||||
|
import { createDeveloperSettingsTabViewModel } from "../settings/DeveloperSettingsTabViewModel";
|
||||||
|
import { type DeveloperSettingsSnapshot } from "../settings/DeveloperSettingsTab";
|
||||||
|
|
||||||
mockConfig({ livekit: { livekit_service_url: "https://example.com" } });
|
mockConfig({ livekit: { livekit_service_url: "https://example.com" } });
|
||||||
|
|
||||||
@@ -140,6 +142,7 @@ export function getBasicCallViewModelEnvironment(
|
|||||||
): {
|
): {
|
||||||
vm: CallViewModel;
|
vm: CallViewModel;
|
||||||
footerVm: ViewModel<FooterSnapshot>;
|
footerVm: ViewModel<FooterSnapshot>;
|
||||||
|
developerSettingsVm: ViewModel<DeveloperSettingsSnapshot>;
|
||||||
rtcMemberships$: BehaviorSubject<CallMembership[]>;
|
rtcMemberships$: BehaviorSubject<CallMembership[]>;
|
||||||
rtcSession: MockRTCSession;
|
rtcSession: MockRTCSession;
|
||||||
handRaisedSubject$: BehaviorSubject<Record<string, RaisedHandInfo>>;
|
handRaisedSubject$: BehaviorSubject<Record<string, RaisedHandInfo>>;
|
||||||
@@ -188,6 +191,7 @@ export function getBasicCallViewModelEnvironment(
|
|||||||
return {
|
return {
|
||||||
vm,
|
vm,
|
||||||
footerVm,
|
footerVm,
|
||||||
|
developerSettingsVm: createDeveloperSettingsTabViewModel(testScope(), vm),
|
||||||
rtcMemberships$,
|
rtcMemberships$,
|
||||||
rtcSession,
|
rtcSession,
|
||||||
handRaisedSubject$: handRaisedSubject$,
|
handRaisedSubject$: handRaisedSubject$,
|
||||||
|
|||||||
Reference in New Issue
Block a user