Remove more usages of singleton store getter in favour of contexts (#34099)
* Expose SDKContextClass via window for debugging * Remove stores from window if they are exposed via sdkContext * Avoid usages of global store instance where React context is accessible * Remove more usages of singleton store getter in favour of contexts * Remove more usages of singleton store getter in favour of contexts * Fix tests by adding SDKContext.Provider * Fix tests by adding SDKContext.Provider * Fix tests by adding SDKContext.Provider * Fix tests by adding SDKContext.Provider * Fix tests * Fix tests * Fix tests * Iterate * Fix bad merge * Iterate * Fix tests * Iterate * Iterate * Iterate * Iterate * Iterate * Improve coverage * Improve coverage
This commit is contained in:
+35
-30
@@ -19,6 +19,7 @@ import AsyncWrapper from "./AsyncWrapper";
|
||||
import { type Defaultize } from "./@types/common";
|
||||
import { type ActionPayload } from "./dispatcher/payloads";
|
||||
import { filterBoolean } from "./utils/arrays.ts";
|
||||
import { SDKContext } from "./contexts/SDKContext.ts";
|
||||
|
||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
|
||||
@@ -437,21 +438,23 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
|
||||
const staticDialog = (
|
||||
<StrictMode>
|
||||
{/* Provide I18nContext for shared-components used inside dialogs rendered in a separate root. */}
|
||||
<I18nContext.Provider value={window.mxModuleApi.i18n}>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{this.staticModal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background mx_Dialog_staticBackground"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</I18nContext.Provider>
|
||||
<SDKContext.Provider value={window.mxSdkContext}>
|
||||
{/* Provide I18nContext for shared-components used inside dialogs rendered in a separate root. */}
|
||||
<I18nContext.Provider value={window.mxModuleApi.i18n}>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{this.staticModal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background mx_Dialog_staticBackground"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</I18nContext.Provider>
|
||||
</SDKContext.Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@@ -469,21 +472,23 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
|
||||
const dialog = (
|
||||
<StrictMode>
|
||||
{/* Provide I18nContext for shared-components used inside dialogs rendered in a separate root. */}
|
||||
<I18nContext.Provider value={window.mxModuleApi.i18n}>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{modal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</I18nContext.Provider>
|
||||
<SDKContext.Provider value={window.mxSdkContext}>
|
||||
{/* Provide I18nContext for shared-components used inside dialogs rendered in a separate root. */}
|
||||
<I18nContext.Provider value={window.mxModuleApi.i18n}>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{modal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</I18nContext.Provider>
|
||||
</SDKContext.Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ import UserView from "./UserView";
|
||||
import { mediaFromMxc } from "../../customisations/Media";
|
||||
import { UserTab } from "../views/dialogs/UserTab";
|
||||
import { type OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { type SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
|
||||
@@ -494,7 +493,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
break;
|
||||
case KeyBindingAction.ToggleRoomSidePanel:
|
||||
if (this.props.page_type === "room_view") {
|
||||
RightPanelStore.instance.togglePanel(null);
|
||||
this.context.rightPanelStore.togglePanel(null);
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -129,7 +129,6 @@ import { WaitingForThirdPartyRoomView } from "./WaitingForThirdPartyRoomView";
|
||||
import { isNotUndefined } from "../../Typeguards";
|
||||
import { type CancelAskToJoinPayload } from "../../dispatcher/payloads/CancelAskToJoinPayload";
|
||||
import { type SubmitAskToJoinPayload } from "../../dispatcher/payloads/SubmitAskToJoinPayload";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { onView3pidInvite } from "../../stores/right-panel/action-handlers";
|
||||
import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel";
|
||||
import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner";
|
||||
@@ -1332,23 +1331,23 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
case Action.ViewUser:
|
||||
if (payload.member) {
|
||||
if (payload.push) {
|
||||
RightPanelStore.instance.pushCard({
|
||||
this.context.rightPanelStore.pushCard({
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
state: { member: payload.member },
|
||||
});
|
||||
} else {
|
||||
RightPanelStore.instance.setCards([
|
||||
this.context.rightPanelStore.setCards([
|
||||
{ phase: RightPanelPhases.RoomSummary },
|
||||
{ phase: RightPanelPhases.MemberList },
|
||||
{ phase: RightPanelPhases.MemberInfo, state: { member: payload.member } },
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.MemberList);
|
||||
this.context.rightPanelStore.showOrHidePhase(RightPanelPhases.MemberList);
|
||||
}
|
||||
break;
|
||||
case Action.View3pidInvite:
|
||||
onView3pidInvite(payload, RightPanelStore.instance);
|
||||
onView3pidInvite(payload, this.context.rightPanelStore);
|
||||
break;
|
||||
case Action.FocusMessageSearch:
|
||||
if ((payload as FocusMessageSearchPayload).initialText) {
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
VideoCallSolidIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import createRoom, { type IOpts } from "../../createRoom";
|
||||
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
@@ -37,7 +36,6 @@ import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { showRoomInviteDialog } from "../../RoomInvite";
|
||||
import { UIComponent } from "../../settings/UIFeature";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
import type ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import {
|
||||
@@ -77,6 +75,7 @@ import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
||||
import SpacePillButton from "./SpacePillButton.tsx";
|
||||
import { useRoomName } from "../../hooks/useRoomName.ts";
|
||||
import MultiInviter from "../../utils/MultiInviter.ts";
|
||||
import { SDKContext } from "../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
space: Room;
|
||||
@@ -212,18 +211,18 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||
};
|
||||
|
||||
const SpaceLanding: React.FC<{ space: Room }> = ({ space }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const myMembership = useMyRoomMembership(space);
|
||||
const userId = cli.getSafeUserId();
|
||||
const userId = sdkContext.client!.getSafeUserId();
|
||||
const name = useRoomName(space);
|
||||
|
||||
const storeIsShowingSpaceMembers = useCallback(
|
||||
() =>
|
||||
RightPanelStore.instance.isOpenForRoom(space.roomId) &&
|
||||
RightPanelStore.instance.currentCardForRoom(space.roomId)?.phase === RightPanelPhases.MemberList,
|
||||
[space.roomId],
|
||||
sdkContext.rightPanelStore.isOpenForRoom(space.roomId) &&
|
||||
sdkContext.rightPanelStore.currentCardForRoom(space.roomId)?.phase === RightPanelPhases.MemberList,
|
||||
[space.roomId, sdkContext.rightPanelStore],
|
||||
);
|
||||
const isShowingMembers = useEventEmitterState(RightPanelStore.instance, UPDATE_EVENT, storeIsShowingSpaceMembers);
|
||||
const isShowingMembers = useEventEmitterState(sdkContext.rightPanelStore, UPDATE_EVENT, storeIsShowingSpaceMembers);
|
||||
|
||||
let inviteButton;
|
||||
if (shouldShowSpaceInvite(space) && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||
@@ -266,7 +265,7 @@ const SpaceLanding: React.FC<{ space: Room }> = ({ space }) => {
|
||||
}
|
||||
|
||||
const onMembersClick = (): void => {
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.MemberList });
|
||||
sdkContext.rightPanelStore.setCard({ phase: RightPanelPhases.MemberList });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -603,18 +602,18 @@ const SpaceSetupPrivateInvite: React.FC<{
|
||||
};
|
||||
|
||||
export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
declare public context: React.ContextType<typeof MatrixClientContext>;
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
private dispatcherRef?: string;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
||||
super(props, context);
|
||||
|
||||
let phase = Phase.Landing;
|
||||
|
||||
const creator = this.props.space.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
|
||||
const showSetup = this.props.justCreatedOpts && context.getSafeUserId() === creator;
|
||||
const showSetup = this.props.justCreatedOpts && this.context.client?.getSafeUserId() === creator;
|
||||
|
||||
if (showSetup) {
|
||||
phase =
|
||||
@@ -625,21 +624,21 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||
|
||||
this.state = {
|
||||
phase,
|
||||
showRightPanel: RightPanelStore.instance.isOpenForRoom(this.props.space.roomId),
|
||||
showRightPanel: this.context.rightPanelStore.isOpenForRoom(this.props.space.roomId),
|
||||
myMembership: this.props.space.getMyMembership(),
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
this.context.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
this.context.client?.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
this.context.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
this.context.client?.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
}
|
||||
|
||||
private onMyMembership = (room: Room, myMembership: string): void => {
|
||||
@@ -650,7 +649,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||
|
||||
private onRightPanelStoreUpdate = (): void => {
|
||||
this.setState({
|
||||
showRightPanel: RightPanelStore.instance.isOpenForRoom(this.props.space.roomId),
|
||||
showRightPanel: this.context.rightPanelStore.isOpenForRoom(this.props.space.roomId),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+7
-7
@@ -8,10 +8,10 @@ import { type MatrixClient, type RoomMember, type User } from "matrix-js-sdk/src
|
||||
import { useContext } from "react";
|
||||
import { type UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||
import { type IDevice } from "../../../views/right_panel/UserInfo";
|
||||
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
|
||||
import { verifyUser } from "../../../../verification";
|
||||
import { SDKContext } from "../../../../contexts/SDKContext.ts";
|
||||
|
||||
export interface UserInfoVerificationSectionState {
|
||||
/**
|
||||
@@ -26,7 +26,7 @@ export interface UserInfoVerificationSectionState {
|
||||
/**
|
||||
* callback function when verifyUser button is clicked
|
||||
*/
|
||||
verifySelectedUser: () => Promise<void>;
|
||||
verifySelectedUser: () => void;
|
||||
}
|
||||
|
||||
const useHasCrossSigningKeys = (cli: MatrixClient, member: User, canVerify: boolean): boolean | undefined => {
|
||||
@@ -44,21 +44,21 @@ export const useUserInfoVerificationViewModel = (
|
||||
member: User | RoomMember,
|
||||
devices: IDevice[],
|
||||
): UserInfoVerificationSectionState => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
const userTrust = useAsyncMemo<UserVerificationStatus | undefined>(
|
||||
async () => cli.getCrypto()?.getUserVerificationStatus(member.userId),
|
||||
async () => sdkContext.client?.getCrypto()?.getUserVerificationStatus(member.userId),
|
||||
[member.userId],
|
||||
// the user verification status is not initialized
|
||||
undefined,
|
||||
);
|
||||
const hasUserVerificationStatus = Boolean(userTrust);
|
||||
const isUserVerified = Boolean(userTrust?.isVerified());
|
||||
const isMe = member.userId === cli.getUserId();
|
||||
const isMe = member.userId === sdkContext.client!.getUserId();
|
||||
const canVerify = hasUserVerificationStatus && !isUserVerified && !isMe && devices && devices.length > 0;
|
||||
|
||||
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify);
|
||||
const verifySelectedUser = (): Promise<void> => verifyUser(cli, member as User);
|
||||
const hasCrossSigningKeys = useHasCrossSigningKeys(sdkContext.client!, member as User, canVerify);
|
||||
const verifySelectedUser = (): void => verifyUser(sdkContext.rightPanelStore, sdkContext.client!, member as User);
|
||||
|
||||
return {
|
||||
canVerify,
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import React, { useCallback, useContext, useEffect } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { LinkIcon, OverflowHorizontalIcon, VisibilityOnIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
@@ -18,9 +18,8 @@ import { copyPlaintext } from "../../../utils/strings";
|
||||
import { ChevronFace, ContextMenuTooltipButton, type MenuProps, useContextMenu } from "../../structures/ContextMenu";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
||||
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
export interface ThreadListContextMenuProps {
|
||||
mxEvent: MatrixEvent;
|
||||
@@ -42,6 +41,7 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
|
||||
onMenuToggle,
|
||||
...props
|
||||
}) => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const [menuDisplayed, button, openMenu, closeThreadOptions] = useContextMenu();
|
||||
|
||||
const viewInRoom = useCallback(
|
||||
@@ -77,8 +77,8 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
|
||||
onMenuToggle?.(menuDisplayed);
|
||||
}, [menuDisplayed, onMenuToggle]);
|
||||
|
||||
const room = MatrixClientPeg.safeGet().getRoom(mxEvent.getRoomId());
|
||||
const isMainSplitTimelineShown = !!room && !WidgetLayoutStore.instance.hasMaximisedWidget(room);
|
||||
const room = sdkContext.client?.getRoom(mxEvent.getRoomId());
|
||||
const isMainSplitTimelineShown = !!room && !sdkContext.widgetLayoutStore.hasMaximisedWidget(room);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ContextMenuTooltipButton
|
||||
|
||||
@@ -135,7 +135,7 @@ interface IState {
|
||||
|
||||
export default class AppTile extends React.Component<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: ContextType<typeof SDKContext>;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public static defaultProps: Partial<IProps> = {
|
||||
waitForIframeLoad: true,
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent, useMemo } from "react";
|
||||
import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent, useMemo, useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import { IconButton, Text } from "@vector-im/compound-web";
|
||||
import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
|
||||
@@ -14,9 +14,9 @@ import ChevronLeftIcon from "@vector-im/compound-design-tokens/assets/web/icons/
|
||||
import { AutoHideScrollbar } from "@element-hq/web-shared-components";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import { backLabelForPhase } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { CardContext } from "./context";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
header?: ReactNode | null;
|
||||
@@ -37,12 +37,6 @@ interface IProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function closeRightPanel(ev: MouseEvent<HTMLButtonElement>): void {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
RightPanelStore.instance.popCard();
|
||||
}
|
||||
|
||||
const BaseCard: React.FC<IProps> = ({
|
||||
closeLabel,
|
||||
onClose,
|
||||
@@ -60,13 +54,15 @@ const BaseCard: React.FC<IProps> = ({
|
||||
closeButtonRef,
|
||||
ref,
|
||||
}: IProps) => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
let backButton;
|
||||
const cardHistory = RightPanelStore.instance.roomPhaseHistory;
|
||||
const cardHistory = sdkContext.rightPanelStore.roomPhaseHistory;
|
||||
if (cardHistory.length > 1 && !hideHeaderButtons) {
|
||||
const prevCard = cardHistory[cardHistory.length - 2];
|
||||
const onBackClick = (ev: MouseEvent<HTMLButtonElement>): void => {
|
||||
onBack?.(ev);
|
||||
RightPanelStore.instance.popCard();
|
||||
sdkContext.rightPanelStore.popCard();
|
||||
};
|
||||
const label = backLabelForPhase(prevCard.phase) ?? _t("action|back");
|
||||
backButton = (
|
||||
@@ -84,6 +80,13 @@ const BaseCard: React.FC<IProps> = ({
|
||||
|
||||
let closeButton;
|
||||
if (!hideHeaderButtons) {
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function closeRightPanel(ev: MouseEvent<HTMLButtonElement>): void {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
sdkContext.rightPanelStore.popCard();
|
||||
}
|
||||
|
||||
closeButton = (
|
||||
<IconButton
|
||||
size="28px"
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { VerificationPhase, type VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
|
||||
import { type RoomMember, type User } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -21,9 +21,8 @@ import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import Modal from "../../../Modal";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
// cancellation codes which constitute a key mismatch
|
||||
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
|
||||
@@ -38,7 +37,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
|
||||
const cli = useMatrixClientContext();
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const { verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted } = props;
|
||||
const [request, setRequest] = useState(verificationRequest);
|
||||
// state to show a spinner immediately after clicking "start verification",
|
||||
@@ -111,11 +110,11 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
|
||||
setRequesting(true);
|
||||
let verificationRequest_: VerificationRequest;
|
||||
try {
|
||||
const roomId = await ensureDMExists(cli, member.userId);
|
||||
const roomId = await ensureDMExists(sdkContext.client!, member.userId);
|
||||
if (!roomId) {
|
||||
throw new Error("Unable to create Room for verification");
|
||||
}
|
||||
verificationRequest_ = await cli.getCrypto()!.requestVerificationDM(member.userId, roomId);
|
||||
verificationRequest_ = await sdkContext.client!.getCrypto()!.requestVerificationDM(member.userId, roomId);
|
||||
} catch (e) {
|
||||
console.error("Error starting verification", e);
|
||||
setRequesting(false);
|
||||
@@ -130,20 +129,20 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
|
||||
setRequest(verificationRequest_);
|
||||
setPhase(verificationRequest_.phase);
|
||||
// Notify the RightPanelStore about this
|
||||
if (RightPanelStore.instance.currentCard.phase != RightPanelPhases.EncryptionPanel) {
|
||||
RightPanelStore.instance.pushCard({
|
||||
if (sdkContext.rightPanelStore.currentCard.phase != RightPanelPhases.EncryptionPanel) {
|
||||
sdkContext.rightPanelStore.pushCard({
|
||||
phase: RightPanelPhases.EncryptionPanel,
|
||||
state: { member, verificationRequest: verificationRequest_ },
|
||||
});
|
||||
}
|
||||
if (!RightPanelStore.instance.isOpen) RightPanelStore.instance.togglePanel(null);
|
||||
}, [cli, member]);
|
||||
if (!sdkContext.rightPanelStore.isOpen) sdkContext.rightPanelStore.togglePanel(null);
|
||||
}, [sdkContext.client, sdkContext.rightPanelStore, member]);
|
||||
|
||||
const requested: boolean =
|
||||
(!request && isRequesting) ||
|
||||
(!!request &&
|
||||
(phase === VerificationPhase.Requested || phase === VerificationPhase.Unsent || phase === undefined));
|
||||
const isSelfVerification = request ? request.isSelfVerification : member.userId === cli.getUserId();
|
||||
const isSelfVerification = request ? request.isSelfVerification : member.userId === sdkContext.client?.getUserId();
|
||||
|
||||
if (!request || requested) {
|
||||
const initiatedByMe = (!request && isRequesting) || (!!request && request.initiatedByMe);
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, useEffect, useMemo, useState } from "react";
|
||||
import React, { type JSX, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
import classNames from "classnames";
|
||||
import { Button, Link, Separator, Text } from "@vector-im/compound-web";
|
||||
@@ -21,10 +21,9 @@ import BaseCard from "./BaseCard";
|
||||
import WidgetUtils, { useWidgets } from "../../../utils/WidgetUtils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { useContextMenu } from "../../structures/ContextMenu";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import { type IApp } from "../../../stores/WidgetStore";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import { MAX_PINNED } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import WidgetAvatar from "../avatars/WidgetAvatar";
|
||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||
@@ -32,6 +31,7 @@ import EmptyState from "./EmptyState";
|
||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents.ts";
|
||||
import { UIComponent } from "../../../settings/UIFeature.ts";
|
||||
import { WidgetContextMenu } from "../../../viewmodels/room/right-panel/WidgetContextMenuViewModel.tsx";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface Props {
|
||||
room: Room;
|
||||
@@ -44,6 +44,7 @@ interface IAppRowProps {
|
||||
}
|
||||
|
||||
const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const name = WidgetUtils.getWidgetName(app);
|
||||
const [canModifyWidget, setCanModifyWidget] = useState<boolean>();
|
||||
|
||||
@@ -52,24 +53,24 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
|
||||
}, [room.client, room.roomId]);
|
||||
|
||||
const onOpenWidgetClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({
|
||||
sdkContext.rightPanelStore.pushCard({
|
||||
phase: RightPanelPhases.Widget,
|
||||
state: { widgetId: app.id },
|
||||
});
|
||||
};
|
||||
|
||||
const isPinned = WidgetLayoutStore.instance.isInContainer(room, app, "top");
|
||||
const isPinned = sdkContext.widgetLayoutStore.isInContainer(room, app, "top");
|
||||
const togglePin = isPinned
|
||||
? () => {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, app, "right");
|
||||
sdkContext.widgetLayoutStore.moveToContainer(room, app, "right");
|
||||
}
|
||||
: () => {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, app, "top");
|
||||
sdkContext.widgetLayoutStore.moveToContainer(room, app, "top");
|
||||
};
|
||||
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
|
||||
|
||||
const cannotPin = !isPinned && !WidgetLayoutStore.instance.canAddToContainer(room, "top");
|
||||
const cannotPin = !isPinned && !sdkContext.widgetLayoutStore.canAddToContainer(room, "top");
|
||||
|
||||
let pinTitle: string;
|
||||
if (cannotPin) {
|
||||
@@ -78,7 +79,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
|
||||
pinTitle = isPinned ? _t("action|unpin") : _t("action|pin");
|
||||
}
|
||||
|
||||
const isMaximised = WidgetLayoutStore.instance.isInContainer(room, app, "center");
|
||||
const isMaximised = sdkContext.widgetLayoutStore.isInContainer(room, app, "center");
|
||||
|
||||
let openTitle = "";
|
||||
if (isPinned) {
|
||||
@@ -142,6 +143,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
|
||||
* @param onClose callback when the card is closed
|
||||
*/
|
||||
const ExtensionsCard: React.FC<Props> = ({ room, onClose }) => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const apps = useWidgets(room);
|
||||
// Filter out virtual widgets
|
||||
const realApps = useMemo(() => apps.filter((app) => app.eventId !== undefined), [apps]);
|
||||
@@ -169,9 +171,9 @@ const ExtensionsCard: React.FC<Props> = ({ room, onClose }) => {
|
||||
);
|
||||
} else {
|
||||
let copyLayoutBtn: JSX.Element | null = null;
|
||||
if (WidgetLayoutStore.instance.canCopyLayoutToRoom(room)) {
|
||||
if (sdkContext.widgetLayoutStore.canCopyLayoutToRoom(room)) {
|
||||
copyLayoutBtn = (
|
||||
<Link onClick={() => WidgetLayoutStore.instance.copyLayoutToRoom(room)}>
|
||||
<Link onClick={() => sdkContext.widgetLayoutStore.copyLayoutToRoom(room)}>
|
||||
{_t("widget|set_room_layout")}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -23,10 +23,10 @@ import EncryptionPanel from "./EncryptionPanel";
|
||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
||||
import BaseCard from "./BaseCard";
|
||||
import QuestionDialog from "../dialogs/QuestionDialog";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { UserInfoHeaderView } from "./user_info/UserInfoHeaderView";
|
||||
import { UserInfoBasicView } from "./user_info/UserInfoBasicView";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
export interface IDevice extends Device {
|
||||
ambiguous?: boolean;
|
||||
@@ -181,18 +181,18 @@ interface IProps {
|
||||
}
|
||||
|
||||
const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPhases.MemberInfo, ...props }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
// fetch latest room member if we have a room, so we don't show historical information, falling back to user
|
||||
const member = useMemo(() => (room ? room.getMember(user.userId) || user : user), [room, user]);
|
||||
|
||||
const isRoomEncrypted = useIsEncrypted(cli, room);
|
||||
const isRoomEncrypted = useIsEncrypted(sdkContext.client!, room);
|
||||
const devices = useDevices(user.userId) ?? [];
|
||||
|
||||
const classes = ["mx_UserInfo"];
|
||||
|
||||
const onEncryptionPanelClose = (): void => {
|
||||
RightPanelStore.instance.popCard();
|
||||
sdkContext.rightPanelStore.popCard();
|
||||
};
|
||||
|
||||
let content: JSX.Element | undefined;
|
||||
@@ -237,7 +237,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
||||
onClose={onClose}
|
||||
closeLabel={closeLabel}
|
||||
onBack={(ev: ButtonEvent) => {
|
||||
if (RightPanelStore.instance.previousCard.phase === RightPanelPhases.MemberList) {
|
||||
if (sdkContext.rightPanelStore.previousCard.phase === RightPanelPhases.MemberList) {
|
||||
PosthogTrackers.trackInteraction("WebRightPanelRoomUserInfoBackButton", ev);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -9,16 +9,14 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type JSX, useContext, useEffect } from "react";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import BaseCard from "./BaseCard";
|
||||
import WidgetUtils, { useWidgets } from "../../../utils/WidgetUtils";
|
||||
import AppTile from "../elements/AppTile";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import Heading from "../typography/Heading";
|
||||
import { WidgetContextMenu } from "../../../viewmodels/room/right-panel/WidgetContextMenuViewModel";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
@@ -27,20 +25,20 @@ interface IProps {
|
||||
}
|
||||
|
||||
const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
const apps = useWidgets(room);
|
||||
const app = apps.find((a) => a.id === widgetId);
|
||||
const isRight = app && WidgetLayoutStore.instance.isInContainer(room, app, "right");
|
||||
const isRight = app && sdkContext.widgetLayoutStore.isInContainer(room, app, "right");
|
||||
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
||||
|
||||
useEffect(() => {
|
||||
if (!app || !isRight) {
|
||||
// stop showing this card
|
||||
RightPanelStore.instance.popCard();
|
||||
sdkContext.rightPanelStore.popCard();
|
||||
}
|
||||
}, [app, isRight]);
|
||||
}, [app, isRight, sdkContext.rightPanelStore]);
|
||||
|
||||
// Don't render anything as we are about to transition
|
||||
if (!app || !isRight) return null;
|
||||
@@ -78,7 +76,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
|
||||
fullWidth
|
||||
showMenubar={false}
|
||||
room={room}
|
||||
userId={cli.getSafeUserId()}
|
||||
userId={sdkContext.client?.getUserId() ?? undefined}
|
||||
creatorUserId={app.creatorUserId}
|
||||
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
|
||||
waitForIframeLoad={app.waitForIframeLoad}
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type AriaRole } from "react";
|
||||
import React, { type AriaRole, useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Resizable, type Size } from "re-resizable";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
@@ -79,14 +79,14 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
|
||||
this.context.resizeNotifier.on("isResizing", this.onIsResizing);
|
||||
|
||||
ScalarMessaging.startListening();
|
||||
WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps);
|
||||
this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.unmounted = true;
|
||||
ScalarMessaging.stopListening();
|
||||
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps);
|
||||
this.context.widgetLayoutStore.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.resizeContainer) {
|
||||
this.resizer.detach();
|
||||
@@ -117,7 +117,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
|
||||
},
|
||||
onResizeStop: () => {
|
||||
this.resizeContainer?.classList.remove("mx_AppsDrawer--resizing");
|
||||
WidgetLayoutStore.instance.setResizerDistributions(
|
||||
this.context.widgetLayoutStore.setResizerDistributions(
|
||||
this.props.room,
|
||||
"top",
|
||||
this.topApps()
|
||||
@@ -166,7 +166,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
private loadResizerPreferences = (): void => {
|
||||
const distributions = WidgetLayoutStore.instance.getResizerDistributions(this.props.room, "top");
|
||||
const distributions = this.context.widgetLayoutStore.getResizerDistributions(this.props.room, "top");
|
||||
if (this.state.apps && this.topApps().length - 1 === distributions.length) {
|
||||
distributions.forEach((size, i) => {
|
||||
const distributor = this.resizer.forHandleAt(i);
|
||||
@@ -206,8 +206,8 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
private getApps = (): IState["apps"] => ({
|
||||
["top"]: WidgetLayoutStore.instance.getContainerWidgets(this.props.room, "top"),
|
||||
["center"]: WidgetLayoutStore.instance.getContainerWidgets(this.props.room, "center"),
|
||||
["top"]: this.context.widgetLayoutStore.getContainerWidgets(this.props.room, "top"),
|
||||
["center"]: this.context.widgetLayoutStore.getContainerWidgets(this.props.room, "center"),
|
||||
});
|
||||
private topApps = (): IWidget[] => this.state.apps["top"];
|
||||
private centerApps = (): IWidget[] => this.state.apps["center"];
|
||||
@@ -321,7 +321,8 @@ const PersistentVResizer: React.FC<IPersistentResizerProps> = ({
|
||||
resizeNotifier,
|
||||
children,
|
||||
}) => {
|
||||
let defaultHeight = WidgetLayoutStore.instance.getContainerHeight(room, "top");
|
||||
const sdkContext = useContext(SDKContext);
|
||||
let defaultHeight = sdkContext.widgetLayoutStore.getContainerHeight(room, "top");
|
||||
|
||||
// Arbitrary defaults to avoid NaN problems. 100 px or 3/4 of the visible window.
|
||||
if (!minHeight) minHeight = 100;
|
||||
@@ -352,7 +353,7 @@ const PersistentVResizer: React.FC<IPersistentResizerProps> = ({
|
||||
let newHeight = defaultHeight! + d.height;
|
||||
newHeight = percentageOf(newHeight, minHeight, maxHeight) * 100;
|
||||
|
||||
WidgetLayoutStore.instance.setContainerHeight(room, "top", newHeight);
|
||||
sdkContext.widgetLayoutStore.setContainerHeight(room, "top", newHeight);
|
||||
|
||||
resizeNotifier.stopResizing();
|
||||
}}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, useContext, useEffect, useId, useRef, useState } from "react";
|
||||
import React, { type JSX, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
|
||||
import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
|
||||
@@ -15,7 +15,6 @@ import { EventPreviewView, useCreateAutoDisposedViewModel } from "@element-hq/we
|
||||
|
||||
import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
@@ -254,11 +253,6 @@ function Indicator({ active, hidden }: IndicatorProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function getRightPanelPhase(roomId: string): RightPanelPhases | null {
|
||||
if (!RightPanelStore.instance.isOpenForRoom(roomId)) return null;
|
||||
return RightPanelStore.instance.currentCard.phase;
|
||||
}
|
||||
|
||||
/**
|
||||
* The props for the {@link BannerButton} component.
|
||||
*/
|
||||
@@ -273,8 +267,18 @@ interface BannerButtonProps {
|
||||
* A button that allows the user to view or close the list of pinned messages.
|
||||
*/
|
||||
function BannerButton({ room }: BannerButtonProps): JSX.Element {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
const getRightPanelPhase = useCallback(
|
||||
(roomId: string): RightPanelPhases | null => {
|
||||
if (!sdkContext.rightPanelStore.isOpenForRoom(roomId)) return null;
|
||||
return sdkContext.rightPanelStore.currentCard.phase;
|
||||
},
|
||||
[sdkContext.rightPanelStore],
|
||||
);
|
||||
|
||||
const [currentPhase, setCurrentPhase] = useState<RightPanelPhases | null>(getRightPanelPhase(room.roomId));
|
||||
useEventEmitter(RightPanelStore.instance, UPDATE_EVENT, () => setCurrentPhase(getRightPanelPhase(room.roomId)));
|
||||
useEventEmitter(sdkContext.rightPanelStore, UPDATE_EVENT, () => setCurrentPhase(getRightPanelPhase(room.roomId)));
|
||||
const isPinnedMessagesPhase = currentPhase === RightPanelPhases.PinnedMessages;
|
||||
|
||||
return (
|
||||
@@ -285,7 +289,7 @@ function BannerButton({ room }: BannerButtonProps): JSX.Element {
|
||||
if (isPinnedMessagesPhase) PosthogTrackers.trackInteraction("PinnedMessageBannerCloseListButton");
|
||||
else PosthogTrackers.trackInteraction("PinnedMessageBannerViewAllButton");
|
||||
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.PinnedMessages);
|
||||
sdkContext.rightPanelStore.showOrHidePhase(RightPanelPhases.PinnedMessages);
|
||||
}}
|
||||
>
|
||||
{isPinnedMessagesPhase
|
||||
|
||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, useCallback, useState } from "react";
|
||||
import React, { type JSX, useCallback, useContext, useState } from "react";
|
||||
import { Text, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web";
|
||||
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
|
||||
import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call-solid";
|
||||
@@ -26,7 +26,6 @@ import { HistoryIcon, UserProfileSolidIcon } from "@vector-im/compound-design-to
|
||||
|
||||
import { useRoomName } from "../../../../hooks/useRoomName.ts";
|
||||
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases.ts";
|
||||
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
|
||||
import { useRoomMemberCount, useRoomMembers } from "../../../../hooks/useRoomMembers.ts";
|
||||
import { _t } from "../../../../languageHandler.tsx";
|
||||
import { getPlatformCallTypeProps, useRoomCall } from "../../../../hooks/room/useRoomCall.tsx";
|
||||
@@ -39,7 +38,6 @@ import FacePile from "../../elements/FacePile.tsx";
|
||||
import { useRoomState } from "../../../../hooks/useRoomState.ts";
|
||||
import RoomAvatar from "../../avatars/RoomAvatar.tsx";
|
||||
import { formatCount } from "../../../../utils/FormattingUtils.ts";
|
||||
import RightPanelStore from "../../../../stores/right-panel/RightPanelStore.ts";
|
||||
import PosthogTrackers from "../../../../PosthogTrackers.ts";
|
||||
import { VideoRoomChatButton } from "./VideoRoomChatButton.tsx";
|
||||
import { RoomKnocksBar } from "../RoomKnocksBar.tsx";
|
||||
@@ -58,6 +56,7 @@ import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/Curr
|
||||
import { LocalRoom } from "../../../../models/LocalRoom.ts";
|
||||
import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";
|
||||
import { useUserStatus } from "../../../../hooks/useUserStatus.ts";
|
||||
import { SDKContext } from "../../../../contexts/SDKContext.ts";
|
||||
|
||||
function RoomHeaderButtons({
|
||||
room,
|
||||
@@ -68,6 +67,7 @@ function RoomHeaderButtons({
|
||||
legacyAdditionalButtons?: ViewRoomOpts["buttons"];
|
||||
extraButtons?: JSX.Element;
|
||||
}): JSX.Element {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const members = useRoomMembers(room, 2500);
|
||||
const memberCount = useRoomMemberCount(room, { throttleWait: 2500, includeInvited: true });
|
||||
|
||||
@@ -338,7 +338,7 @@ function RoomHeaderButtons({
|
||||
indicator={notificationLevelToIndicator(threadNotifications)}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.ThreadPanel);
|
||||
sdkContext.rightPanelStore.showOrHidePhase(RightPanelPhases.ThreadPanel);
|
||||
PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", evt);
|
||||
}}
|
||||
aria-label={_t("common|threads")}
|
||||
@@ -352,7 +352,7 @@ function RoomHeaderButtons({
|
||||
indicator={notificationLevelToIndicator(globalNotificationState.level)}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.NotificationPanel);
|
||||
sdkContext.rightPanelStore.showOrHidePhase(RightPanelPhases.NotificationPanel);
|
||||
}}
|
||||
aria-label={_t("notifications|enable_prompt_toast_title")}
|
||||
>
|
||||
@@ -365,7 +365,7 @@ function RoomHeaderButtons({
|
||||
<IconButton
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary);
|
||||
sdkContext.rightPanelStore.showOrHidePhase(RightPanelPhases.RoomSummary);
|
||||
}}
|
||||
aria-label={_t("right_panel|room_summary_card|title")}
|
||||
>
|
||||
@@ -383,7 +383,7 @@ function RoomHeaderButtons({
|
||||
viewUserOnClick={false}
|
||||
tooltipLabel={_t("room|header_face_pile_tooltip")}
|
||||
onClick={(e: ButtonEvent) => {
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.MemberList);
|
||||
sdkContext.rightPanelStore.showOrHidePhase(RightPanelPhases.MemberList);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
aria-label={_t("common|n_members", { count: memberCount })}
|
||||
@@ -443,15 +443,15 @@ export default function RoomHeader({
|
||||
legacyAdditionalButtons?: ViewRoomOpts["buttons"];
|
||||
oobData?: IOOBData;
|
||||
}): JSX.Element {
|
||||
const client = useMatrixClientContext();
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const roomName = useRoomName(room);
|
||||
const joinRule = useRoomState(room, (state) => state.getJoinRule());
|
||||
const historyVisibility = useRoomState(room, (state) => state.getHistoryVisibility());
|
||||
const dmMember = useDmMember(room);
|
||||
const isDirectMessage = !!dmMember;
|
||||
const dmUserStatus = useUserStatus(dmMember?.userId);
|
||||
const isRoomEncrypted = useIsEncrypted(client, room);
|
||||
const e2eStatus = useEncryptionStatus(client, room);
|
||||
const isRoomEncrypted = useIsEncrypted(sdkContext.client!, room);
|
||||
const e2eStatus = useEncryptionStatus(sdkContext.client!, room);
|
||||
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
|
||||
const onAvatarClick = (): void => {
|
||||
defaultDispatcher.dispatch({
|
||||
@@ -482,7 +482,7 @@ export default function RoomHeader({
|
||||
onClick={
|
||||
room instanceof LocalRoom
|
||||
? undefined
|
||||
: () => RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary)
|
||||
: () => sdkContext.rightPanelStore.showOrHidePhase(RightPanelPhases.RoomSummary)
|
||||
}
|
||||
className="mx_RoomHeader_infoWrapper"
|
||||
>
|
||||
|
||||
@@ -6,25 +6,27 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, type FC } from "react";
|
||||
import React, { type JSX, type FC, useContext } from "react";
|
||||
import { type Room, JoinRule, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { LockSolidIcon, VideoCallSolidIcon, PublicIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
|
||||
import { useRoomState } from "../../../hooks/useRoomState";
|
||||
import { useRoomMemberCount, useMyRoomMembership } from "../../../hooks/useRoomMembers";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
const RoomInfoLine: FC<IProps> = ({ room }) => {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
// summary will begin as undefined whilst loading and go null if it fails to load or we are not invited.
|
||||
const summary = useAsyncMemo(async (): Promise<Awaited<ReturnType<MatrixClient["getRoomSummary"]>> | null> => {
|
||||
if (room.getMyMembership() !== KnownMembership.Invite) return null;
|
||||
@@ -64,7 +66,7 @@ const RoomInfoLine: FC<IProps> = ({ room }) => {
|
||||
} else if (memberCount && summary !== undefined) {
|
||||
// summary is not still loading
|
||||
const viewMembers = (): void =>
|
||||
RightPanelStore.instance.setCard({
|
||||
sdkContext.rightPanelStore.setCard({
|
||||
phase: RightPanelPhases.MemberList,
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { type IWidget } from "matrix-widget-api";
|
||||
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import AppTile from "../elements/AppTile";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import WidgetUtils, { type UserWidget } from "../../../utils/WidgetUtils";
|
||||
@@ -24,9 +23,9 @@ import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingSto
|
||||
import { type ActionPayload } from "../../../dispatcher/payloads";
|
||||
import type ScalarAuthClient from "../../../ScalarAuthClient";
|
||||
import GenericElementContextMenu from "../context_menus/GenericElementContextMenu";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import StickerpackPlaceholder from "../../../../res/img/stickerpack-placeholder.png";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
// This should be below the dialog level (4000), but above the rest of the UI (1000-2000).
|
||||
// We sit in a context menu, so this should be given to the context menu.
|
||||
@@ -50,6 +49,9 @@ interface IState {
|
||||
}
|
||||
|
||||
export default class Stickerpicker extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public static defaultProps: Partial<IProps> = {
|
||||
threadId: null,
|
||||
};
|
||||
@@ -130,17 +132,16 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
|
||||
// Track updates to widget state in account data
|
||||
MatrixClientPeg.safeGet().on(ClientEvent.AccountData, this.updateWidget);
|
||||
this.context.client?.on(ClientEvent.AccountData, this.updateWidget);
|
||||
|
||||
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
// Initialise widget state from current account data
|
||||
this.updateWidget();
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client) client.removeListener(ClientEvent.AccountData, this.updateWidget);
|
||||
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
this.context.client?.removeListener(ClientEvent.AccountData, this.updateWidget);
|
||||
this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
@@ -280,9 +281,9 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
|
||||
room={this.props.room}
|
||||
threadId={this.props.threadId}
|
||||
fullWidth={true}
|
||||
userId={MatrixClientPeg.safeGet().credentials.userId!}
|
||||
userId={this.context.client?.credentials.userId ?? undefined}
|
||||
creatorUserId={
|
||||
stickerpickerWidget.sender || MatrixClientPeg.safeGet().credentials.userId!
|
||||
stickerpickerWidget.sender || this.context.client?.credentials.userId || undefined
|
||||
}
|
||||
waitForIframeLoad={true}
|
||||
showMenubar={true}
|
||||
|
||||
+5
-3
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, useState } from "react";
|
||||
import React, { type JSX, useContext, useState } from "react";
|
||||
import { Menu, MenuItem } from "@vector-im/compound-web";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
@@ -16,7 +16,6 @@ import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
|
||||
import { Action } from "../../../../dispatcher/actions";
|
||||
import defaultDispatcher from "../../../../dispatcher/dispatcher";
|
||||
import { type ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import RightPanelStore from "../../../../stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { useUnreadThreadRooms } from "./useUnreadThreadRooms";
|
||||
import { StatelessNotificationBadge } from "../../rooms/NotificationBadge/StatelessNotificationBadge";
|
||||
@@ -25,6 +24,7 @@ import PosthogTrackers from "../../../../PosthogTrackers";
|
||||
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
|
||||
import { useSettingValue } from "../../../../hooks/useSettings";
|
||||
import { SDKContext } from "../../../../contexts/SDKContext.ts";
|
||||
|
||||
interface ThreadsActivityCentreProps {
|
||||
/**
|
||||
@@ -117,6 +117,8 @@ interface ThreadsActivityRow {
|
||||
* Display a room with unread threads.
|
||||
*/
|
||||
function ThreadsActivityCentreRow({ room, onClick, notificationLevel }: ThreadsActivityRow): JSX.Element {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className="mx_ThreadsActivityCentreRow"
|
||||
@@ -125,7 +127,7 @@ function ThreadsActivityCentreRow({ room, onClick, notificationLevel }: ThreadsA
|
||||
|
||||
// Set the right panel card for that room so the threads panel is open before we dispatch,
|
||||
// so it will open once the room appears.
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.ThreadPanel }, true, room.roomId);
|
||||
sdkContext.rightPanelStore.setCard({ phase: RightPanelPhases.ThreadPanel }, true, room.roomId);
|
||||
|
||||
// Track the click on the room
|
||||
PosthogTrackers.trackInteraction("WebThreadsActivityCentreRoomItem", event);
|
||||
|
||||
@@ -16,7 +16,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type Device } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
@@ -25,9 +24,9 @@ import Modal from "../../../Modal";
|
||||
import GenericToast from "./GenericToast";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import VerificationRequestDialog from "../dialogs/VerificationRequestDialog";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getDeviceCryptoInfo } from "../../../utils/crypto/deviceInfo";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
toastKey: string;
|
||||
@@ -42,6 +41,9 @@ interface IState {
|
||||
}
|
||||
|
||||
export default class VerificationRequestToast extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
private intervalHandle?: number;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
@@ -69,7 +71,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||
|
||||
const otherDeviceId = request.otherDeviceId;
|
||||
if (request.isSelfVerification && !!otherDeviceId) {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const cli = this.context.client!;
|
||||
const device = await cli.getDevice(otherDeviceId);
|
||||
this.setState({
|
||||
ip: device.last_seen_ip,
|
||||
@@ -104,7 +106,6 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||
const { request } = this.props;
|
||||
// no room id for to_device requests
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
try {
|
||||
if (request.roomId) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
@@ -113,8 +114,8 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||
should_peek: false,
|
||||
metricsTrigger: "VerificationRequest",
|
||||
});
|
||||
const member = cli.getUser(request.otherUserId) ?? undefined;
|
||||
RightPanelStore.instance.setCards(
|
||||
const member = this.context.client?.getUser(request.otherUserId) ?? undefined;
|
||||
this.context.rightPanelStore.setCards(
|
||||
[
|
||||
{ phase: RightPanelPhases.RoomSummary },
|
||||
{ phase: RightPanelPhases.MemberInfo, state: { member } },
|
||||
@@ -154,7 +155,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
const client = this.context.client!;
|
||||
const userId = request.otherUserId;
|
||||
const roomId = request.roomId;
|
||||
description = roomId ? userLabelForEventRoom(client, userId, roomId) : userId;
|
||||
|
||||
@@ -199,6 +199,7 @@ function CallStartedTileViewWrapped({ mxEvent, getRelationsForEvent }: IBodyProp
|
||||
cli,
|
||||
callStore: sdkContext.callStore,
|
||||
latestRtcNotificationEventStore: sdkContext.latestRtcNotificationEventStore,
|
||||
legacyCallHandler: sdkContext.legacyCallHandler,
|
||||
}),
|
||||
);
|
||||
return <RootCallTileView vm={vm} />;
|
||||
|
||||
@@ -215,10 +215,10 @@ export const useRoomCall = (
|
||||
widget = groupCall?.widget ?? jitsiWidget;
|
||||
}
|
||||
const updateWidgetState = useCallback((): void => {
|
||||
setCanPinWidget(WidgetLayoutStore.instance.canAddToContainer(room, "top"));
|
||||
setWidgetPinned(!!widget && WidgetLayoutStore.instance.isInContainer(room, widget, "top"));
|
||||
}, [room, widget]);
|
||||
useEventEmitter(WidgetLayoutStore.instance, WidgetLayoutStore.emissionForRoom(room), updateWidgetState);
|
||||
setCanPinWidget(sdkContext.widgetLayoutStore.canAddToContainer(room, "top"));
|
||||
setWidgetPinned(!!widget && sdkContext.widgetLayoutStore.isInContainer(room, widget, "top"));
|
||||
}, [room, widget, sdkContext.widgetLayoutStore]);
|
||||
useEventEmitter(sdkContext.widgetLayoutStore, WidgetLayoutStore.emissionForRoom(room), updateWidgetState);
|
||||
useEffect(() => {
|
||||
updateWidgetState();
|
||||
}, [room, jitsiWidget, groupCall, updateWidgetState]);
|
||||
@@ -267,25 +267,39 @@ export const useRoomCall = (
|
||||
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||
evt?.stopPropagation();
|
||||
if (widget && promptPinWidget) {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, "top");
|
||||
sdkContext.widgetLayoutStore.moveToContainer(room, widget, "top");
|
||||
} else {
|
||||
placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined, true);
|
||||
placeCall(
|
||||
sdkContext.legacyCallHandler,
|
||||
room,
|
||||
CallType.Voice,
|
||||
callPlatformType,
|
||||
evt?.shiftKey || undefined,
|
||||
true,
|
||||
);
|
||||
}
|
||||
},
|
||||
[promptPinWidget, room, widget],
|
||||
[promptPinWidget, room, widget, sdkContext.widgetLayoutStore, sdkContext.legacyCallHandler],
|
||||
);
|
||||
const videoCallClick = useCallback(
|
||||
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||
evt?.stopPropagation();
|
||||
if (widget && promptPinWidget) {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, "top");
|
||||
sdkContext.widgetLayoutStore.moveToContainer(room, widget, "top");
|
||||
} else {
|
||||
// If we have pressed shift then always skip the lobby, otherwise `undefined` will defer
|
||||
// to the defaults of the call implementation.
|
||||
placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined, false);
|
||||
placeCall(
|
||||
sdkContext.legacyCallHandler,
|
||||
room,
|
||||
CallType.Video,
|
||||
callPlatformType,
|
||||
evt?.shiftKey || undefined,
|
||||
false,
|
||||
);
|
||||
}
|
||||
},
|
||||
[widget, promptPinWidget, room],
|
||||
[widget, promptPinWidget, room, sdkContext.widgetLayoutStore, sdkContext.legacyCallHandler],
|
||||
);
|
||||
|
||||
let voiceCallDisabledReason: string | null;
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import { LegacyCallHandlerEvent } from "../LegacyCallHandler";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { _t } from "../languageHandler";
|
||||
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
||||
import AccessibleButton, { type ButtonEvent } from "../components/views/elements/AccessibleButton";
|
||||
@@ -96,7 +95,7 @@ export default class IncomingLegacyCallToast extends React.Component<IProps, ISt
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const room = MatrixClientPeg.safeGet().getRoom(this.roomId);
|
||||
const room = this.context.client?.getRoom(this.roomId);
|
||||
const isVoice = this.props.call.type === CallType.Voice;
|
||||
const callForcedSilent = this.context.legacyCallHandler.isForcedSilent();
|
||||
|
||||
@@ -109,7 +108,7 @@ export default class IncomingLegacyCallToast extends React.Component<IProps, ISt
|
||||
<React.Fragment>
|
||||
<RoomAvatar room={room ?? undefined} size="32px" />
|
||||
<div className="mx_IncomingLegacyCallToast_content">
|
||||
<span className="mx_LegacyCallEvent_caller">{room ? room.name : _t("voip|unknown_caller")}</span>
|
||||
<span className="mx_LegacyCallEvent_caller">{room?.name ?? _t("voip|unknown_caller")}</span>
|
||||
<div className="mx_LegacyCallEvent_type">
|
||||
{getCallStateIcon(isVoice, undefined)}
|
||||
{isVoice ? _t("voip|voice_call") : _t("voip|video_call")}
|
||||
|
||||
@@ -9,12 +9,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { type CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { getPlatformCallTypeProps, PlatformCallType } from "../../hooks/room/useRoomCall";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { SDKContextClass } from "../../contexts/SDKContextClass.ts";
|
||||
|
||||
/**
|
||||
* Helper to place a call in a room that works with all the legacy modes
|
||||
@@ -24,6 +24,7 @@ import { SDKContextClass } from "../../contexts/SDKContextClass.ts";
|
||||
* @param skipLobby Has the user indicated they would like to skip the lobby. Otherwise, defer to platform defaults.
|
||||
*/
|
||||
export const placeCall = async (
|
||||
legacyCallHandler: LegacyCallHandler,
|
||||
room: Room,
|
||||
callType: CallType,
|
||||
platformCallType: PlatformCallType,
|
||||
@@ -34,7 +35,7 @@ export const placeCall = async (
|
||||
PosthogTrackers.trackInteraction(analyticsName);
|
||||
|
||||
if (platformCallType == PlatformCallType.LegacyCall || platformCallType == PlatformCallType.JitsiCall) {
|
||||
await SDKContextClass.instance.legacyCallHandler.placeCall(room.roomId, callType);
|
||||
await legacyCallHandler.placeCall(room.roomId, callType);
|
||||
} else if (platformCallType == PlatformCallType.ElementCall) {
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { vi, describe, it, expect } from "vitest";
|
||||
import { createTestClient, TestSDKContext } from "test-utils";
|
||||
|
||||
import { verifyUser } from "./verification";
|
||||
import defaultDispatcher from "./dispatcher/dispatcher";
|
||||
import DMRoomMap from "./utils/DMRoomMap.ts";
|
||||
import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases.ts";
|
||||
|
||||
describe("verifyUser", () => {
|
||||
const sdkContext = new TestSDKContext();
|
||||
sdkContext._client = createTestClient();
|
||||
DMRoomMap.makeShared(sdkContext._client);
|
||||
|
||||
it("should require registration if user is a guest", () => {
|
||||
vi.spyOn(defaultDispatcher, "dispatch");
|
||||
vi.spyOn(sdkContext._client!, "isGuest").mockReturnValue(true);
|
||||
verifyUser(
|
||||
sdkContext.rightPanelStore,
|
||||
sdkContext.client!,
|
||||
sdkContext.client!.getUser(sdkContext.client!.getUserId()!)!,
|
||||
);
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "require_registration" });
|
||||
});
|
||||
|
||||
it("should open verification in right panel", () => {
|
||||
vi.spyOn(sdkContext.rightPanelStore, "setCards");
|
||||
vi.spyOn(sdkContext._client!, "isGuest").mockReturnValue(false);
|
||||
verifyUser(
|
||||
sdkContext.rightPanelStore,
|
||||
sdkContext.client!,
|
||||
sdkContext.client!.getUser(sdkContext.client!.getUserId()!)!,
|
||||
);
|
||||
expect(sdkContext.rightPanelStore.setCards).toHaveBeenCalledWith([
|
||||
{ phase: RightPanelPhases.RoomSummary },
|
||||
expect.objectContaining({ phase: RightPanelPhases.MemberInfo }),
|
||||
expect.objectContaining({ phase: RightPanelPhases.EncryptionPanel }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import { type VerificationRequest } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import dis from "./dispatcher/dispatcher";
|
||||
import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
||||
import type RightPanelStore from "./stores/right-panel/RightPanelStore";
|
||||
import { type IRightPanelCardState } from "./stores/right-panel/RightPanelStoreIPanelState";
|
||||
import { findDMForUser } from "./utils/dm/findDMForUser";
|
||||
|
||||
@@ -20,20 +20,20 @@ import { findDMForUser } from "./utils/dm/findDMForUser";
|
||||
*
|
||||
* Note: cross-signing must be set up before calling this function.
|
||||
*/
|
||||
export async function verifyUser(matrixClient: MatrixClient, user: User): Promise<void> {
|
||||
export function verifyUser(rightPanelStore: RightPanelStore, matrixClient: MatrixClient, user: User): void {
|
||||
if (matrixClient.isGuest()) {
|
||||
dis.dispatch({ action: "require_registration" });
|
||||
return;
|
||||
}
|
||||
const existingRequest = pendingVerificationRequestForUser(matrixClient, user);
|
||||
setRightPanel({ member: user, verificationRequest: existingRequest });
|
||||
setRightPanel(rightPanelStore, { member: user, verificationRequest: existingRequest });
|
||||
}
|
||||
|
||||
function setRightPanel(state: IRightPanelCardState): void {
|
||||
if (RightPanelStore.instance.roomPhaseHistory.some((card) => card.phase == RightPanelPhases.RoomSummary)) {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.EncryptionPanel, state });
|
||||
function setRightPanel(rightPanelStore: RightPanelStore, state: IRightPanelCardState): void {
|
||||
if (rightPanelStore.roomPhaseHistory.some((card) => card.phase == RightPanelPhases.RoomSummary)) {
|
||||
rightPanelStore.pushCard({ phase: RightPanelPhases.EncryptionPanel, state });
|
||||
} else {
|
||||
RightPanelStore.instance.setCards([
|
||||
rightPanelStore.setCards([
|
||||
{ phase: RightPanelPhases.RoomSummary },
|
||||
{ phase: RightPanelPhases.MemberInfo, state: { member: state.member } },
|
||||
{ phase: RightPanelPhases.EncryptionPanel, state },
|
||||
|
||||
+46
-6
@@ -11,7 +11,14 @@ import { it, describe, expect, vi } from "vitest";
|
||||
import { type EventTimeline, EventType, type MatrixEvent, type RoomState } from "matrix-js-sdk/src/matrix";
|
||||
import { EventEmitter } from "node:stream";
|
||||
|
||||
import { mkEvent, mkMessage, mkRoomMember, mkStubRoom, stubClient } from "../../../../../../test/test-utils";
|
||||
import {
|
||||
mkEvent,
|
||||
mkMessage,
|
||||
mkRoomMember,
|
||||
mkStubRoom,
|
||||
stubClient,
|
||||
TestSDKContext,
|
||||
} from "../../../../../../test/test-utils";
|
||||
import { getMockedRtcNotificationEvent, MockedCall, MockedCallStore } from "./call-mocks";
|
||||
import { RootCallTileViewModel } from "./RootCallTileViewModel";
|
||||
import {
|
||||
@@ -92,10 +99,19 @@ function getMocked(userIds: string[]) {
|
||||
}
|
||||
|
||||
describe("RootCallTileViewModel", () => {
|
||||
const sdkContext = new TestSDKContext();
|
||||
const legacyCallHandler = sdkContext.legacyCallHandler;
|
||||
|
||||
it("computes correct tileType for ongoing call in DM", () => {
|
||||
const { callStore, cli, mxEvent, latestRtcNotificationEventStore } = getMocked(["@alice:m.org", "@bob:m.org"]);
|
||||
latestRtcNotificationEventStore.getLatestEventId = () => "new-event";
|
||||
const vm = new RootCallTileViewModel({ latestRtcNotificationEventStore, callStore, cli, mxEvent });
|
||||
const vm = new RootCallTileViewModel({
|
||||
latestRtcNotificationEventStore,
|
||||
callStore,
|
||||
cli,
|
||||
mxEvent,
|
||||
legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().tileType).toStrictEqual("ongoing-call-dm");
|
||||
});
|
||||
@@ -107,7 +123,13 @@ describe("RootCallTileViewModel", () => {
|
||||
"@jack:m.org",
|
||||
]);
|
||||
latestRtcNotificationEventStore.getLatestEventId = () => "new-event";
|
||||
const vm = new RootCallTileViewModel({ latestRtcNotificationEventStore, callStore, cli, mxEvent });
|
||||
const vm = new RootCallTileViewModel({
|
||||
latestRtcNotificationEventStore,
|
||||
callStore,
|
||||
cli,
|
||||
mxEvent,
|
||||
legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().tileType).toStrictEqual("ongoing-call-room");
|
||||
});
|
||||
@@ -115,7 +137,13 @@ describe("RootCallTileViewModel", () => {
|
||||
it("computes correct tileType for tombstone call in DM", () => {
|
||||
// When there's an ongoing call
|
||||
const { callStore, cli, mxEvent, latestRtcNotificationEventStore } = getMocked(["@alice:m.org", "@bob:m.org"]);
|
||||
const vm = new RootCallTileViewModel({ latestRtcNotificationEventStore, callStore, cli, mxEvent });
|
||||
const vm = new RootCallTileViewModel({
|
||||
latestRtcNotificationEventStore,
|
||||
callStore,
|
||||
cli,
|
||||
mxEvent,
|
||||
legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().tileType).toStrictEqual("tombstone-call-dm");
|
||||
});
|
||||
@@ -127,7 +155,13 @@ describe("RootCallTileViewModel", () => {
|
||||
"@bob:m.org",
|
||||
"@jack:m.org",
|
||||
]);
|
||||
const vm = new RootCallTileViewModel({ latestRtcNotificationEventStore, callStore, cli, mxEvent });
|
||||
const vm = new RootCallTileViewModel({
|
||||
latestRtcNotificationEventStore,
|
||||
callStore,
|
||||
cli,
|
||||
mxEvent,
|
||||
legacyCallHandler,
|
||||
});
|
||||
expect(vm.getSnapshot().tileType).toStrictEqual("tombstone-call-room");
|
||||
});
|
||||
|
||||
@@ -138,7 +172,13 @@ describe("RootCallTileViewModel", () => {
|
||||
"@bob:m.org",
|
||||
"@jack:m.org",
|
||||
]);
|
||||
const vm = new RootCallTileViewModel({ latestRtcNotificationEventStore, callStore, cli, mxEvent });
|
||||
const vm = new RootCallTileViewModel({
|
||||
latestRtcNotificationEventStore,
|
||||
callStore,
|
||||
cli,
|
||||
mxEvent,
|
||||
legacyCallHandler,
|
||||
});
|
||||
expect(vm.getSnapshot().tileType).toStrictEqual("tombstone-call-room");
|
||||
|
||||
// Tile type should update on event
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type LatestRtcNotificationEventStore,
|
||||
} from "../../../../../stores/LatestRtcNotificationEventStore";
|
||||
import { JitsiCall } from "../../../../../models/Call";
|
||||
import type LegacyCallHandler from "../../../../../LegacyCallHandler.tsx";
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
@@ -41,6 +42,11 @@ interface Props {
|
||||
*/
|
||||
callStore: CallStore;
|
||||
|
||||
/**
|
||||
* {@link LegacyCallHandler} to handle calls in a room.
|
||||
*/
|
||||
legacyCallHandler: LegacyCallHandler;
|
||||
|
||||
/**
|
||||
* {@link LatestRtcNotificationEventStore} to track the latest notification event id.
|
||||
*/
|
||||
@@ -83,6 +89,7 @@ function computeSnapshot(props: Props): RootCallTileViewSnapshot {
|
||||
getRelationsForEvent: props.getRelationsForEvent,
|
||||
callStore: props.callStore,
|
||||
cli: props.cli,
|
||||
legacyCallHandler: props.legacyCallHandler,
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -94,6 +101,7 @@ function computeSnapshot(props: Props): RootCallTileViewSnapshot {
|
||||
getRelationsForEvent: props.getRelationsForEvent,
|
||||
callStore: props.callStore,
|
||||
cli: props.cli,
|
||||
legacyCallHandler: props.legacyCallHandler,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
+11
-8
@@ -18,6 +18,7 @@ import { getMockedMember, getMockedRtcNotificationEvent, MockedCall, MockedCallS
|
||||
import type { EventTimeline, RoomState } from "matrix-js-sdk/src/matrix";
|
||||
import { placeCall } from "../../../../../../../utils/room/placeCall";
|
||||
import { PlatformCallType } from "../../../../../../../hooks/room/useRoomCall";
|
||||
import { SDKContextClass } from "../../../../../../../contexts/SDKContextClass.ts";
|
||||
|
||||
/**
|
||||
* There's a nasty circular dependency in useRoomCall so that we end up with:
|
||||
@@ -40,6 +41,8 @@ vi.mock(import("../../../../../../../utils/room/placeCall"), () => {
|
||||
const roomId = "!my-room:m.org";
|
||||
|
||||
describe("BaseOngoingCallViewModel", () => {
|
||||
const legacyCallHandler = SDKContextClass.instance.legacyCallHandler;
|
||||
|
||||
describe("should compute the correct snapshot", () => {
|
||||
it("startedByDisplayName", () => {
|
||||
const cli = stubClient();
|
||||
@@ -50,7 +53,7 @@ describe("BaseOngoingCallViewModel", () => {
|
||||
const call = MockedCall.create();
|
||||
const callStore = MockedCallStore.create(call);
|
||||
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
expect(vm.getSnapshot().startedByDisplayName).toStrictEqual("Alice");
|
||||
});
|
||||
|
||||
@@ -65,7 +68,7 @@ describe("BaseOngoingCallViewModel", () => {
|
||||
const callStore = MockedCallStore.create(call);
|
||||
|
||||
// Alice hasn't joined the call yet
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
expect(vm.getSnapshot().isJoined).toStrictEqual(false);
|
||||
|
||||
// Alice has joined call
|
||||
@@ -85,11 +88,11 @@ describe("BaseOngoingCallViewModel", () => {
|
||||
const call = MockedCall.create();
|
||||
const callStore = MockedCallStore.create(call);
|
||||
|
||||
const vm1 = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm1 = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
expect(vm1.getSnapshot().callDirection).toStrictEqual(CallDirection.Incoming);
|
||||
|
||||
vi.spyOn(cli, "getUserId").mockReturnValue("@alice:m.org");
|
||||
const vm2 = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm2 = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
expect(vm2.getSnapshot().callDirection).toStrictEqual(CallDirection.Outgoing);
|
||||
});
|
||||
|
||||
@@ -103,7 +106,7 @@ describe("BaseOngoingCallViewModel", () => {
|
||||
const callStore = MockedCallStore.create(call);
|
||||
|
||||
// Call has no other participants other than alice
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
expect(vm.getSnapshot().callHasOtherParticipants).toStrictEqual(false);
|
||||
|
||||
// Let's say others join
|
||||
@@ -135,7 +138,7 @@ describe("BaseOngoingCallViewModel", () => {
|
||||
const call = MockedCall.create();
|
||||
const callStore = MockedCallStore.create(call);
|
||||
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
expect(vm.getSnapshot().isJoinable).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -148,10 +151,10 @@ describe("BaseOngoingCallViewModel", () => {
|
||||
|
||||
const call = MockedCall.create().withParticipants([mxEvent.sender]);
|
||||
const callStore = MockedCallStore.create(call);
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new BaseOngoingCallViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
|
||||
vm.join();
|
||||
const [room, callType, platformCallType] = vi.mocked(placeCall).mock.calls[0];
|
||||
const [_, room, callType, platformCallType] = vi.mocked(placeCall).mock.calls[0];
|
||||
expect(room.roomId).toStrictEqual(roomId);
|
||||
expect(callType).toStrictEqual(CallType.Video);
|
||||
expect(platformCallType).toStrictEqual(PlatformCallType.ElementCall);
|
||||
|
||||
+13
-1
@@ -29,6 +29,7 @@ import { PlatformCallType } from "../../../../../../../hooks/room/useRoomCall";
|
||||
import { type GetRelationsForEvent } from "../../../../../../../components/views/rooms/EventTile";
|
||||
import { getIntentFromEvent } from "../../common";
|
||||
import { DurationViewModel } from "./components/DurationViewModel";
|
||||
import type LegacyCallHandler from "../../../../../../../LegacyCallHandler.tsx";
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
@@ -51,6 +52,10 @@ export interface Props {
|
||||
* {@link CallStore} to access calls in a room.
|
||||
*/
|
||||
callStore: CallStore;
|
||||
/**
|
||||
* {@link LegacyCallHandler} to handle calls in a room.
|
||||
*/
|
||||
legacyCallHandler: LegacyCallHandler;
|
||||
}
|
||||
|
||||
function getCallOrThrow(store: CallStore, roomId: string): ElementCall {
|
||||
@@ -167,7 +172,14 @@ export class BaseOngoingCallViewModel<
|
||||
}
|
||||
const callType = getIntentFromEvent(this.props.mxEvent);
|
||||
const type = callType === SharedComponentsCallType.Voice ? CallType.Voice : CallType.Video;
|
||||
placeCall(room, type, PlatformCallType.ElementCall, event?.shiftKey || undefined, type === CallType.Voice);
|
||||
placeCall(
|
||||
this.props.legacyCallHandler,
|
||||
room,
|
||||
type,
|
||||
PlatformCallType.ElementCall,
|
||||
event?.shiftKey || undefined,
|
||||
type === CallType.Voice,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-3
@@ -10,13 +10,16 @@
|
||||
import { it, describe, expect } from "vitest";
|
||||
import { CallType } from "@element-hq/web-shared-components";
|
||||
|
||||
import { stubClient } from "../../../../../../../../test/test-utils";
|
||||
import { stubClient, TestSDKContext } from "../../../../../../../../test/test-utils";
|
||||
import { getMockedMember, getMockedRtcNotificationEvent, MockedCall, MockedCallStore } from "../../call-mocks";
|
||||
import { DmOngoingCallTileViewModel } from "./DmOngoingCallTileViewModel";
|
||||
|
||||
const roomId = "!my-room:m.org";
|
||||
|
||||
describe("DmOngoingCallTileViewModel", () => {
|
||||
const sdkContext = new TestSDKContext();
|
||||
const legacyCallHandler = sdkContext.legacyCallHandler;
|
||||
|
||||
describe("should compute the correct snapshot", () => {
|
||||
describe("callType", () => {
|
||||
it("voice", () => {
|
||||
@@ -27,7 +30,7 @@ describe("DmOngoingCallTileViewModel", () => {
|
||||
|
||||
const call = MockedCall.create().withParticipants([mxEvent.sender]);
|
||||
const callStore = MockedCallStore.create(call);
|
||||
const vm = new DmOngoingCallTileViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new DmOngoingCallTileViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
|
||||
expect(vm.getSnapshot().callType).toStrictEqual(CallType.Voice);
|
||||
});
|
||||
@@ -40,7 +43,7 @@ describe("DmOngoingCallTileViewModel", () => {
|
||||
|
||||
const call = MockedCall.create().withParticipants([mxEvent.sender]);
|
||||
const callStore = MockedCallStore.create(call);
|
||||
const vm = new DmOngoingCallTileViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new DmOngoingCallTileViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
|
||||
expect(vm.getSnapshot().callType).toStrictEqual(CallType.Video);
|
||||
});
|
||||
|
||||
+13
-3
@@ -10,7 +10,7 @@
|
||||
import { it, describe, expect, vi } from "vitest";
|
||||
import { EventType, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { stubClient } from "../../../../../../../../test/test-utils";
|
||||
import { stubClient, TestSDKContext } from "../../../../../../../../test/test-utils";
|
||||
import {
|
||||
getMockedMember,
|
||||
getMockedRtcDeclineEvent,
|
||||
@@ -24,6 +24,9 @@ import { CallEvent } from "../../../../../../../models/Call";
|
||||
const roomId = "!my-room:m.org";
|
||||
|
||||
describe("RoomOngoingCallTileViewModel", () => {
|
||||
const sdkContext = new TestSDKContext();
|
||||
const legacyCallHandler = sdkContext.legacyCallHandler;
|
||||
|
||||
describe("should compute the correct snapshot", () => {
|
||||
it("totalParticipants", () => {
|
||||
const cli = stubClient();
|
||||
@@ -35,7 +38,7 @@ describe("RoomOngoingCallTileViewModel", () => {
|
||||
const james = getMockedMember(roomId, "@james:m.org", "James");
|
||||
const call = MockedCall.create().withParticipants([mxEvent.sender, bob, james]);
|
||||
const callStore = MockedCallStore.create(call);
|
||||
const vm = new RoomOngoingCallTileViewModel({ mxEvent, cli, callStore, roomId });
|
||||
const vm = new RoomOngoingCallTileViewModel({ mxEvent, cli, callStore, roomId, legacyCallHandler });
|
||||
|
||||
// Call has 3 participants now
|
||||
expect(vm.getSnapshot().totalParticipants).toStrictEqual(3);
|
||||
@@ -59,7 +62,14 @@ describe("RoomOngoingCallTileViewModel", () => {
|
||||
|
||||
const call = MockedCall.create().withParticipants([mxEvent.sender]);
|
||||
const callStore = MockedCallStore.create(call);
|
||||
const vm = new RoomOngoingCallTileViewModel({ mxEvent, cli, callStore, roomId, getRelationsForEvent });
|
||||
const vm = new RoomOngoingCallTileViewModel({
|
||||
mxEvent,
|
||||
cli,
|
||||
callStore,
|
||||
roomId,
|
||||
getRelationsForEvent,
|
||||
legacyCallHandler,
|
||||
});
|
||||
|
||||
// Call hasn't been ignored yet
|
||||
expect(vm.getSnapshot().isCallIgnored).toStrictEqual(false);
|
||||
|
||||
Reference in New Issue
Block a user