2025-01-27 20:35:46 +05:30
|
|
|
/*
|
2025-12-03 15:21:15 +00:00
|
|
|
Copyright (C) 2025 Element Creations Ltd
|
2025-01-27 20:35:46 +05:30
|
|
|
Copyright 2024 New Vector Ltd.
|
|
|
|
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
|
|
|
Please see LICENSE files in the repository root for full details.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-12-03 15:21:15 +00:00
|
|
|
import React, { type JSX, useCallback, useState } from "react";
|
2025-11-03 13:25:08 +00:00
|
|
|
import { Text, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web";
|
2025-01-27 20:35:46 +05:30
|
|
|
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
|
2025-02-05 11:38:40 +00:00
|
|
|
import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call-solid";
|
2025-01-27 20:35:46 +05:30
|
|
|
import CloseCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
|
|
|
|
|
import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid";
|
|
|
|
|
import RoomInfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info-solid";
|
|
|
|
|
import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-solid";
|
|
|
|
|
import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
|
2025-02-24 18:08:12 +01:00
|
|
|
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
2025-01-27 20:35:46 +05:30
|
|
|
import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
|
2026-01-27 15:06:22 +00:00
|
|
|
import { HistoryVisibility, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
2025-02-05 13:25:06 +00:00
|
|
|
import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
2025-11-03 16:26:47 +00:00
|
|
|
import { Flex, Box } from "@element-hq/web-shared-components";
|
2026-01-02 17:51:15 +00:00
|
|
|
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
2026-01-29 14:31:21 +00:00
|
|
|
import { HistoryIcon, UserProfileSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
2025-01-27 20:35:46 +05:30
|
|
|
|
|
|
|
|
import { useRoomName } from "../../../../hooks/useRoomName.ts";
|
|
|
|
|
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases.ts";
|
|
|
|
|
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
|
|
|
|
|
import { useRoomMemberCount, useRoomMembers } from "../../../../hooks/useRoomMembers.ts";
|
|
|
|
|
import { _t } from "../../../../languageHandler.tsx";
|
|
|
|
|
import { getPlatformCallTypeProps, useRoomCall } from "../../../../hooks/room/useRoomCall.tsx";
|
|
|
|
|
import { useRoomThreadNotifications } from "../../../../hooks/room/useRoomThreadNotifications.ts";
|
|
|
|
|
import { useGlobalNotificationState } from "../../../../hooks/useGlobalNotificationState.ts";
|
|
|
|
|
import { useFeatureEnabled } from "../../../../hooks/useSettings.ts";
|
|
|
|
|
import { useEncryptionStatus } from "../../../../hooks/useEncryptionStatus.ts";
|
|
|
|
|
import { E2EStatus } from "../../../../utils/ShieldUtils.ts";
|
|
|
|
|
import FacePile from "../../elements/FacePile.tsx";
|
|
|
|
|
import { useRoomState } from "../../../../hooks/useRoomState.ts";
|
|
|
|
|
import RoomAvatar from "../../avatars/RoomAvatar.tsx";
|
|
|
|
|
import { formatCount } from "../../../../utils/FormattingUtils.ts";
|
|
|
|
|
import RightPanelStore from "../../../../stores/right-panel/RightPanelStore.ts";
|
|
|
|
|
import PosthogTrackers from "../../../../PosthogTrackers.ts";
|
|
|
|
|
import { VideoRoomChatButton } from "./VideoRoomChatButton.tsx";
|
|
|
|
|
import { RoomKnocksBar } from "../RoomKnocksBar.tsx";
|
|
|
|
|
import { isVideoRoom as calcIsVideoRoom } from "../../../../utils/video-rooms.ts";
|
|
|
|
|
import { notificationLevelToIndicator } from "../../../../utils/notifications.ts";
|
|
|
|
|
import { CallGuestLinkButton } from "./CallGuestLinkButton.tsx";
|
2025-02-05 13:25:06 +00:00
|
|
|
import { type ButtonEvent } from "../../elements/AccessibleButton.tsx";
|
2025-01-27 20:35:46 +05:30
|
|
|
import WithPresenceIndicator, { useDmMember } from "../../avatars/WithPresenceIndicator.tsx";
|
2025-02-05 13:25:06 +00:00
|
|
|
import { type IOOBData } from "../../../../stores/ThreepidInviteStore.ts";
|
2026-06-29 13:58:52 +01:00
|
|
|
import { MainSplitContentType } from "../../../../contexts/RoomContext.ts";
|
2025-01-27 20:35:46 +05:30
|
|
|
import defaultDispatcher from "../../../../dispatcher/dispatcher.ts";
|
2026-06-30 13:49:28 +01:00
|
|
|
import { RoomSettingsTab } from "../../dialogs/RoomSettingsDialog-tab";
|
2025-01-27 20:35:46 +05:30
|
|
|
import { useScopedRoomContext } from "../../../../contexts/ScopedRoomContext.tsx";
|
|
|
|
|
import { ToggleableIcon } from "./toggle/ToggleableIcon.tsx";
|
|
|
|
|
import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/CurrentRightPanelPhaseContext.tsx";
|
2025-11-03 13:25:08 +00:00
|
|
|
import { LocalRoom } from "../../../../models/LocalRoom.ts";
|
2026-01-27 15:06:22 +00:00
|
|
|
import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";
|
2025-01-27 20:35:46 +05:30
|
|
|
|
2025-11-03 13:25:08 +00:00
|
|
|
function RoomHeaderButtons({
|
2025-01-27 20:35:46 +05:30
|
|
|
room,
|
2026-03-13 13:44:18 +00:00
|
|
|
legacyAdditionalButtons,
|
|
|
|
|
extraButtons,
|
2025-01-27 20:35:46 +05:30
|
|
|
}: {
|
2025-11-03 13:25:08 +00:00
|
|
|
room: Room;
|
2026-03-13 13:44:18 +00:00
|
|
|
legacyAdditionalButtons?: ViewRoomOpts["buttons"];
|
|
|
|
|
extraButtons?: JSX.Element;
|
2025-01-27 20:35:46 +05:30
|
|
|
}): JSX.Element {
|
|
|
|
|
const members = useRoomMembers(room, 2500);
|
2025-10-09 12:40:20 +02:00
|
|
|
const memberCount = useRoomMemberCount(room, { throttleWait: 2500, includeInvited: true });
|
2025-01-27 20:35:46 +05:30
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
voiceCallDisabledReason,
|
|
|
|
|
voiceCallClick,
|
|
|
|
|
videoCallDisabledReason,
|
|
|
|
|
videoCallClick,
|
|
|
|
|
toggleCallMaximized: toggleCall,
|
|
|
|
|
isViewingCall,
|
|
|
|
|
isConnectedToCall,
|
2026-01-02 17:51:15 +00:00
|
|
|
activeCallSessionType,
|
2025-01-27 20:35:46 +05:30
|
|
|
callOptions,
|
|
|
|
|
showVoiceCallButton,
|
|
|
|
|
showVideoCallButton,
|
|
|
|
|
} = useRoomCall(room);
|
|
|
|
|
const threadNotifications = useRoomThreadNotifications(room);
|
|
|
|
|
const globalNotificationState = useGlobalNotificationState();
|
|
|
|
|
|
|
|
|
|
const dmMember = useDmMember(room);
|
|
|
|
|
const isDirectMessage = !!dmMember;
|
|
|
|
|
|
|
|
|
|
const notificationsEnabled = useFeatureEnabled("feature_notifications");
|
|
|
|
|
|
|
|
|
|
const videoClick = useCallback(
|
|
|
|
|
(ev: React.MouseEvent) => videoCallClick(ev, callOptions[0]),
|
|
|
|
|
[callOptions, videoCallClick],
|
|
|
|
|
);
|
|
|
|
|
|
2025-12-03 15:21:15 +00:00
|
|
|
const voiceClick = useCallback(
|
|
|
|
|
(ev: React.MouseEvent) => voiceCallClick(ev, callOptions[0]),
|
|
|
|
|
[callOptions, voiceCallClick],
|
|
|
|
|
);
|
|
|
|
|
|
2025-01-27 20:35:46 +05:30
|
|
|
const toggleCallButton = (
|
|
|
|
|
<Tooltip label={isViewingCall ? _t("voip|minimise_call") : _t("voip|maximise_call")}>
|
|
|
|
|
<IconButton onClick={toggleCall}>
|
|
|
|
|
<VideoCallIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const joinCallButton = (
|
2026-01-02 17:51:15 +00:00
|
|
|
<Tooltip
|
|
|
|
|
label={
|
|
|
|
|
videoCallDisabledReason ??
|
|
|
|
|
(activeCallSessionType === CallType.Voice ? _t("voip|voice_call") : _t("voip|video_call"))
|
|
|
|
|
}
|
|
|
|
|
>
|
2025-01-27 20:35:46 +05:30
|
|
|
<Button
|
2026-04-23 13:18:56 +01:00
|
|
|
size="md"
|
2026-07-06 23:57:26 +08:00
|
|
|
onClick={activeCallSessionType === CallType.Video ? videoClick : voiceClick}
|
2026-01-02 17:51:15 +00:00
|
|
|
// If we know this is a voice session, show the voice call. All other kinds of call are video calls.
|
|
|
|
|
Icon={activeCallSessionType === CallType.Voice ? VoiceCallIcon : VideoCallIcon}
|
2025-01-27 20:35:46 +05:30
|
|
|
className="mx_RoomHeader_join_button"
|
|
|
|
|
disabled={!!videoCallDisabledReason}
|
|
|
|
|
color="primary"
|
2026-01-02 17:51:15 +00:00
|
|
|
aria-label={
|
|
|
|
|
videoCallDisabledReason ??
|
|
|
|
|
(activeCallSessionType === CallType.Voice
|
|
|
|
|
? _t("room|header|join_voice_call")
|
|
|
|
|
: _t("room|header|join_video_call"))
|
|
|
|
|
}
|
2025-09-23 13:38:18 +01:00
|
|
|
data-testId="join-call-button"
|
2025-01-27 20:35:46 +05:30
|
|
|
>
|
|
|
|
|
{_t("action|join")}
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
|
2025-12-03 15:21:15 +00:00
|
|
|
const videoCallIconWithTooltip = (
|
2025-01-27 20:35:46 +05:30
|
|
|
<Tooltip label={videoCallDisabledReason ?? _t("voip|video_call")}>
|
|
|
|
|
<VideoCallIcon />
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
|
2025-12-03 15:21:15 +00:00
|
|
|
const voiceCallIconWithTooltip = (
|
|
|
|
|
<Tooltip label={videoCallDisabledReason ?? _t("voip|voice_call")}>
|
|
|
|
|
<VoiceCallIcon />
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
2025-01-27 20:35:46 +05:30
|
|
|
|
2025-12-03 15:21:15 +00:00
|
|
|
const [videoMenuOpen, setVideoMenuOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
const onVideoOpenChange = useCallback(
|
2025-01-27 20:35:46 +05:30
|
|
|
(newOpen: boolean) => {
|
2025-12-03 15:21:15 +00:00
|
|
|
if (!videoCallDisabledReason) setVideoMenuOpen(newOpen);
|
2025-01-27 20:35:46 +05:30
|
|
|
},
|
|
|
|
|
[videoCallDisabledReason],
|
|
|
|
|
);
|
|
|
|
|
|
2025-12-03 15:21:15 +00:00
|
|
|
const [voiceMenuOpen, setVoiceMenuOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
const onVoiceOpenChange = useCallback(
|
|
|
|
|
(newOpen: boolean) => {
|
|
|
|
|
if (!voiceCallDisabledReason) setVoiceMenuOpen(newOpen);
|
|
|
|
|
},
|
|
|
|
|
[voiceCallDisabledReason],
|
|
|
|
|
);
|
|
|
|
|
|
2025-01-27 20:35:46 +05:30
|
|
|
const startVideoCallButton = (
|
|
|
|
|
<>
|
|
|
|
|
{/* Can be either a menu or just a button depending on the number of call options.*/}
|
|
|
|
|
{callOptions.length > 1 ? (
|
|
|
|
|
<Menu
|
2025-12-03 15:21:15 +00:00
|
|
|
open={videoMenuOpen}
|
|
|
|
|
onOpenChange={onVideoOpenChange}
|
2025-01-27 20:35:46 +05:30
|
|
|
title={_t("voip|video_call_using")}
|
|
|
|
|
trigger={
|
|
|
|
|
<IconButton
|
|
|
|
|
disabled={!!videoCallDisabledReason}
|
|
|
|
|
aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
|
|
|
|
|
>
|
2025-12-03 15:21:15 +00:00
|
|
|
{videoCallIconWithTooltip}
|
2025-01-27 20:35:46 +05:30
|
|
|
</IconButton>
|
|
|
|
|
}
|
|
|
|
|
side="left"
|
|
|
|
|
align="start"
|
|
|
|
|
>
|
|
|
|
|
{callOptions.map((option) => {
|
|
|
|
|
const { label, children } = getPlatformCallTypeProps(option);
|
|
|
|
|
return (
|
|
|
|
|
<MenuItem
|
|
|
|
|
key={option}
|
|
|
|
|
label={label}
|
|
|
|
|
aria-label={label}
|
|
|
|
|
children={children}
|
|
|
|
|
className="mx_RoomHeader_videoCallOption"
|
2025-05-29 14:26:54 +01:00
|
|
|
onClick={(ev) => {
|
2025-12-03 15:21:15 +00:00
|
|
|
setVideoMenuOpen(false);
|
2025-05-29 14:26:54 +01:00
|
|
|
videoCallClick(ev, option);
|
|
|
|
|
}}
|
2025-01-27 20:35:46 +05:30
|
|
|
Icon={VideoCallIcon}
|
|
|
|
|
onSelect={() => {} /* Dummy handler since we want the click event.*/}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Menu>
|
|
|
|
|
) : (
|
|
|
|
|
<IconButton
|
|
|
|
|
disabled={!!videoCallDisabledReason}
|
|
|
|
|
aria-label={videoCallDisabledReason ?? _t("voip|video_call")}
|
|
|
|
|
onClick={videoClick}
|
|
|
|
|
>
|
2025-12-03 15:21:15 +00:00
|
|
|
{videoCallIconWithTooltip}
|
2025-01-27 20:35:46 +05:30
|
|
|
</IconButton>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
2025-12-03 15:21:15 +00:00
|
|
|
const startVoiceCallButton = (
|
|
|
|
|
<>
|
|
|
|
|
{/* Can be either a menu or just a button depending on the number of call options.*/}
|
|
|
|
|
{callOptions.length > 1 ? (
|
|
|
|
|
<Menu
|
|
|
|
|
open={voiceMenuOpen}
|
|
|
|
|
onOpenChange={onVoiceOpenChange}
|
|
|
|
|
title={_t("voip|voice_call_using")}
|
|
|
|
|
trigger={
|
|
|
|
|
<IconButton
|
|
|
|
|
disabled={!!voiceCallDisabledReason}
|
|
|
|
|
aria-label={voiceCallDisabledReason ?? _t("voip|voice_call")}
|
|
|
|
|
>
|
|
|
|
|
{voiceCallIconWithTooltip}
|
|
|
|
|
</IconButton>
|
|
|
|
|
}
|
|
|
|
|
side="left"
|
|
|
|
|
align="start"
|
|
|
|
|
>
|
|
|
|
|
{callOptions.map((option) => {
|
|
|
|
|
const { label, children } = getPlatformCallTypeProps(option);
|
|
|
|
|
return (
|
|
|
|
|
<MenuItem
|
|
|
|
|
key={option}
|
|
|
|
|
label={label}
|
|
|
|
|
aria-label={label}
|
|
|
|
|
children={children}
|
|
|
|
|
className="mx_RoomHeader_videoCallOption"
|
|
|
|
|
onClick={(ev) => {
|
|
|
|
|
setVoiceMenuOpen(false);
|
|
|
|
|
voiceCallClick(ev, option);
|
|
|
|
|
}}
|
|
|
|
|
Icon={VoiceCallIcon}
|
|
|
|
|
onSelect={() => {} /* Dummy handler since we want the click event.*/}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Menu>
|
|
|
|
|
) : (
|
|
|
|
|
<IconButton
|
|
|
|
|
disabled={!!voiceCallDisabledReason}
|
|
|
|
|
aria-label={voiceCallDisabledReason ?? _t("voip|voice_call")}
|
|
|
|
|
onClick={voiceClick}
|
|
|
|
|
>
|
|
|
|
|
{voiceCallIconWithTooltip}
|
|
|
|
|
</IconButton>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
2025-01-27 20:35:46 +05:30
|
|
|
);
|
2025-12-03 15:21:15 +00:00
|
|
|
|
2025-01-27 20:35:46 +05:30
|
|
|
const closeLobbyButton = (
|
|
|
|
|
<Tooltip label={_t("voip|close_lobby")}>
|
|
|
|
|
<IconButton onClick={toggleCall}>
|
|
|
|
|
<CloseCallIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
let videoCallButton: JSX.Element | undefined = startVideoCallButton;
|
2025-12-03 15:21:15 +00:00
|
|
|
let voiceCallButton: JSX.Element | undefined = startVoiceCallButton;
|
2025-01-27 20:35:46 +05:30
|
|
|
if (isConnectedToCall) {
|
|
|
|
|
videoCallButton = toggleCallButton;
|
2025-12-03 15:21:15 +00:00
|
|
|
voiceCallButton = undefined;
|
2025-01-27 20:35:46 +05:30
|
|
|
} else if (isViewingCall) {
|
|
|
|
|
videoCallButton = closeLobbyButton;
|
2025-12-03 15:21:15 +00:00
|
|
|
voiceCallButton = undefined;
|
2025-01-27 20:35:46 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!showVideoCallButton) {
|
|
|
|
|
videoCallButton = undefined;
|
|
|
|
|
}
|
2025-12-03 15:21:15 +00:00
|
|
|
|
2025-01-27 20:35:46 +05:30
|
|
|
if (!showVoiceCallButton) {
|
|
|
|
|
voiceCallButton = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const roomContext = useScopedRoomContext("mainSplitContentType");
|
|
|
|
|
const isVideoRoom = calcIsVideoRoom(room);
|
|
|
|
|
const showChatButton =
|
|
|
|
|
isVideoRoom ||
|
|
|
|
|
roomContext.mainSplitContentType === MainSplitContentType.MaximisedWidget ||
|
|
|
|
|
roomContext.mainSplitContentType === MainSplitContentType.Call;
|
2025-11-03 13:25:08 +00:00
|
|
|
return (
|
|
|
|
|
<>
|
2026-03-13 13:44:18 +00:00
|
|
|
{extraButtons}
|
|
|
|
|
|
|
|
|
|
{legacyAdditionalButtons?.map((props) => {
|
2025-11-03 13:25:08 +00:00
|
|
|
const label = props.label();
|
2025-01-27 20:35:46 +05:30
|
|
|
|
2025-11-03 13:25:08 +00:00
|
|
|
return (
|
|
|
|
|
<Tooltip label={label} key={props.id}>
|
|
|
|
|
<IconButton
|
|
|
|
|
aria-label={label}
|
|
|
|
|
onClick={(event) => {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
props.onClick();
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{typeof props.icon === "function" ? props.icon() : props.icon}
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
|
|
{isViewingCall && <CallGuestLinkButton room={room} />}
|
|
|
|
|
|
2026-01-02 17:51:15 +00:00
|
|
|
{activeCallSessionType && !isConnectedToCall && !isViewingCall ? (
|
2025-11-03 13:25:08 +00:00
|
|
|
joinCallButton
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
{!isVideoRoom && videoCallButton}
|
2025-12-03 15:21:15 +00:00
|
|
|
{!isVideoRoom && voiceCallButton}
|
2025-11-03 13:25:08 +00:00
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{showChatButton && <VideoRoomChatButton room={room} />}
|
|
|
|
|
|
|
|
|
|
<Tooltip label={_t("common|threads")}>
|
|
|
|
|
<IconButton
|
|
|
|
|
indicator={notificationLevelToIndicator(threadNotifications)}
|
|
|
|
|
onClick={(evt) => {
|
|
|
|
|
evt.stopPropagation();
|
|
|
|
|
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.ThreadPanel);
|
|
|
|
|
PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", evt);
|
|
|
|
|
}}
|
|
|
|
|
aria-label={_t("common|threads")}
|
|
|
|
|
>
|
|
|
|
|
<ToggleableIcon Icon={ThreadsIcon} phase={RightPanelPhases.ThreadPanel} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
{notificationsEnabled && (
|
|
|
|
|
<Tooltip label={_t("notifications|enable_prompt_toast_title")}>
|
|
|
|
|
<IconButton
|
|
|
|
|
indicator={notificationLevelToIndicator(globalNotificationState.level)}
|
|
|
|
|
onClick={(evt) => {
|
|
|
|
|
evt.stopPropagation();
|
|
|
|
|
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.NotificationPanel);
|
|
|
|
|
}}
|
|
|
|
|
aria-label={_t("notifications|enable_prompt_toast_title")}
|
|
|
|
|
>
|
|
|
|
|
<ToggleableIcon Icon={NotificationsIcon} phase={RightPanelPhases.NotificationPanel} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Tooltip label={_t("right_panel|room_summary_card|title")}>
|
|
|
|
|
<IconButton
|
|
|
|
|
onClick={(evt) => {
|
|
|
|
|
evt.stopPropagation();
|
|
|
|
|
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary);
|
|
|
|
|
}}
|
|
|
|
|
aria-label={_t("right_panel|room_summary_card|title")}
|
|
|
|
|
>
|
|
|
|
|
<ToggleableIcon Icon={RoomInfoIcon} phase={RightPanelPhases.RoomSummary} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
|
|
|
|
{!isDirectMessage && (
|
|
|
|
|
<Text as="div" size="sm" weight="medium">
|
|
|
|
|
<FacePile
|
|
|
|
|
className="mx_RoomHeader_members"
|
|
|
|
|
members={members.slice(0, 3)}
|
|
|
|
|
size="20px"
|
|
|
|
|
overflow={false}
|
|
|
|
|
viewUserOnClick={false}
|
|
|
|
|
tooltipLabel={_t("room|header_face_pile_tooltip")}
|
|
|
|
|
onClick={(e: ButtonEvent) => {
|
|
|
|
|
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.MemberList);
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}}
|
|
|
|
|
aria-label={_t("common|n_members", { count: memberCount })}
|
|
|
|
|
>
|
|
|
|
|
{formatCount(memberCount)}
|
|
|
|
|
</FacePile>
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 14:31:21 +00:00
|
|
|
/** Create an icon to warn the user about shared history visibility, in encrypted rooms.
|
|
|
|
|
*
|
|
|
|
|
* Note that we use the same icon as in the room summary card and elsewhere, to aid user recognition.
|
|
|
|
|
*/
|
|
|
|
|
function historyVisibilityIcon(historyVisibility: HistoryVisibility): JSX.Element | null {
|
|
|
|
|
if (historyVisibility === HistoryVisibility.Shared) {
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip label={_t("room|header|shared_history_tooltip")} placement="right">
|
|
|
|
|
<HistoryIcon
|
|
|
|
|
width="16px"
|
|
|
|
|
height="16px"
|
|
|
|
|
className="mx_RoomHeader_icon"
|
|
|
|
|
color="var(--cpd-color-icon-info-primary)"
|
|
|
|
|
aria-label={_t("room|header|shared_history_tooltip")}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
} else if (historyVisibility === HistoryVisibility.WorldReadable) {
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip label={_t("room|header|world_readable_history_tooltip")} placement="right">
|
|
|
|
|
<UserProfileSolidIcon
|
|
|
|
|
width="16px"
|
|
|
|
|
height="16px"
|
|
|
|
|
className="mx_RoomHeader_icon"
|
|
|
|
|
color="var(--cpd-color-icon-info-primary)"
|
|
|
|
|
aria-label={_t("room|header|world_readable_history_tooltip")}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 13:25:08 +00:00
|
|
|
export default function RoomHeader({
|
|
|
|
|
room,
|
2026-03-13 13:44:18 +00:00
|
|
|
extraButtons,
|
|
|
|
|
legacyAdditionalButtons,
|
2025-11-03 13:25:08 +00:00
|
|
|
oobData,
|
|
|
|
|
}: {
|
|
|
|
|
room: Room | LocalRoom;
|
2026-03-13 13:44:18 +00:00
|
|
|
// Extra buttons added by a new element web module API module
|
|
|
|
|
extraButtons?: JSX.Element;
|
|
|
|
|
// DEPRECATED: Buttons added by a legacy react-sdk module API module.
|
|
|
|
|
legacyAdditionalButtons?: ViewRoomOpts["buttons"];
|
2025-11-03 13:25:08 +00:00
|
|
|
oobData?: IOOBData;
|
|
|
|
|
}): JSX.Element {
|
|
|
|
|
const client = useMatrixClientContext();
|
|
|
|
|
const roomName = useRoomName(room);
|
|
|
|
|
const joinRule = useRoomState(room, (state) => state.getJoinRule());
|
2026-01-27 15:06:22 +00:00
|
|
|
const historyVisibility = useRoomState(room, (state) => state.getHistoryVisibility());
|
2025-11-03 13:25:08 +00:00
|
|
|
const dmMember = useDmMember(room);
|
|
|
|
|
const isDirectMessage = !!dmMember;
|
2026-01-27 15:06:22 +00:00
|
|
|
const isRoomEncrypted = useIsEncrypted(client, room);
|
2025-11-03 13:25:08 +00:00
|
|
|
const e2eStatus = useEncryptionStatus(client, room);
|
|
|
|
|
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
|
2025-01-27 20:35:46 +05:30
|
|
|
const onAvatarClick = (): void => {
|
|
|
|
|
defaultDispatcher.dispatch({
|
|
|
|
|
action: "open_room_settings",
|
|
|
|
|
initial_tab_id: RoomSettingsTab.General,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2026-06-23 17:40:42 +01:00
|
|
|
<CurrentRightPanelPhaseContextProvider roomId={room.roomId}>
|
|
|
|
|
<Flex as="header" align="center" gap="var(--cpd-space-3x)" className="mx_RoomHeader light-panel">
|
|
|
|
|
<WithPresenceIndicator room={room}>
|
|
|
|
|
{/* We hide this from the tabIndex list as it is a pointer shortcut and superfluous for a11y */}
|
2025-11-03 13:25:08 +00:00
|
|
|
{/* Disable on-click actions until the room is created */}
|
2026-06-23 17:40:42 +01:00
|
|
|
<RoomAvatar
|
|
|
|
|
room={room}
|
|
|
|
|
size="40px"
|
|
|
|
|
oobData={oobData}
|
|
|
|
|
onClick={room instanceof LocalRoom ? undefined : onAvatarClick}
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
aria-label={_t("room|header_avatar_open_settings_label")}
|
|
|
|
|
/>
|
|
|
|
|
</WithPresenceIndicator>
|
|
|
|
|
{/* Disable on-click actions until the room is created */}
|
|
|
|
|
<button
|
|
|
|
|
aria-label={_t("right_panel|room_summary_card|title")}
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
onClick={
|
|
|
|
|
room instanceof LocalRoom
|
|
|
|
|
? undefined
|
|
|
|
|
: () => RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary)
|
|
|
|
|
}
|
|
|
|
|
className="mx_RoomHeader_infoWrapper"
|
|
|
|
|
>
|
|
|
|
|
<Box flex="1" className="mx_RoomHeader_info">
|
|
|
|
|
<Text
|
|
|
|
|
as="div"
|
|
|
|
|
size="lg"
|
|
|
|
|
weight="semibold"
|
|
|
|
|
dir="auto"
|
|
|
|
|
role="heading"
|
|
|
|
|
aria-level={1}
|
|
|
|
|
className="mx_RoomHeader_heading"
|
|
|
|
|
>
|
|
|
|
|
<span className="mx_RoomHeader_truncated mx_lineClamp">{roomName}</span>
|
2025-01-27 20:35:46 +05:30
|
|
|
|
2026-06-23 17:40:42 +01:00
|
|
|
{!isDirectMessage && joinRule === JoinRule.Public && (
|
|
|
|
|
<Tooltip label={_t("common|public_room")} placement="right">
|
|
|
|
|
<PublicIcon
|
|
|
|
|
width="16px"
|
|
|
|
|
height="16px"
|
|
|
|
|
className="mx_RoomHeader_icon"
|
|
|
|
|
color="var(--cpd-color-icon-info-primary)"
|
|
|
|
|
aria-label={_t("common|public_room")}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
2025-01-27 20:35:46 +05:30
|
|
|
|
2026-06-23 17:40:42 +01:00
|
|
|
{isDirectMessage && e2eStatus === E2EStatus.Verified && (
|
|
|
|
|
<Tooltip label={_t("common|verified")} placement="right">
|
|
|
|
|
<VerifiedIcon
|
|
|
|
|
width="16px"
|
|
|
|
|
height="16px"
|
|
|
|
|
className="mx_RoomHeader_icon mx_Verified"
|
|
|
|
|
aria-label={_t("common|verified")}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
2025-01-27 20:35:46 +05:30
|
|
|
|
2026-06-23 17:40:42 +01:00
|
|
|
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
|
|
|
|
|
<Tooltip label={_t("room|header_untrusted_label")} placement="right">
|
|
|
|
|
<ErrorIcon
|
|
|
|
|
width="16px"
|
|
|
|
|
height="16px"
|
|
|
|
|
className="mx_RoomHeader_icon mx_Untrusted"
|
|
|
|
|
aria-label={_t("room|header_untrusted_label")}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
2026-01-27 15:06:22 +00:00
|
|
|
|
2026-06-23 17:40:42 +01:00
|
|
|
{isRoomEncrypted && historyVisibilityIcon(historyVisibility)}
|
|
|
|
|
</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
</button>
|
|
|
|
|
{/* If the room is local-only then we don't want to show any additional buttons, as it won't work */}
|
|
|
|
|
{room instanceof LocalRoom === false && (
|
|
|
|
|
<RoomHeaderButtons
|
|
|
|
|
room={room}
|
|
|
|
|
legacyAdditionalButtons={legacyAdditionalButtons}
|
|
|
|
|
extraButtons={extraButtons}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</Flex>
|
|
|
|
|
{askToJoinEnabled && <RoomKnocksBar room={room} />}
|
|
|
|
|
</CurrentRightPanelPhaseContextProvider>
|
2025-01-27 20:35:46 +05:30
|
|
|
);
|
|
|
|
|
}
|