Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add RoomKnocksBar to RoomHeader #12077

Merged
merged 1 commit into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 144 additions & 138 deletions src/components/views/rooms/RoomHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { Linkify, topicToHtml } from "../../../HtmlUtils";
import PosthogTrackers from "../../../PosthogTrackers";
import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton";
import { RoomKnocksBar } from "./RoomKnocksBar";

/**
* A helper to transform a notification color to the what the Compound Icon Button
Expand Down Expand Up @@ -115,165 +116,170 @@ export default function RoomHeader({
[roomTopic?.html, roomTopic?.text],
);

const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");

return (
<Flex as="header" align="center" gap="var(--cpd-space-3x)" className="mx_RoomHeader light-panel">
<button
aria-label={_t("right_panel|room_summary_card|title")}
tabIndex={0}
onClick={() => {
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomSummary);
}}
className="mx_RoomHeader_infoWrapper"
>
<RoomAvatar room={room} size="40px" />
<Box flex="1" className="mx_RoomHeader_info">
<BodyText
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>
<>
<Flex as="header" align="center" gap="var(--cpd-space-3x)" className="mx_RoomHeader light-panel">
<button
aria-label={_t("right_panel|room_summary_card|title")}
tabIndex={0}
onClick={() => {
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomSummary);
}}
className="mx_RoomHeader_infoWrapper"
>
<RoomAvatar room={room} size="40px" />
<Box flex="1" className="mx_RoomHeader_info">
<BodyText
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>

{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
<Tooltip label={_t("common|public_room")} side="right">
<PublicIcon
width="16px"
height="16px"
className="mx_RoomHeader_icon text-secondary"
aria-label={_t("common|public_room")}
/>
</Tooltip>
)}
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
<Tooltip label={_t("common|public_room")} side="right">
<PublicIcon
width="16px"
height="16px"
className="mx_RoomHeader_icon text-secondary"
aria-label={_t("common|public_room")}
/>
</Tooltip>
)}

{isDirectMessage && e2eStatus === E2EStatus.Verified && (
<Tooltip label={_t("common|verified")} side="right">
<VerifiedIcon
width="16px"
height="16px"
className="mx_RoomHeader_icon mx_Verified"
aria-label={_t("common|verified")}
/>
</Tooltip>
{isDirectMessage && e2eStatus === E2EStatus.Verified && (
<Tooltip label={_t("common|verified")} side="right">
<VerifiedIcon
width="16px"
height="16px"
className="mx_RoomHeader_icon mx_Verified"
aria-label={_t("common|verified")}
/>
</Tooltip>
)}

{isDirectMessage && e2eStatus === E2EStatus.Warning && (
<Tooltip label={_t("room|header_untrusted_label")} side="right">
<ErrorIcon
width="16px"
height="16px"
className="mx_RoomHeader_icon mx_Untrusted"
aria-label={_t("room|header_untrusted_label")}
/>
</Tooltip>
)}
</BodyText>
{roomTopic && (
<BodyText
as="div"
size="sm"
className="mx_RoomHeader_topic mx_RoomHeader_truncated mx_lineClamp"
>
<Linkify>{roomTopicBody}</Linkify>
</BodyText>
)}
</Box>
</button>
<Flex align="center" gap="var(--cpd-space-2x)">
{additionalButtons?.map((props) => {
const label = props.label();

{isDirectMessage && e2eStatus === E2EStatus.Warning && (
<Tooltip label={_t("room|header_untrusted_label")} side="right">
<ErrorIcon
width="16px"
height="16px"
className="mx_RoomHeader_icon mx_Untrusted"
aria-label={_t("room|header_untrusted_label")}
/>
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>
)}
</BodyText>
{roomTopic && (
<BodyText
as="div"
size="sm"
className="mx_RoomHeader_topic mx_RoomHeader_truncated mx_lineClamp"
);
})}
<Tooltip label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}>
<IconButton
disabled={!!videoCallDisabledReason}
aria-label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}
onClick={videoCallClick}
>
<Linkify>{roomTopicBody}</Linkify>
</BodyText>
)}
</Box>
</button>
<Flex align="center" gap="var(--cpd-space-2x)">
{additionalButtons?.map((props) => {
const label = props.label();

return (
<Tooltip label={label} key={props.id}>
<VideoCallIcon />
</IconButton>
</Tooltip>
{!useElementCallExclusively && (
<Tooltip label={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}>
<IconButton
aria-label={label}
onClick={(event) => {
event.stopPropagation();
props.onClick();
}}
disabled={!!voiceCallDisabledReason}
aria-label={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}
onClick={voiceCallClick}
>
{typeof props.icon === "function" ? props.icon() : props.icon}
<VoiceCallIcon />
</IconButton>
</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 */}
<VideoRoomChatButton room={room} />
{/* Renders nothing when room is not a video room */}
<VideoRoomChatButton room={room} />

