From 97e5c74205cf841937973d9243e116a5ffda0f79 Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 31 Oct 2023 15:25:42 +0000 Subject: [PATCH 01/17] Update RoomSummaryCard navigation links --- package.json | 4 +- .../views/right_panel/_RoomSummaryCard.pcss | 54 ---- .../views/context_menus/RoomContextMenu.tsx | 13 +- .../views/right_panel/RoomSummaryCard.tsx | 127 ++++---- src/i18n/strings/en_EN.json | 3 - src/utils/room/canInviteTo.ts | 33 ++ src/utils/room/inviteToRoom.ts | 36 +++ src/utils/room/tagRoom.ts | 40 +++ .../RoomSummaryCard-test.tsx.snap | 286 +++++++++++++++--- yarn.lock | 25 +- 10 files changed, 440 insertions(+), 181 deletions(-) create mode 100644 src/utils/room/canInviteTo.ts create mode 100644 src/utils/room/inviteToRoom.ts create mode 100644 src/utils/room/tagRoom.ts diff --git a/package.json b/package.json index 95b30f18346..36608e21a80 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "@sentry/browser": "^7.0.0", "@sentry/tracing": "^7.0.0", "@testing-library/react-hooks": "^8.0.1", - "@vector-im/compound-design-tokens": "^0.0.6", - "@vector-im/compound-web": "0.5.4", + "@vector-im/compound-design-tokens": "0.0.7", + "@vector-im/compound-web": "0.6.3", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-en": "^3.0.2", diff --git a/res/css/views/right_panel/_RoomSummaryCard.pcss b/res/css/views/right_panel/_RoomSummaryCard.pcss index e2b71e1a150..cae32859633 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.pcss +++ b/res/css/views/right_panel/_RoomSummaryCard.pcss @@ -93,24 +93,6 @@ limitations under the License. } } - .mx_RoomSummaryCard_aboutGroup { - .mx_RoomSummaryCard_Button { - padding-left: 44px; - - &::before { - content: ""; - position: absolute; - top: 8px; - left: 10px; - height: 24px; - width: 24px; - mask-repeat: no-repeat; - mask-position: center; - background-color: $icon-button-color; - } - } - } - .mx_RoomSummaryCard_appsGroup { .mx_RoomSummaryCard_Button { /* this button is special so we have to override some of the original styling */ @@ -259,42 +241,6 @@ limitations under the License. cursor: pointer; } -.mx_RoomSummaryCard_icon_people::before { - mask-image: url("$(res)/img/element-icons/room/members.svg"); -} - -.mx_RoomSummaryCard_icon_files::before { - mask-image: url("$(res)/img/element-icons/room/files.svg"); -} - -.mx_RoomSummaryCard_icon_pins::before { - mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); -} - -.mx_RoomSummaryCard_icon_threads::before { - mask-image: url("$(res)/img/element-icons/message/thread.svg"); -} - -.mx_RoomSummaryCard_icon_share::before { - mask-image: url("$(res)/img/element-icons/room/share.svg"); -} - -.mx_RoomSummaryCard_icon_settings::before { - mask-image: url("$(res)/img/element-icons/settings.svg"); -} - -.mx_RoomSummaryCard_icon_export::before { - mask-image: url("$(res)/img/element-icons/export.svg"); -} - -.mx_RoomSummaryCard_icon_poll::before { - mask-image: url("$(res)/img/element-icons/room/composer/poll.svg"); -} - -.mx_RoomSummaryCard_icon_search::before { - mask-image: url("$(res)/img/element-icons/room/search-inset.svg"); -} - .mx_RoomSummaryCard_searchBtn { background: var(--cpd-color-bg-canvas-default); color: var(--cpd-color-icon-primary); diff --git a/src/components/views/context_menus/RoomContextMenu.tsx b/src/components/views/context_menus/RoomContextMenu.tsx index ee3c6ded8a9..7527ef4feef 100644 --- a/src/components/views/context_menus/RoomContextMenu.tsx +++ b/src/components/views/context_menus/RoomContextMenu.tsx @@ -16,7 +16,6 @@ limitations under the License. import React, { useContext } from "react"; import { Room } from "matrix-js-sdk/src/matrix"; -import { logger } from "matrix-js-sdk/src/logger"; import { IProps as IContextMenuProps } from "../../structures/ContextMenu"; import IconizedContextMenu, { @@ -30,7 +29,6 @@ import { ButtonEvent } from "../elements/AccessibleButton"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import dis from "../../../dispatcher/dispatcher"; -import RoomListActions from "../../../actions/RoomListActions"; import { EchoChamber } from "../../../stores/local-echo/EchoChamber"; import { RoomNotifState } from "../../../RoomNotifs"; import Modal from "../../../Modal"; @@ -52,6 +50,7 @@ import { SdkContextClass } from "../../../contexts/SDKContext"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; import { DeveloperToolsOption } from "./DeveloperToolsOption"; +import { tagRoom } from "../../../utils/room/tagRoom"; interface IProps extends IContextMenuProps { room: Room; @@ -333,15 +332,7 @@ const RoomContextMenu: React.FC = ({ room, onFinished, ...props }) => { ev.preventDefault(); ev.stopPropagation(); - if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) { - const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite; - const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId); - const removeTag = isApplied ? tagId : inverseTag; - const addTag = isApplied ? null : tagId; - dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0)); - } else { - logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`); - } + tagRoom(room, tagId); const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent); switch (action) { diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 98876fce93d..1fd0d435fb5 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -17,20 +17,29 @@ limitations under the License. import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import classNames from "classnames"; import { Room } from "matrix-js-sdk/src/matrix"; -import { Tooltip } from "@vector-im/compound-web"; +import { MenuItem, Tooltip, Separator, ToggleMenuItem, Text } from "@vector-im/compound-web"; import { Icon as SearchIcon } from "@vector-im/compound-design-tokens/icons/search.svg"; +import { Icon as FavouriteIcon } from "@vector-im/compound-design-tokens/icons/favourite-off.svg"; +import { Icon as UserAddIcon } from "@vector-im/compound-design-tokens/icons/user-add.svg"; +import { Icon as LinkIcon } from "@vector-im/compound-design-tokens/icons/link.svg"; +import { Icon as SettingsIcon } from "@vector-im/compound-design-tokens/icons/settings.svg"; +import { Icon as ExportArchiveIcon } from "@vector-im/compound-design-tokens/icons/export-archive.svg"; +import { Icon as LeaveIcon } from "@vector-im/compound-design-tokens/icons/leave.svg"; +import { Icon as FilesIcon } from "@vector-im/compound-design-tokens/icons/files.svg"; +import { Icon as PollsIcon } from "@vector-im/compound-design-tokens/icons/polls.svg"; +import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin-off.svg"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useIsEncrypted } from "../../../hooks/useIsEncrypted"; import BaseCard, { Group } from "./BaseCard"; import { _t } from "../../../languageHandler"; import RoomAvatar from "../avatars/RoomAvatar"; -import AccessibleButton, { ButtonEvent, IAccessibleButtonProps } from "../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; import Modal from "../../../Modal"; import ShareDialog from "../dialogs/ShareDialog"; -import { useEventEmitter } from "../../../hooks/useEventEmitter"; +import { useEventEmitter, useEventEmitterState } from "../../../hooks/useEventEmitter"; import WidgetUtils from "../../../utils/WidgetUtils"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; @@ -44,7 +53,6 @@ import RoomContext from "../../../contexts/RoomContext"; import { UIComponent, UIFeature } from "../../../settings/UIFeature"; import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; import { WidgetContextMenu } from "../context_menus/WidgetContextMenu"; -import { useRoomMemberCount } from "../../../hooks/useRoomMembers"; import { useFeatureEnabled } from "../../../hooks/useSettings"; import { usePinnedEvents } from "./PinnedMessagesCard"; import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; @@ -56,6 +64,11 @@ import PosthogTrackers from "../../../PosthogTrackers"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; 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"; interface IProps { room: Room; @@ -68,23 +81,6 @@ interface IAppsSectionProps { room: Room; } -interface IButtonProps extends IAccessibleButtonProps { - className: string; - onClick(ev: ButtonEvent): void; -} - -const Button: React.FC = ({ children, className, onClick, ...props }) => { - return ( - - {children} - - ); -}; - export const useWidgets = (room: Room): IApp[] => { const [apps, setApps] = useState(() => WidgetStore.instance.getApps(room.roomId)); @@ -258,11 +254,6 @@ const AppsSection: React.FC = ({ room }) => { ); }; -const onRoomMembersClick = (ev: ButtonEvent): void => { - RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, true); - PosthogTrackers.trackInteraction("WebRightPanelRoomInfoPeopleButton", ev); -}; - const onRoomFilesClick = (): void => { RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true); }; @@ -299,6 +290,13 @@ const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onClose, on }); }; + const onLeaveRoomClick = (): void => { + defaultDispatcher.dispatch({ + action: "leave_room", + room_id: room.roomId, + }); + }; + const isRoomEncrypted = useIsEncrypted(cli, room); const roomContext = useContext(RoomContext); const e2eStatus = roomContext.e2eStatus; @@ -335,10 +333,14 @@ const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onClose, on ); - const memberCount = useRoomMemberCount(room); const pinningEnabled = useFeatureEnabled("feature_pinning"); const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length; + const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => + RoomListStore.instance.getTagsForRoom(room), + ); + const isFavorite = roomTags.includes(DefaultTagID.Favourite); + return ( = ({ room, permalinkCreator, onClose, on {header} - - - {!isVideoRoom && ( - - )} - {!isVideoRoom && ( - - )} - {pinningEnabled && !isVideoRoom && ( - - )} - {!isVideoRoom && ( - - )} - - - + + + + tagRoom(room, DefaultTagID.Favourite)} + /> + inviteToRoom(room)} + /> + + + + + + + + {pinningEnabled && ( + + + {pinCount} + + + )} + + + + + {SettingsStore.getValue(UIFeature.Widgets) && !isVideoRoom && diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2ff6dd3bd6b..a8c5e6d0fad 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -418,7 +418,6 @@ "spaceinvaders_message": "sends space invaders" }, "common": { - "about": "About", "access_token": "Access Token", "accessibility": "Accessibility", "advanced": "Advanced", @@ -1852,8 +1851,6 @@ "room_summary_card": { "title": "Room info" }, - "settings_button": "Room settings", - "share_button": "Share room", "thread_list": { "context_menu_label": "Thread options" }, diff --git a/src/utils/room/canInviteTo.ts b/src/utils/room/canInviteTo.ts new file mode 100644 index 00000000000..324bae9a0a9 --- /dev/null +++ b/src/utils/room/canInviteTo.ts @@ -0,0 +1,33 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 { JoinRule, Room } from "matrix-js-sdk/src/matrix"; + +import { shouldShowComponent } from "../../customisations/helpers/UIComponents"; +import { UIComponent } from "../../settings/UIFeature"; + +/** + * Can a user invite new members to the room + * @param room + * @returns whether the user can invite new members to the room + */ +export function canInviteTo(room: Room): boolean { + const client = room.client; + const canInvite = + !!room?.canInvite(client.getSafeUserId()) || !!(room?.isSpaceRoom() && room.getJoinRule() === JoinRule.Public); + + return canInvite && room?.getMyMembership() === "join" && shouldShowComponent(UIComponent.InviteUsers); +} diff --git a/src/utils/room/inviteToRoom.ts b/src/utils/room/inviteToRoom.ts new file mode 100644 index 00000000000..b8fb1214fef --- /dev/null +++ b/src/utils/room/inviteToRoom.ts @@ -0,0 +1,36 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 { Room } from "matrix-js-sdk/src/matrix"; + +import dis from "../../dispatcher/dispatcher"; + +/** + * Invite to a room and prompts guests to registers + * @param room + */ +export function inviteToRoom(room: Room): void { + if (room.client.isGuest()) { + dis.dispatch({ action: "require_registration" }); + return; + } + + // open the room inviter + dis.dispatch({ + action: "view_invite", + roomId: room.roomId, + }); +} diff --git a/src/utils/room/tagRoom.ts b/src/utils/room/tagRoom.ts new file mode 100644 index 00000000000..f358b288ae4 --- /dev/null +++ b/src/utils/room/tagRoom.ts @@ -0,0 +1,40 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 { Room } from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; + +import RoomListStore from "../../stores/room-list/RoomListStore"; +import { DefaultTagID, TagID } from "../../stores/room-list/models"; +import RoomListActions from "../../actions/RoomListActions"; +import dis from "../../dispatcher/dispatcher"; + +/** + * Toggle tag for a given room + * @param room The room to tag + * @param tagId The tag to invert + */ +export function tagRoom(room: Room, tagId: TagID): void { + if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) { + const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite; + const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId); + const removeTag = isApplied ? tagId : inverseTag; + const addTag = isApplied ? null : tagId; + dis.dispatch(RoomListActions.tagRoom(room.client, room, removeTag, addTag, 0)); + } else { + logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`); + } +} diff --git a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap index 8c5891b5765..3a856d39ecd 100644 --- a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap +++ b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap @@ -67,60 +67,268 @@ exports[` renders the room summary 1`] = ` />