Merge remote-tracking branch 'origin/livekit' into hs/reactions-viewcallmodel

This commit is contained in:
Half-Shot
2024-12-12 09:18:36 +00:00
155 changed files with 1772 additions and 996 deletions

View File

@@ -5,7 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, MouseEvent, useCallback, useMemo, useState } from "react";
import {
type FC,
type MouseEvent,
useCallback,
useMemo,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import { Button, Text } from "@vector-im/compound-web";
import { PopOutIcon } from "@vector-im/compound-design-tokens/assets/web/icons";

View File

@@ -5,8 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, FormEventHandler, ReactNode, useCallback, useState } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import {
type FC,
type FormEventHandler,
type ReactNode,
useCallback,
useState,
} from "react";
import { type MatrixClient } from "matrix-js-sdk/src/client";
import { Trans, useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { Button, Heading, Text } from "@vector-im/compound-web";

View File

@@ -10,13 +10,13 @@ import {
afterAll,
beforeEach,
expect,
MockedFunction,
type MockedFunction,
test,
vitest,
} from "vitest";
import { afterEach } from "node:test";
import { act } from "react";
import { CallMembership } from "matrix-js-sdk/src/matrixrtc";
import { type CallMembership } from "matrix-js-sdk/src/matrixrtc";
import { mockRtcMembership } from "../utils/test";
import {

View File

@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ReactNode, useEffect } from "react";
import { type ReactNode, useEffect } from "react";
import { filter, interval, throttle } from "rxjs";
import { CallViewModel } from "../state/CallViewModel";
import { type CallViewModel } from "../state/CallViewModel";
import joinCallSoundMp3 from "../sound/join_call.mp3";
import joinCallSoundOgg from "../sound/join_call.ogg";
import leftCallSoundMp3 from "../sound/left_call.mp3";
@@ -24,7 +24,7 @@ import { useLatest } from "../useLatest";
export const MAX_PARTICIPANT_COUNT_FOR_SOUND = 8;
export const THROTTLE_SOUND_EFFECT_MS = 500;
const sounds = prefetchSounds({
export const callEventAudioSounds = prefetchSounds({
join: {
mp3: joinCallSoundMp3,
ogg: joinCallSoundOgg,
@@ -45,7 +45,7 @@ export function CallEventAudioRenderer({
vm: CallViewModel;
}): ReactNode {
const audioEngineCtx = useAudioContext({
sounds,
sounds: callEventAudioSounds,
latencyHint: "interactive",
});
const audioEngineRef = useLatest(audioEngineCtx);
@@ -60,7 +60,7 @@ export function CallEventAudioRenderer({
throttle(() => interval(THROTTLE_SOUND_EFFECT_MS)),
)
.subscribe(() => {
audioEngineRef.current?.playSound("join");
void audioEngineRef.current?.playSound("join");
});
const leftSub = vm.memberChanges
@@ -72,7 +72,7 @@ export function CallEventAudioRenderer({
throttle(() => interval(THROTTLE_SOUND_EFFECT_MS)),
)
.subscribe(() => {
audioEngineRef.current?.playSound("left");
void audioEngineRef.current?.playSound("left");
});
const handRaisedSub = vm.handRaised.subscribe(() => {

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC } from "react";
import { type FC } from "react";
import { Tooltip } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import {

View File

@@ -0,0 +1,153 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { beforeEach, expect, type MockedFunction, test, vitest } from "vitest";
import { render } from "@testing-library/react";
import { type MatrixClient } from "matrix-js-sdk/src/client";
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
import { of } from "rxjs";
import { JoinRule, type RoomState } from "matrix-js-sdk/src/matrix";
import { Router } from "react-router-dom";
import { createBrowserHistory } from "history";
import userEvent from "@testing-library/user-event";
import { type MuteStates } from "./MuteStates";
import { prefetchSounds } from "../soundUtils";
import { useAudioContext } from "../useAudioContext";
import { ActiveCall } from "./InCallView";
import {
mockMatrixRoom,
mockMatrixRoomMember,
mockRtcMembership,
MockRTCSession,
} from "../utils/test";
import { GroupCallView } from "./GroupCallView";
import { leaveRTCSession } from "../rtcSessionHelpers";
import { type WidgetHelpers } from "../widget";
import { LazyEventEmitter } from "../LazyEventEmitter";
vitest.mock("../soundUtils");
vitest.mock("../useAudioContext");
vitest.mock("./InCallView");
vitest.mock("../rtcSessionHelpers", async (importOriginal) => {
// TODO: perhaps there is a more elegant way to manage the type import here?
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const orig = await importOriginal<typeof import("../rtcSessionHelpers")>();
vitest.spyOn(orig, "leaveRTCSession");
return orig;
});
let playSound: MockedFunction<
NonNullable<ReturnType<typeof useAudioContext>>["playSound"]
>;
const localRtcMember = mockRtcMembership("@carol:example.org", "CCCC");
const carol = mockMatrixRoomMember(localRtcMember);
const roomMembers = new Map([carol].map((p) => [p.userId, p]));
const roomId = "!foo:bar";
const soundPromise = Promise.resolve(true);
beforeEach(() => {
(prefetchSounds as MockedFunction<typeof prefetchSounds>).mockResolvedValue({
sound: new ArrayBuffer(0),
});
playSound = vitest.fn().mockReturnValue(soundPromise);
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
playSound,
});
// A trivial implementation of Active call to ensure we are testing GroupCallView exclusively here.
(ActiveCall as MockedFunction<typeof ActiveCall>).mockImplementation(
({ onLeave }) => {
return (
<div>
<button onClick={() => onLeave()}>Leave</button>
</div>
);
},
);
});
function createGroupCallView(widget: WidgetHelpers | null): {
rtcSession: MockRTCSession;
getByText: ReturnType<typeof render>["getByText"];
} {
const history = createBrowserHistory();
const client = {
getUser: () => null,
getUserId: () => localRtcMember.sender,
getDeviceId: () => localRtcMember.deviceId,
getRoom: (rId) => (rId === roomId ? room : null),
} as Partial<MatrixClient> as MatrixClient;
const room = mockMatrixRoom({
client,
roomId,
getMember: (userId) => roomMembers.get(userId) ?? null,
getMxcAvatarUrl: () => null,
getCanonicalAlias: () => null,
currentState: {
getJoinRule: () => JoinRule.Invite,
} as Partial<RoomState> as RoomState,
});
const rtcSession = new MockRTCSession(
room,
localRtcMember,
[],
).withMemberships(of([]));
const muteState = {
audio: { enabled: false },
video: { enabled: false },
} as MuteStates;
const { getByText } = render(
<Router history={history}>
<GroupCallView
client={client}
isPasswordlessUser={false}
confineToRoom={false}
preload={false}
skipLobby={false}
hideHeader={true}
rtcSession={rtcSession as unknown as MatrixRTCSession}
muteStates={muteState}
widget={widget}
/>
</Router>,
);
return {
getByText,
rtcSession,
};
}
test("will play a leave sound asynchronously in SPA mode", async () => {
const user = userEvent.setup();
const { getByText, rtcSession } = createGroupCallView(null);
const leaveButton = getByText("Leave");
await user.click(leaveButton);
expect(playSound).toHaveBeenCalledWith("left");
expect(leaveRTCSession).toHaveBeenCalledWith(rtcSession, undefined);
expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce();
});
test("will play a leave sound synchronously in widget mode", async () => {
const user = userEvent.setup();
const widget = {
api: {
setAlwaysOnScreen: async () => Promise.resolve(true),
} as Partial<WidgetHelpers["api"]>,
lazyActions: new LazyEventEmitter(),
};
const { getByText, rtcSession } = createGroupCallView(
widget as WidgetHelpers,
);
const leaveButton = getByText("Leave");
await user.click(leaveButton);
expect(playSound).toHaveBeenCalledWith("left");
expect(leaveRTCSession).toHaveBeenCalledWith(rtcSession, soundPromise);
expect(rtcSession.leaveRoomSession).toHaveBeenCalledOnce();
});

View File

@@ -5,31 +5,45 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
type FC,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useHistory } from "react-router-dom";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { type MatrixClient } from "matrix-js-sdk/src/client";
import {
Room,
isE2EESupported as isE2EESupportedBrowser,
} from "livekit-client";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { JoinRule } from "matrix-js-sdk/src/matrix";
import { Heading, Text } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import type { IWidgetApiRequest } from "matrix-widget-api";
import { widget, ElementWidgetActions, JoinCallData } from "../widget";
import {
ElementWidgetActions,
type JoinCallData,
type WidgetHelpers,
} from "../widget";
import { FullScreenView } from "../FullScreenView";
import { LobbyView } from "./LobbyView";
import { MatrixInfo } from "./VideoPreview";
import { type MatrixInfo } from "./VideoPreview";
import { CallEndedView } from "./CallEndedView";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { useProfile } from "../profile/useProfile";
import { findDeviceByName } from "../utils/media";
import { ActiveCall } from "./InCallView";
import { MUTE_PARTICIPANT_COUNT, MuteStates } from "./MuteStates";
import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext";
import { MUTE_PARTICIPANT_COUNT, type MuteStates } from "./MuteStates";
import {
useMediaDevices,
type MediaDevices,
} from "../livekit/MediaDevicesContext";
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers";
import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState";
@@ -42,6 +56,9 @@ import { useUrlParams } from "../UrlParams";
import { E2eeType } from "../e2ee/e2eeType";
import { Link } from "../button/Link";
import { ReactionsProvider } from "../useReactions";
import { useAudioContext } from "../useAudioContext";
import { callEventAudioSounds } from "./CallEventAudioRenderer";
import { useLatest } from "../useLatest";
declare global {
interface Window {
@@ -58,6 +75,7 @@ interface Props {
hideHeader: boolean;
rtcSession: MatrixRTCSession;
muteStates: MuteStates;
widget: WidgetHelpers | null;
}
export const GroupCallView: FC<Props> = ({
@@ -69,10 +87,16 @@ export const GroupCallView: FC<Props> = ({
hideHeader,
rtcSession,
muteStates,
widget,
}) => {
const memberships = useMatrixRTCSessionMemberships(rtcSession);
const isJoined = useMatrixRTCSessionJoinState(rtcSession);
const leaveSoundContext = useLatest(
useAudioContext({
sounds: callEventAudioSounds,
latencyHint: "interactive",
}),
);
// This should use `useEffectEvent` (only available in experimental versions)
useEffect(() => {
if (memberships.length >= MUTE_PARTICIPANT_COUNT)
@@ -186,14 +210,14 @@ export const GroupCallView: FC<Props> = ({
ev.detail.data as unknown as JoinCallData,
);
await enterRTCSession(rtcSession, perParticipantE2EE);
widget!.api.transport.reply(ev.detail, {});
widget.api.transport.reply(ev.detail, {});
})().catch((e) => {
logger.error("Error joining RTC session", e);
});
};
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
return (): void => {
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
widget.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
};
} else {
// No lobby and no preload: we enter the rtc session right away
@@ -207,7 +231,7 @@ export const GroupCallView: FC<Props> = ({
void enterRTCSession(rtcSession, perParticipantE2EE);
}
}
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
}, [widget, rtcSession, preload, skipLobby, perParticipantE2EE]);
const [left, setLeft] = useState(false);
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
@@ -215,12 +239,12 @@ export const GroupCallView: FC<Props> = ({
const onLeave = useCallback(
(leaveError?: Error): void => {
setLeaveError(leaveError);
setLeft(true);
const audioPromise = leaveSoundContext.current?.playSound("left");
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
// therefore we want the event to be sent instantly without getting queued/batched.
const sendInstantly = !!widget;
setLeaveError(leaveError);
setLeft(true);
PosthogAnalytics.instance.eventCallEnded.track(
rtcSession.room.roomId,
rtcSession.memberships.length,
@@ -228,8 +252,12 @@ export const GroupCallView: FC<Props> = ({
rtcSession,
);
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
leaveRTCSession(rtcSession)
leaveRTCSession(
rtcSession,
// Wait for the sound in widget mode (it's not long)
sendInstantly && audioPromise ? audioPromise : undefined,
)
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
.then(() => {
if (
!isPasswordlessUser &&
@@ -243,18 +271,25 @@ export const GroupCallView: FC<Props> = ({
logger.error("Error leaving RTC session", e);
});
},
[rtcSession, isPasswordlessUser, confineToRoom, history],
[
widget,
rtcSession,
isPasswordlessUser,
confineToRoom,
leaveSoundContext,
history,
],
);
useEffect(() => {
if (widget && isJoined) {
// set widget to sticky once joined.
widget!.api.setAlwaysOnScreen(true).catch((e) => {
widget.api.setAlwaysOnScreen(true).catch((e) => {
logger.error("Error calling setAlwaysOnScreen(true)", e);
});
const onHangup = (ev: CustomEvent<IWidgetApiRequest>): void => {
widget!.api.transport.reply(ev.detail, {});
widget.api.transport.reply(ev.detail, {});
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
leaveRTCSession(rtcSession).catch((e) => {
logger.error("Failed to leave RTC session", e);
@@ -262,10 +297,10 @@ export const GroupCallView: FC<Props> = ({
};
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
return (): void => {
widget!.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
widget.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
};
}
}, [isJoined, rtcSession]);
}, [widget, isJoined, rtcSession]);
const onReconnect = useCallback(() => {
setLeft(false);
@@ -360,14 +395,17 @@ export const GroupCallView: FC<Props> = ({
leaveError
) {
return (
<CallEndedView
endedCallId={rtcSession.room.roomId}
client={client}
isPasswordlessUser={isPasswordlessUser}
confineToRoom={confineToRoom}
leaveError={leaveError}
reconnect={onReconnect}
/>
<>
<CallEndedView
endedCallId={rtcSession.room.roomId}
client={client}
isPasswordlessUser={isPasswordlessUser}
confineToRoom={confineToRoom}
leaveError={leaveError}
reconnect={onReconnect}
/>
;
</>
);
} else {
// If the user is a regular user, we'll have sent them back to the homepage,

View File

@@ -10,13 +10,13 @@ import {
RoomContext,
useLocalParticipant,
} from "@livekit/components-react";
import { ConnectionState, Room } from "livekit-client";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { ConnectionState, type Room } from "livekit-client";
import { type MatrixClient } from "matrix-js-sdk/src/client";
import {
FC,
PointerEvent,
PropsWithoutRef,
TouchEvent,
type FC,
type PointerEvent,
type PropsWithoutRef,
type TouchEvent,
forwardRef,
useCallback,
useEffect,
@@ -25,7 +25,7 @@ import {
useState,
} from "react";
import useMeasure from "react-use-measure";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import classNames from "classnames";
import { BehaviorSubject, map } from "rxjs";
import { useObservable, useObservableEagerState } from "observable-hooks";
@@ -49,28 +49,32 @@ import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
import { ElementWidgetActions, widget } from "../widget";
import styles from "./InCallView.module.css";
import { GridTile } from "../tile/GridTile";
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
import { type OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
import { RageshakeRequestModal } from "./RageshakeRequestModal";
import { useLiveKit } from "../livekit/useLiveKit";
import { useWakeLock } from "../useWakeLock";
import { useMergedRefs } from "../useMergedRefs";
import { MuteStates } from "./MuteStates";
import { MatrixInfo } from "./VideoPreview";
import { type MuteStates } from "./MuteStates";
import { type MatrixInfo } from "./VideoPreview";
import { InviteButton } from "../button/InviteButton";
import { LayoutToggle } from "./LayoutToggle";
import { ECConnectionState } from "../livekit/useECConnectionState";
import { type ECConnectionState } from "../livekit/useECConnectionState";
import { useOpenIDSFU } from "../livekit/openIDSFU";
import { CallViewModel, GridMode, Layout } from "../state/CallViewModel";
import { Grid, TileProps } from "../grid/Grid";
import {
CallViewModel,
type GridMode,
type Layout,
} from "../state/CallViewModel";
import { Grid, type TileProps } from "../grid/Grid";
import { useInitial } from "../useInitial";
import { SpotlightTile } from "../tile/SpotlightTile";
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
import { E2eeType } from "../e2ee/e2eeType";
import { makeGridLayout } from "../grid/GridLayout";
import {
CallLayoutOutputs,
type CallLayoutOutputs,
defaultPipAlignment,
defaultSpotlightAlignment,
} from "../grid/CallLayout";
@@ -78,12 +82,16 @@ import { makeOneOnOneLayout } from "../grid/OneOnOneLayout";
import { makeSpotlightExpandedLayout } from "../grid/SpotlightExpandedLayout";
import { makeSpotlightLandscapeLayout } from "../grid/SpotlightLandscapeLayout";
import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout";
import { GridTileViewModel, TileViewModel } from "../state/TileViewModel";
import { GridTileViewModel, type TileViewModel } from "../state/TileViewModel";
import { ReactionsProvider, useReactions } from "../useReactions";
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
import { useSwitchCamera } from "./useSwitchCamera";
import { ReactionsOverlay } from "./ReactionsOverlay";
import { CallEventAudioRenderer } from "./CallEventAudioRenderer";
import {
debugTileLayout as debugTileLayoutSetting,
useSetting,
} from "../settings/settings";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
@@ -224,6 +232,8 @@ export const InCallView: FC<InCallViewProps> = ({
const windowMode = useObservableEagerState(vm.windowMode);
const layout = useObservableEagerState(vm.layout);
const tileStoreGeneration = useObservableEagerState(vm.tileStoreGeneration);
const [debugTileLayout] = useSetting(debugTileLayoutSetting);
const gridMode = useObservableEagerState(vm.gridMode);
const showHeader = useObservableEagerState(vm.showHeader);
const showFooter = useObservableEagerState(vm.showFooter);
@@ -587,6 +597,10 @@ export const InCallView: FC<InCallViewProps> = ({
height={11}
aria-label={import.meta.env.VITE_PRODUCT_NAME || "Element Call"}
/>
{/* Don't mind this odd placement, it's just a little debug label */}
{debugTileLayout
? `Tiles generation: ${tileStoreGeneration}`
: undefined}
</div>
)}
{showControls && <div className={styles.buttons}>{buttons}</div>}

View File

@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
import { render, screen } from "@testing-library/react";
import { expect, test, vi } from "vitest";
import { Room } from "matrix-js-sdk/src/matrix";
import { type Room } from "matrix-js-sdk/src/matrix";
import { axe } from "vitest-axe";
import { BrowserRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";

View File

@@ -5,9 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, MouseEvent, useCallback, useMemo, useState } from "react";
import {
type FC,
type MouseEvent,
useCallback,
useMemo,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import { Room } from "matrix-js-sdk/src/matrix";
import { type Room } from "matrix-js-sdk/src/matrix";
import { Button, Text } from "@vector-im/compound-web";
import {
LinkIcon,

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ChangeEvent, FC, TouchEvent, useCallback } from "react";
import { type ChangeEvent, type FC, type TouchEvent, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Tooltip } from "@vector-im/compound-web";
import {

View File

@@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, useCallback, useMemo, useState } from "react";
import { type FC, useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { Button } from "@vector-im/compound-web";
import classNames from "classnames";
import { useHistory } from "react-router-dom";
import { logger } from "matrix-js-sdk/src/logger";
import { usePreviewTracks } from "@livekit/components-react";
import { LocalVideoTrack, Track } from "livekit-client";
import { type LocalVideoTrack, Track } from "livekit-client";
import { useObservable } from "observable-hooks";
import { map } from "rxjs";
@@ -21,8 +21,8 @@ import inCallStyles from "./InCallView.module.css";
import styles from "./LobbyView.module.css";
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
import { useLocationNavigation } from "../useLocationNavigation";
import { MatrixInfo, VideoPreview } from "./VideoPreview";
import { MuteStates } from "./MuteStates";
import { type MatrixInfo, VideoPreview } from "./VideoPreview";
import { type MuteStates } from "./MuteStates";
import { InviteButton } from "../button/InviteButton";
import {
EndCallButton,

View File

@@ -6,15 +6,15 @@ Please see LICENSE in the repository root for full details.
*/
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import React, { ReactNode } from "react";
import React, { type ReactNode } from "react";
import { beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { useMuteStates } from "./MuteStates";
import {
MediaDevice,
MediaDevices,
type MediaDevice,
type MediaDevices,
MediaDevicesContext,
} from "../livekit/MediaDevicesContext";
import { mockConfig } from "../utils/test";

View File

@@ -6,16 +6,19 @@ Please see LICENSE in the repository root for full details.
*/
import {
Dispatch,
SetStateAction,
type Dispatch,
type SetStateAction,
useCallback,
useEffect,
useMemo,
} from "react";
import { IWidgetApiRequest } from "matrix-widget-api";
import { type IWidgetApiRequest } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext";
import {
type MediaDevice,
useMediaDevices,
} from "../livekit/MediaDevicesContext";
import { useReactiveState } from "../useReactiveState";
import { ElementWidgetActions, widget } from "../widget";
import { Config } from "../config/Config";

View File

@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, useEffect } from "react";
import { type FC, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Button, Text } from "@vector-im/compound-web";
import { Modal, Props as ModalProps } from "../Modal";
import { Modal, type Props as ModalProps } from "../Modal";
import { FieldRow, ErrorMessage } from "../input/Input";
import { useSubmitRageshake } from "../settings/submit-rageshake";

View File

@@ -12,11 +12,11 @@ import {
expect,
test,
vitest,
MockedFunction,
Mock,
type MockedFunction,
type Mock,
} from "vitest";
import { TooltipProvider } from "@vector-im/compound-web";
import { act, ReactNode } from "react";
import { act, type ReactNode } from "react";
import { afterEach } from "node:test";
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ReactNode, useEffect, useState } from "react";
import { type ReactNode, useEffect, useState } from "react";
import { playReactionsSound, useSetting } from "../settings/settings";
import { GenericReaction, ReactionSet } from "../reactions";

View File

@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ReactNode } from "react";
import { useObservableState } from "observable-hooks";
import { type ReactNode } from "react";
import styles from "./ReactionsOverlay.module.css";
import { CallViewModel } from "../state/CallViewModel";
import { useObservableState } from "observable-hooks";
export function ReactionsOverlay({ vm }: { vm: CallViewModel }): ReactNode {
const reactionsIcons = useObservableState(vm.visibleReactions);

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, useCallback, useState } from "react";
import { type FC, useCallback, useState } from "react";
import { useLocation } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
import { logger } from "matrix-js-sdk/src/logger";

View File

@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { FC, useEffect, useState, ReactNode, useRef } from "react";
import { type FC, useEffect, useState, type ReactNode, useRef } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { useTranslation } from "react-i18next";
import { CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { type MatrixError } from "matrix-js-sdk/src/http-api";
import { Heading, Text } from "@vector-im/compound-web";
import { useClientLegacy } from "../ClientContext";
@@ -98,6 +98,7 @@ export const RoomPage: FC = () => {
case "loaded":
return (
<GroupCallView
widget={widget}
client={client!}
rtcSession={groupCallState.rtcSession}
isPasswordlessUser={passwordlessUser}

View File

@@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { useEffect, useRef, FC, ReactNode } from "react";
import { useEffect, useRef, type FC, type ReactNode } from "react";
import useMeasure from "react-use-measure";
import { facingModeFromLocalTrack, LocalVideoTrack } from "livekit-client";
import { facingModeFromLocalTrack, type LocalVideoTrack } from "livekit-client";
import classNames from "classnames";
import { Avatar } from "../Avatar";
import styles from "./VideoPreview.module.css";
import { MuteStates } from "./MuteStates";
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
import { type MuteStates } from "./MuteStates";
import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
export type MatrixInfo = {
userId: string;

View File

@@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { vi, Mocked, test, expect } from "vitest";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { vi, type Mocked, test, expect } from "vitest";
import { type RoomState } from "matrix-js-sdk/src/models/room-state";
import { PosthogAnalytics } from "../../src/analytics/PosthogAnalytics";
import { checkForParallelCalls } from "../../src/room/checkForParallelCalls";

View File

@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/
import { EventType } from "matrix-js-sdk/src/@types/event";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { type RoomState } from "matrix-js-sdk/src/models/room-state";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";

View File

@@ -6,14 +6,14 @@ Please see LICENSE in the repository root for full details.
*/
import {
MatrixRTCSession,
type MatrixRTCSession,
MatrixRTCSessionEvent,
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { useCallback, useEffect, useState } from "react";
import { deepCompare } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import {
LivekitFocus,
type LivekitFocus,
isLivekitFocus,
} from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
@@ -38,7 +38,7 @@ export function useActiveLivekitFocus(
const oldestMembership = rtcSession.getOldestMembership();
logger.warn(
`Got new active focus from membership: ${oldestMembership?.sender}/${oldestMembership?.deviceId}.
Updating focus (focus switch) from ${activeFocus} to ${newActiveFocus}`,
Updating focus (focus switch) from ${JSON.stringify(activeFocus)} to ${JSON.stringify(newActiveFocus)}`,
);
setActiveFocus(newActiveFocus);
}

View File

@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/
import { useCallback } from "react";
import { JoinRule } from "matrix-js-sdk/src/matrix";
import { type JoinRule } from "matrix-js-sdk/src/matrix";
import type { Room } from "matrix-js-sdk/src/models/room";
import { useRoomState } from "./useRoomState";

View File

@@ -10,12 +10,12 @@ import { logger } from "matrix-js-sdk/src/logger";
import { EventType } from "matrix-js-sdk/src/@types/event";
import {
ClientEvent,
MatrixClient,
RoomSummary,
type MatrixClient,
type RoomSummary,
} from "matrix-js-sdk/src/client";
import { SyncState } from "matrix-js-sdk/src/sync";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { RoomEvent, Room } from "matrix-js-sdk/src/models/room";
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { RoomEvent, type Room } from "matrix-js-sdk/src/models/room";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { JoinRule, MatrixError } from "matrix-js-sdk/src/matrix";
import { useTranslation } from "react-i18next";

View File

@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
*/
import { useCallback } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { type Room } from "matrix-js-sdk/src/models/room";
import { useRoomState } from "./useRoomState";

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { useState } from "react";
import { useTypedEventEmitter } from "../useEvents";

View File

@@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { RoomState, RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import {
type RoomState,
RoomStateEvent,
} from "matrix-js-sdk/src/models/room-state";
import { useCallback, useMemo, useState } from "react";
import type { Room } from "matrix-js-sdk/src/models/room";

View File

@@ -9,14 +9,14 @@ import {
fromEvent,
map,
merge,
Observable,
type Observable,
of,
startWith,
switchMap,
} from "rxjs";
import {
facingModeFromLocalTrack,
LocalVideoTrack,
type LocalVideoTrack,
TrackEvent,
} from "livekit-client";
import { useObservable, useObservableEagerState } from "observable-hooks";