Merge branch 'develop' into germain-gg/fix-right-panel-member
This commit is contained in:
commit
1b8d00d1d4
141 changed files with 6493 additions and 5556 deletions
|
@ -981,9 +981,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||
mx_EventTile_lastInSection: this.props.lastInSection,
|
||||
mx_EventTile_contextual: this.props.contextual,
|
||||
mx_EventTile_actionBarFocused: this.state.actionBarFocused,
|
||||
mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2EState.Verified,
|
||||
mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2EState.Warning,
|
||||
mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2EState.Unknown,
|
||||
mx_EventTile_bad: isEncryptionFailure,
|
||||
mx_EventTile_emote: msgtype === MsgType.Emote,
|
||||
mx_EventTile_noSender: this.props.hideSender,
|
||||
|
|
|
@ -36,6 +36,7 @@ import RoomTopic from "../elements/RoomTopic";
|
|||
import RoomName from "../elements/RoomName";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||
import { RoomKnocksBar } from "./RoomKnocksBar";
|
||||
import { SearchScope } from "./SearchBar";
|
||||
import { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import RoomContextMenu from "../context_menus/RoomContextMenu";
|
||||
|
@ -820,6 +821,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
|||
</div>
|
||||
{!isVideoRoom && <RoomCallBanner roomId={this.props.room.roomId} />}
|
||||
<RoomLiveShareWarning roomId={this.props.room.roomId} />
|
||||
<RoomKnocksBar room={this.props.room} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,38 +14,41 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { Body as BodyText, IconButton } from "@vector-im/compound-web";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Body as BodyText, IconButton, Tooltip } from "@vector-im/compound-web";
|
||||
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call.svg";
|
||||
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.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 VerifiedIcon } from "@vector-im/compound-design-tokens/icons/verified.svg";
|
||||
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
|
||||
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useRoomName } from "../../../hooks/useRoomName";
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import { useTopic } from "../../../hooks/room/useTopic";
|
||||
import { useAccountData } from "../../../hooks/useAccountData";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMembers";
|
||||
import { _t, getCurrentLanguage } from "../../../languageHandler";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import { Box } from "../../utils/Box";
|
||||
import { useRoomCallStatus } from "../../../hooks/room/useRoomCallStatus";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
|
||||
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import { placeCall } from "../../../utils/room/placeCall";
|
||||
import { useEncryptionStatus } from "../../../hooks/useEncryptionStatus";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import FacePile from "../elements/FacePile";
|
||||
import { setPhase } from "../../../utils/room/setPhase";
|
||||
import { useRoomState } from "../../../hooks/useRoomState";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import { formatCount } from "../../../utils/FormattingUtils";
|
||||
|
||||
/**
|
||||
* A helper to transform a notification color to the what the Compound Icon Button
|
||||
|
@ -66,19 +69,10 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
|
||||
const roomName = useRoomName(room);
|
||||
const roomTopic = useTopic(room);
|
||||
const roomState = useRoomState(room);
|
||||
|
||||
const members = useRoomMembers(room);
|
||||
const memberCount = useRoomMemberCount(room);
|
||||
|
||||
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
|
||||
const isDirectMessage = useMemo(() => {
|
||||
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
|
||||
if (dmRoomList.includes(room?.roomId ?? "")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, [directRoomsList, room?.roomId]);
|
||||
const members = useRoomMembers(room, 2500);
|
||||
const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
|
||||
|
||||
const { voiceCallDisabledReason, voiceCallType, videoCallDisabledReason, videoCallType } = useRoomCallStatus(room);
|
||||
|
||||
|
@ -91,37 +85,21 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
return SdkConfig.get("element_call").use_exclusively && groupCallsEnabled;
|
||||
}, [groupCallsEnabled]);
|
||||
|
||||
const placeCall = useCallback(
|
||||
async (callType: CallType, platformCallType: typeof voiceCallType) => {
|
||||
switch (platformCallType) {
|
||||
case "legacy_or_jitsi":
|
||||
await LegacyCallHandler.instance.placeCall(room.roomId, callType);
|
||||
break;
|
||||
// TODO: Remove the jitsi_or_element_call case and
|
||||
// use the commented code below
|
||||
case "element_call":
|
||||
case "jitsi_or_element_call":
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
view_call: true,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
// case "jitsi_or_element_call":
|
||||
// TODO: Open dropdown menu to choice between
|
||||
// EC and Jitsi. Waiting on Compound's dropdown
|
||||
// component
|
||||
// break;
|
||||
}
|
||||
},
|
||||
[room.roomId],
|
||||
);
|
||||
|
||||
const threadNotifications = useRoomThreadNotifications(room);
|
||||
const globalNotificationState = useGlobalNotificationState();
|
||||
|
||||
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
|
||||
const [isDirectMessage, setDirectMessage] = useState(false);
|
||||
useEffect(() => {
|
||||
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
|
||||
if (dmRoomList.includes(room?.roomId ?? "")) {
|
||||
setDirectMessage(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [room, directRoomsList]);
|
||||
const e2eStatus = useEncryptionStatus(client, room);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="header"
|
||||
|
@ -132,7 +110,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
setPhase(RightPanelPhases.RoomSummary);
|
||||
}}
|
||||
>
|
||||
<DecoratedRoomAvatar room={room} size="40px" displayBadge={false} />
|
||||
<RoomAvatar room={room} size="40px" />
|
||||
<Box flex="1" className="mx_RoomHeader_info">
|
||||
<BodyText
|
||||
as="div"
|
||||
|
@ -142,8 +120,42 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
title={roomName}
|
||||
role="heading"
|
||||
aria-level={1}
|
||||
className="mx_RoomHeader_heading"
|
||||
>
|
||||
{roomName}
|
||||
|
||||
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
|
||||
<Tooltip label={_t("Public room")}>
|
||||
<PublicIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="text-secondary"
|
||||
aria-label={_t("Public room")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isDirectMessage && e2eStatus === E2EStatus.Verified && (
|
||||
<Tooltip label={_t("common|verified")}>
|
||||
<VerifiedIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="mx_Verified"
|
||||
aria-label={_t("common|verified")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
|
||||
<Tooltip label={_t("Untrusted")}>
|
||||
<ErrorIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="mx_Untrusted"
|
||||
aria-label={_t("Untrusted")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</BodyText>
|
||||
{roomTopic && (
|
||||
<BodyText as="div" size="sm" className="mx_RoomHeader_topic">
|
||||
|
@ -156,8 +168,8 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
<IconButton
|
||||
disabled={!!voiceCallDisabledReason}
|
||||
title={!voiceCallDisabledReason ? _t("Voice call") : voiceCallDisabledReason!}
|
||||
onClick={async () => {
|
||||
placeCall(CallType.Voice, voiceCallType);
|
||||
onClick={() => {
|
||||
placeCall(room, CallType.Voice, voiceCallType);
|
||||
}}
|
||||
>
|
||||
<VoiceCallIcon />
|
||||
|
@ -167,7 +179,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
disabled={!!videoCallDisabledReason}
|
||||
title={!videoCallDisabledReason ? _t("Video call") : videoCallDisabledReason!}
|
||||
onClick={() => {
|
||||
placeCall(CallType.Video, videoCallType);
|
||||
placeCall(room, CallType.Video, videoCallType);
|
||||
}}
|
||||
>
|
||||
<VideoCallIcon />
|
||||
|
@ -208,7 +220,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
|||
size="20px"
|
||||
overflow={false}
|
||||
>
|
||||
{memberCount.toLocaleString(getCurrentLanguage())}
|
||||
{formatCount(memberCount)}
|
||||
</FacePile>
|
||||
</BodyText>
|
||||
)}
|
||||
|
|
159
src/components/views/rooms/RoomKnocksBar.tsx
Normal file
159
src/components/views/rooms/RoomKnocksBar.tsx
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
Copyright 2023 Nordeck IT + Consulting GmbH
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react";
|
||||
|
||||
import { Icon as CheckIcon } from "../../../../res/img/feather-customised/check.svg";
|
||||
import { Icon as XIcon } from "../../../../res/img/feather-customised/x.svg";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Heading from "../typography/Heading";
|
||||
|
||||
export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const knockMembers = useTypedEventEmitterState(
|
||||
room,
|
||||
RoomStateEvent.Members,
|
||||
useCallback(() => room.getMembersWithMembership("knock"), [room]),
|
||||
);
|
||||
const knockMembersCount = knockMembers.length;
|
||||
|
||||
if (room.getJoinRule() !== JoinRule.Knock || knockMembersCount === 0) return null;
|
||||
|
||||
const client = room.client;
|
||||
const userId = client.getUserId() || "";
|
||||
const canInvite = room.canInvite(userId);
|
||||
const member = room.getMember(userId);
|
||||
const state = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
const canKick = member && state ? state.hasSufficientPowerLevelFor("kick", member.powerLevel) : false;
|
||||
|
||||
if (!canInvite && !canKick) return null;
|
||||
|
||||
const onError = (error: MatrixError): void => {
|
||||
Modal.createDialog(ErrorDialog, { title: error.name, description: error.message });
|
||||
};
|
||||
|
||||
const handleApprove = (userId: string): void => {
|
||||
setDisabled(true);
|
||||
client
|
||||
.invite(room.roomId, userId)
|
||||
.catch(onError)
|
||||
.finally(() => setDisabled(false));
|
||||
};
|
||||
|
||||
const handleDeny = (userId: string): void => {
|
||||
setDisabled(true);
|
||||
client
|
||||
.kick(room.roomId, userId)
|
||||
.catch(onError)
|
||||
.finally(() => setDisabled(false));
|
||||
};
|
||||
|
||||
const handleOpenRoomSettings = (): void =>
|
||||
dis.dispatch({ action: "open_room_settings", room_id: room.roomId, initial_tab_id: RoomSettingsTab.People });
|
||||
|
||||
let buttons: ReactElement = (
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
kind="primary"
|
||||
onClick={handleOpenRoomSettings}
|
||||
title={_t("action|view")}
|
||||
>
|
||||
{_t("action|view")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
let names: string = knockMembers
|
||||
.slice(0, 2)
|
||||
.map((knockMember) => knockMember.name)
|
||||
.join(", ");
|
||||
let link: ReactNode = null;
|
||||
switch (knockMembersCount) {
|
||||
case 1: {
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canKick || disabled}
|
||||
kind="icon_primary_outline"
|
||||
onClick={() => handleDeny(knockMembers[0].userId)}
|
||||
title={_t("action|deny")}
|
||||
>
|
||||
<XIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_action"
|
||||
disabled={!canInvite || disabled}
|
||||
kind="icon_primary"
|
||||
onClick={() => handleApprove(knockMembers[0].userId)}
|
||||
title={_t("action|approve")}
|
||||
>
|
||||
<CheckIcon width={18} height={18} />
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
|
||||
link = (
|
||||
<AccessibleButton
|
||||
className="mx_RoomKnocksBar_link"
|
||||
element="a"
|
||||
kind="link_inline"
|
||||
onClick={handleOpenRoomSettings}
|
||||
>
|
||||
{_t("action|view_message")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
names = _t("%(names)s and %(name)s", { names: knockMembers[0].name, name: knockMembers[1].name });
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
names = _t("%(names)s and %(name)s", { names, name: knockMembers[2].name });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
names = _t("%(names)s and %(count)s others", { names, count: knockMembersCount - 2 });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomKnocksBar">
|
||||
{knockMembers.slice(0, 2).map((knockMember) => (
|
||||
<MemberAvatar
|
||||
className="mx_RoomKnocksBar_avatar"
|
||||
key={knockMember.userId}
|
||||
member={knockMember}
|
||||
size="32px"
|
||||
/>
|
||||
))}
|
||||
<div className="mx_RoomKnocksBar_content">
|
||||
<Heading size="4">{_t("%(count)s people asking to join", { count: knockMembersCount })}</Heading>
|
||||
<p className="mx_RoomKnocksBar_paragraph">
|
||||
{names}
|
||||
{link}
|
||||
</p>
|
||||
</div>
|
||||
{buttons}
|
||||
</div>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue