Mvvm RoomSummaryCard (#29674)
* feat: create roomsummarycard viewmodel * feat: use roomsummurycard vm in component * test: jest unit RoomSummaryCard and RoomSummaryCardViewModel * chore: rename to roomsummarycardview * feat: reput room topic without vm * test: roomSummaryCard and roomSummaryCardVM tests * chore: add comments on roomsummarycardVM * fix: merge conflict with roomsummarytopic, and move to vm right_panel * fix(roomsummarycard): remove usetransition for search update * fix: merged file that should be deleted * fix: roomsummurycard not well merge with roomtopic * test: update snapshots
This commit is contained in:
@@ -15,7 +15,7 @@ import dis from "../../dispatcher/dispatcher";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
||||
import RoomSummaryCardView from "../views/right_panel/RoomSummaryCardView";
|
||||
import WidgetCard from "../views/right_panel/WidgetCard";
|
||||
import UserInfo from "../views/right_panel/UserInfo";
|
||||
import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
|
||||
@@ -255,7 +255,7 @@ export default class RightPanel extends React.Component<Props, IState> {
|
||||
case RightPanelPhases.RoomSummary:
|
||||
if (!!this.props.room) {
|
||||
card = (
|
||||
<RoomSummaryCard
|
||||
<RoomSummaryCardView
|
||||
room={this.props.room}
|
||||
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||
permalinkCreator={this.props.permalinkCreator!}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { EventType, type JoinRule, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
||||
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext";
|
||||
import { type E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
|
||||
import { useRoomState } from "../../../hooks/useRoomState";
|
||||
import { useAccountData } from "../../../hooks/useAccountData";
|
||||
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import { canInviteTo } from "../../../utils/room/canInviteTo";
|
||||
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { PollHistoryDialog } from "../../views/dialogs/PollHistoryDialog";
|
||||
import Modal from "../../../Modal";
|
||||
import ExportDialog from "../../views/dialogs/ExportDialog";
|
||||
import { ShareDialog } from "../../views/dialogs/ShareDialog";
|
||||
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import { ReportRoomDialog } from "../../views/dialogs/ReportRoomDialog";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
|
||||
import { tagRoom } from "../../../utils/room/tagRoom";
|
||||
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
|
||||
|
||||
export interface RoomSummaryCardState {
|
||||
isDirectMessage: boolean;
|
||||
/**
|
||||
* Whether the room is encrypted, used to display the correct badge and icon
|
||||
*/
|
||||
isRoomEncrypted: boolean;
|
||||
/**
|
||||
* The e2e status of the room, used to display the correct badge and icon
|
||||
*/
|
||||
e2eStatus: E2EStatus | undefined;
|
||||
/**
|
||||
* The join rule of the room, used to display the correct badge and icon
|
||||
*/
|
||||
roomJoinRule: JoinRule;
|
||||
/**
|
||||
* if it is a video room, it should not display export chat, polls, files, extensions
|
||||
*/
|
||||
isVideoRoom: boolean;
|
||||
/**
|
||||
* display the alias of the room, if it exists
|
||||
*/
|
||||
alias: string;
|
||||
/**
|
||||
* value to check if the room is a favorite or not
|
||||
*/
|
||||
isFavorite: boolean;
|
||||
/**
|
||||
* value to check if we disable invite button or not
|
||||
*/
|
||||
canInviteToState: boolean;
|
||||
/**
|
||||
* Getting the number of pinned messages in the room, next to the pin button
|
||||
*/
|
||||
pinCount: number;
|
||||
searchInputRef: React.RefObject<HTMLInputElement | null>;
|
||||
/**
|
||||
* The callback when new value is entered in the search input
|
||||
*/
|
||||
onUpdateSearchInput: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
/**
|
||||
* Callbacks to all the actions button in the right panel
|
||||
*/
|
||||
onRoomMembersClick: () => void;
|
||||
onRoomThreadsClick: () => void;
|
||||
onRoomFilesClick: () => void;
|
||||
onRoomExtensionsClick: () => void;
|
||||
onRoomPinsClick: () => void;
|
||||
onRoomSettingsClick: (ev: Event) => void;
|
||||
onLeaveRoomClick: () => void;
|
||||
onShareRoomClick: () => void;
|
||||
onRoomExportClick: () => Promise<void>;
|
||||
onRoomPollHistoryClick: () => void;
|
||||
onReportRoomClick: () => Promise<void>;
|
||||
onFavoriteToggleClick: () => void;
|
||||
onInviteToRoomClick: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to check if the room is a direct message or not
|
||||
* @param room - The room to check
|
||||
* @returns Whether the room is a direct message
|
||||
*/
|
||||
const useIsDirectMessage = (room: Room): boolean => {
|
||||
const directRoomsList = useAccountData<Record<string, string[]>>(room.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]);
|
||||
|
||||
return isDirectMessage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to handle the search input in the right panel
|
||||
* @param onSearchCancel - The callback when the search input is cancelled
|
||||
* @returns The search input ref and the callback when the search input is updated
|
||||
*/
|
||||
const useSearchInput = (
|
||||
onSearchCancel?: () => void,
|
||||
): {
|
||||
searchInputRef: React.RefObject<HTMLInputElement | null>;
|
||||
onUpdateSearchInput: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
} => {
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onUpdateSearchInput = (e: React.KeyboardEvent<HTMLInputElement>): void => {
|
||||
if (searchInputRef.current && e.key === Key.ESCAPE) {
|
||||
searchInputRef.current.value = "";
|
||||
onSearchCancel?.();
|
||||
}
|
||||
};
|
||||
|
||||
// Focus the search field when the user clicks on the search button component
|
||||
useDispatcher(defaultDispatcher, (payload) => {
|
||||
if (payload.action === Action.FocusMessageSearch) {
|
||||
searchInputRef.current?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
searchInputRef,
|
||||
onUpdateSearchInput,
|
||||
};
|
||||
};
|
||||
|
||||
export function useRoomSummaryCardViewModel(
|
||||
room: Room,
|
||||
permalinkCreator: RoomPermalinkCreator,
|
||||
onSearchCancel?: () => void,
|
||||
): RoomSummaryCardState {
|
||||
const cli = useMatrixClientContext();
|
||||
|
||||
const isRoomEncrypted = useIsEncrypted(cli, room) ?? false;
|
||||
const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType");
|
||||
const e2eStatus = roomContext.e2eStatus;
|
||||
const isVideoRoom = calcIsVideoRoom(room);
|
||||
|
||||
const roomState = useRoomState(room);
|
||||
// used to check if the room is public or not
|
||||
const roomJoinRule = roomState.getJoinRule();
|
||||
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
|
||||
const pinCount = usePinnedEvents(room).length;
|
||||
// value to check if the user can invite to the room
|
||||
const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room));
|
||||
|
||||
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
|
||||
RoomListStore.instance.getTagsForRoom(room),
|
||||
);
|
||||
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
||||
|
||||
const isDirectMessage = useIsDirectMessage(room);
|
||||
|
||||
const onRoomMembersClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.MemberList }, true);
|
||||
};
|
||||
|
||||
const onRoomThreadsClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true);
|
||||
};
|
||||
|
||||
const onRoomFilesClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true);
|
||||
};
|
||||
|
||||
const onRoomExtensionsClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true);
|
||||
};
|
||||
|
||||
const onRoomPinsClick = (): void => {
|
||||
PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton");
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true);
|
||||
};
|
||||
|
||||
const onRoomSettingsClick = (ev: Event): void => {
|
||||
defaultDispatcher.dispatch({ action: "open_room_settings" });
|
||||
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
|
||||
};
|
||||
|
||||
const onShareRoomClick = (): void => {
|
||||
Modal.createDialog(ShareDialog, {
|
||||
target: room,
|
||||
});
|
||||
};
|
||||
|
||||
const onRoomExportClick = async (): Promise<void> => {
|
||||
Modal.createDialog(ExportDialog, {
|
||||
room,
|
||||
});
|
||||
};
|
||||
|
||||
const onRoomPollHistoryClick = (): void => {
|
||||
Modal.createDialog(PollHistoryDialog, {
|
||||
room,
|
||||
matrixClient: cli,
|
||||
permalinkCreator,
|
||||
});
|
||||
};
|
||||
|
||||
const onLeaveRoomClick = (): void => {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "leave_room",
|
||||
room_id: room.roomId,
|
||||
});
|
||||
};
|
||||
|
||||
const onReportRoomClick = async (): Promise<void> => {
|
||||
const [leave] = await Modal.createDialog(ReportRoomDialog, {
|
||||
roomId: room.roomId,
|
||||
}).finished;
|
||||
if (leave) {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "leave_room",
|
||||
room_id: room.roomId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onFavoriteToggleClick = (): void => {
|
||||
tagRoom(room, DefaultTagID.Favourite);
|
||||
};
|
||||
|
||||
const onInviteToRoomClick = (): void => {
|
||||
inviteToRoom(room);
|
||||
};
|
||||
|
||||
// Room Search element ref
|
||||
const { searchInputRef, onUpdateSearchInput } = useSearchInput(onSearchCancel);
|
||||
|
||||
return {
|
||||
isDirectMessage,
|
||||
isRoomEncrypted,
|
||||
roomJoinRule,
|
||||
e2eStatus,
|
||||
isVideoRoom,
|
||||
alias,
|
||||
isFavorite,
|
||||
canInviteToState,
|
||||
searchInputRef,
|
||||
pinCount,
|
||||
onRoomMembersClick,
|
||||
onRoomThreadsClick,
|
||||
onRoomFilesClick,
|
||||
onRoomExtensionsClick,
|
||||
onRoomPinsClick,
|
||||
onRoomSettingsClick,
|
||||
onLeaveRoomClick,
|
||||
onShareRoomClick,
|
||||
onRoomExportClick,
|
||||
onRoomPollHistoryClick,
|
||||
onReportRoomClick,
|
||||
onUpdateSearchInput,
|
||||
onFavoriteToggleClick,
|
||||
onInviteToRoomClick,
|
||||
};
|
||||
}
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, useContext, useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useState, type JSX } from "react";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
MenuItem,
|
||||
@@ -38,43 +38,19 @@ import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/publi
|
||||
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
|
||||
import ErrorSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
||||
import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
|
||||
import { EventType, JoinRule, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
||||
import BaseCard from "./BaseCard";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import Modal from "../../../Modal";
|
||||
import { ShareDialog } from "../dialogs/ShareDialog";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import RoomName from "../elements/RoomName";
|
||||
import ExportDialog from "../dialogs/ExportDialog";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { PollHistoryDialog } from "../dialogs/PollHistoryDialog";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||
import { tagRoom } from "../../../utils/room/tagRoom";
|
||||
import { canInviteTo } from "../../../utils/room/canInviteTo";
|
||||
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
|
||||
import { useAccountData } from "../../../hooks/useAccountData";
|
||||
import { useRoomState } from "../../../hooks/useRoomState";
|
||||
import { Linkify, topicToHtml } from "../../../HtmlUtils";
|
||||
import { Box } from "../../utils/Box";
|
||||
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
|
||||
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
|
||||
import BaseCard from "./BaseCard.tsx";
|
||||
import { _t } from "../../../languageHandler.tsx";
|
||||
import RoomAvatar from "../avatars/RoomAvatar.tsx";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils.ts";
|
||||
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks.ts";
|
||||
import RoomName from "../elements/RoomName.tsx";
|
||||
import { Flex } from "../../utils/Flex.tsx";
|
||||
import { Linkify, topicToHtml } from "../../../HtmlUtils.tsx";
|
||||
import { Box } from "../../utils/Box.tsx";
|
||||
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
|
||||
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
|
||||
import { ReportRoomDialog } from "../dialogs/ReportRoomDialog.tsx";
|
||||
import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx";
|
||||
import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";
|
||||
|
||||
interface IProps {
|
||||
@@ -86,32 +62,6 @@ interface IProps {
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
const onRoomMembersClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.MemberList }, true);
|
||||
};
|
||||
|
||||
const onRoomThreadsClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true);
|
||||
};
|
||||
|
||||
const onRoomFilesClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true);
|
||||
};
|
||||
|
||||
const onRoomExtensionsClick = (): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true);
|
||||
};
|
||||
|
||||
const onRoomPinsClick = (): void => {
|
||||
PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton");
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true);
|
||||
};
|
||||
|
||||
const onRoomSettingsClick = (ev: Event): void => {
|
||||
defaultDispatcher.dispatch({ action: "open_room_settings" });
|
||||
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
|
||||
};
|
||||
|
||||
const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
|
||||
const vm = useRoomTopicViewModel(room);
|
||||
|
||||
@@ -142,6 +92,7 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
|
||||
}
|
||||
|
||||
const content = vm.expanded ? <Linkify>{body}</Linkify> : body;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="section"
|
||||
@@ -173,7 +124,7 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
|
||||
);
|
||||
};
|
||||
|
||||
const RoomSummaryCard: React.FC<IProps> = ({
|
||||
const RoomSummaryCardView: React.FC<IProps> = ({
|
||||
room,
|
||||
permalinkCreator,
|
||||
onSearchChange,
|
||||
@@ -181,69 +132,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
focusRoomSearch,
|
||||
searchTerm = "",
|
||||
}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const onShareRoomClick = (): void => {
|
||||
Modal.createDialog(ShareDialog, {
|
||||
target: room,
|
||||
});
|
||||
};
|
||||
|
||||
const onRoomExportClick = async (): Promise<void> => {
|
||||
Modal.createDialog(ExportDialog, {
|
||||
room,
|
||||
});
|
||||
};
|
||||
|
||||
const onRoomPollHistoryClick = (): void => {
|
||||
Modal.createDialog(PollHistoryDialog, {
|
||||
room,
|
||||
matrixClient: cli,
|
||||
permalinkCreator,
|
||||
});
|
||||
};
|
||||
|
||||
const onLeaveRoomClick = (): void => {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "leave_room",
|
||||
room_id: room.roomId,
|
||||
});
|
||||
};
|
||||
const onReportRoomClick = async (): Promise<void> => {
|
||||
const [leave] = await Modal.createDialog(ReportRoomDialog, {
|
||||
roomId: room.roomId,
|
||||
}).finished;
|
||||
if (leave) {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "leave_room",
|
||||
room_id: room.roomId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isRoomEncrypted = useIsEncrypted(cli, room);
|
||||
const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType");
|
||||
const e2eStatus = roomContext.e2eStatus;
|
||||
const isVideoRoom = calcIsVideoRoom(room);
|
||||
|
||||
const roomState = useRoomState(room);
|
||||
const directRoomsList = useAccountData<Record<string, string[]>>(room.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 searchInputRef = useRef<HTMLInputElement>(null);
|
||||
useDispatcher(defaultDispatcher, (payload) => {
|
||||
if (payload.action === Action.FocusMessageSearch) {
|
||||
searchInputRef.current?.focus();
|
||||
}
|
||||
});
|
||||
const vm = useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel);
|
||||
|
||||
// The search field is controlled and onSearchChange is debounced in RoomView,
|
||||
// so we need to set the value of the input right away
|
||||
@@ -252,7 +141,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
setSearchValue(searchTerm);
|
||||
}, [searchTerm]);
|
||||
|
||||
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
|
||||
const roomInfo = (
|
||||
<header className="mx_RoomSummaryCard_container">
|
||||
<RoomAvatar room={room} size="80px" viewAvatarOnClick />
|
||||
@@ -274,34 +162,34 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
size="sm"
|
||||
weight="semibold"
|
||||
className="mx_RoomSummaryCard_alias text-secondary"
|
||||
title={alias}
|
||||
title={vm.alias}
|
||||
>
|
||||
{alias}
|
||||
{vm.alias}
|
||||
</Text>
|
||||
|
||||
<Flex as="section" justify="center" gap="var(--cpd-space-2x)" className="mx_RoomSummaryCard_badges">
|
||||
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
|
||||
{!vm.isDirectMessage && vm.roomJoinRule === JoinRule.Public && (
|
||||
<Badge kind="grey">
|
||||
<PublicIcon width="1em" />
|
||||
{_t("common|public_room")}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{isRoomEncrypted && e2eStatus !== E2EStatus.Warning && (
|
||||
{vm.isRoomEncrypted && vm.e2eStatus !== E2EStatus.Warning && (
|
||||
<Badge kind="green">
|
||||
<LockIcon width="1em" />
|
||||
{_t("common|encrypted")}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{!e2eStatus && (
|
||||
{!vm.isRoomEncrypted && (
|
||||
<Badge kind="grey">
|
||||
<LockOffIcon width="1em" />
|
||||
{_t("common|unencrypted")}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{e2eStatus === E2EStatus.Warning && (
|
||||
{vm.e2eStatus === E2EStatus.Warning && (
|
||||
<Badge kind="red">
|
||||
<ErrorSolidIcon width="1em" />
|
||||
{_t("common|not_trusted")}
|
||||
@@ -313,14 +201,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
</header>
|
||||
);
|
||||
|
||||
const pinCount = usePinnedEvents(room).length;
|
||||
|
||||
const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () =>
|
||||
RoomListStore.instance.getTagsForRoom(room),
|
||||
);
|
||||
const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room));
|
||||
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
||||
|
||||
const header = onSearchChange && (
|
||||
<Form.Root className="mx_RoomSummaryCard_search" onSubmit={(e) => e.preventDefault()}>
|
||||
<Search
|
||||
@@ -332,14 +212,9 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
}}
|
||||
value={searchValue}
|
||||
className="mx_no_textinput"
|
||||
ref={searchInputRef}
|
||||
ref={vm.searchInputRef}
|
||||
autoFocus={focusRoomSearch}
|
||||
onKeyDown={(e) => {
|
||||
if (searchInputRef.current && e.key === Key.ESCAPE) {
|
||||
searchInputRef.current.value = "";
|
||||
onSearchCancel?.();
|
||||
}
|
||||
}}
|
||||
onKeyDown={vm.onUpdateSearchInput}
|
||||
/>
|
||||
</Form.Root>
|
||||
);
|
||||
@@ -360,21 +235,21 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
<ToggleMenuItem
|
||||
Icon={FavouriteIcon}
|
||||
label={_t("room|context_menu|favourite")}
|
||||
checked={isFavorite}
|
||||
onSelect={() => tagRoom(room, DefaultTagID.Favourite)}
|
||||
checked={vm.isFavorite}
|
||||
onSelect={vm.onFavoriteToggleClick}
|
||||
/>
|
||||
<MenuItem
|
||||
Icon={UserAddIcon}
|
||||
label={_t("action|invite")}
|
||||
disabled={!canInviteToState}
|
||||
onSelect={() => inviteToRoom(room)}
|
||||
disabled={!vm.canInviteToState}
|
||||
onSelect={vm.onInviteToRoomClick}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
<MenuItem Icon={UserProfileIcon} label={_t("common|people")} onSelect={onRoomMembersClick} />
|
||||
<MenuItem Icon={ThreadsIcon} label={_t("common|threads")} onSelect={onRoomThreadsClick} />
|
||||
{!isVideoRoom && (
|
||||
<MenuItem Icon={UserProfileIcon} label={_t("common|people")} onSelect={vm.onRoomMembersClick} />
|
||||
<MenuItem Icon={ThreadsIcon} label={_t("common|threads")} onSelect={vm.onRoomThreadsClick} />
|
||||
{!vm.isVideoRoom && (
|
||||
<>
|
||||
<ReleaseAnnouncement
|
||||
feature="pinningMessageList"
|
||||
@@ -387,43 +262,47 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
<MenuItem
|
||||
Icon={PinIcon}
|
||||
label={_t("right_panel|pinned_messages_button")}
|
||||
onSelect={onRoomPinsClick}
|
||||
onSelect={vm.onRoomPinsClick}
|
||||
>
|
||||
<Text as="span" size="sm">
|
||||
{pinCount}
|
||||
{vm.pinCount}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</ReleaseAnnouncement>
|
||||
<MenuItem Icon={FilesIcon} label={_t("right_panel|files_button")} onSelect={onRoomFilesClick} />
|
||||
<MenuItem
|
||||
Icon={FilesIcon}
|
||||
label={_t("right_panel|files_button")}
|
||||
onSelect={vm.onRoomFilesClick}
|
||||
/>
|
||||
<MenuItem
|
||||
Icon={ExtensionsIcon}
|
||||
label={_t("right_panel|extensions_button")}
|
||||
onSelect={onRoomExtensionsClick}
|
||||
onSelect={vm.onRoomExtensionsClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={onShareRoomClick} />
|
||||
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={vm.onShareRoomClick} />
|
||||
|
||||
{!isVideoRoom && (
|
||||
{!vm.isVideoRoom && (
|
||||
<>
|
||||
<MenuItem
|
||||
Icon={PollsIcon}
|
||||
label={_t("right_panel|polls_button")}
|
||||
onSelect={onRoomPollHistoryClick}
|
||||
onSelect={vm.onRoomPollHistoryClick}
|
||||
/>
|
||||
<MenuItem
|
||||
Icon={ExportArchiveIcon}
|
||||
label={_t("export_chat|title")}
|
||||
onSelect={onRoomExportClick}
|
||||
onSelect={vm.onRoomExportClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={onRoomSettingsClick} />
|
||||
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={vm.onRoomSettingsClick} />
|
||||
|
||||
<Separator />
|
||||
<div className="mx_RoomSummaryCard_bottomOptions">
|
||||
@@ -431,14 +310,14 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
Icon={ErrorIcon}
|
||||
kind="critical"
|
||||
label={_t("action|report_room")}
|
||||
onSelect={onReportRoomClick}
|
||||
onSelect={vm.onReportRoomClick}
|
||||
/>
|
||||
<MenuItem
|
||||
className="mx_RoomSummaryCard_leave"
|
||||
Icon={LeaveIcon}
|
||||
kind="critical"
|
||||
label={_t("action|leave_room")}
|
||||
onSelect={onLeaveRoomClick}
|
||||
onSelect={vm.onLeaveRoomClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -446,4 +325,4 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomSummaryCard;
|
||||
export default RoomSummaryCardView;
|
||||
Reference in New Issue
Block a user