<Tooltip label={_t("common|threads")}>
<IconButton
indicator={notificationColorToIndicator(threadNotifications)}
onClick={(evt) => {
evt.stopPropagation();
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.ThreadPanel);
PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", evt);
}}
aria-label={_t("common|threads")}
>
<ThreadsIcon />
</IconButton>
</Tooltip>
{notificationsEnabled && (
<Tooltip label={_t("notifications|enable_prompt_toast_title")}>
<Tooltip label={_t("common|threads")}>
<IconButton
indicator={notificationColorToIndicator(globalNotificationState.color)}
indicator={notificationColorToIndicator(threadNotifications)}
onClick={(evt) => {
evt.stopPropagation();
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.NotificationPanel);
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.ThreadPanel);
PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", evt);
}}
aria-label={_t("notifications|enable_prompt_toast_title")}
aria-label={_t("common|threads")}
>
<NotificationsIcon />
<ThreadsIcon />
</IconButton>
</Tooltip>
{notificationsEnabled && (
<Tooltip label={_t("notifications|enable_prompt_toast_title")}>
<IconButton
indicator={notificationColorToIndicator(globalNotificationState.color)}
onClick={(evt) => {
evt.stopPropagation();
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.NotificationPanel);
}}
aria-label={_t("notifications|enable_prompt_toast_title")}
>
<NotificationsIcon />
</IconButton>
</Tooltip>
)}
</Flex>
{!isDirectMessage && (
<BodyText
as="div"
size="sm"
weight="medium"
aria-label={_t("common|n_members", { count: memberCount })}
onClick={(e: React.MouseEvent) => {
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomMemberList);
e.stopPropagation();
}}
>
<FacePile
className="mx_RoomHeader_members"
members={members.slice(0, 3)}
size="20px"
overflow={false}
viewUserOnClick={false}
>
{formatCount(memberCount)}
</FacePile>
</BodyText>
)}
</Flex>
{!isDirectMessage && (
<BodyText
as="div"
size="sm"
weight="medium"
aria-label={_t("common|n_members", { count: memberCount })}
onClick={(e: React.MouseEvent) => {
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomMemberList);
e.stopPropagation();
}}
>
<FacePile
className="mx_RoomHeader_members"
members={members.slice(0, 3)}
size="20px"
overflow={false}
viewUserOnClick={false}
>
{formatCount(memberCount)}
</FacePile>
</BodyText>
)}
</Flex>
{askToJoinEnabled && <RoomKnocksBar room={room} />}
</>
);
}
29 changes: 28 additions & 1 deletion test/components/views/rooms/RoomHeader-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ limitations under the License.

import React from "react";
import { CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import { EventType, JoinRule, MatrixClient, MatrixEvent, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
import {
EventType,
JoinRule,
MatrixClient,
MatrixEvent,
PendingEventOrdering,
Room,
RoomMember,
} from "matrix-js-sdk/src/matrix";
import {
createEvent,
fireEvent,
Expand Down Expand Up @@ -562,6 +570,25 @@ describe("RoomHeader", () => {
expect(callback).toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalled();
});

describe("ask to join disabled", () => {
it("does not render the RoomKnocksBar", () => {
render(<RoomHeader room={room} />, withClientContextRenderOptions(MatrixClientPeg.get()!));
expect(screen.queryByRole("heading", { name: "Asking to join" })).not.toBeInTheDocument();
});
});

describe("ask to join enabled", () => {
it("does render the RoomKnocksBar", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => feature === "feature_ask_to_join");
jest.spyOn(room, "canInvite").mockReturnValue(true);
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
jest.spyOn(room, "getMembersWithMembership").mockReturnValue([new RoomMember(room.roomId, "@foo")]);

render(<RoomHeader room={room} />, withClientContextRenderOptions(MatrixClientPeg.get()!));
expect(screen.getByRole("heading", { name: "Asking to join" })).toBeInTheDocument();
});
});
});

/**
Expand Down