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

@ -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]}