Prepare for Element Call integration (#9224)

* Improve accessibility and testability of Tooltip

Adding a role to Tooltip was motivated by React Testing Library's
reliance on accessibility-related attributes to locate elements.

* Make the ReadyWatchingStore constructor safer

The ReadyWatchingStore constructor previously had a chance to
immediately call onReady, which was dangerous because it was potentially
calling the derived class's onReady at a point when the derived class
hadn't even finished construction yet. In normal usage, I guess this
never was a problem, but it was causing some of the tests I was writing
to crash. This is solved by separating out the onReady call into a start
method.

* Rename 1:1 call components to 'LegacyCall'

to reflect the fact that they're slated for removal, and to not clash
with the new Call code.

* Refactor VideoChannelStore into Call and CallStore

Call is an abstract class that currently only has a Jitsi
implementation, but this will make it easy to later add an Element Call
implementation.

* Remove WidgetReady, ClientReady, and ForceHangupCall hacks

These are no longer used by the new Jitsi call implementation, and can
be removed.

* yarn i18n

* Delete call map entries instead of inserting nulls

* Allow multiple active calls and consolidate call listeners

* Fix a race condition when creating a video room

* Un-hardcode the media device fallback labels

* Apply misc code review fixes

* yarn i18n

* Disconnect from calls more politely on logout

* Fix some strict mode errors

* Fix another updateRoom race condition
This commit is contained in:
Robin 2022-08-30 15:13:39 -04:00 committed by GitHub
parent 50f6986f6c
commit 0d6a550c33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 2573 additions and 2157 deletions

View file

@ -20,13 +20,13 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { _t } from '../../../languageHandler';
import ContextMenu, { IProps as IContextMenuProps, MenuItem } from '../../structures/ContextMenu';
import CallHandler from '../../../CallHandler';
import LegacyCallHandler from '../../../LegacyCallHandler';
interface IProps extends IContextMenuProps {
call: MatrixCall;
}
export default class CallContextMenu extends React.Component<IProps> {
export default class LegacyCallContextMenu extends React.Component<IProps> {
static propTypes = {
// js-sdk User object. Not required because it might not exist.
user: PropTypes.object,
@ -42,13 +42,13 @@ export default class CallContextMenu extends React.Component<IProps> {
};
onUnholdClick = () => {
CallHandler.instance.setActiveCallRoomId(this.props.call.roomId);
LegacyCallHandler.instance.setActiveCallRoomId(this.props.call.roomId);
this.props.onFinished();
};
onTransferClick = () => {
CallHandler.instance.showTransferDialog(this.props.call);
LegacyCallHandler.instance.showTransferDialog(this.props.call);
this.props.onFinished();
};
@ -58,13 +58,13 @@ export default class CallContextMenu extends React.Component<IProps> {
let transferItem;
if (this.props.call.opponentCanBeTransferred()) {
transferItem = <MenuItem className="mx_CallContextMenu_item" onClick={this.onTransferClick}>
transferItem = <MenuItem className="mx_LegacyCallContextMenu_item" onClick={this.onTransferClick}>
{ _t("Transfer") }
</MenuItem>;
}
return <ContextMenu {...this.props}>
<MenuItem className="mx_CallContextMenu_item" onClick={handler}>
<MenuItem className="mx_LegacyCallContextMenu_item" onClick={handler}>
{ holdUnholdCaption }
</MenuItem>
{ transferItem }

View file

@ -56,7 +56,7 @@ import QuestionDialog from "./QuestionDialog";
import Spinner from "../elements/Spinner";
import BaseDialog from "./BaseDialog";
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
import CallHandler from "../../../CallHandler";
import LegacyCallHandler from "../../../LegacyCallHandler";
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
import CopyableText from "../elements/CopyableText";
import { ScreenName } from '../../../PosthogTrackers';
@ -510,13 +510,13 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return;
}
CallHandler.instance.startTransferToMatrixID(
LegacyCallHandler.instance.startTransferToMatrixID(
this.props.call,
targetIds[0],
this.state.consultFirst,
);
} else {
CallHandler.instance.startTransferToPhoneNumber(
LegacyCallHandler.instance.startTransferToPhoneNumber(
this.props.call,
this.state.dialPadValue,
this.state.consultFirst,

View file

@ -36,10 +36,9 @@ import { aboveLeftOf, ContextMenuButton } from "../../structures/ContextMenu";
import PersistedElement, { getPersistKey } from "./PersistedElement";
import { WidgetType } from "../../../widgets/WidgetType";
import { ElementWidget, StopGapWidget } from "../../../stores/widgets/StopGapWidget";
import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions";
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
import WidgetAvatar from "../avatars/WidgetAvatar";
import CallHandler from '../../../CallHandler';
import LegacyCallHandler from '../../../LegacyCallHandler';
import { IApp } from "../../../stores/WidgetStore";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
@ -305,7 +304,6 @@ export default class AppTile extends React.Component<IProps, IState> {
private setupSgListeners() {
this.sgWidget.on("preparing", this.onWidgetPreparing);
this.sgWidget.on("ready", this.onWidgetReady);
// emits when the capabilities have been set up or changed
this.sgWidget.on("capabilitiesNotified", this.onWidgetCapabilitiesNotified);
}
@ -313,7 +311,6 @@ export default class AppTile extends React.Component<IProps, IState> {
private stopSgListeners() {
if (!this.sgWidget) return;
this.sgWidget.off("preparing", this.onWidgetPreparing);
this.sgWidget.off("ready", this.onWidgetReady);
this.sgWidget.off("capabilitiesNotified", this.onWidgetCapabilitiesNotified);
}
@ -393,7 +390,7 @@ export default class AppTile extends React.Component<IProps, IState> {
}
if (WidgetType.JITSI.matches(this.props.app.type) && this.props.room) {
CallHandler.instance.hangupCallApp(this.props.room.roomId);
LegacyCallHandler.instance.hangupCallApp(this.props.room.roomId);
}
// Delete the widget from the persisted store for good measure.
@ -407,12 +404,6 @@ export default class AppTile extends React.Component<IProps, IState> {
this.setState({ loading: false });
};
private onWidgetReady = (): void => {
if (WidgetType.JITSI.matches(this.props.app.type)) {
this.sgWidget.widgetApi.transport.send(ElementWidgetActions.ClientReady, {});
}
};
private onWidgetCapabilitiesNotified = (): void => {
this.setState({
requiresClient: this.sgWidget.widgetApi.hasCapability(ElementWidgetCapabilities.RequiresClient),

View file

@ -181,7 +181,7 @@ export default class Tooltip extends React.PureComponent<ITooltipProps, State> {
style.display = this.props.visible ? "block" : "none";
const tooltip = (
<div className={tooltipClasses} style={style}>
<div role="tooltip" className={tooltipClasses} style={style}>
<div className="mx_Tooltip_chevron" />
{ this.props.label }
</div>

View file

@ -21,7 +21,10 @@ import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import MemberAvatar from '../avatars/MemberAvatar';
import CallEventGrouper, { CallEventGrouperEvent, CustomCallState } from '../../structures/CallEventGrouper';
import LegacyCallEventGrouper, {
LegacyCallEventGrouperEvent,
CustomCallState,
} from '../../structures/LegacyCallEventGrouper';
import AccessibleButton from '../elements/AccessibleButton';
import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip';
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
@ -32,7 +35,7 @@ const MAX_NON_NARROW_WIDTH = 450 / 70 * 100;
interface IProps {
mxEvent: MatrixEvent;
callEventGrouper: CallEventGrouper;
callEventGrouper: LegacyCallEventGrouper;
timestamp?: JSX.Element;
}
@ -43,7 +46,7 @@ interface IState {
length: number;
}
export default class CallEvent extends React.PureComponent<IProps, IState> {
export default class LegacyCallEvent extends React.PureComponent<IProps, IState> {
private wrapperElement = createRef<HTMLDivElement>();
private resizeObserver: ResizeObserver;
@ -59,18 +62,18 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
componentDidMount() {
this.props.callEventGrouper.addListener(CallEventGrouperEvent.StateChanged, this.onStateChanged);
this.props.callEventGrouper.addListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged);
this.props.callEventGrouper.addListener(CallEventGrouperEvent.LengthChanged, this.onLengthChanged);
this.props.callEventGrouper.addListener(LegacyCallEventGrouperEvent.StateChanged, this.onStateChanged);
this.props.callEventGrouper.addListener(LegacyCallEventGrouperEvent.SilencedChanged, this.onSilencedChanged);
this.props.callEventGrouper.addListener(LegacyCallEventGrouperEvent.LengthChanged, this.onLengthChanged);
this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
this.wrapperElement.current && this.resizeObserver.observe(this.wrapperElement.current);
}
componentWillUnmount() {
this.props.callEventGrouper.removeListener(CallEventGrouperEvent.StateChanged, this.onStateChanged);
this.props.callEventGrouper.removeListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged);
this.props.callEventGrouper.removeListener(CallEventGrouperEvent.LengthChanged, this.onLengthChanged);
this.props.callEventGrouper.removeListener(LegacyCallEventGrouperEvent.StateChanged, this.onStateChanged);
this.props.callEventGrouper.removeListener(LegacyCallEventGrouperEvent.SilencedChanged, this.onSilencedChanged);
this.props.callEventGrouper.removeListener(LegacyCallEventGrouperEvent.LengthChanged, this.onLengthChanged);
this.resizeObserver.disconnect();
}
@ -97,7 +100,7 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
private renderCallBackButton(text: string): JSX.Element {
return (
<AccessibleButton
className="mx_CallEvent_content_button mx_CallEvent_content_button_callBack"
className="mx_LegacyCallEvent_content_button mx_LegacyCallEvent_content_button_callBack"
onClick={this.props.callEventGrouper.callBack}
kind="primary"
>
@ -108,9 +111,9 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
private renderSilenceIcon(): JSX.Element {
const silenceClass = classNames({
"mx_CallEvent_iconButton": true,
"mx_CallEvent_unSilence": this.state.silenced,
"mx_CallEvent_silence": !this.state.silenced,
"mx_LegacyCallEvent_iconButton": true,
"mx_LegacyCallEvent_unSilence": this.state.silenced,
"mx_LegacyCallEvent_silence": !this.state.silenced,
});
return (
@ -130,17 +133,17 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
{ silenceIcon }
<AccessibleButton
className="mx_CallEvent_content_button mx_CallEvent_content_button_reject"
className="mx_LegacyCallEvent_content_button mx_LegacyCallEvent_content_button_reject"
onClick={this.props.callEventGrouper.rejectCall}
kind="danger"
>
<span> { _t("Decline") } </span>
</AccessibleButton>
<AccessibleButton
className="mx_CallEvent_content_button mx_CallEvent_content_button_answer"
className="mx_LegacyCallEvent_content_button mx_LegacyCallEvent_content_button_answer"
onClick={this.props.callEventGrouper.answerCall}
kind="primary"
>
@ -156,7 +159,7 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
if (gotRejected) {
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
{ _t("Call declined") }
{ this.renderCallBackButton(_t("Call back")) }
{ this.props.timestamp }
@ -175,14 +178,14 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
text += " • " + formatCallTime(duration);
}
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
{ text }
{ this.props.timestamp }
</div>
);
} else if (hangupReason === CallErrorCode.InviteTimeout) {
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
{ _t("No answer") }
{ this.renderCallBackButton(_t("Call back")) }
{ this.props.timestamp }
@ -212,10 +215,10 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
<InfoTooltip
tooltip={reason}
className="mx_CallEvent_content_tooltip"
className="mx_LegacyCallEvent_content_tooltip"
kind={InfoTooltipKind.Warning}
/>
{ _t("Connection failed") }
@ -226,7 +229,7 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
if (state === CallState.Connected) {
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
<Clock seconds={this.state.length} aria-live="off" />
{ this.props.timestamp }
</div>
@ -234,7 +237,7 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
if (state === CallState.Connecting) {
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
{ _t("Connecting") }
{ this.props.timestamp }
</div>
@ -242,7 +245,7 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
if (state === CustomCallState.Missed) {
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
{ _t("Missed call") }
{ this.renderCallBackButton(_t("Call back")) }
{ this.props.timestamp }
@ -251,7 +254,7 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
return (
<div className="mx_CallEvent_content">
<div className="mx_LegacyCallEvent_content">
{ _t("The call is in an unknown state!") }
{ this.props.timestamp }
</div>
@ -266,13 +269,13 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
const callState = this.state.callState;
const hangupReason = this.props.callEventGrouper.hangupReason;
const content = this.renderContent(callState);
const className = classNames("mx_CallEvent", {
mx_CallEvent_voice: isVoice,
mx_CallEvent_video: !isVoice,
mx_CallEvent_narrow: this.state.narrow,
mx_CallEvent_missed: callState === CustomCallState.Missed,
mx_CallEvent_noAnswer: callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout,
mx_CallEvent_rejected: callState === CallState.Ended && this.props.callEventGrouper.gotRejected,
const className = classNames("mx_LegacyCallEvent", {
mx_LegacyCallEvent_voice: isVoice,
mx_LegacyCallEvent_video: !isVoice,
mx_LegacyCallEvent_narrow: this.state.narrow,
mx_LegacyCallEvent_missed: callState === CustomCallState.Missed,
mx_LegacyCallEvent_noAnswer: callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout,
mx_LegacyCallEvent_rejected: callState === CallState.Ended && this.props.callEventGrouper.gotRejected,
});
let silenceIcon;
if (this.state.narrow && this.state.callState === CallState.Ringing) {
@ -280,21 +283,21 @@ export default class CallEvent extends React.PureComponent<IProps, IState> {
}
return (
<div className="mx_CallEvent_wrapper" ref={this.wrapperElement}>
<div className="mx_LegacyCallEvent_wrapper" ref={this.wrapperElement}>
<div className={className}>
{ silenceIcon }
<div className="mx_CallEvent_info">
<div className="mx_LegacyCallEvent_info">
<MemberAvatar
member={event.sender}
width={32}
height={32}
/>
<div className="mx_CallEvent_info_basic">
<div className="mx_CallEvent_sender">
<div className="mx_LegacyCallEvent_info_basic">
<div className="mx_LegacyCallEvent_sender">
{ sender }
</div>
<div className="mx_CallEvent_type">
<div className="mx_CallEvent_type_icon" />
<div className="mx_LegacyCallEvent_type">
<div className="mx_LegacyCallEvent_type_icon" />
{ callType }
</div>
</div>

View file

@ -27,7 +27,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { UIFeature } from "../../../settings/UIFeature";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import CallViewForRoom from '../voip/CallViewForRoom';
import LegacyCallViewForRoom from '../voip/LegacyCallViewForRoom';
import { objectHasDiff } from "../../../utils/objects";
interface IProps {
@ -123,7 +123,7 @@ export default class AuxPanel extends React.Component<IProps, IState> {
render() {
const callView = (
<CallViewForRoom
<LegacyCallViewForRoom
roomId={this.props.room.roomId}
resizeNotifier={this.props.resizeNotifier}
showApps={this.props.showApps}

View file

@ -47,7 +47,7 @@ import EditorStateTransfer from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "./NotificationBadge";
import CallEventGrouper from "../../structures/CallEventGrouper";
import LegacyCallEventGrouper from "../../structures/LegacyCallEventGrouper";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from '../../../dispatcher/actions';
import PlatformPeg from '../../../PlatformPeg';
@ -200,8 +200,8 @@ interface IProps {
// Helper to build permalinks for the room
permalinkCreator?: RoomPermalinkCreator;
// CallEventGrouper for this event
callEventGrouper?: CallEventGrouper;
// LegacyCallEventGrouper for this event
callEventGrouper?: LegacyCallEventGrouper;
// Symbol of the root node
as?: string;

View file

@ -19,11 +19,11 @@ import React, { createRef } from "react";
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import classNames from "classnames";
import type { Call } from "../../../models/Call";
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
import defaultDispatcher from '../../../dispatcher/dispatcher';
import { Action } from "../../../dispatcher/actions";
import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
@ -45,8 +45,9 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { RoomViewStore } from "../../../stores/RoomViewStore";
import VideoRoomSummary from "./VideoRoomSummary";
import { RoomTileCallSummary } from "./RoomTileCallSummary";
import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu";
import { CallStore, CallStoreEvent } from "../../../stores/CallStore";
interface IProps {
room: Room;
@ -61,6 +62,7 @@ interface IState {
selected: boolean;
notificationsMenuPosition: PartialDOMRect;
generalMenuPosition: PartialDOMRect;
call: Call | null;
messagePreview?: string;
}
@ -79,7 +81,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
private roomTileRef = createRef<HTMLDivElement>();
private notificationState: NotificationState;
private roomProps: RoomEchoChamber;
private isVideoRoom: boolean;
constructor(props: IProps) {
super(props);
@ -88,6 +89,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
selected: RoomViewStore.instance.getRoomId() === this.props.room.roomId,
notificationsMenuPosition: null,
generalMenuPosition: null,
call: CallStore.instance.get(this.props.room.roomId),
// generatePreview() will return nothing if the user has previews disabled
messagePreview: "",
};
@ -95,7 +97,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room);
this.roomProps = EchoChamber.forRoom(this.props.room);
this.isVideoRoom = SettingsStore.getValue("feature_video_rooms") && this.props.room.isElementVideoRoom();
}
private onRoomNameUpdate = (room: Room) => {
@ -154,6 +155,11 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
this.notificationState.on(NotificationStateEvents.Update, this.onNotificationUpdate);
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
this.props.room.on(RoomEvent.Name, this.onRoomNameUpdate);
CallStore.instance.on(CallStoreEvent.Call, this.onCallChanged);
// Recalculate the call for this room, since it could've changed between
// construction and mounting
this.setState({ call: CallStore.instance.get(this.props.room.roomId) });
}
public componentWillUnmount() {
@ -166,6 +172,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
defaultDispatcher.unregister(this.dispatcherRef);
this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate);
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
CallStore.instance.off(CallStoreEvent.Call, this.onCallChanged);
}
private onAction = (payload: ActionPayload) => {
@ -185,6 +192,10 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
}
};
private onCallChanged = (call: Call, roomId: string) => {
if (roomId === this.props.room?.roomId) this.setState({ call });
};
private async generatePreview() {
if (!this.showMessagePreview) {
return null;
@ -362,10 +373,10 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
}
let subtitle;
if (this.isVideoRoom) {
if (this.state.call) {
subtitle = (
<div className="mx_RoomTile_subtitle">
<VideoRoomSummary room={this.props.room} />
<RoomTileCallSummary call={this.state.call} />
</div>
);
} else if (this.showMessagePreview && this.state.messagePreview) {

View file

@ -16,66 +16,56 @@ limitations under the License.
import React, { FC } from "react";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import type { Call } from "../../../models/Call";
import { _t, TranslatedString } from "../../../languageHandler";
import {
ConnectionState,
useConnectionState,
useConnectedMembers,
useJitsiParticipants,
} from "../../../utils/VideoChannelUtils";
import { useConnectionState, useParticipants } from "../../../hooks/useCall";
import { ConnectionState } from "../../../models/Call";
interface IProps {
room: Room;
interface Props {
call: Call;
}
const VideoRoomSummary: FC<IProps> = ({ room }) => {
const connectionState = useConnectionState(room);
const videoMembers = useConnectedMembers(room, connectionState === ConnectionState.Connected);
const jitsiParticipants = useJitsiParticipants(room);
export const RoomTileCallSummary: FC<Props> = ({ call }) => {
const connectionState = useConnectionState(call);
const participants = useParticipants(call);
let indicator: TranslatedString;
let text: TranslatedString;
let active: boolean;
let participantCount: number;
switch (connectionState) {
case ConnectionState.Disconnected:
indicator = _t("Video");
text = _t("Video");
active = false;
participantCount = videoMembers.size;
break;
case ConnectionState.Connecting:
indicator = _t("Joining…");
text = _t("Joining…");
active = true;
participantCount = videoMembers.size;
break;
case ConnectionState.Connected:
indicator = _t("Joined");
case ConnectionState.Disconnecting:
text = _t("Joined");
active = true;
participantCount = jitsiParticipants.length;
break;
}
return <span className="mx_VideoRoomSummary">
return <span className="mx_RoomTileCallSummary">
<span
className={classNames(
"mx_VideoRoomSummary_indicator",
{ "mx_VideoRoomSummary_indicator_active": active },
"mx_RoomTileCallSummary_text",
{ "mx_RoomTileCallSummary_text_active": active },
)}
>
{ indicator }
{ text }
</span>
{ participantCount ? <>
{ participants.size ? <>
{ " · " }
<span
className="mx_VideoRoomSummary_participants"
aria-label={_t("%(count)s participants", { count: participantCount })}
className="mx_RoomTileCallSummary_participants"
aria-label={_t("%(count)s participants", { count: participants.size })}
>
{ participantCount }
{ participants.size }
</span>
</> : null }
</span>;
};
export default VideoRoomSummary;

View file

@ -26,7 +26,7 @@ import DateSeparator from "../messages/DateSeparator";
import EventTile from "./EventTile";
import { shouldFormContinuation } from "../../structures/MessagePanel";
import { wantsDateSeparator } from "../../../DateUtils";
import CallEventGrouper, { buildCallEventGroupers } from "../../structures/CallEventGrouper";
import LegacyCallEventGrouper, { buildLegacyCallEventGroupers } from "../../structures/LegacyCallEventGrouper";
import { haveRendererForEvent } from "../../../events/EventTileFactory";
interface IProps {
@ -44,17 +44,17 @@ export default class SearchResultTile extends React.Component<IProps> {
static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
// A map of <callId, CallEventGrouper>
private callEventGroupers = new Map<string, CallEventGrouper>();
// A map of <callId, LegacyCallEventGrouper>
private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
constructor(props, context) {
super(props, context);
this.buildCallEventGroupers(this.props.searchResult.context.getTimeline());
this.buildLegacyCallEventGroupers(this.props.searchResult.context.getTimeline());
}
private buildCallEventGroupers(events?: MatrixEvent[]): void {
this.callEventGroupers = buildCallEventGroupers(this.callEventGroupers, events);
private buildLegacyCallEventGroupers(events?: MatrixEvent[]): void {
this.callEventGroupers = buildLegacyCallEventGroupers(this.callEventGroupers, events);
}
public render() {

View file

@ -28,7 +28,7 @@ interface IState {
feeds: Array<CallFeed>;
}
export default class AudioFeedArrayForCall extends React.Component<IProps, IState> {
export default class AudioFeedArrayForLegacyCall extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);

View file

@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { FC, useState, useMemo, useRef, useEffect } from "react";
import React, { FC, useState, useMemo, useRef, useEffect, useCallback } from "react";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from "../../../languageHandler";
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
import { useConnectedMembers } from "../../../utils/VideoChannelUtils";
import VideoChannelStore from "../../../stores/VideoChannelStore";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
import { useParticipants } from "../../../hooks/useCall";
import { CallStore } from "../../../stores/CallStore";
import { Call } from "../../../models/Call";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
@ -34,25 +36,22 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import FacePile from "../elements/FacePile";
import MemberAvatar from "../avatars/MemberAvatar";
interface IDeviceButtonProps {
interface DeviceButtonProps {
kind: string;
devices: MediaDeviceInfo[];
setDevice: (device: MediaDeviceInfo) => void;
deviceListLabel: string;
active: boolean;
fallbackDeviceLabel: (n: number) => string;
muted: boolean;
disabled: boolean;
toggle: () => void;
activeTitle: string;
inactiveTitle: string;
unmutedTitle: string;
mutedTitle: string;
}
const DeviceButton: FC<IDeviceButtonProps> = ({
kind, devices, setDevice, deviceListLabel, active, disabled, toggle, activeTitle, inactiveTitle,
const DeviceButton: FC<DeviceButtonProps> = ({
kind, devices, setDevice, deviceListLabel, fallbackDeviceLabel, muted, disabled, toggle, unmutedTitle, mutedTitle,
}) => {
// Depending on permissions, the browser might not let us know device labels,
// in which case there's nothing helpful we can display
const labelledDevices = useMemo(() => devices.filter(d => d.label.length), [devices]);
const [menuDisplayed, buttonRef, openMenu, closeMenu] = useContextMenu();
let contextMenu;
if (menuDisplayed) {
@ -61,13 +60,13 @@ const DeviceButton: FC<IDeviceButtonProps> = ({
closeMenu();
};
const buttonRect = buttonRef.current.getBoundingClientRect();
const buttonRect = buttonRef.current!.getBoundingClientRect();
contextMenu = <IconizedContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu}>
<IconizedContextMenuOptionList>
{ labelledDevices.map(d =>
{ devices.map((d, index) =>
<IconizedContextMenuOption
key={d.deviceId}
label={d.label}
label={d.label || fallbackDeviceLabel(index + 1)}
onClick={() => selectDevice(d)}
/>,
) }
@ -78,21 +77,20 @@ const DeviceButton: FC<IDeviceButtonProps> = ({
if (!devices.length) return null;
return <div
className={classNames({
"mx_VideoLobby_deviceButtonWrapper": true,
"mx_VideoLobby_deviceButtonWrapper_active": active,
className={classNames("mx_CallLobby_deviceButtonWrapper", {
"mx_CallLobby_deviceButtonWrapper_muted": muted,
})}
>
<AccessibleTooltipButton
className={`mx_VideoLobby_deviceButton mx_VideoLobby_deviceButton_${kind}`}
title={active ? activeTitle : inactiveTitle}
className={`mx_CallLobby_deviceButton mx_CallLobby_deviceButton_${kind}`}
title={muted ? mutedTitle : unmutedTitle}
alignment={Alignment.Top}
onClick={toggle}
disabled={disabled}
/>
{ labelledDevices.length > 1 ? (
{ devices.length > 1 ? (
<ContextMenuButton
className="mx_VideoLobby_deviceListButton"
className="mx_CallLobby_deviceListButton"
inputRef={buttonRef}
onClick={openMenu}
isExpanded={menuDisplayed}
@ -106,57 +104,65 @@ const DeviceButton: FC<IDeviceButtonProps> = ({
const MAX_FACES = 8;
const VideoLobby: FC<{ room: Room }> = ({ room }) => {
const store = VideoChannelStore.instance;
interface Props {
room: Room;
call: Call;
}
export const CallLobby: FC<Props> = ({ room, call }) => {
const [connecting, setConnecting] = useState(false);
const me = useMemo(() => room.getMember(room.myUserId), [room]);
const connectedMembers = useConnectedMembers(room, false);
const videoRef = useRef<HTMLVideoElement>();
const me = useMemo(() => room.getMember(room.myUserId)!, [room]);
const participants = useParticipants(call);
const videoRef = useRef<HTMLVideoElement>(null);
const devices = useAsyncMemo(async () => {
const [audioInputs, videoInputs] = useAsyncMemo(async () => {
try {
return await navigator.mediaDevices.enumerateDevices();
const devices = await MediaDeviceHandler.getDevices();
return [devices[MediaDeviceKindEnum.AudioInput], devices[MediaDeviceKindEnum.VideoInput]];
} catch (e) {
logger.warn(`Failed to get media device list: ${e}`);
return [];
logger.warn(`Failed to get media device list`, e);
return [[], []];
}
}, [], []);
const audioDevices = useMemo(() => devices.filter(d => d.kind === "audioinput"), [devices]);
const videoDevices = useMemo(() => devices.filter(d => d.kind === "videoinput"), [devices]);
}, [], [[], []]);
const [selectedAudioDevice, selectAudioDevice] = useState<MediaDeviceInfo>(null);
const [selectedVideoDevice, selectVideoDevice] = useState<MediaDeviceInfo>(null);
const [videoInputId, setVideoInputId] = useState<string>(() => MediaDeviceHandler.getVideoInput());
const audioDevice = selectedAudioDevice ?? audioDevices[0];
const videoDevice = selectedVideoDevice ?? videoDevices[0];
const setAudioInput = useCallback((device: MediaDeviceInfo) => {
MediaDeviceHandler.instance.setAudioInput(device.deviceId);
}, []);
const setVideoInput = useCallback((device: MediaDeviceInfo) => {
MediaDeviceHandler.instance.setVideoInput(device.deviceId);
setVideoInputId(device.deviceId);
}, []);
const [audioActive, setAudioActive] = useState(!store.audioMuted);
const [videoActive, setVideoActive] = useState(!store.videoMuted);
const toggleAudio = () => {
store.audioMuted = audioActive;
setAudioActive(!audioActive);
};
const toggleVideo = () => {
store.videoMuted = videoActive;
setVideoActive(!videoActive);
};
const [audioMuted, setAudioMuted] = useState(() => MediaDeviceHandler.startWithAudioMuted);
const [videoMuted, setVideoMuted] = useState(() => MediaDeviceHandler.startWithVideoMuted);
const toggleAudio = useCallback(() => {
MediaDeviceHandler.startWithAudioMuted = !audioMuted;
setAudioMuted(!audioMuted);
}, [audioMuted, setAudioMuted]);
const toggleVideo = useCallback(() => {
MediaDeviceHandler.startWithVideoMuted = !videoMuted;
setVideoMuted(!videoMuted);
}, [videoMuted, setVideoMuted]);
const videoStream = useAsyncMemo(async () => {
if (videoDevice && videoActive) {
if (videoInputId && !videoMuted) {
try {
return await navigator.mediaDevices.getUserMedia({
video: { deviceId: videoDevice.deviceId },
video: { deviceId: videoInputId },
});
} catch (e) {
logger.error(`Failed to get stream for device ${videoDevice.deviceId}: ${e}`);
logger.error(`Failed to get stream for device ${videoInputId}`, e);
}
}
return null;
}, [videoDevice, videoActive]);
}, [videoInputId, videoMuted]);
useEffect(() => {
if (videoStream) {
const videoElement = videoRef.current;
const videoElement = videoRef.current!;
videoElement.srcObject = videoStream;
videoElement.play();
@ -167,67 +173,69 @@ const VideoLobby: FC<{ room: Room }> = ({ room }) => {
}
}, [videoStream]);
const connect = async () => {
const connect = useCallback(async () => {
setConnecting(true);
try {
await store.connect(
room.roomId, audioActive ? audioDevice : null, videoActive ? videoDevice : null,
);
// Disconnect from any other active calls first, since we don't yet support holding
await Promise.all([...CallStore.instance.activeCalls].map(call => call.disconnect()));
await call.connect();
} catch (e) {
logger.error(e);
setConnecting(false);
}
};
}, [call, setConnecting]);
let facePile;
if (connectedMembers.size) {
const shownMembers = [...connectedMembers].slice(0, MAX_FACES);
const overflow = connectedMembers.size > shownMembers.length;
let facePile: JSX.Element | null = null;
if (participants.size) {
const shownMembers = [...participants].slice(0, MAX_FACES);
const overflow = participants.size > shownMembers.length;
facePile = <div className="mx_VideoLobby_connectedMembers">
{ _t("%(count)s people joined", { count: connectedMembers.size }) }
facePile = <div className="mx_CallLobby_participants">
{ _t("%(count)s people joined", { count: participants.size }) }
<FacePile members={shownMembers} faceSize={24} overflow={overflow} />
</div>;
}
return <div className="mx_VideoLobby">
return <div className="mx_CallLobby">
{ facePile }
<div className="mx_VideoLobby_preview">
<div className="mx_CallLobby_preview">
<MemberAvatar key={me.userId} member={me} width={200} height={200} resizeMethod="scale" />
<video
ref={videoRef}
style={{ visibility: videoActive ? null : "hidden" }}
style={{ visibility: videoMuted ? "hidden" : undefined }}
muted
playsInline
disablePictureInPicture
/>
<div className="mx_VideoLobby_controls">
<div className="mx_CallLobby_controls">
<DeviceButton
kind="audio"
devices={audioDevices}
setDevice={selectAudioDevice}
devices={audioInputs}
setDevice={setAudioInput}
deviceListLabel={_t("Audio devices")}
active={audioActive}
fallbackDeviceLabel={n => _t("Audio input %(n)s", { n })}
muted={audioMuted}
disabled={connecting}
toggle={toggleAudio}
activeTitle={_t("Mute microphone")}
inactiveTitle={_t("Unmute microphone")}
unmutedTitle={_t("Mute microphone")}
mutedTitle={_t("Unmute microphone")}
/>
<DeviceButton
kind="video"
devices={videoDevices}
setDevice={selectVideoDevice}
devices={videoInputs}
setDevice={setVideoInput}
deviceListLabel={_t("Video devices")}
active={videoActive}
fallbackDeviceLabel={n => _t("Video input %(n)s", { n })}
muted={videoMuted}
disabled={connecting}
toggle={toggleVideo}
activeTitle={_t("Turn off camera")}
inactiveTitle={_t("Turn on camera")}
unmutedTitle={_t("Turn off camera")}
mutedTitle={_t("Turn on camera")}
/>
</div>
</div>
<AccessibleButton
className="mx_VideoLobby_joinButton"
className="mx_CallLobby_connectButton"
kind="primary"
disabled={connecting}
onClick={connect}
@ -236,5 +244,3 @@ const VideoLobby: FC<{ room: Room }> = ({ room }) => {
</AccessibleButton>
</div>;
};
export default VideoLobby;

View file

@ -21,7 +21,7 @@ import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import Field from "../elements/Field";
import DialPad from './DialPad';
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
import CallHandler from "../../../CallHandler";
import LegacyCallHandler from "../../../LegacyCallHandler";
interface IProps {
onFinished: (boolean) => void;
@ -78,7 +78,7 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
};
onDialPress = async () => {
CallHandler.instance.dialNumber(this.state.value);
LegacyCallHandler.instance.dialNumber(this.state.value);
this.props.onFinished(true);
};

View file

@ -23,7 +23,7 @@ import { CallFeed } from 'matrix-js-sdk/src/webrtc/callFeed';
import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes';
import dis from '../../../dispatcher/dispatcher';
import CallHandler from '../../../CallHandler';
import LegacyCallHandler from '../../../LegacyCallHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t, _td } from '../../../languageHandler';
import VideoFeed from './VideoFeed';
@ -32,9 +32,9 @@ import AccessibleButton from '../elements/AccessibleButton';
import { avatarUrlForMember } from '../../../Avatar';
import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker";
import Modal from '../../../Modal';
import CallViewSidebar from './CallViewSidebar';
import CallViewHeader from './CallView/CallViewHeader';
import CallViewButtons from "./CallView/CallViewButtons";
import LegacyCallViewSidebar from './LegacyCallViewSidebar';
import LegacyCallViewHeader from './LegacyCallView/LegacyCallViewHeader';
import LegacyCallViewButtons from "./LegacyCallView/LegacyCallViewButtons";
import PlatformPeg from "../../../PlatformPeg";
import { ActionPayload } from "../../../dispatcher/payloads";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
@ -47,7 +47,7 @@ interface IProps {
// Another ongoing call to display information about
secondaryCall?: MatrixCall;
// a callback which is called when the content in the CallView changes
// a callback which is called when the content in the LegacyCallView changes
// in a way that is likely to cause a resize.
onResize?: (event: Event) => void;
@ -57,7 +57,7 @@ interface IProps {
// need to control those things separately, so this is simpler.
pipMode?: boolean;
// Used for dragging the PiP CallView
// Used for dragging the PiP LegacyCallView
onMouseDownOnHeader?: (event: React.MouseEvent<Element, MouseEvent>) => void;
showApps?: boolean;
@ -104,15 +104,15 @@ function exitFullscreen() {
if (exitMethod) exitMethod.call(document);
}
export default class CallView extends React.Component<IProps, IState> {
export default class LegacyCallView extends React.Component<IProps, IState> {
private dispatcherRef: string;
private contentWrapperRef = createRef<HTMLDivElement>();
private buttonsRef = createRef<CallViewButtons>();
private buttonsRef = createRef<LegacyCallViewButtons>();
constructor(props: IProps) {
super(props);
const { primary, secondary, sidebar } = CallView.getOrderedFeeds(this.props.call.getFeeds());
const { primary, secondary, sidebar } = LegacyCallView.getOrderedFeeds(this.props.call.getFeeds());
this.state = {
isLocalOnHold: this.props.call.isLocalOnHold(),
@ -146,7 +146,7 @@ export default class CallView extends React.Component<IProps, IState> {
}
static getDerivedStateFromProps(props: IProps): Partial<IState> {
const { primary, secondary, sidebar } = CallView.getOrderedFeeds(props.call.getFeeds());
const { primary, secondary, sidebar } = LegacyCallView.getOrderedFeeds(props.call.getFeeds());
return {
primaryFeed: primary,
@ -209,7 +209,7 @@ export default class CallView extends React.Component<IProps, IState> {
};
private onFeedsChanged = (newFeeds: Array<CallFeed>): void => {
const { primary, secondary, sidebar } = CallView.getOrderedFeeds(newFeeds);
const { primary, secondary, sidebar } = LegacyCallView.getOrderedFeeds(newFeeds);
this.setState({
primaryFeed: primary,
secondaryFeed: secondary,
@ -310,8 +310,8 @@ export default class CallView extends React.Component<IProps, IState> {
};
// we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire
// Note that this assumes we always have a CallView on screen at any given time
// CallHandler would probably be a better place for this
// Note that this assumes we always have a LegacyCallView on screen at any given time
// LegacyCallHandler would probably be a better place for this
private onNativeKeyDown = (ev): void => {
let handled = false;
@ -339,17 +339,17 @@ export default class CallView extends React.Component<IProps, IState> {
};
private onCallResumeClick = (): void => {
const userFacingRoomId = CallHandler.instance.roomIdForCall(this.props.call);
CallHandler.instance.setActiveCallRoomId(userFacingRoomId);
const userFacingRoomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
LegacyCallHandler.instance.setActiveCallRoomId(userFacingRoomId);
};
private onTransferClick = (): void => {
const transfereeCall = CallHandler.instance.getTransfereeForCallId(this.props.call.callId);
const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(this.props.call.callId);
this.props.call.transferToCall(transfereeCall);
};
private onHangupClick = (): void => {
CallHandler.instance.hangupOrReject(CallHandler.instance.roomIdForCall(this.props.call));
LegacyCallHandler.instance.hangupOrReject(LegacyCallHandler.instance.roomIdForCall(this.props.call));
};
private onToggleSidebar = (): void => {
@ -380,7 +380,7 @@ export default class CallView extends React.Component<IProps, IState> {
);
return (
<CallViewButtons
<LegacyCallViewButtons
ref={this.buttonsRef}
call={call}
pipMode={pipMode}
@ -431,7 +431,7 @@ export default class CallView extends React.Component<IProps, IState> {
}
return (
<div className="mx_CallView_toast">
<div className="mx_LegacyCallView_toast">
{ text }
</div>
);
@ -443,7 +443,7 @@ export default class CallView extends React.Component<IProps, IState> {
const callRoom = MatrixClientPeg.get().getRoom(call.roomId);
const avatarSize = pipMode ? 76 : 160;
const transfereeCall = CallHandler.instance.getTransfereeForCallId(call.callId);
const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(call.callId);
const isOnHold = isLocalOnHold || isRemoteOnHold;
let secondaryFeedElement: React.ReactNode;
@ -460,23 +460,23 @@ export default class CallView extends React.Component<IProps, IState> {
}
if (transfereeCall || isOnHold) {
const containerClasses = classNames("mx_CallView_content", {
mx_CallView_content_hold: isOnHold,
const containerClasses = classNames("mx_LegacyCallView_content", {
mx_LegacyCallView_content_hold: isOnHold,
});
const backgroundAvatarUrl = avatarUrlForMember(call.getOpponentMember(), 1024, 1024, 'crop');
let holdTransferContent: React.ReactNode;
if (transfereeCall) {
const transferTargetRoom = MatrixClientPeg.get().getRoom(
CallHandler.instance.roomIdForCall(call),
LegacyCallHandler.instance.roomIdForCall(call),
);
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person");
const transfereeRoom = MatrixClientPeg.get().getRoom(
CallHandler.instance.roomIdForCall(transfereeCall),
LegacyCallHandler.instance.roomIdForCall(transfereeCall),
);
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person");
holdTransferContent = <div className="mx_CallView_status">
holdTransferContent = <div className="mx_LegacyCallView_status">
{ _t(
"Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>",
{
@ -494,7 +494,7 @@ export default class CallView extends React.Component<IProps, IState> {
let onHoldText: React.ReactNode;
if (isRemoteOnHold) {
onHoldText = _t(
CallHandler.instance.hasAnyUnheldCall()
LegacyCallHandler.instance.hasAnyUnheldCall()
? _td("You held the call <a>Switch</a>")
: _td("You held the call <a>Resume</a>"),
{},
@ -511,7 +511,7 @@ export default class CallView extends React.Component<IProps, IState> {
}
holdTransferContent = (
<div className="mx_CallView_status">
<div className="mx_LegacyCallView_status">
{ onHoldText }
</div>
);
@ -519,16 +519,16 @@ export default class CallView extends React.Component<IProps, IState> {
return (
<div className={containerClasses} onMouseMove={this.onMouseMove}>
<div className="mx_CallView_holdBackground" style={{ backgroundImage: 'url(' + backgroundAvatarUrl + ')' }} />
<div className="mx_LegacyCallView_holdBackground" style={{ backgroundImage: 'url(' + backgroundAvatarUrl + ')' }} />
{ holdTransferContent }
</div>
);
} else if (call.noIncomingFeeds()) {
return (
<div className="mx_CallView_content" onMouseMove={this.onMouseMove}>
<div className="mx_CallView_avatarsContainer">
<div className="mx_LegacyCallView_content" onMouseMove={this.onMouseMove}>
<div className="mx_LegacyCallView_avatarsContainer">
<div
className="mx_CallView_avatarContainer"
className="mx_LegacyCallView_avatarContainer"
style={{ width: avatarSize, height: avatarSize }}
>
<RoomAvatar
@ -538,14 +538,14 @@ export default class CallView extends React.Component<IProps, IState> {
/>
</div>
</div>
<div className="mx_CallView_status">{ _t("Connecting") }</div>
<div className="mx_LegacyCallView_status">{ _t("Connecting") }</div>
{ secondaryFeedElement }
</div>
);
} else if (pipMode) {
return (
<div
className="mx_CallView_content"
className="mx_LegacyCallView_content"
onMouseMove={this.onMouseMove}
>
<VideoFeed
@ -559,7 +559,7 @@ export default class CallView extends React.Component<IProps, IState> {
);
} else if (secondaryFeed) {
return (
<div className="mx_CallView_content" onMouseMove={this.onMouseMove}>
<div className="mx_LegacyCallView_content" onMouseMove={this.onMouseMove}>
<VideoFeed
feed={primaryFeed}
call={call}
@ -572,7 +572,7 @@ export default class CallView extends React.Component<IProps, IState> {
);
} else {
return (
<div className="mx_CallView_content" onMouseMove={this.onMouseMove}>
<div className="mx_LegacyCallView_content" onMouseMove={this.onMouseMove}>
<VideoFeed
feed={primaryFeed}
call={call}
@ -580,7 +580,7 @@ export default class CallView extends React.Component<IProps, IState> {
onResize={onResize}
primary={true}
/>
{ sidebarShown && <CallViewSidebar
{ sidebarShown && <LegacyCallViewSidebar
feeds={sidebarFeeds}
call={call}
pipMode={pipMode}
@ -604,27 +604,27 @@ export default class CallView extends React.Component<IProps, IState> {
} = this.state;
const client = MatrixClientPeg.get();
const callRoomId = CallHandler.instance.roomIdForCall(call);
const secondaryCallRoomId = CallHandler.instance.roomIdForCall(secondaryCall);
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
const callRoom = client.getRoom(callRoomId);
const secCallRoom = secondaryCall ? client.getRoom(secondaryCallRoomId) : null;
const callViewClasses = classNames({
mx_CallView: true,
mx_CallView_pip: pipMode,
mx_CallView_large: !pipMode,
mx_CallView_sidebar: sidebarShown && sidebarFeeds.length !== 0 && !pipMode,
mx_CallView_belowWidget: showApps, // css to correct the margins if the call is below the AppsDrawer.
mx_LegacyCallView: true,
mx_LegacyCallView_pip: pipMode,
mx_LegacyCallView_large: !pipMode,
mx_LegacyCallView_sidebar: sidebarShown && sidebarFeeds.length !== 0 && !pipMode,
mx_LegacyCallView_belowWidget: showApps, // css to correct the margins if the call is below the AppsDrawer.
});
return <div className={callViewClasses}>
<CallViewHeader
<LegacyCallViewHeader
onPipMouseDown={onMouseDownOnHeader}
pipMode={pipMode}
callRooms={[callRoom, secCallRoom]}
onMaximize={this.onMaximizeClick}
/>
<div className="mx_CallView_content_wrapper" ref={this.contentWrapperRef}>
<div className="mx_LegacyCallView_content_wrapper" ref={this.contentWrapperRef}>
{ this.renderToast() }
{ this.renderContent() }
{ this.renderCallControls() }

View file

@ -21,7 +21,7 @@ import classNames from "classnames";
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton";
import CallContextMenu from "../../context_menus/CallContextMenu";
import LegacyCallContextMenu from "../../context_menus/LegacyCallContextMenu";
import DialpadContextMenu from "../../context_menus/DialpadContextMenu";
import { Alignment } from "../../elements/Tooltip";
import {
@ -49,7 +49,7 @@ interface IButtonProps extends Omit<React.ComponentProps<typeof AccessibleToolti
onClick: (event: React.MouseEvent) => void;
}
const CallViewToggleButton: React.FC<IButtonProps> = ({
const LegacyCallViewToggleButton: React.FC<IButtonProps> = ({
children,
state: isOn,
className,
@ -57,9 +57,9 @@ const CallViewToggleButton: React.FC<IButtonProps> = ({
offLabel,
...props
}) => {
const classes = classNames("mx_CallViewButtons_button", className, {
mx_CallViewButtons_button_on: isOn,
mx_CallViewButtons_button_off: !isOn,
const classes = classNames("mx_LegacyCallViewButtons_button", className, {
mx_LegacyCallViewButtons_button_on: isOn,
mx_LegacyCallViewButtons_button_off: !isOn,
});
return (
@ -78,12 +78,12 @@ interface IDropdownButtonProps extends IButtonProps {
deviceKinds: MediaDeviceKindEnum[];
}
const CallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, deviceKinds, ...props }) => {
const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, deviceKinds, ...props }) => {
const [menuDisplayed, buttonRef, openMenu, closeMenu] = useContextMenu();
const [hoveringDropdown, setHoveringDropdown] = useState(false);
const classes = classNames("mx_CallViewButtons_button", "mx_CallViewButtons_dropdownButton", {
mx_CallViewButtons_dropdownButton_collapsed: !menuDisplayed,
const classes = classNames("mx_LegacyCallViewButtons_button", "mx_LegacyCallViewButtons_dropdownButton", {
mx_LegacyCallViewButtons_dropdownButton_collapsed: !menuDisplayed,
});
const onClick = (event: React.MouseEvent): void => {
@ -92,8 +92,8 @@ const CallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, deviceK
};
return (
<CallViewToggleButton inputRef={buttonRef} forceHide={menuDisplayed || hoveringDropdown} state={state} {...props}>
<CallViewToggleButton
<LegacyCallViewToggleButton inputRef={buttonRef} forceHide={menuDisplayed || hoveringDropdown} state={state} {...props}>
<LegacyCallViewToggleButton
className={classes}
onClick={onClick}
onHover={(hovering) => setHoveringDropdown(hovering)}
@ -105,7 +105,7 @@ const CallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, deviceK
onFinished={closeMenu}
deviceKinds={deviceKinds}
/> }
</CallViewToggleButton>
</LegacyCallViewToggleButton>
);
};
@ -141,7 +141,7 @@ interface IState {
showMoreMenu: boolean;
}
export default class CallViewButtons extends React.Component<IProps, IState> {
export default class LegacyCallViewButtons extends React.Component<IProps, IState> {
private dialpadButton = createRef<HTMLDivElement>();
private contextMenuButton = createRef<HTMLDivElement>();
private controlsHideTimer: number = null;
@ -212,8 +212,8 @@ export default class CallViewButtons extends React.Component<IProps, IState> {
};
public render(): JSX.Element {
const callControlsClasses = classNames("mx_CallViewButtons", {
mx_CallViewButtons_hidden: !this.state.visible,
const callControlsClasses = classNames("mx_LegacyCallViewButtons", {
mx_LegacyCallViewButtons_hidden: !this.state.visible,
});
let dialPad;
@ -236,7 +236,7 @@ export default class CallViewButtons extends React.Component<IProps, IState> {
let contextMenu;
if (this.state.showMoreMenu) {
contextMenu = <CallContextMenu
contextMenu = <LegacyCallContextMenu
{...alwaysAboveLeftOf(
this.contextMenuButton.current.getBoundingClientRect(),
ChevronFace.None,
@ -258,45 +258,45 @@ export default class CallViewButtons extends React.Component<IProps, IState> {
{ contextMenu }
{ this.props.buttonsVisibility.dialpad && <ContextMenuTooltipButton
className="mx_CallViewButtons_button mx_CallViewButtons_dialpad"
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_dialpad"
inputRef={this.dialpadButton}
onClick={this.onDialpadClick}
isExpanded={this.state.showDialpad}
title={_t("Dialpad")}
alignment={Alignment.Top}
/> }
<CallViewDropdownButton
<LegacyCallViewDropdownButton
state={!this.props.buttonsState.micMuted}
className="mx_CallViewButtons_button_mic"
className="mx_LegacyCallViewButtons_button_mic"
onLabel={_t("Mute the microphone")}
offLabel={_t("Unmute the microphone")}
onClick={this.props.handlers.onMicMuteClick}
deviceKinds={[MediaDeviceKindEnum.AudioInput, MediaDeviceKindEnum.AudioOutput]}
/>
{ this.props.buttonsVisibility.vidMute && <CallViewDropdownButton
{ this.props.buttonsVisibility.vidMute && <LegacyCallViewDropdownButton
state={!this.props.buttonsState.vidMuted}
className="mx_CallViewButtons_button_vid"
className="mx_LegacyCallViewButtons_button_vid"
onLabel={_t("Stop the camera")}
offLabel={_t("Start the camera")}
onClick={this.props.handlers.onVidMuteClick}
deviceKinds={[MediaDeviceKindEnum.VideoInput]}
/> }
{ this.props.buttonsVisibility.screensharing && <CallViewToggleButton
{ this.props.buttonsVisibility.screensharing && <LegacyCallViewToggleButton
state={this.props.buttonsState.screensharing}
className="mx_CallViewButtons_button_screensharing"
className="mx_LegacyCallViewButtons_button_screensharing"
onLabel={_t("Stop sharing your screen")}
offLabel={_t("Start sharing your screen")}
onClick={this.props.handlers.onScreenshareClick}
/> }
{ this.props.buttonsVisibility.sidebar && <CallViewToggleButton
{ this.props.buttonsVisibility.sidebar && <LegacyCallViewToggleButton
state={this.props.buttonsState.sidebarShown}
className="mx_CallViewButtons_button_sidebar"
className="mx_LegacyCallViewButtons_button_sidebar"
onLabel={_t("Hide sidebar")}
offLabel={_t("Show sidebar")}
onClick={this.props.handlers.onToggleSidebarClick}
/> }
{ this.props.buttonsVisibility.contextMenu && <ContextMenuTooltipButton
className="mx_CallViewButtons_button mx_CallViewButtons_button_more"
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_button_more"
onClick={this.onMoreClick}
inputRef={this.contextMenuButton}
isExpanded={this.state.showMoreMenu}
@ -304,7 +304,7 @@ export default class CallViewButtons extends React.Component<IProps, IState> {
alignment={Alignment.Top}
/> }
<AccessibleTooltipButton
className="mx_CallViewButtons_button mx_CallViewButtons_button_hangup"
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_button_hangup"
onClick={this.props.handlers.onHangupClick}
title={_t("Hangup")}
alignment={Alignment.Top}

View file

@ -21,26 +21,26 @@ import { _t } from '../../../../languageHandler';
import RoomAvatar from '../../avatars/RoomAvatar';
import AccessibleTooltipButton from '../../elements/AccessibleTooltipButton';
interface CallControlsProps {
interface LegacyCallControlsProps {
onExpand?: () => void;
onPin?: () => void;
onMaximize?: () => void;
}
const CallViewHeaderControls: React.FC<CallControlsProps> = ({ onExpand, onPin, onMaximize }) => {
return <div className="mx_CallViewHeader_controls">
const LegacyCallViewHeaderControls: React.FC<LegacyCallControlsProps> = ({ onExpand, onPin, onMaximize }) => {
return <div className="mx_LegacyCallViewHeader_controls">
{ onMaximize && <AccessibleTooltipButton
className="mx_CallViewHeader_button mx_CallViewHeader_button_fullscreen"
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_fullscreen"
onClick={onMaximize}
title={_t("Fill Screen")}
/> }
{ onPin && <AccessibleTooltipButton
className="mx_CallViewHeader_button mx_CallViewHeader_button_pin"
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_pin"
onClick={onPin}
title={_t("Pin")}
/> }
{ onExpand && <AccessibleTooltipButton
className="mx_CallViewHeader_button mx_CallViewHeader_button_expand"
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_expand"
onClick={onExpand}
title={_t("Return to call")}
/> }
@ -52,15 +52,15 @@ interface ISecondaryCallInfoProps {
}
const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
return <span className="mx_CallViewHeader_secondaryCallInfo">
return <span className="mx_LegacyCallViewHeader_secondaryCallInfo">
<RoomAvatar room={callRoom} height={16} width={16} />
<span className="mx_CallView_secondaryCall_roomName">
<span className="mx_LegacyCallView_secondaryCall_roomName">
{ _t("%(name)s on hold", { name: callRoom.name }) }
</span>
</span>;
};
interface CallViewHeaderProps {
interface LegacyCallViewHeaderProps {
pipMode: boolean;
callRooms?: Room[];
onPipMouseDown: (event: React.MouseEvent<Element, MouseEvent>) => void;
@ -69,7 +69,7 @@ interface CallViewHeaderProps {
onMaximize?: () => void;
}
const CallViewHeader: React.FC<CallViewHeaderProps> = ({
const LegacyCallViewHeader: React.FC<LegacyCallViewHeaderProps> = ({
pipMode = false,
callRooms = [],
onPipMouseDown,
@ -81,25 +81,25 @@ const CallViewHeader: React.FC<CallViewHeaderProps> = ({
const callRoomName = callRoom.name;
if (!pipMode) {
return <div className="mx_CallViewHeader">
<div className="mx_CallViewHeader_icon" />
<span className="mx_CallViewHeader_text">{ _t("Call") }</span>
<CallViewHeaderControls onMaximize={onMaximize} />
return <div className="mx_LegacyCallViewHeader">
<div className="mx_LegacyCallViewHeader_icon" />
<span className="mx_LegacyCallViewHeader_text">{ _t("Call") }</span>
<LegacyCallViewHeaderControls onMaximize={onMaximize} />
</div>;
}
return (
<div
className="mx_CallViewHeader mx_CallViewHeader_pip"
className="mx_LegacyCallViewHeader mx_LegacyCallViewHeader_pip"
onMouseDown={onPipMouseDown}
>
<RoomAvatar room={callRoom} height={32} width={32} />
<div className="mx_CallViewHeader_callInfo">
<div className="mx_CallViewHeader_roomName">{ callRoomName }</div>
<div className="mx_LegacyCallViewHeader_callInfo">
<div className="mx_LegacyCallViewHeader_roomName">{ callRoomName }</div>
{ onHoldCallRoom && <SecondaryCallInfo callRoom={onHoldCallRoom} /> }
</div>
<CallViewHeaderControls onExpand={onExpand} onPin={onPin} onMaximize={onMaximize} />
<LegacyCallViewHeaderControls onExpand={onExpand} onPin={onPin} onMaximize={onMaximize} />
</div>
);
};
export default CallViewHeader;
export default LegacyCallViewHeader;

View file

@ -18,8 +18,8 @@ import { CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import React from 'react';
import { Resizable } from "re-resizable";
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
import CallView from './CallView';
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler';
import LegacyCallView from './LegacyCallView';
import ResizeNotifier from "../../../utils/ResizeNotifier";
interface IProps {
@ -32,14 +32,14 @@ interface IProps {
}
interface IState {
call: MatrixCall;
call: MatrixCall | null;
}
/*
* Wrapper for CallView that always display the call in a given room,
* Wrapper for LegacyCallView that always display the call in a given room,
* or nothing if there is no call in that room.
*/
export default class CallViewForRoom extends React.Component<IProps, IState> {
export default class LegacyCallViewForRoom extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
@ -48,13 +48,13 @@ export default class CallViewForRoom extends React.Component<IProps, IState> {
}
public componentDidMount() {
CallHandler.instance.addListener(CallHandlerEvent.CallState, this.updateCall);
CallHandler.instance.addListener(CallHandlerEvent.CallChangeRoom, this.updateCall);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCall);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
}
public componentWillUnmount() {
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.updateCall);
CallHandler.instance.removeListener(CallHandlerEvent.CallChangeRoom, this.updateCall);
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCall);
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
}
private updateCall = () => {
@ -64,8 +64,8 @@ export default class CallViewForRoom extends React.Component<IProps, IState> {
}
};
private getCall(): MatrixCall {
const call = CallHandler.instance.getCallForRoom(this.props.roomId);
private getCall(): MatrixCall | null {
const call = LegacyCallHandler.instance.getCallForRoom(this.props.roomId);
if (call && [CallState.Ended, CallState.Ringing].includes(call.state)) return null;
return call;
@ -87,7 +87,7 @@ export default class CallViewForRoom extends React.Component<IProps, IState> {
if (!this.state.call) return null;
return (
<div className="mx_CallViewForRoom">
<div className="mx_LegacyCallViewForRoom">
<Resizable
minHeight={380}
maxHeight="80vh"
@ -104,10 +104,10 @@ export default class CallViewForRoom extends React.Component<IProps, IState> {
onResizeStart={this.onResizeStart}
onResize={this.onResize}
onResizeStop={this.onResizeStop}
className="mx_CallViewForRoom_ResizeWrapper"
handleClasses={{ bottom: "mx_CallViewForRoom_ResizeHandle" }}
className="mx_LegacyCallViewForRoom_ResizeWrapper"
handleClasses={{ bottom: "mx_LegacyCallViewForRoom_ResizeHandle" }}
>
<CallView
<LegacyCallView
call={this.state.call}
pipMode={false}
showApps={this.props.showApps}

View file

@ -27,7 +27,7 @@ interface IProps {
pipMode: boolean;
}
export default class CallViewSidebar extends React.Component<IProps> {
export default class LegacyCallViewSidebar extends React.Component<IProps> {
render() {
const feeds = this.props.feeds.map((feed) => {
return (
@ -41,8 +41,8 @@ export default class CallViewSidebar extends React.Component<IProps> {
);
});
const className = classNames("mx_CallViewSidebar", {
mx_CallViewSidebar_pipMode: this.props.pipMode,
const className = classNames("mx_LegacyCallViewSidebar", {
mx_LegacyCallViewSidebar_pipMode: this.props.pipMode,
});
return (

View file

@ -21,9 +21,9 @@ import { logger } from "matrix-js-sdk/src/logger";
import classNames from 'classnames';
import { Room } from "matrix-js-sdk/src/models/room";
import CallView from "./CallView";
import LegacyCallView from "./LegacyCallView";
import { RoomViewStore } from '../../../stores/RoomViewStore';
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler';
import PersistentApp from "../elements/PersistentApp";
import SettingsStore from "../../../settings/SettingsStore";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -31,7 +31,7 @@ import PictureInPictureDragger from './PictureInPictureDragger';
import dis from '../../../dispatcher/dispatcher';
import { Action } from "../../../dispatcher/actions";
import { Container, WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
import CallViewHeader from './CallView/CallViewHeader';
import LegacyCallViewHeader from './LegacyCallView/LegacyCallViewHeader';
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/ActiveWidgetStore';
import WidgetStore, { IApp } from "../../../stores/WidgetStore";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
@ -81,7 +81,7 @@ const getRoomAndAppForWidget = (widgetId: string, roomId: string): [Room, IApp]
// The primary will be the one not on hold, or an arbitrary one
// if they're all on hold)
function getPrimarySecondaryCallsForPip(roomId: string): [MatrixCall, MatrixCall[]] {
const calls = CallHandler.instance.getAllActiveCallsForPip(roomId);
const calls = LegacyCallHandler.instance.getAllActiveCallsForPip(roomId);
let primary: MatrixCall = null;
let secondaries: MatrixCall[] = [];
@ -110,7 +110,7 @@ function getPrimarySecondaryCallsForPip(roomId: string): [MatrixCall, MatrixCall
}
/**
* PipView shows a small version of the CallView or a sticky widget hovering over the UI in 'picture-in-picture'
* PipView shows a small version of the LegacyCallView or a sticky widget hovering over the UI in 'picture-in-picture'
* (PiP mode). It displays the call(s) which is *not* in the room the user is currently viewing
* and all widgets that are active but not shown in any other possible container.
*/
@ -139,8 +139,8 @@ export default class PipView extends React.Component<IProps, IState> {
}
public componentDidMount() {
CallHandler.instance.addListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
CallHandler.instance.addListener(CallHandlerEvent.CallState, this.updateCalls);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
this.roomStoreToken = RoomViewStore.instance.addListener(this.onRoomViewStoreUpdate);
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
const room = MatrixClientPeg.get()?.getRoom(this.state.viewedRoomId);
@ -154,8 +154,8 @@ export default class PipView extends React.Component<IProps, IState> {
}
public componentWillUnmount() {
CallHandler.instance.removeListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.updateCalls);
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
this.roomStoreToken?.remove();
SettingsStore.unwatchSetting(this.settingsWatcherRef);
@ -308,7 +308,7 @@ export default class PipView extends React.Component<IProps, IState> {
if (this.state.primaryCall) {
pipContent = ({ onStartMoving, onResize }) =>
<CallView
<LegacyCallView
onMouseDownOnHeader={onStartMoving}
call={this.state.primaryCall}
secondaryCall={this.state.secondaryCall}
@ -329,7 +329,7 @@ export default class PipView extends React.Component<IProps, IState> {
pipContent = ({ onStartMoving, _onResize }) =>
<div className={pipViewClasses}>
<CallViewHeader
<LegacyCallViewHeader
onPipMouseDown={(event) => { onStartMoving(event); this.onStartMoving.bind(this)(); }}
pipMode={pipMode}
callRooms={[roomForWidget]}