Files
ThreadNet-Web/src/components/views/right_panel/RoomSummaryCardView.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

329 lines
13 KiB
TypeScript
Raw Normal View History

2020-09-08 08:48:03 +01:00
/*
Copyright 2024, 2025 New Vector Ltd.
2020-09-08 08:48:03 +01:00
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
2024-09-09 14:57:16 +01:00
Please see LICENSE files in the repository root for full details.
2020-09-08 08:48:03 +01:00
*/
2025-05-15 16:17:21 +02:00
import React, { useEffect, useState, type JSX } from "react";
2020-09-08 08:48:03 +01:00
import classNames from "classnames";
import {
MenuItem,
Separator,
ToggleMenuItem,
Text,
Badge,
Heading,
IconButton,
Link,
2024-07-08 10:57:41 +01:00
Search,
Form,
} from "@vector-im/compound-web";
import FavouriteIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite";
import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
import SettingsIcon from "@vector-im/compound-design-tokens/assets/web/icons/settings";
import ExportArchiveIcon from "@vector-im/compound-design-tokens/assets/web/icons/export-archive";
import LeaveIcon from "@vector-im/compound-design-tokens/assets/web/icons/leave";
import FilesIcon from "@vector-im/compound-design-tokens/assets/web/icons/files";
import ExtensionsIcon from "@vector-im/compound-design-tokens/assets/web/icons/extensions";
import UserProfileIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile";
import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads";
import PollsIcon from "@vector-im/compound-design-tokens/assets/web/icons/polls";
import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin";
import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
import LockOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-off";
import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
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";
2025-05-15 16:17:21 +02:00
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
2020-09-08 08:48:03 +01:00
2025-05-15 16:17:21 +02:00
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 "../../../shared-components/utils/Flex";
2025-05-15 16:17:21 +02:00
import { Linkify, topicToHtml } from "../../../HtmlUtils.tsx";
import { Box } from "../../../shared-components/utils/Box";
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
2025-05-15 16:17:21 +02:00
import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx";
2025-04-17 17:56:19 +02:00
import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";
2020-09-08 08:48:03 +01:00
interface IProps {
room: Room;
2023-02-28 09:39:55 +13:00
permalinkCreator: RoomPermalinkCreator;
onSearchChange?: (term: string) => void;
2024-07-08 10:57:41 +01:00
onSearchCancel?: () => void;
focusRoomSearch?: boolean;
searchTerm?: string;
2020-09-08 08:48:03 +01:00
}
const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
2025-04-17 17:56:19 +02:00
const vm = useRoomTopicViewModel(room);
2025-04-17 17:56:19 +02:00
const body = topicToHtml(vm.topic?.text, vm.topic?.html);
2025-04-17 17:56:19 +02:00
if (!body && !vm.canEditTopic) {
return null;
}
if (!body) {
return (
<Flex
as="section"
direction="column"
justify="center"
gap="var(--cpd-space-2x)"
className="mx_RoomSummaryCard_topic"
>
<Box flex="1" className="mx_RoomSummaryCard_topic_box">
2025-04-17 17:56:19 +02:00
<Link kind="primary" onClick={vm.onEditClick}>
<Text size="sm" weight="regular">
{_t("right_panel|add_topic")}
</Text>
</Link>
</Box>
</Flex>
);
}
2025-04-17 17:56:19 +02:00
const content = vm.expanded ? <Linkify>{body}</Linkify> : body;
2025-05-15 16:17:21 +02:00
return (
<Flex
as="section"
direction="column"
justify="center"
gap="var(--cpd-space-2x)"
className={classNames("mx_RoomSummaryCard_topic", {
2025-04-17 17:56:19 +02:00
mx_RoomSummaryCard_topic_collapsed: !vm.expanded,
})}
>
<Box flex="1" className="mx_RoomSummaryCard_topic_container mx_RoomSummaryCard_topic_box">
2025-04-17 17:56:19 +02:00
<Text size="sm" weight="regular" onClick={vm.onTopicLinkClick}>
{content}
</Text>
2025-04-17 17:56:19 +02:00
<IconButton className="mx_RoomSummaryCard_topic_chevron" size="24px" onClick={vm.onExpandedClick}>
<ChevronDownIcon />
</IconButton>
</Box>
2025-04-17 17:56:19 +02:00
{vm.expanded && vm.canEditTopic && (
<Box flex="1" className="mx_RoomSummaryCard_topic_edit">
2025-04-17 17:56:19 +02:00
<Link kind="primary" onClick={vm.onEditClick}>
<Text size="sm" weight="regular">
{_t("action|edit")}
</Text>
</Link>
</Box>
)}
</Flex>
);
};
2025-05-15 16:17:21 +02:00
const RoomSummaryCardView: React.FC<IProps> = ({
2024-07-08 10:57:41 +01:00
room,
permalinkCreator,
onSearchChange,
onSearchCancel,
focusRoomSearch,
2025-05-06 17:24:06 +02:00
searchTerm = "",
2024-07-08 10:57:41 +01:00
}) => {
2025-05-15 16:17:21 +02:00
const vm = useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel);
2024-07-08 10:57:41 +01:00
2025-05-06 17:24:06 +02:00
// The search field is controlled and onSearchChange is debounced in RoomView,
// so we need to set the value of the input right away
const [searchValue, setSearchValue] = useState(searchTerm);
useEffect(() => {
setSearchValue(searchTerm);
}, [searchTerm]);
const roomInfo = (
2023-10-20 14:30:37 +01:00
<header className="mx_RoomSummaryCard_container">
2023-11-11 07:24:48 +00:00
<RoomAvatar room={room} size="80px" viewAvatarOnClick />
<RoomName room={room}>
{(name) => (
2023-11-11 07:24:48 +00:00
<Heading
as="h1"
size="md"
weight="semibold"
className="mx_RoomSummaryCard_roomName text-primary"
title={name}
>
{name}
2023-11-11 07:24:48 +00:00
</Heading>
)}
</RoomName>
2023-11-11 07:24:48 +00:00
<Text
as="div"
size="sm"
weight="semibold"
className="mx_RoomSummaryCard_alias text-secondary"
2025-05-15 16:17:21 +02:00
title={vm.alias}
2023-11-11 07:24:48 +00:00
>
2025-05-15 16:17:21 +02:00
{vm.alias}
2023-11-11 07:24:48 +00:00
</Text>
<Flex as="section" justify="center" gap="var(--cpd-space-2x)" className="mx_RoomSummaryCard_badges">
2025-05-15 16:17:21 +02:00
{!vm.isDirectMessage && vm.roomJoinRule === JoinRule.Public && (
<Badge kind="blue">
<PublicIcon width="1em" color="var(--cpd-color-icon-info-primary)" />
2023-11-11 07:24:48 +00:00
{_t("common|public_room")}
</Badge>
)}
2025-05-15 16:17:21 +02:00
{vm.isRoomEncrypted && vm.e2eStatus !== E2EStatus.Warning && (
2024-10-15 09:56:43 +01:00
<Badge kind="green">
2023-11-11 07:24:48 +00:00
<LockIcon width="1em" />
{_t("common|encrypted")}
</Badge>
)}
2025-05-15 16:17:21 +02:00
{!vm.isRoomEncrypted && (
<Badge kind="blue">
<LockOffIcon width="1em" color="var(--cpd-color-icon-info-primary)" />
2023-11-11 07:24:48 +00:00
{_t("common|unencrypted")}
</Badge>
)}
2025-05-15 16:17:21 +02:00
{vm.e2eStatus === E2EStatus.Warning && (
2024-10-15 09:56:43 +01:00
<Badge kind="red">
<ErrorSolidIcon width="1em" />
2023-11-11 07:24:48 +00:00
{_t("common|not_trusted")}
</Badge>
)}
</Flex>
<RoomTopic room={room} />
2023-10-20 14:30:37 +01:00
</header>
2020-09-08 08:48:03 +01:00
);
const header = onSearchChange && (
<Form.Root className="mx_RoomSummaryCard_search" onSubmit={(e) => e.preventDefault()}>
<Search
placeholder={_t("room|search|placeholder")}
name="room_message_search"
onChange={(e) => {
2025-05-06 17:24:06 +02:00
setSearchValue(e.currentTarget.value);
onSearchChange(e.currentTarget.value);
}}
2025-05-06 17:24:06 +02:00
value={searchValue}
className="mx_no_textinput"
2025-05-15 16:17:21 +02:00
ref={vm.searchInputRef}
autoFocus={focusRoomSearch}
2025-05-15 16:17:21 +02:00
onKeyDown={vm.onUpdateSearchInput}
/>
</Form.Root>
);
2020-09-08 08:48:03 +01:00
return (
2024-07-09 17:06:50 +05:30
<BaseCard
id="room-summary-panel"
className="mx_RoomSummaryCard"
ariaLabelledBy="room-summary-panel-tab"
role="tabpanel"
header={header}
2024-07-09 17:06:50 +05:30
>
{roomInfo}
<Separator />
<div role="menubar" aria-orientation="vertical">
<ToggleMenuItem
Icon={FavouriteIcon}
label={_t("room|context_menu|favourite")}
2025-05-15 16:17:21 +02:00
checked={vm.isFavorite}
onSelect={vm.onFavoriteToggleClick}
/>
<MenuItem
Icon={UserAddIcon}
label={_t("action|invite")}
2025-05-15 16:17:21 +02:00
disabled={!vm.canInviteToState}
onSelect={vm.onInviteToRoomClick}
/>
<Separator />
2025-05-15 16:17:21 +02:00
<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"
header={_t("right_panel|pinned_messages|release_announcement|title")}
description={_t("right_panel|pinned_messages|release_announcement|description")}
closeLabel={_t("right_panel|pinned_messages|release_announcement|close")}
placement="top"
2024-09-16 16:51:59 +02:00
>
<div>
<MenuItem
Icon={PinIcon}
label={_t("right_panel|pinned_messages_button")}
2025-05-15 16:17:21 +02:00
onSelect={vm.onRoomPinsClick}
>
<Text as="span" size="sm">
2025-05-15 16:17:21 +02:00
{vm.pinCount}
</Text>
</MenuItem>
</div>
</ReleaseAnnouncement>
2025-05-15 16:17:21 +02:00
<MenuItem
Icon={FilesIcon}
label={_t("right_panel|files_button")}
onSelect={vm.onRoomFilesClick}
/>
<MenuItem
Icon={ExtensionsIcon}
label={_t("right_panel|extensions_button")}
2025-05-15 16:17:21 +02:00
onSelect={vm.onRoomExtensionsClick}
/>
</>
)}
<Separator />
2025-05-15 16:17:21 +02:00
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={vm.onShareRoomClick} />
2025-05-15 16:17:21 +02:00
{!vm.isVideoRoom && (
<>
<MenuItem
Icon={PollsIcon}
label={_t("right_panel|polls_button")}
2025-05-15 16:17:21 +02:00
onSelect={vm.onRoomPollHistoryClick}
/>
<MenuItem
Icon={ExportArchiveIcon}
label={_t("export_chat|title")}
2025-05-15 16:17:21 +02:00
onSelect={vm.onRoomExportClick}
/>
</>
)}
2025-05-15 16:17:21 +02:00
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={vm.onRoomSettingsClick} />
<Separator />
<div className="mx_RoomSummaryCard_bottomOptions">
<MenuItem
Icon={ErrorIcon}
kind="critical"
label={_t("action|report_room")}
2025-05-15 16:17:21 +02:00
onSelect={vm.onReportRoomClick}
/>
<MenuItem
className="mx_RoomSummaryCard_leave"
Icon={LeaveIcon}
kind="critical"
label={_t("action|leave_room")}
2025-05-15 16:17:21 +02:00
onSelect={vm.onLeaveRoomClick}
/>
</div>
</div>
2020-09-08 08:48:03 +01:00
</BaseCard>
);
};
2025-05-15 16:17:21 +02:00
export default RoomSummaryCardView;