mirror of https://github.com/vector-im/riot-web
Add Element call related functionality to new room header (#12091)
* New room header - add chat button during call - close lobby button in lobby - join button if session exists - allow to toggle call <-> timeline during call with call button Compound style for join button in call notify toast. Signed-off-by: Timo K <toger5@hotmail.de> * dont show start call, join button in video rooms. Signed-off-by: Timo K <toger5@hotmail.de> * Make active call check based on participant count Not based on available call object Signed-off-by: Timo K <toger5@hotmail.de> * fix room header tests Signed-off-by: Timo K <toger5@hotmail.de> * fix room header tests Signed-off-by: Timo K <toger5@hotmail.de> * remove chat button test for displaying. Chat button display logic is now part of the RoomHeader. Signed-off-by: Timo K <toger5@hotmail.de> * remove duplicate notification Tread icon Signed-off-by: Timo K <toger5@hotmail.de> * remove obsolete jest snapshot Signed-off-by: Timo K <toger5@hotmail.de> * Update src/components/views/rooms/RoomHeader.tsx Co-authored-by: Robin <robin@robin.town> * update isECWidget logic Signed-off-by: Timo K <toger5@hotmail.de> * remove dead code Signed-off-by: Timo K <toger5@hotmail.de> * refactor call options Add menu to choose if there are multiple options Signed-off-by: Timo K <toger5@hotmail.de> * join ec when clicking join button (dont start jitsi) Use icon buttons don't show call icon when join button is visible Signed-off-by: Timo K <toger5@hotmail.de> * refactor isViewingCall Signed-off-by: Timo K <toger5@hotmail.de> * fix room header tests Signed-off-by: Timo K <toger5@hotmail.de> * fix header snapshot Signed-off-by: Timo K <toger5@hotmail.de> * sonar proposals Signed-off-by: Timo K <toger5@hotmail.de> * fix event shiftKey may be undefined Signed-off-by: Timo K <toger5@hotmail.de> * more lobby time before timeout only await sticky promise on becoming sticky. Signed-off-by: Timo K <toger5@hotmail.de> * don't allow starting new calls if there is an ongoing other call. Signed-off-by: Timo K <toger5@hotmail.de> * review Signed-off-by: Timo K <toger5@hotmail.de> * fix translation typo Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: Robin <robin@robin.town>pull/28788/head^2
parent
31449d6f80
commit
73b16239a5
|
@ -821,7 +821,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
private onActiveCalls = (): void => {
|
private onActiveCalls = (): void => {
|
||||||
if (this.state.roomId === undefined) return;
|
if (this.state.roomId === undefined) return;
|
||||||
const activeCall = CallStore.instance.getActiveCall(this.state.roomId);
|
const activeCall = CallStore.instance.getActiveCall(this.state.roomId);
|
||||||
|
|
||||||
if (activeCall === null) {
|
if (activeCall === null) {
|
||||||
// We disconnected from the call, so stop viewing it
|
// We disconnected from the call, so stop viewing it
|
||||||
dis.dispatch<ViewRoomPayload>(
|
dis.dispatch<ViewRoomPayload>(
|
||||||
|
|
|
@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Body as BodyText, IconButton, Tooltip } from "@vector-im/compound-web";
|
import { Body as BodyText, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web";
|
||||||
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call-solid.svg";
|
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call-solid.svg";
|
||||||
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
|
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
|
||||||
|
import { Icon as CloseCallIcon } from "@vector-im/compound-design-tokens/icons/close.svg";
|
||||||
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
|
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
|
||||||
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
|
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
|
||||||
import { Icon as VerifiedIcon } from "@vector-im/compound-design-tokens/icons/verified.svg";
|
import { Icon as VerifiedIcon } from "@vector-im/compound-design-tokens/icons/verified.svg";
|
||||||
|
@ -35,7 +36,7 @@ import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMember
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { Flex } from "../../utils/Flex";
|
import { Flex } from "../../utils/Flex";
|
||||||
import { Box } from "../../utils/Box";
|
import { Box } from "../../utils/Box";
|
||||||
import { useRoomCall } from "../../../hooks/room/useRoomCall";
|
import { getPlatformCallTypeLabel, useRoomCall } from "../../../hooks/room/useRoomCall";
|
||||||
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
|
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
|
||||||
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
|
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
@ -51,6 +52,7 @@ import { Linkify, topicToHtml } from "../../../HtmlUtils";
|
||||||
import PosthogTrackers from "../../../PosthogTrackers";
|
import PosthogTrackers from "../../../PosthogTrackers";
|
||||||
import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton";
|
import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton";
|
||||||
import { RoomKnocksBar } from "./RoomKnocksBar";
|
import { RoomKnocksBar } from "./RoomKnocksBar";
|
||||||
|
import { isVideoRoom } from "../../../utils/video-rooms";
|
||||||
import { notificationLevelToIndicator } from "../../../utils/notifications";
|
import { notificationLevelToIndicator } from "../../../utils/notifications";
|
||||||
|
|
||||||
export default function RoomHeader({
|
export default function RoomHeader({
|
||||||
|
@ -69,7 +71,17 @@ export default function RoomHeader({
|
||||||
const members = useRoomMembers(room, 2500);
|
const members = useRoomMembers(room, 2500);
|
||||||
const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
|
const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
|
||||||
|
|
||||||
const { voiceCallDisabledReason, voiceCallClick, videoCallDisabledReason, videoCallClick } = useRoomCall(room);
|
const {
|
||||||
|
voiceCallDisabledReason,
|
||||||
|
voiceCallClick,
|
||||||
|
videoCallDisabledReason,
|
||||||
|
videoCallClick,
|
||||||
|
toggleCallMaximized: toggleCall,
|
||||||
|
isViewingCall,
|
||||||
|
isConnectedToCall,
|
||||||
|
hasActiveCallSession,
|
||||||
|
callOptions,
|
||||||
|
} = useRoomCall(room);
|
||||||
|
|
||||||
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
|
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
|
||||||
/**
|
/**
|
||||||
|
@ -104,6 +116,97 @@ export default function RoomHeader({
|
||||||
|
|
||||||
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
|
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
|
||||||
|
|
||||||
|
const videoClick = useCallback((ev) => videoCallClick(ev, callOptions[0]), [callOptions, videoCallClick]);
|
||||||
|
|
||||||
|
const toggleCallButton = (
|
||||||
|
<Tooltip label={isViewingCall ? _t("voip|minimise_call") : _t("voip|maximise_call")}>
|
||||||
|
<IconButton onClick={toggleCall}>
|
||||||
|
<VideoCallIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
const joinCallButton = (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={videoClick}
|
||||||
|
Icon={VideoCallIcon}
|
||||||
|
className="mx_RoomHeader_join_button"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{_t("action|join")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const callIconWithTooltip = (
|
||||||
|
<Tooltip label={videoCallDisabledReason ?? _t("voip|video_call")}>
|
||||||
|
<VideoCallIcon />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
const startVideoCallButton = (
|
||||||
|
<>
|
||||||
|
{/* Can be either a menu or just a button depending on the number of call options.*/}
|
||||||
|
{callOptions.length > 1 ? (
|
||||||
|
<Menu
|
||||||
|
open={menuOpen}
|
||||||
|
onOpenChange={setMenuOpen}
|
||||||
|
title={_t("voip|video_call_using")}
|
||||||
|
trigger={
|
||||||
|
<IconButton
|
||||||
|
disabled={!!videoCallDisabledReason}
|
||||||
|
aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
|
||||||
|
>
|
||||||
|
{callIconWithTooltip}
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
side="left"
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
{callOptions.map((option) => (
|
||||||
|
<MenuItem
|
||||||
|
key={option}
|
||||||
|
label={getPlatformCallTypeLabel(option)}
|
||||||
|
onClick={(ev) => videoCallClick(ev, option)}
|
||||||
|
Icon={VideoCallIcon}
|
||||||
|
onSelect={() => {} /* Dummy handler since we want the click event.*/}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
) : (
|
||||||
|
<IconButton
|
||||||
|
disabled={!!videoCallDisabledReason}
|
||||||
|
aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
|
||||||
|
onClick={videoClick}
|
||||||
|
>
|
||||||
|
{callIconWithTooltip}
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
const voiceCallButton = (
|
||||||
|
<Tooltip label={voiceCallDisabledReason ?? _t("voip|voice_call")}>
|
||||||
|
<IconButton
|
||||||
|
disabled={!!voiceCallDisabledReason}
|
||||||
|
aria-label={voiceCallDisabledReason ?? _t("voip|voice_call")}
|
||||||
|
onClick={(ev) => voiceCallClick(ev, callOptions[0])}
|
||||||
|
>
|
||||||
|
<VoiceCallIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
const closeLobbyButton = (
|
||||||
|
<Tooltip label={_t("voip|close_lobby")}>
|
||||||
|
<IconButton onClick={toggleCall}>
|
||||||
|
<CloseCallIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
let videoCallButton = startVideoCallButton;
|
||||||
|
if (isConnectedToCall) {
|
||||||
|
videoCallButton = toggleCallButton;
|
||||||
|
} else if (isViewingCall) {
|
||||||
|
videoCallButton = closeLobbyButton;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex as="header" align="center" gap="var(--cpd-space-3x)" className="mx_RoomHeader light-panel">
|
<Flex as="header" align="center" gap="var(--cpd-space-3x)" className="mx_RoomHeader light-panel">
|
||||||
|
@ -190,29 +293,17 @@ export default function RoomHeader({
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<Tooltip label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}>
|
|
||||||
<IconButton
|
|
||||||
disabled={!!videoCallDisabledReason}
|
|
||||||
aria-label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}
|
|
||||||
onClick={videoCallClick}
|
|
||||||
>
|
|
||||||
<VideoCallIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
{!useElementCallExclusively && (
|
|
||||||
<Tooltip label={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}>
|
|
||||||
<IconButton
|
|
||||||
disabled={!!voiceCallDisabledReason}
|
|
||||||
aria-label={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}
|
|
||||||
onClick={voiceCallClick}
|
|
||||||
>
|
|
||||||
<VoiceCallIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Renders nothing when room is not a video room */}
|
{((isConnectedToCall && isViewingCall) || isVideoRoom(room)) && <VideoRoomChatButton room={room} />}
|
||||||
<VideoRoomChatButton room={room} />
|
|
||||||
|
{hasActiveCallSession && !isConnectedToCall ? (
|
||||||
|
joinCallButton
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{!isVideoRoom(room) && videoCallButton}
|
||||||
|
{!useElementCallExclusively && !isVideoRoom(room) && voiceCallButton}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Tooltip label={_t("common|threads")}>
|
<Tooltip label={_t("common|threads")}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -19,7 +19,6 @@ import { Icon as ChatIcon } from "@vector-im/compound-design-tokens/icons/chat-s
|
||||||
import { Room } from "matrix-js-sdk/src/matrix";
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { IconButton, Tooltip } from "@vector-im/compound-web";
|
import { IconButton, Tooltip } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { isVideoRoom as calcIsVideoRoom } from "../../../../utils/video-rooms";
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
|
import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
|
||||||
import { NotificationStateEvents } from "../../../../stores/notifications/NotificationState";
|
import { NotificationStateEvents } from "../../../../stores/notifications/NotificationState";
|
||||||
|
@ -31,25 +30,18 @@ import { ButtonEvent } from "../../elements/AccessibleButton";
|
||||||
/**
|
/**
|
||||||
* Display a button to toggle timeline for video rooms
|
* Display a button to toggle timeline for video rooms
|
||||||
* @param room
|
* @param room
|
||||||
* @returns for a video room: a button to toggle timeline in the right panel
|
* @returns A button to toggle timeline in the right panel.
|
||||||
* otherwise null
|
|
||||||
*/
|
*/
|
||||||
export const VideoRoomChatButton: React.FC<{ room: Room }> = ({ room }) => {
|
export const VideoRoomChatButton: React.FC<{ room: Room }> = ({ room }) => {
|
||||||
const sdkContext = useContext(SDKContext);
|
const sdkContext = useContext(SDKContext);
|
||||||
|
|
||||||
const isVideoRoom = calcIsVideoRoom(room);
|
const notificationState = sdkContext.roomNotificationStateStore.getRoomState(room);
|
||||||
|
|
||||||
const notificationState = isVideoRoom ? sdkContext.roomNotificationStateStore.getRoomState(room) : undefined;
|
|
||||||
const notificationColor = useEventEmitterState(
|
const notificationColor = useEventEmitterState(
|
||||||
notificationState,
|
notificationState,
|
||||||
NotificationStateEvents.Update,
|
NotificationStateEvents.Update,
|
||||||
() => notificationState?.level,
|
() => notificationState?.level,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isVideoRoom) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayUnreadIndicator =
|
const displayUnreadIndicator =
|
||||||
!!notificationColor &&
|
!!notificationColor &&
|
||||||
[NotificationLevel.Activity, NotificationLevel.Notification, NotificationLevel.Highlight].includes(
|
[NotificationLevel.Activity, NotificationLevel.Notification, NotificationLevel.Highlight].includes(
|
||||||
|
|
|
@ -24,18 +24,37 @@ import { useEventEmitter, useEventEmitterState } from "../useEventEmitter";
|
||||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||||
import { useWidgets } from "../../components/views/right_panel/RoomSummaryCard";
|
import { useWidgets } from "../../components/views/right_panel/RoomSummaryCard";
|
||||||
import { WidgetType } from "../../widgets/WidgetType";
|
import { WidgetType } from "../../widgets/WidgetType";
|
||||||
import { useCall } from "../useCall";
|
import { useCall, useConnectionState, useParticipantCount } from "../useCall";
|
||||||
import { useRoomMemberCount } from "../useRoomMembers";
|
import { useRoomMemberCount } from "../useRoomMembers";
|
||||||
import { ElementCall } from "../../models/Call";
|
import { Call, ConnectionState, ElementCall } from "../../models/Call";
|
||||||
import { placeCall } from "../../utils/room/placeCall";
|
import { placeCall } from "../../utils/room/placeCall";
|
||||||
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
||||||
import { useRoomState } from "../useRoomState";
|
import { useRoomState } from "../useRoomState";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import { isManagedHybridWidget } from "../../widgets/ManagedHybrid";
|
import { isManagedHybridWidget } from "../../widgets/ManagedHybrid";
|
||||||
import { IApp } from "../../stores/WidgetStore";
|
import { IApp } from "../../stores/WidgetStore";
|
||||||
|
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||||
|
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||||
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
||||||
|
|
||||||
export type PlatformCallType = "element_call" | "jitsi_or_element_call" | "legacy_or_jitsi";
|
export enum PlatformCallType {
|
||||||
|
ElementCall,
|
||||||
|
JitsiCall,
|
||||||
|
LegacyCall,
|
||||||
|
}
|
||||||
|
export const getPlatformCallTypeLabel = (platformCallType: PlatformCallType): string => {
|
||||||
|
switch (platformCallType) {
|
||||||
|
case PlatformCallType.ElementCall:
|
||||||
|
return _t("voip|element_call");
|
||||||
|
case PlatformCallType.JitsiCall:
|
||||||
|
return _t("voip|jitsi_call");
|
||||||
|
case PlatformCallType.LegacyCall:
|
||||||
|
return _t("voip|legacy_call");
|
||||||
|
}
|
||||||
|
};
|
||||||
const enum State {
|
const enum State {
|
||||||
NoCall,
|
NoCall,
|
||||||
NoOneHere,
|
NoOneHere,
|
||||||
|
@ -53,9 +72,14 @@ export const useRoomCall = (
|
||||||
room: Room,
|
room: Room,
|
||||||
): {
|
): {
|
||||||
voiceCallDisabledReason: string | null;
|
voiceCallDisabledReason: string | null;
|
||||||
voiceCallClick(evt: React.MouseEvent): void;
|
voiceCallClick(evt: React.MouseEvent | undefined, selectedType: PlatformCallType): void;
|
||||||
videoCallDisabledReason: string | null;
|
videoCallDisabledReason: string | null;
|
||||||
videoCallClick(evt: React.MouseEvent): void;
|
videoCallClick(evt: React.MouseEvent | undefined, selectedType: PlatformCallType): void;
|
||||||
|
toggleCallMaximized: () => void;
|
||||||
|
isViewingCall: boolean;
|
||||||
|
isConnectedToCall: boolean;
|
||||||
|
hasActiveCallSession: boolean;
|
||||||
|
callOptions: PlatformCallType[];
|
||||||
} => {
|
} => {
|
||||||
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
|
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
|
||||||
const useElementCallExclusively = useMemo(() => {
|
const useElementCallExclusively = useMemo(() => {
|
||||||
|
@ -75,69 +99,83 @@ export const useRoomCall = (
|
||||||
const hasManagedHybridWidget = !!managedHybridWidget;
|
const hasManagedHybridWidget = !!managedHybridWidget;
|
||||||
|
|
||||||
const groupCall = useCall(room.roomId);
|
const groupCall = useCall(room.roomId);
|
||||||
|
const isConnectedToCall = useConnectionState(groupCall) === ConnectionState.Connected;
|
||||||
const hasGroupCall = groupCall !== null;
|
const hasGroupCall = groupCall !== null;
|
||||||
|
const hasActiveCallSession = useParticipantCount(groupCall) > 0;
|
||||||
|
const isViewingCall = useEventEmitterState(SdkContextClass.instance.roomViewStore, UPDATE_EVENT, () =>
|
||||||
|
SdkContextClass.instance.roomViewStore.isViewingCall(),
|
||||||
|
);
|
||||||
|
|
||||||
const memberCount = useRoomMemberCount(room);
|
const memberCount = useRoomMemberCount(room);
|
||||||
|
|
||||||
const [mayEditWidgets, mayCreateElementCalls] = useRoomState(room, () => [
|
const [mayEditWidgets, mayCreateElementCalls] = useRoomState(room, () => [
|
||||||
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
||||||
room.currentState.mayClientSendStateEvent(ElementCall.CALL_EVENT_TYPE.name, room.client),
|
room.currentState.mayClientSendStateEvent(ElementCall.MEMBER_EVENT_TYPE.name, room.client),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const callType = useMemo((): PlatformCallType => {
|
// The options provided to the RoomHeader.
|
||||||
|
// If there are multiple options, the user will be prompted to choose.
|
||||||
|
const callOptions = useMemo((): PlatformCallType[] => {
|
||||||
|
const options = [];
|
||||||
|
if (memberCount <= 2) {
|
||||||
|
options.push(PlatformCallType.LegacyCall);
|
||||||
|
} else if (mayEditWidgets || hasJitsiWidget) {
|
||||||
|
options.push(PlatformCallType.JitsiCall);
|
||||||
|
}
|
||||||
if (groupCallsEnabled) {
|
if (groupCallsEnabled) {
|
||||||
if (hasGroupCall) {
|
if (hasGroupCall || mayCreateElementCalls) {
|
||||||
return "jitsi_or_element_call";
|
options.push(PlatformCallType.ElementCall);
|
||||||
}
|
}
|
||||||
if (mayCreateElementCalls && hasJitsiWidget) {
|
if (useElementCallExclusively && !hasJitsiWidget) {
|
||||||
return "jitsi_or_element_call";
|
return [PlatformCallType.ElementCall];
|
||||||
}
|
}
|
||||||
if (useElementCallExclusively) {
|
if (hasGroupCall && WidgetType.CALL.matches(groupCall.widget.type)) {
|
||||||
return "element_call";
|
// only allow joining joining the ongoing Element call if there is one.
|
||||||
}
|
return [PlatformCallType.ElementCall];
|
||||||
if (memberCount <= 2) {
|
|
||||||
return "legacy_or_jitsi";
|
|
||||||
}
|
|
||||||
if (mayCreateElementCalls) {
|
|
||||||
return "element_call";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "legacy_or_jitsi";
|
return options;
|
||||||
}, [
|
}, [
|
||||||
|
memberCount,
|
||||||
|
mayEditWidgets,
|
||||||
|
hasJitsiWidget,
|
||||||
groupCallsEnabled,
|
groupCallsEnabled,
|
||||||
hasGroupCall,
|
hasGroupCall,
|
||||||
mayCreateElementCalls,
|
mayCreateElementCalls,
|
||||||
hasJitsiWidget,
|
|
||||||
useElementCallExclusively,
|
useElementCallExclusively,
|
||||||
memberCount,
|
groupCall?.widget.type,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let widget: IApp | undefined;
|
let widget: IApp | undefined;
|
||||||
if (callType === "legacy_or_jitsi") {
|
if (callOptions.includes(PlatformCallType.JitsiCall) || callOptions.includes(PlatformCallType.LegacyCall)) {
|
||||||
widget = jitsiWidget ?? managedHybridWidget;
|
widget = jitsiWidget ?? managedHybridWidget;
|
||||||
} else if (callType === "element_call") {
|
}
|
||||||
|
if (callOptions.includes(PlatformCallType.ElementCall)) {
|
||||||
widget = groupCall?.widget;
|
widget = groupCall?.widget;
|
||||||
} else {
|
} else {
|
||||||
widget = groupCall?.widget ?? jitsiWidget;
|
widget = groupCall?.widget ?? jitsiWidget;
|
||||||
}
|
}
|
||||||
|
const updateWidgetState = useCallback((): void => {
|
||||||
|
setCanPinWidget(WidgetLayoutStore.instance.canAddToContainer(room, Container.Top));
|
||||||
|
setWidgetPinned(!!widget && WidgetLayoutStore.instance.isInContainer(room, widget, Container.Top));
|
||||||
|
}, [room, widget]);
|
||||||
|
useEventEmitter(WidgetLayoutStore.instance, WidgetLayoutStore.emissionForRoom(room), updateWidgetState);
|
||||||
|
useEffect(() => {
|
||||||
|
updateWidgetState();
|
||||||
|
}, [room, jitsiWidget, groupCall, updateWidgetState]);
|
||||||
|
const [activeCalls, setActiveCalls] = useState<Call[]>(Array.from(CallStore.instance.activeCalls));
|
||||||
|
useEventEmitter(CallStore.instance, CallStoreEvent.ActiveCalls, () => {
|
||||||
|
setActiveCalls(Array.from(CallStore.instance.activeCalls));
|
||||||
|
});
|
||||||
const [canPinWidget, setCanPinWidget] = useState(false);
|
const [canPinWidget, setCanPinWidget] = useState(false);
|
||||||
const [widgetPinned, setWidgetPinned] = useState(false);
|
const [widgetPinned, setWidgetPinned] = useState(false);
|
||||||
// We only want to prompt to pin the widget if it's not element call based.
|
// We only want to prompt to pin the widget if it's not element call based.
|
||||||
const isECWidget = WidgetType.CALL.matches(widget?.type ?? "");
|
const isECWidget = WidgetType.CALL.matches(widget?.type ?? "");
|
||||||
const promptPinWidget = !isECWidget && canPinWidget && !widgetPinned;
|
const promptPinWidget = !isECWidget && canPinWidget && !widgetPinned;
|
||||||
|
|
||||||
const updateWidgetState = useCallback((): void => {
|
|
||||||
setCanPinWidget(WidgetLayoutStore.instance.canAddToContainer(room, Container.Top));
|
|
||||||
setWidgetPinned(!!widget && WidgetLayoutStore.instance.isInContainer(room, widget, Container.Top));
|
|
||||||
}, [room, widget]);
|
|
||||||
|
|
||||||
useEventEmitter(WidgetLayoutStore.instance, WidgetLayoutStore.emissionForRoom(room), updateWidgetState);
|
|
||||||
useEffect(() => {
|
|
||||||
updateWidgetState();
|
|
||||||
}, [room, jitsiWidget, groupCall, updateWidgetState]);
|
|
||||||
|
|
||||||
const state = useMemo((): State => {
|
const state = useMemo((): State => {
|
||||||
|
if (activeCalls.find((call) => call.roomId != room.roomId)) {
|
||||||
|
return State.Ongoing;
|
||||||
|
}
|
||||||
if (hasGroupCall || hasJitsiWidget || hasManagedHybridWidget) {
|
if (hasGroupCall || hasJitsiWidget || hasManagedHybridWidget) {
|
||||||
return promptPinWidget ? State.Unpinned : State.Ongoing;
|
return promptPinWidget ? State.Unpinned : State.Ongoing;
|
||||||
}
|
}
|
||||||
|
@ -152,9 +190,9 @@ export const useRoomCall = (
|
||||||
if (!mayCreateElementCalls && !mayEditWidgets) {
|
if (!mayCreateElementCalls && !mayEditWidgets) {
|
||||||
return State.NoPermission;
|
return State.NoPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
return State.NoCall;
|
return State.NoCall;
|
||||||
}, [
|
}, [
|
||||||
|
activeCalls,
|
||||||
hasGroupCall,
|
hasGroupCall,
|
||||||
hasJitsiWidget,
|
hasJitsiWidget,
|
||||||
hasLegacyCall,
|
hasLegacyCall,
|
||||||
|
@ -163,29 +201,30 @@ export const useRoomCall = (
|
||||||
mayEditWidgets,
|
mayEditWidgets,
|
||||||
memberCount,
|
memberCount,
|
||||||
promptPinWidget,
|
promptPinWidget,
|
||||||
|
room.roomId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const voiceCallClick = useCallback(
|
const voiceCallClick = useCallback(
|
||||||
(evt: React.MouseEvent): void => {
|
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||||
evt.stopPropagation();
|
evt?.stopPropagation();
|
||||||
if (widget && promptPinWidget) {
|
if (widget && promptPinWidget) {
|
||||||
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
||||||
} else {
|
} else {
|
||||||
placeCall(room, CallType.Voice, callType, evt.shiftKey);
|
placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey ?? false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[promptPinWidget, room, widget, callType],
|
[promptPinWidget, room, widget],
|
||||||
);
|
);
|
||||||
const videoCallClick = useCallback(
|
const videoCallClick = useCallback(
|
||||||
(evt: React.MouseEvent): void => {
|
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||||
evt.stopPropagation();
|
evt?.stopPropagation();
|
||||||
if (widget && promptPinWidget) {
|
if (widget && promptPinWidget) {
|
||||||
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
||||||
} else {
|
} else {
|
||||||
placeCall(room, CallType.Video, callType, evt.shiftKey);
|
placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey ?? false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[widget, promptPinWidget, room, callType],
|
[widget, promptPinWidget, room],
|
||||||
);
|
);
|
||||||
|
|
||||||
let voiceCallDisabledReason: string | null;
|
let voiceCallDisabledReason: string | null;
|
||||||
|
@ -208,6 +247,14 @@ export const useRoomCall = (
|
||||||
voiceCallDisabledReason = null;
|
voiceCallDisabledReason = null;
|
||||||
videoCallDisabledReason = null;
|
videoCallDisabledReason = null;
|
||||||
}
|
}
|
||||||
|
const toggleCallMaximized = useCallback(() => {
|
||||||
|
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: room.roomId,
|
||||||
|
metricsTrigger: undefined,
|
||||||
|
view_call: !isViewingCall,
|
||||||
|
});
|
||||||
|
}, [isViewingCall, room.roomId]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We've gone through all the steps
|
* We've gone through all the steps
|
||||||
|
@ -217,5 +264,10 @@ export const useRoomCall = (
|
||||||
voiceCallClick,
|
voiceCallClick,
|
||||||
videoCallDisabledReason,
|
videoCallDisabledReason,
|
||||||
videoCallClick,
|
videoCallClick,
|
||||||
|
toggleCallMaximized: toggleCallMaximized,
|
||||||
|
isViewingCall: isViewingCall,
|
||||||
|
isConnectedToCall: isConnectedToCall,
|
||||||
|
hasActiveCallSession: hasActiveCallSession,
|
||||||
|
callOptions,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,21 +36,22 @@ export const useCallForWidget = (widgetId: string, roomId: string): Call | null
|
||||||
return call?.widget.id === widgetId ? call : null;
|
return call?.widget.id === widgetId ? call : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useConnectionState = (call: Call): ConnectionState =>
|
export const useConnectionState = (call: Call | null): ConnectionState =>
|
||||||
useTypedEventEmitterState(
|
useTypedEventEmitterState(
|
||||||
call,
|
call ?? undefined,
|
||||||
CallEvent.ConnectionState,
|
CallEvent.ConnectionState,
|
||||||
useCallback((state) => state ?? call.connectionState, [call]),
|
useCallback((state) => state ?? call?.connectionState ?? ConnectionState.Disconnected, [call]),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useParticipants = (call: Call): Map<RoomMember, Set<string>> =>
|
export const useParticipants = (call: Call | null): Map<RoomMember, Set<string>> => {
|
||||||
useTypedEventEmitterState(
|
return useTypedEventEmitterState(
|
||||||
call,
|
call ?? undefined,
|
||||||
CallEvent.Participants,
|
CallEvent.Participants,
|
||||||
useCallback((state) => state ?? call.participants, [call]),
|
useCallback((state) => state ?? call?.participants ?? [], [call]),
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const useParticipantCount = (call: Call): number => {
|
export const useParticipantCount = (call: Call | null): number => {
|
||||||
const participants = useParticipants(call);
|
const participants = useParticipants(call);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
|
|
@ -3807,6 +3807,7 @@
|
||||||
"camera_enabled": "Your camera is still enabled",
|
"camera_enabled": "Your camera is still enabled",
|
||||||
"cannot_call_yourself_description": "You cannot place a call with yourself.",
|
"cannot_call_yourself_description": "You cannot place a call with yourself.",
|
||||||
"change_input_device": "Change input device",
|
"change_input_device": "Change input device",
|
||||||
|
"close_lobby": "Close lobby",
|
||||||
"connecting": "Connecting",
|
"connecting": "Connecting",
|
||||||
"connection_lost": "Connectivity to the server has been lost",
|
"connection_lost": "Connectivity to the server has been lost",
|
||||||
"connection_lost_description": "You cannot place calls without a connection to the server.",
|
"connection_lost_description": "You cannot place calls without a connection to the server.",
|
||||||
|
@ -3820,6 +3821,7 @@
|
||||||
"disabled_no_perms_start_video_call": "You do not have permission to start video calls",
|
"disabled_no_perms_start_video_call": "You do not have permission to start video calls",
|
||||||
"disabled_no_perms_start_voice_call": "You do not have permission to start voice calls",
|
"disabled_no_perms_start_voice_call": "You do not have permission to start voice calls",
|
||||||
"disabled_ongoing_call": "Ongoing call",
|
"disabled_ongoing_call": "Ongoing call",
|
||||||
|
"element_call": "Element Call",
|
||||||
"enable_camera": "Turn on camera",
|
"enable_camera": "Turn on camera",
|
||||||
"enable_microphone": "Unmute microphone",
|
"enable_microphone": "Unmute microphone",
|
||||||
"expand": "Return to call",
|
"expand": "Return to call",
|
||||||
|
@ -3828,9 +3830,13 @@
|
||||||
"hangup": "Hangup",
|
"hangup": "Hangup",
|
||||||
"hide_sidebar_button": "Hide sidebar",
|
"hide_sidebar_button": "Hide sidebar",
|
||||||
"input_devices": "Input devices",
|
"input_devices": "Input devices",
|
||||||
|
"jitsi_call": "Jitsi Conference",
|
||||||
"join_button_tooltip_call_full": "Sorry — this call is currently full",
|
"join_button_tooltip_call_full": "Sorry — this call is currently full",
|
||||||
"join_button_tooltip_connecting": "Connecting",
|
"join_button_tooltip_connecting": "Connecting",
|
||||||
|
"legacy_call": "Legacy Call",
|
||||||
"maximise": "Fill screen",
|
"maximise": "Fill screen",
|
||||||
|
"maximise_call": "Maximise call",
|
||||||
|
"minimise_call": "Minimise call",
|
||||||
"misconfigured_server": "Call failed due to misconfigured server",
|
"misconfigured_server": "Call failed due to misconfigured server",
|
||||||
"misconfigured_server_description": "Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.",
|
"misconfigured_server_description": "Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.",
|
||||||
"misconfigured_server_fallback": "Alternatively, you can try to use the public server at <server/>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.",
|
"misconfigured_server_fallback": "Alternatively, you can try to use the public server at <server/>, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.",
|
||||||
|
@ -3878,6 +3884,7 @@
|
||||||
"user_is_presenting": "%(sharerName)s is presenting",
|
"user_is_presenting": "%(sharerName)s is presenting",
|
||||||
"video_call": "Video call",
|
"video_call": "Video call",
|
||||||
"video_call_started": "Video call started",
|
"video_call_started": "Video call started",
|
||||||
|
"video_call_using": "Video call using:",
|
||||||
"voice_call": "Voice call",
|
"voice_call": "Voice call",
|
||||||
"you_are_presenting": "You are presenting"
|
"you_are_presenting": "You are presenting"
|
||||||
},
|
},
|
||||||
|
|
|
@ -65,18 +65,26 @@ const waitForEvent = async (
|
||||||
emitter: EventEmitter,
|
emitter: EventEmitter,
|
||||||
event: string,
|
event: string,
|
||||||
pred: (...args: any[]) => boolean = () => true,
|
pred: (...args: any[]) => boolean = () => true,
|
||||||
|
customTimeout?: number | false,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
let listener: (...args: any[]) => void;
|
let listener: (...args: any[]) => void;
|
||||||
const wait = new Promise<void>((resolve) => {
|
const wait = new Promise<void>((resolve) => {
|
||||||
listener = (...args) => {
|
listener = (...args) => {
|
||||||
if (pred(...args)) resolve();
|
if (pred(...args)) {
|
||||||
|
resolve();
|
||||||
|
if (customTimeout === false) {
|
||||||
|
emitter.off(event, listener!);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
emitter.on(event, listener);
|
emitter.on(event, listener);
|
||||||
});
|
});
|
||||||
|
|
||||||
const timedOut = (await timeout(wait, false, TIMEOUT_MS)) === false;
|
if (customTimeout !== false) {
|
||||||
emitter.off(event, listener!);
|
const timedOut = (await timeout(wait, false, customTimeout ?? TIMEOUT_MS)) === false;
|
||||||
if (timedOut) throw new Error("Timed out");
|
emitter.off(event, listener!);
|
||||||
|
if (timedOut) throw new Error("Timed out");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ConnectionState {
|
export enum ConnectionState {
|
||||||
|
@ -899,6 +907,7 @@ export class ElementCall extends Call {
|
||||||
MatrixRTCSessionEvent.MembershipsChanged,
|
MatrixRTCSessionEvent.MembershipsChanged,
|
||||||
(_, newMemberships: CallMembership[]) =>
|
(_, newMemberships: CallMembership[]) =>
|
||||||
newMemberships.some((m) => m.sender === this.client.getUserId()),
|
newMemberships.some((m) => m.sender === this.client.getUserId()),
|
||||||
|
false, // allow user to wait as long as they want (no timeout)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await waitForEvent(
|
await waitForEvent(
|
||||||
|
@ -906,6 +915,7 @@ export class ElementCall extends Call {
|
||||||
MatrixRTCSessionManagerEvents.SessionStarted,
|
MatrixRTCSessionManagerEvents.SessionStarted,
|
||||||
(roomId: string, session: MatrixRTCSession) =>
|
(roomId: string, session: MatrixRTCSession) =>
|
||||||
this.session.callId === session.callId && roomId === this.roomId,
|
this.session.callId === session.callId && roomId === this.roomId,
|
||||||
|
false, // allow user to wait as long as they want (no timeout)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.sendCallNotify();
|
this.sendCallNotify();
|
||||||
|
|
|
@ -345,8 +345,11 @@ export class StopGapWidget extends EventEmitter {
|
||||||
if (this.messaging?.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
|
if (this.messaging?.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack
|
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack
|
||||||
|
if (ev.detail.data.value) {
|
||||||
if (this.stickyPromise) await this.stickyPromise();
|
// If the widget wants to become sticky we wait for the stickyPromise to resolve
|
||||||
|
if (this.stickyPromise) await this.stickyPromise();
|
||||||
|
}
|
||||||
|
// Stop being persistent can be done instantly
|
||||||
ActiveWidgetStore.instance.setWidgetPersistence(
|
ActiveWidgetStore.instance.setWidgetPersistence(
|
||||||
this.mockWidget.id,
|
this.mockWidget.id,
|
||||||
this.roomId ?? null,
|
this.roomId ?? null,
|
||||||
|
|
|
@ -20,6 +20,8 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call-solid.svg";
|
||||||
|
|
||||||
import { _t } from "../languageHandler";
|
import { _t } from "../languageHandler";
|
||||||
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
||||||
|
@ -28,7 +30,6 @@ import defaultDispatcher from "../dispatcher/dispatcher";
|
||||||
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { Action } from "../dispatcher/actions";
|
import { Action } from "../dispatcher/actions";
|
||||||
import ToastStore from "../stores/ToastStore";
|
import ToastStore from "../stores/ToastStore";
|
||||||
import AccessibleTooltipButton from "../components/views/elements/AccessibleTooltipButton";
|
|
||||||
import {
|
import {
|
||||||
LiveContentSummary,
|
LiveContentSummary,
|
||||||
LiveContentSummaryWithCall,
|
LiveContentSummaryWithCall,
|
||||||
|
@ -41,6 +42,7 @@ import { ActionPayload } from "../dispatcher/payloads";
|
||||||
import { Call } from "../models/Call";
|
import { Call } from "../models/Call";
|
||||||
import { AudioID } from "../LegacyCallHandler";
|
import { AudioID } from "../LegacyCallHandler";
|
||||||
import { useTypedEventEmitter } from "../hooks/useEventEmitter";
|
import { useTypedEventEmitter } from "../hooks/useEventEmitter";
|
||||||
|
import AccessibleTooltipButton from "../components/views/elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
export const getIncomingCallToastKey = (callId: string, roomId: string): string => `call_${callId}_${roomId}`;
|
export const getIncomingCallToastKey = (callId: string, roomId: string): string => `call_${callId}_${roomId}`;
|
||||||
const MAX_RING_TIME_MS = 10 * 1000;
|
const MAX_RING_TIME_MS = 10 * 1000;
|
||||||
|
@ -54,15 +56,15 @@ function JoinCallButtonWithCall({ onClick, call }: JoinCallButtonWithCallProps):
|
||||||
const disabledTooltip = useJoinCallButtonDisabledTooltip(call);
|
const disabledTooltip = useJoinCallButtonDisabledTooltip(call);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleTooltipButton
|
<Button
|
||||||
className="mx_IncomingCallToast_joinButton"
|
className="mx_IncomingCallToast_joinButton"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabledTooltip !== null}
|
disabled={disabledTooltip !== null}
|
||||||
tooltip={disabledTooltip ?? undefined}
|
|
||||||
kind="primary"
|
kind="primary"
|
||||||
|
size="sm"
|
||||||
>
|
>
|
||||||
{_t("action|join")}
|
{_t("action|join")}
|
||||||
</AccessibleTooltipButton>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,13 +181,15 @@ export function IncomingCallToast({ notifyEvent }: Props): JSX.Element {
|
||||||
{call ? (
|
{call ? (
|
||||||
<JoinCallButtonWithCall onClick={onJoinClick} call={call} />
|
<JoinCallButtonWithCall onClick={onJoinClick} call={call} />
|
||||||
) : (
|
) : (
|
||||||
<AccessibleTooltipButton
|
<Button
|
||||||
className="mx_IncomingCallToast_joinButton"
|
className="mx_IncomingCallToast_joinButton"
|
||||||
onClick={onJoinClick}
|
onClick={onJoinClick}
|
||||||
kind="primary"
|
kind="primary"
|
||||||
|
size="sm"
|
||||||
|
Icon={VideoCallIcon}
|
||||||
>
|
>
|
||||||
{_t("action|join")}
|
{_t("action|join")}
|
||||||
</AccessibleTooltipButton>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
|
|
|
@ -35,27 +35,15 @@ export const placeCall = async (
|
||||||
platformCallType: PlatformCallType,
|
platformCallType: PlatformCallType,
|
||||||
skipLobby: boolean,
|
skipLobby: boolean,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
switch (platformCallType) {
|
if (platformCallType == PlatformCallType.LegacyCall || platformCallType == PlatformCallType.JitsiCall) {
|
||||||
case "legacy_or_jitsi":
|
await LegacyCallHandler.instance.placeCall(room.roomId, callType);
|
||||||
await LegacyCallHandler.instance.placeCall(room.roomId, callType);
|
} else if (platformCallType == PlatformCallType.ElementCall) {
|
||||||
break;
|
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||||
// TODO: Remove the jitsi_or_element_call case and
|
action: Action.ViewRoom,
|
||||||
// use the commented code below
|
room_id: room.roomId,
|
||||||
case "element_call":
|
view_call: true,
|
||||||
case "jitsi_or_element_call":
|
skipLobby,
|
||||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
metricsTrigger: undefined,
|
||||||
action: Action.ViewRoom,
|
});
|
||||||
room_id: room.roomId,
|
|
||||||
view_call: true,
|
|
||||||
metricsTrigger: undefined,
|
|
||||||
skipLobby,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
// case "jitsi_or_element_call":
|
|
||||||
// TODO: Open dropdown menu to choice between
|
|
||||||
// EC and Jitsi. Waiting on Compound's dropdown
|
|
||||||
// component
|
|
||||||
// break;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -323,7 +323,7 @@ describe("RoomHeader", () => {
|
||||||
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
|
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
|
||||||
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(true);
|
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(true);
|
||||||
|
|
||||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({ widget: {} } as Call);
|
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({ widget: {}, on: () => {} } as unknown as Call);
|
||||||
|
|
||||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||||
expect(getByLabelText(container, "Ongoing call")).toHaveAttribute("aria-disabled", "true");
|
expect(getByLabelText(container, "Ongoing call")).toHaveAttribute("aria-disabled", "true");
|
||||||
|
@ -336,8 +336,11 @@ describe("RoomHeader", () => {
|
||||||
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(false);
|
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(false);
|
||||||
const spy = jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
|
const spy = jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
|
||||||
|
|
||||||
const widget = { eventId: "some_id_so_it_is_interpreted_as_non_virtual_widget" };
|
const widget = {};
|
||||||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({ widget } as Call);
|
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({
|
||||||
|
widget,
|
||||||
|
on: () => {},
|
||||||
|
} as unknown as Call);
|
||||||
|
|
||||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||||
expect(getByLabelText(container, "Video call")).not.toHaveAttribute("aria-disabled", "true");
|
expect(getByLabelText(container, "Video call")).not.toHaveAttribute("aria-disabled", "true");
|
||||||
|
@ -367,6 +370,10 @@ describe("RoomHeader", () => {
|
||||||
|
|
||||||
it("calls using legacy or jitsi", async () => {
|
it("calls using legacy or jitsi", async () => {
|
||||||
mockRoomMembers(room, 2);
|
mockRoomMembers(room, 2);
|
||||||
|
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
||||||
|
if (key === "im.vector.modular.widgets") return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||||
|
|
||||||
const voiceButton = getByLabelText(container, "Voice call");
|
const voiceButton = getByLabelText(container, "Voice call");
|
||||||
|
@ -409,8 +416,7 @@ describe("RoomHeader", () => {
|
||||||
mockRoomMembers(room, 3);
|
mockRoomMembers(room, 3);
|
||||||
|
|
||||||
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
||||||
if (key === "im.vector.modular.widgets") return true;
|
if (key === ElementCall.MEMBER_EVENT_TYPE.name) return true;
|
||||||
if (key === ElementCall.CALL_EVENT_TYPE.name) return true;
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -80,20 +80,6 @@ describe("<VideoRoomChatButton />", () => {
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render button when room is not a video room", () => {
|
|
||||||
const room = makeRoom(false);
|
|
||||||
getComponent(room);
|
|
||||||
|
|
||||||
expect(screen.queryByLabelText("Chat")).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders button when room is a video room", () => {
|
|
||||||
const room = makeRoom();
|
|
||||||
getComponent(room);
|
|
||||||
|
|
||||||
expect(screen.getByLabelText("Chat")).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("toggles timeline in right panel on click", () => {
|
it("toggles timeline in right panel on click", () => {
|
||||||
const room = makeRoom();
|
const room = makeRoom();
|
||||||
getComponent(room);
|
getComponent(room);
|
||||||
|
|
|
@ -1,23 +1,5 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<VideoRoomChatButton /> renders button when room is a video room 1`] = `
|
|
||||||
<button
|
|
||||||
aria-label="Chat"
|
|
||||||
class="_icon-button_16nk7_17"
|
|
||||||
data-state="closed"
|
|
||||||
role="button"
|
|
||||||
style="--cpd-icon-button-size: 32px;"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="_indicator-icon_jtb4d_26"
|
|
||||||
style="--cpd-icon-button-size: 100%;"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<VideoRoomChatButton /> renders button with an unread marker when room is unread 1`] = `
|
exports[`<VideoRoomChatButton /> renders button with an unread marker when room is unread 1`] = `
|
||||||
<button
|
<button
|
||||||
aria-label="Chat"
|
aria-label="Chat"
|
||||||
|
|
|
@ -47,7 +47,6 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
aria-label="There's no one here to call"
|
aria-label="There's no one here to call"
|
||||||
class="_icon-button_16nk7_17"
|
class="_icon-button_16nk7_17"
|
||||||
data-state="closed"
|
|
||||||
role="button"
|
role="button"
|
||||||
style="--cpd-icon-button-size: 32px;"
|
style="--cpd-icon-button-size: 32px;"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -56,7 +55,9 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
|
||||||
class="_indicator-icon_jtb4d_26"
|
class="_indicator-icon_jtb4d_26"
|
||||||
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
|
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
|
||||||
>
|
>
|
||||||
<div />
|
<div
|
||||||
|
data-state="closed"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
Loading…
Reference in New Issue