From 5d6ebd8190a877c8b59ee4574f9f0af69e98ebe8 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 6 Sep 2024 18:19:24 +0100 Subject: [PATCH 01/14] feat: handle `RoomManager` open secondLevel --- apps/meteor/app/api/server/v1/rooms.ts | 2 +- .../client/hooks/useRoomInfoEndpoint.ts | 4 +- apps/meteor/client/lib/RoomManager.ts | 28 ++++++- .../views/room/providers/RoomProvider.tsx | 77 +++++++++++++++++-- apps/meteor/lib/publishFields.ts | 1 + packages/core-typings/src/IRoom.ts | 3 + packages/rest-typings/src/v1/rooms.ts | 2 +- 7 files changed, 105 insertions(+), 12 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 5da59d977fb1..3dc62e462ddf 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -420,7 +420,7 @@ API.v1.addRoute( const discussionParent = room.prid && (await Rooms.findOneById>(room.prid, { - projection: { name: 1, fname: 1, t: 1, prid: 1, u: 1 }, + projection: { name: 1, fname: 1, t: 1, prid: 1, u: 1, sidepanel: 1 }, })); const { team, parentRoom } = await Team.getRoomInfo(room); const parent = discussionParent || parentRoom; diff --git a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts index 47ea84af20b6..0bac1d7eb413 100644 --- a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts +++ b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts @@ -1,6 +1,6 @@ import type { IRoom } from '@rocket.chat/core-typings'; import type { OperationResult } from '@rocket.chat/rest-typings'; -import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useUserId } from '@rocket.chat/ui-contexts'; import type { UseQueryResult } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; import { minutesToMilliseconds } from 'date-fns'; @@ -8,6 +8,7 @@ import type { Meteor } from 'meteor/meteor'; export const useRoomInfoEndpoint = (rid: IRoom['_id']): UseQueryResult> => { const getRoomInfo = useEndpoint('GET', '/v1/rooms.info'); + const uid = useUserId(); return useQuery(['/v1/rooms.info', rid], () => getRoomInfo({ roomId: rid }), { cacheTime: minutesToMilliseconds(15), staleTime: minutesToMilliseconds(5), @@ -17,5 +18,6 @@ export const useRoomInfoEndpoint = (rid: IRoom['_id']): UseQueryResult = new Map(); + private parentRid?: IRoom['_id'] | undefined; + constructor() { super(); debugRoomManager && @@ -78,6 +80,13 @@ export const RoomManager = new (class RoomManager extends Emitter<{ } get opened(): IRoom['_id'] | undefined { + return this.parentRid ?? this.rid; + } + + get openedSecondLevel(): IRoom['_id'] | undefined { + if (!this.parentRid) { + return undefined; + } return this.rid; } @@ -106,20 +115,28 @@ export const RoomManager = new (class RoomManager extends Emitter<{ this.emit('changed', this.rid); } - open(rid: IRoom['_id']): void { + private _open(rid: IRoom['_id'], parent?: IRoom['_id']): void { if (rid === this.rid) { return; } - this.back(rid); if (!this.rooms.has(rid)) { this.rooms.set(rid, new RoomStore(rid)); } this.rid = rid; + this.parentRid = parent; this.emit('opened', this.rid); this.emit('changed', this.rid); } + open(rid: IRoom['_id']): void { + this._open(rid); + } + + openSecondLevel(parentId: IRoom['_id'], rid: IRoom['_id']): void { + this._open(rid, parentId); + } + getStore(rid: IRoom['_id']): RoomStore | undefined { return this.rooms.get(rid); } @@ -130,4 +147,11 @@ const subscribeOpenedRoom = [ (): IRoom['_id'] | undefined => RoomManager.opened, ] as const; +const subscribeOpenedSecondLevelRoom = [ + (callback: () => void): (() => void) => RoomManager.on('changed', callback), + (): IRoom['_id'] | undefined => RoomManager.openedSecondLevel, +] as const; + export const useOpenedRoom = (): IRoom['_id'] | undefined => useSyncExternalStore(...subscribeOpenedRoom); + +export const useSecondLevelOpenedRoom = (): IRoom['_id'] | undefined => useSyncExternalStore(...subscribeOpenedSecondLevelRoom); diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index 65c83100b1c0..5fa257174199 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -1,4 +1,5 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import { useFeaturePreview } from '@rocket.chat/ui-client'; import { useRouter } from '@rocket.chat/ui-contexts'; import type { ReactNode, ContextType, ReactElement } from 'react'; import React, { useMemo, memo, useEffect, useCallback } from 'react'; @@ -8,12 +9,14 @@ import { RoomHistoryManager } from '../../../../app/ui-utils/client'; import { UserAction } from '../../../../app/ui/client/lib/UserAction'; import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { useReactiveValue } from '../../../hooks/useReactiveValue'; +import { useRoomInfoEndpoint } from '../../../hooks/useRoomInfoEndpoint'; import { RoomManager } from '../../../lib/RoomManager'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import ImageGalleryProvider from '../../../providers/ImageGalleryProvider'; import RoomNotFound from '../RoomNotFound'; import RoomSkeleton from '../RoomSkeleton'; import { useRoomRolesManagement } from '../body/hooks/useRoomRolesManagement'; +import type { IRoomWithFederationOriginalName } from '../contexts/RoomContext'; import { RoomContext } from '../contexts/RoomContext'; import ComposerPopupProvider from './ComposerPopupProvider'; import RoomToolboxProvider from './RoomToolboxProvider'; @@ -30,15 +33,17 @@ type RoomProviderProps = { const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { useRoomRolesManagement(rid); - const { data: room, isSuccess } = useRoomQuery(rid); + const resultFromServer = useRoomInfoEndpoint(rid); + + const resultFromLocal = useRoomQuery(rid); // TODO: the following effect is a workaround while we don't have a general and definitive solution for it const router = useRouter(); useEffect(() => { - if (isSuccess && !room) { + if (resultFromLocal.isSuccess && !resultFromLocal.data) { router.navigate('/home'); } - }, [isSuccess, room, router]); + }, [resultFromLocal.data, resultFromLocal.isSuccess, resultFromServer, router]); const subscriptionQuery = useReactiveQuery(['subscriptions', { rid }], () => ChatSubscription.findOne({ rid }) ?? null); @@ -46,7 +51,8 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { useUsersNameChanged(); - const pseudoRoom = useMemo(() => { + const pseudoRoom: IRoomWithFederationOriginalName | null = useMemo(() => { + const room = resultFromLocal.data; if (!room) { return null; } @@ -57,7 +63,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { name: roomCoordinator.getRoomName(room.t, room), federationOriginalName: room.name, }; - }, [room, subscriptionQuery.data]); + }, [resultFromLocal.data, subscriptionQuery.data]); const { hasMorePreviousMessages, hasMoreNextMessages, isLoadingMoreMessages } = useReactiveValue( useCallback(() => { @@ -86,12 +92,69 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }; }, [hasMoreNextMessages, hasMorePreviousMessages, isLoadingMoreMessages, pseudoRoom, rid, subscriptionQuery.data]); + const isSidepanelFeatureEnabled = useFeaturePreview('sidepanelNavigation'); + useEffect(() => { + if (isSidepanelFeatureEnabled) { + if (resultFromServer.isSuccess) { + if (resultFromServer.data.room?.teamMain) { + if ( + resultFromServer.data.room.sidepanel?.items.includes('channels') || + resultFromServer.data.room?.sidepanel?.items.includes('discussions') + ) { + RoomManager.openSecondLevel(rid, rid); + } else { + RoomManager.open(rid); + } + return (): void => { + RoomManager.back(rid); + }; + } + + switch (true) { + case resultFromServer.data.room?.prid && + resultFromServer.data.parent && + resultFromServer.data.parent.sidepanel?.items.includes('discussions'): + RoomManager.openSecondLevel(resultFromServer.data.parent._id, rid); + break; + case resultFromServer.data.team?.roomId && + !resultFromServer.data.room?.teamMain && + resultFromServer.data.parent?.sidepanel?.items.includes('channels'): + RoomManager.openSecondLevel(resultFromServer.data.team.roomId, rid); + break; + + default: + if ( + resultFromServer.data.parent?.sidepanel?.items.includes('channels') || + resultFromServer.data.parent?.sidepanel?.items.includes('discussions') + ) { + RoomManager.openSecondLevel(rid, rid); + } else { + RoomManager.open(rid); + } + break; + } + } + return (): void => { + RoomManager.back(rid); + }; + } + RoomManager.open(rid); return (): void => { RoomManager.back(rid); }; - }, [rid]); + }, [ + isSidepanelFeatureEnabled, + rid, + resultFromServer.data?.room?.prid, + resultFromServer.data?.room?.teamId, + resultFromServer.data?.room?.teamMain, + resultFromServer.isSuccess, + resultFromServer.data?.parent, + resultFromServer.data?.team?.roomId, + resultFromServer.data, + ]); const subscribed = !!subscriptionQuery.data; @@ -104,7 +167,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }, [rid, subscribed]); if (!pseudoRoom) { - return isSuccess && !room ? : ; + return resultFromLocal.isSuccess && !resultFromLocal.data ? : ; } return ( diff --git a/apps/meteor/lib/publishFields.ts b/apps/meteor/lib/publishFields.ts index c1483ea86cd1..48d0f9c87d5c 100644 --- a/apps/meteor/lib/publishFields.ts +++ b/apps/meteor/lib/publishFields.ts @@ -74,6 +74,7 @@ export const roomFields = { avatarETag: 1, usersCount: 1, msgs: 1, + sidepanel: 1, // @TODO create an API to register this fields based on room type tags: 1, diff --git a/packages/core-typings/src/IRoom.ts b/packages/core-typings/src/IRoom.ts index 0ce1b1041de6..95f2836da1e7 100644 --- a/packages/core-typings/src/IRoom.ts +++ b/packages/core-typings/src/IRoom.ts @@ -99,6 +99,9 @@ export const isSidepanelItem = (item: any): item is SidepanelItem => { }; export const isValidSidepanel = (sidepanel: IRoom['sidepanel']) => { + if (sidepanel === null) { + return true; + } if (!sidepanel?.items) { return false; } diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index c837ba7186bd..16debe87e44c 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -626,7 +626,7 @@ export type RoomsEndpoints = { '/v1/rooms.info': { GET: (params: RoomsInfoProps) => { room: IRoom | undefined; - parent?: Pick; + parent?: Pick; team?: Pick; }; }; From 4161e07246b71e5e3f1a1e573c50bc5038608de7 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 6 Sep 2024 18:37:27 +0100 Subject: [PATCH 02/14] feat: implement `RoomSidepanel` and enable feature preview --- apps/meteor/client/views/room/RoomOpener.tsx | 79 +++++++++++-------- .../views/room/Sidepanel/RoomSidepanel.tsx | 72 +++++++++++++++++ .../Sidepanel/RoomSidepanelListWrapper.tsx | 19 +++++ .../room/Sidepanel/RoomSidepanelLoading.tsx | 20 +++++ .../SidepanelItem/RoomSidepanelItem.tsx | 29 +++++++ .../room/Sidepanel/SidepanelItem/index.ts | 1 + .../room/Sidepanel/hooks/useItemData.tsx | 68 ++++++++++++++++ .../client/views/room/Sidepanel/index.ts | 1 + .../client/views/room/layout/RoomLayout.tsx | 4 +- .../src/hooks/useFeaturePreviewList.ts | 2 +- 10 files changed, 260 insertions(+), 35 deletions(-) create mode 100644 apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx create mode 100644 apps/meteor/client/views/room/Sidepanel/RoomSidepanelListWrapper.tsx create mode 100644 apps/meteor/client/views/room/Sidepanel/RoomSidepanelLoading.tsx create mode 100644 apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx create mode 100644 apps/meteor/client/views/room/Sidepanel/SidepanelItem/index.ts create mode 100644 apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx create mode 100644 apps/meteor/client/views/room/Sidepanel/index.ts diff --git a/apps/meteor/client/views/room/RoomOpener.tsx b/apps/meteor/client/views/room/RoomOpener.tsx index c30acf6f0e83..f51c4ca04754 100644 --- a/apps/meteor/client/views/room/RoomOpener.tsx +++ b/apps/meteor/client/views/room/RoomOpener.tsx @@ -1,5 +1,6 @@ import type { RoomType } from '@rocket.chat/core-typings'; -import { States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; +import { Box, States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; +import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; import type { ReactElement } from 'react'; import React, { lazy, Suspense } from 'react'; import { useTranslation } from 'react-i18next'; @@ -10,6 +11,7 @@ import { NotAuthorizedError } from '../../lib/errors/NotAuthorizedError'; import { OldUrlRoomError } from '../../lib/errors/OldUrlRoomError'; import { RoomNotFoundError } from '../../lib/errors/RoomNotFoundError'; import RoomSkeleton from './RoomSkeleton'; +import RoomSidepanel from './Sidepanel/RoomSidepanel'; import { useOpenRoom } from './hooks/useOpenRoom'; const RoomProvider = lazy(() => import('./providers/RoomProvider')); @@ -23,46 +25,59 @@ type RoomOpenerProps = { reference: string; }; +const isDirectOrOmnichannelRoom = (type: RoomType) => type === 'd' || type === 'l'; + const RoomOpener = ({ type, reference }: RoomOpenerProps): ReactElement => { const { data, error, isSuccess, isError, isLoading } = useOpenRoom({ type, reference }); const { t } = useTranslation(); return ( - }> - {isLoading && } - {isSuccess && ( - - - + + {!isDirectOrOmnichannelRoom(type) && ( + + {null} + + + + )} - {isError && - (() => { - if (error instanceof OldUrlRoomError) { - return ; - } - if (error instanceof RoomNotFoundError) { - return ; - } + }> + {isLoading && } + {isSuccess && ( + + + + )} + {isError && + (() => { + if (error instanceof OldUrlRoomError) { + return ; + } + + if (error instanceof RoomNotFoundError) { + return ; + } - if (error instanceof NotAuthorizedError) { - return ; - } + if (error instanceof NotAuthorizedError) { + return ; + } - return ( - } - body={ - - - {t('core.Error')} - {getErrorMessage(error)} - - } - /> - ); - })()} - + return ( + } + body={ + + + {t('core.Error')} + {getErrorMessage(error)} + + } + /> + ); + })()} + + ); }; diff --git a/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx b/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx new file mode 100644 index 000000000000..950c8cbde5e7 --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx @@ -0,0 +1,72 @@ +/* eslint-disable react/no-multi-comp */ +import { Box, Sidepanel, SidepanelListItem } from '@rocket.chat/fuselage'; +import { useEndpoint, useUserPreference } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import React, { memo } from 'react'; +import { Virtuoso } from 'react-virtuoso'; + +import { VirtuosoScrollbars } from '../../../components/CustomScrollbars'; +import { useRoomInfoEndpoint } from '../../../hooks/useRoomInfoEndpoint'; +import { useOpenedRoom, useSecondLevelOpenedRoom } from '../../../lib/RoomManager'; +import RoomSidepanelListWrapper from './RoomSidepanelListWrapper'; +import RoomSidepanelLoading from './RoomSidepanelLoading'; +import RoomSidepanelItem from './SidepanelItem'; + +const RoomSidepanel = () => { + const parentRid = useOpenedRoom(); + const secondLevelOpenedRoom = useSecondLevelOpenedRoom() ?? parentRid; + + if (!parentRid || !secondLevelOpenedRoom) { + return null; + } + + return ; +}; + +const RoomSidepanelWithData = ({ parentRid, openedRoom }: { parentRid: string; openedRoom: string }) => { + const sidebarViewMode = useUserPreference<'extended' | 'medium' | 'condensed'>('sidebarViewMode'); + + const roomInfo = useRoomInfoEndpoint(parentRid); + const sidepanelItems = roomInfo.data?.room?.sidepanel?.items || roomInfo.data?.parent?.sidepanel?.items; + + const listRoomsAndDiscussions = useEndpoint('GET', '/v1/teams.listChildren'); + const result = useQuery({ + queryKey: ['sidepanel', parentRid], + queryFn: () => + listRoomsAndDiscussions({ + roomId: parentRid, + sort: JSON.stringify({ lm: -1 }), + type: sidepanelItems?.length === 1 ? sidepanelItems[0] : undefined, + }), + enabled: !!sidepanelItems, + }); + + if (roomInfo.isSuccess && !roomInfo.data.room?.sidepanel && !roomInfo.data.parent?.sidepanel) { + return null; + } + + if (roomInfo.isLoading || (roomInfo.isSuccess && result.isLoading)) { + return ; + } + + if (!result.isSuccess || !roomInfo.isSuccess) { + return null; + } + + return ( + + + ( + + )} + /> + + + ); +}; + +export default memo(RoomSidepanel); diff --git a/apps/meteor/client/views/room/Sidepanel/RoomSidepanelListWrapper.tsx b/apps/meteor/client/views/room/Sidepanel/RoomSidepanelListWrapper.tsx new file mode 100644 index 000000000000..dd9e6e6ec221 --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/RoomSidepanelListWrapper.tsx @@ -0,0 +1,19 @@ +import { SidepanelList } from '@rocket.chat/fuselage'; +import { useMergedRefs } from '@rocket.chat/fuselage-hooks'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ForwardedRef, HTMLAttributes } from 'react'; +import React, { forwardRef } from 'react'; + +import { useSidebarListNavigation } from '../../../sidebar/RoomList/useSidebarListNavigation'; + +type RoomListWrapperProps = HTMLAttributes; + +const RoomSidepanelListWrapper = forwardRef(function RoomListWrapper(props: RoomListWrapperProps, ref: ForwardedRef) { + const t = useTranslation(); + const { sidebarListRef } = useSidebarListNavigation(); + const mergedRefs = useMergedRefs(ref, sidebarListRef); + + return ; +}); + +export default RoomSidepanelListWrapper; diff --git a/apps/meteor/client/views/room/Sidepanel/RoomSidepanelLoading.tsx b/apps/meteor/client/views/room/Sidepanel/RoomSidepanelLoading.tsx new file mode 100644 index 000000000000..00609ae6c496 --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/RoomSidepanelLoading.tsx @@ -0,0 +1,20 @@ +import { SidebarV2Item as SidebarItem, Sidepanel, SidepanelList, Skeleton } from '@rocket.chat/fuselage'; +import React from 'react'; + +const RoomSidepanelLoading = () => ( + + + + + + + + + + + + + +); + +export default RoomSidepanelLoading; diff --git a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx new file mode 100644 index 000000000000..459cd5287a3e --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx @@ -0,0 +1,29 @@ +import type { IRoom, Serialized } from '@rocket.chat/core-typings'; +import { useUserSubscription } from '@rocket.chat/ui-contexts'; +import React, { memo } from 'react'; + +import { goToRoomById } from '../../../../lib/utils/goToRoomById'; +import { useTemplateByViewMode } from '../../../../sidebarv2/hooks/useTemplateByViewMode'; +import { useItemData } from '../hooks/useItemData'; + +export type RoomSidepanelItemProps = { + openedRoom?: string; + room: Serialized; + parentRid: string; + viewMode?: 'extended' | 'medium' | 'condensed'; +}; + +const RoomSidepanelItem = ({ room, openedRoom, viewMode }: RoomSidepanelItemProps) => { + const SidepanelItem = useTemplateByViewMode(); + const subscription = useUserSubscription(room._id); + + const itemData = useItemData({ ...subscription, ...room } as any, { viewMode, openedRoom }); // as any because of divergent and overlaping timestamp types in subs and room (type Date vs type string) + + if (!subscription) { + return ; + } + + return ; +}; + +export default memo(RoomSidepanelItem); diff --git a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/index.ts b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/index.ts new file mode 100644 index 000000000000..5cfc0da3055b --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/index.ts @@ -0,0 +1 @@ +export { default } from './RoomSidepanelItem'; diff --git a/apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx b/apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx new file mode 100644 index 000000000000..f5a89b9df25d --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx @@ -0,0 +1,68 @@ +import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; +import { SidebarV2ItemBadge as SidebarItemBadge, SidebarV2ItemIcon as SidebarItemIcon } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useMemo } from 'react'; + +import { RoomIcon } from '../../../../components/RoomIcon'; +import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; +import { getBadgeTitle, getMessage } from '../../../../sidebarv2/RoomList/SidebarItemTemplateWithData'; +import { useAvatarTemplate } from '../../../../sidebarv2/hooks/useAvatarTemplate'; + +export const useItemData = ( + room: ISubscription & IRoom, + { openedRoom, viewMode }: { openedRoom: string | undefined; viewMode?: 'extended' | 'medium' | 'condensed' }, +) => { + const t = useTranslation(); + const AvatarTemplate = useAvatarTemplate(); + + const highlighted = Boolean(!room.hideUnreadStatus && (room.alert || room.unread)); + + const icon = useMemo( + () => } />, + [highlighted, room], + ); + const time = 'lastMessage' in room ? room.lastMessage?.ts : undefined; + const message = viewMode === 'extended' && getMessage(room, room.lastMessage, t); + + const threadUnread = Number(room.tunread?.length) > 0; + const isUnread = room.unread > 0 || threadUnread; + const showBadge = + !room.hideUnreadStatus || (!room.hideMentionStatus && (Boolean(room.userMentions) || Number(room.tunreadUser?.length) > 0)); + const badgeTitle = getBadgeTitle(room.userMentions, Number(room.tunread?.length), room.groupMentions, room.unread, t); + const variant = + ((room.userMentions || room.tunreadUser?.length) && 'danger') || + (threadUnread && 'primary') || + (room.groupMentions && 'warning') || + 'secondary'; + + const badges = useMemo( + () => ( + <> + {showBadge && isUnread && ( + + {room.unread + (room.tunread?.length || 0)} + + )} + + ), + [badgeTitle, isUnread, room.tunread?.length, room.unread, showBadge, variant], + ); + + const itemData = useMemo( + () => ({ + unread: highlighted, + selected: room._id === openedRoom, + t, + href: roomCoordinator.getRouteLink(room.t, room) || '', + title: roomCoordinator.getRoomName(room.t, room) || '', + icon, + time, + badges, + avatar: AvatarTemplate && , + subtitle: message, + }), + [AvatarTemplate, badges, highlighted, icon, message, openedRoom, room, t, time], + ); + + return itemData; +}; diff --git a/apps/meteor/client/views/room/Sidepanel/index.ts b/apps/meteor/client/views/room/Sidepanel/index.ts new file mode 100644 index 000000000000..c236142b8b0f --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/index.ts @@ -0,0 +1 @@ +export { default } from './RoomSidepanel'; diff --git a/apps/meteor/client/views/room/layout/RoomLayout.tsx b/apps/meteor/client/views/room/layout/RoomLayout.tsx index b5e29c05799f..3c0c4c313c99 100644 --- a/apps/meteor/client/views/room/layout/RoomLayout.tsx +++ b/apps/meteor/client/views/room/layout/RoomLayout.tsx @@ -59,7 +59,7 @@ const RoomLayout = ({ header, body, footer, aside, ...props }: RoomLayoutProps): [layout, contextualbarPosition, contextualbarSize], )} > - + @@ -82,7 +82,7 @@ const RoomLayout = ({ header, body, footer, aside, ...props }: RoomLayoutProps): {footer && {footer}} {aside && ( - + {aside} )} diff --git a/packages/ui-client/src/hooks/useFeaturePreviewList.ts b/packages/ui-client/src/hooks/useFeaturePreviewList.ts index 172045197f8c..ff103a8d84ef 100644 --- a/packages/ui-client/src/hooks/useFeaturePreviewList.ts +++ b/packages/ui-client/src/hooks/useFeaturePreviewList.ts @@ -72,7 +72,7 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [ description: 'Sidepanel_navigation_description', group: 'Navigation', value: false, - enabled: false, + enabled: true, enableQuery: { name: 'newNavigation', value: true, From 52563a1859ba096d8167413c7ed3ea49ecfe2291 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 6 Sep 2024 18:54:52 +0100 Subject: [PATCH 03/14] feat: `CreateTeamModal` and `EditRoomInfo` add sidepanel fields to the UI --- .../sidebarv2/header/CreateTeamModal.tsx | 54 ++++++++++++++++ .../Info/EditRoomInfo/EditRoomInfo.tsx | 61 ++++++++++++++++++- .../EditRoomInfo/useEditRoomInitialValues.ts | 18 +++++- 3 files changed, 129 insertions(+), 4 deletions(-) diff --git a/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx b/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx index a7e7b506de0f..3db0c7e89df7 100644 --- a/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx +++ b/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx @@ -1,3 +1,4 @@ +import type { SidepanelItem } from '@rocket.chat/core-typings'; import { Box, Button, @@ -40,6 +41,8 @@ type CreateTeamModalInputs = { encrypted: boolean; broadcast: boolean; members?: string[]; + showDiscussions?: boolean; + showChannels?: boolean; }; type CreateTeamModalProps = { onClose: () => void }; @@ -50,6 +53,7 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => { const e2eEnabledForPrivateByDefault = useSetting('E2E_Enabled_Default_PrivateRooms'); const namesValidation = useSetting('UTF8_Channel_Names_Validation'); const allowSpecialNames = useSetting('UI_Allow_room_names_with_special_chars'); + const dispatchToastMessage = useToastMessageDispatch(); const canCreateTeam = usePermission('create-team'); const canSetReadOnly = usePermissionWithScopedRoles('set-readonly', ['owner']); @@ -94,6 +98,8 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => { encrypted: (e2eEnabledForPrivateByDefault as boolean) ?? false, broadcast: false, members: [], + showChannels: false, + showDiscussions: false, }, }); @@ -123,7 +129,10 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => { topic, broadcast, encrypted, + showChannels, + showDiscussions, }: CreateTeamModalInputs): Promise => { + const sidepanelItem = [showChannels && 'channels', showDiscussions && 'discussions'].filter(Boolean) as [SidepanelItem, SidepanelItem?]; const params = { name, members, @@ -136,6 +145,7 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => { encrypted, }, }, + ...((showChannels || showDiscussions) && { sidepanel: { items: sidepanelItem } }), }; try { @@ -157,6 +167,8 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => { const encryptedId = useUniqueId(); const broadcastId = useUniqueId(); const addMembersId = useUniqueId(); + const showChannelsId = useUniqueId(); + const showDiscussionsId = useUniqueId(); return ( { + + {t('Navigation')} + + + + {t('Channels')} + ( + + )} + /> + + {t('Show_channels_description')} + + + + + {t('Discussions')} + ( + + )} + /> + + {t('Show_discussions_description')} + {t('Security_and_permissions')} diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx index b2aac49927a1..f5e9f866bea8 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx @@ -1,5 +1,5 @@ /* eslint-disable complexity */ -import type { IRoomWithRetentionPolicy } from '@rocket.chat/core-typings'; +import type { IRoomWithRetentionPolicy, SidepanelItem } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; import type { SelectOption } from '@rocket.chat/fuselage'; import { @@ -21,6 +21,7 @@ import { Box, TextAreaInput, AccordionItem, + Divider, } from '@rocket.chat/fuselage'; import { useEffectEvent, useUniqueId } from '@rocket.chat/fuselage-hooks'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; @@ -76,7 +77,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => const dispatchToastMessage = useToastMessageDispatch(); const isFederated = useMemo(() => isRoomFederated(room), [room]); // eslint-disable-next-line no-nested-ternary - const roomType = 'prid' in room ? 'discussion' : room.teamId ? 'team' : 'channel'; + const roomType = 'prid' in room ? 'discussion' : room.teamMain ? 'team' : 'channel'; const retentionPolicy = useRetentionPolicy(room); const retentionMaxAgeDefault = msToTimeUnit(TIMEUNIT.days, Number(useSetting(getRetentionSetting(room.t)))) ?? 30; @@ -118,6 +119,8 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => retentionOverrideGlobal, roomType: roomTypeP, reactWhenReadOnly, + showChannels, + showDiscussions, } = watch(); const { @@ -158,13 +161,23 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => retentionIgnoreThreads, ...formData }) => { - const data = getDirtyFields(formData, dirtyFields); + const data = getDirtyFields>(formData, dirtyFields); delete data.archived; + delete data.showChannels; + delete data.showDiscussions; + + const sidepanelItems = [showChannels && 'channels', showDiscussions && 'discussions'].filter(Boolean) as [ + SidepanelItem, + SidepanelItem?, + ]; + + const sidepanel = sidepanelItems.length > 0 ? { items: sidepanelItems } : null; try { await saveAction({ rid: room._id, ...data, + ...(roomType === 'team' ? { sidepanel } : null), ...((data.joinCode || 'joinCodeRequired' in data) && { joinCode: joinCodeRequired ? data.joinCode : '' }), ...((data.systemMessages || !hideSysMes) && { systemMessages: hideSysMes && data.systemMessages, @@ -224,6 +237,8 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => const retentionExcludePinnedField = useUniqueId(); const retentionFilesOnlyField = useUniqueId(); const retentionIgnoreThreads = useUniqueId(); + const showDiscussionsField = useUniqueId(); + const showChannelsField = useUniqueId(); const showAdvancedSettings = canViewEncrypted || canViewReadOnly || readOnly || canViewArchived || canViewJoinCode || canViewHideSysMes; const showRetentionPolicy = canEditRoomRetentionPolicy && retentionPolicy?.enabled; @@ -355,6 +370,46 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => {showAdvancedSettings && ( + {roomType === 'team' && ( + <> + + + {t('Navigation')} + + + + {t('Channels')} + ( + + )} + /> + + + {t('Show_channels_description')} + + + + + {t('Discussions')} + ( + + )} + /> + + + {t('Show_discussions_description')} + + + + + + )} {t('Security_and_permissions')} diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts index 1a002727358f..b71f8e1b5bc5 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts @@ -10,7 +10,20 @@ export const useEditRoomInitialValues = (room: IRoomWithRetentionPolicy) => { const retentionPolicy = useRetentionPolicy(room); const canEditRoomRetentionPolicy = usePermission('edit-room-retention-policy', room._id); - const { t, ro, archived, topic, description, announcement, joinCodeRequired, sysMes, encrypted, retention, reactWhenReadOnly } = room; + const { + t, + ro, + archived, + topic, + description, + announcement, + joinCodeRequired, + sysMes, + encrypted, + retention, + reactWhenReadOnly, + sidepanel, + } = room; return useMemo( () => ({ @@ -37,6 +50,8 @@ export const useEditRoomInitialValues = (room: IRoomWithRetentionPolicy) => { retentionFilesOnly: retention?.filesOnly ?? retentionPolicy.filesOnly, retentionIgnoreThreads: retention?.ignoreThreads ?? retentionPolicy.ignoreThreads, }), + showDiscussions: sidepanel?.items.includes('discussions'), + showChannels: sidepanel?.items.includes('channels'), }), [ announcement, @@ -53,6 +68,7 @@ export const useEditRoomInitialValues = (room: IRoomWithRetentionPolicy) => { encrypted, reactWhenReadOnly, canEditRoomRetentionPolicy, + sidepanel, ], ); }; From 3c047433cbd31e12fd5a97dfa2c628566797aaf9 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 6 Sep 2024 19:47:08 +0100 Subject: [PATCH 04/14] changeset --- .changeset/witty-lemons-type.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/witty-lemons-type.md diff --git a/.changeset/witty-lemons-type.md b/.changeset/witty-lemons-type.md new file mode 100644 index 000000000000..a007cbe6260e --- /dev/null +++ b/.changeset/witty-lemons-type.md @@ -0,0 +1,10 @@ +--- +'@rocket.chat/core-services': minor +'@rocket.chat/model-typings': minor +'@rocket.chat/core-typings': minor +'@rocket.chat/rest-typings': minor +'@rocket.chat/ui-client': minor +'@rocket.chat/meteor': minor +--- + +Implemented new feature preview for Sidepanel From 749708a7598d6431a44107310e5ed5b88c716b2f Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 6 Sep 2024 20:24:53 +0100 Subject: [PATCH 05/14] add sidepanel to `getParentRoom` projection --- apps/meteor/server/services/team/service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index f5218c88402a..4be96bff9866 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -1055,8 +1055,10 @@ export class TeamService extends ServiceClassInternal implements ITeamService { return rooms; } - private getParentRoom(team: AtLeast): Promise | null> { - return Rooms.findOneById>(team.roomId, { projection: { name: 1, fname: 1, t: 1 } }); + private getParentRoom(team: AtLeast): Promise | null> { + return Rooms.findOneById>(team.roomId, { + projection: { name: 1, fname: 1, t: 1, sidepanel: 1 }, + }); } async getRoomInfo( From d32c751bcbed6ba95b2d0b759b5c112bcec73f6f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 17 Sep 2024 13:25:37 -0300 Subject: [PATCH 06/14] regression: SIDE-170 --- .../sidebarv2/header/CreateTeamModal.tsx | 88 ++++++++++--------- .../Info/EditRoomInfo/EditRoomInfo.tsx | 80 +++++++++-------- 2 files changed, 90 insertions(+), 78 deletions(-) diff --git a/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx b/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx index 3db0c7e89df7..b0023b6680ec 100644 --- a/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx +++ b/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx @@ -17,6 +17,7 @@ import { AccordionItem, } from '@rocket.chat/fuselage'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; import { useEndpoint, usePermission, @@ -248,49 +249,56 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => { - - - {t('Navigation')} - - - - {t('Channels')} - ( - + {null} + + + + {t('Navigation')} + + + + {t('Channels')} + ( + + )} /> - )} - /> - - {t('Show_channels_description')} - + + {t('Show_channels_description')} + - - - {t('Discussions')} - ( - + + {t('Discussions')} + ( + + )} /> - )} - /> - - {t('Show_discussions_description')} - + + {t('Show_discussions_description')} + + + + + {t('Security_and_permissions')} diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx index f5e9f866bea8..5583be83d0ba 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx @@ -24,6 +24,7 @@ import { Divider, } from '@rocket.chat/fuselage'; import { useEffectEvent, useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useSetting, useTranslation, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts'; import type { ChangeEvent } from 'react'; @@ -371,44 +372,47 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => {showAdvancedSettings && ( {roomType === 'team' && ( - <> - - - {t('Navigation')} - - - - {t('Channels')} - ( - - )} - /> - - - {t('Show_channels_description')} - - - - - {t('Discussions')} - ( - - )} - /> - - - {t('Show_discussions_description')} - - - - - + + {null} + + + + {t('Navigation')} + + + + {t('Channels')} + ( + + )} + /> + + + {t('Show_channels_description')} + + + + + {t('Discussions')} + ( + + )} + /> + + + {t('Show_discussions_description')} + + + + + + )} From 4411431fc7871063595423644053b640a21db2be Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 17 Sep 2024 13:30:04 -0300 Subject: [PATCH 07/14] regression: SIDE-160 --- apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx b/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx index b0023b6680ec..9de721d8bbcd 100644 --- a/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx +++ b/apps/meteor/client/sidebarv2/header/CreateTeamModal.tsx @@ -99,8 +99,8 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => { encrypted: (e2eEnabledForPrivateByDefault as boolean) ?? false, broadcast: false, members: [], - showChannels: false, - showDiscussions: false, + showChannels: true, + showDiscussions: true, }, }); From 49308db5b983d288efa2e4c9d70e9364dc186d96 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 17 Sep 2024 14:21:50 -0300 Subject: [PATCH 08/14] regression: SIDE-162 --- apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx | 6 ++++-- .../room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx b/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx index 950c8cbde5e7..e9f243de736a 100644 --- a/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx +++ b/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx @@ -31,14 +31,16 @@ const RoomSidepanelWithData = ({ parentRid, openedRoom }: { parentRid: string; o const listRoomsAndDiscussions = useEndpoint('GET', '/v1/teams.listChildren'); const result = useQuery({ - queryKey: ['sidepanel', parentRid], + queryKey: ['sidepanel', 'list', parentRid, sidepanelItems], queryFn: () => listRoomsAndDiscussions({ roomId: parentRid, sort: JSON.stringify({ lm: -1 }), type: sidepanelItems?.length === 1 ? sidepanelItems[0] : undefined, }), - enabled: !!sidepanelItems, + enabled: roomInfo.isFetched && !!sidepanelItems, + staleTime: Infinity, + keepPreviousData: true, }); if (roomInfo.isSuccess && !roomInfo.data.room?.sidepanel && !roomInfo.data.parent?.sidepanel) { diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx index 5583be83d0ba..1233768a671c 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx @@ -27,6 +27,7 @@ import { useEffectEvent, useUniqueId } from '@rocket.chat/fuselage-hooks'; import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useSetting, useTranslation, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQueryClient } from '@tanstack/react-query'; import type { ChangeEvent } from 'react'; import React, { useMemo } from 'react'; import { useForm, Controller } from 'react-hook-form'; @@ -74,6 +75,7 @@ const getRetentionSetting = (roomType: IRoomWithRetentionPolicy['t']): string => }; const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => { + const query = useQueryClient(); const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); const isFederated = useMemo(() => isRoomFederated(room), [room]); @@ -194,6 +196,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => }), }); + await query.invalidateQueries(['/v1/rooms.info', room._id]); dispatchToastMessage({ type: 'success', message: t('Room_updated_successfully') }); onClickClose(); } catch (error) { From 13a1de544b5e7fb204c3e6557c1a104d9930efc1 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 17 Sep 2024 15:39:29 -0300 Subject: [PATCH 09/14] regression: SIDE-169 --- .../client/sidebarv2/header/SearchSection.tsx | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/sidebarv2/header/SearchSection.tsx b/apps/meteor/client/sidebarv2/header/SearchSection.tsx index 660b8ee19cd5..71b26b2e4053 100644 --- a/apps/meteor/client/sidebarv2/header/SearchSection.tsx +++ b/apps/meteor/client/sidebarv2/header/SearchSection.tsx @@ -22,6 +22,32 @@ const wrapperStyle = css` background-color: ${Palette.surface['surface-sidebar']}; `; +const mobileCheck = function () { + let check = false; + (function (a: string) { + if ( + /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( + a, + ) || + /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( + a.substr(0, 4), + ) + ) + check = true; + })(navigator.userAgent || navigator.vendor || window.opera || ''); + return check; +}; + +const shortcut = ((): string => { + if (navigator.userAgentData?.mobile || mobileCheck()) { + return ''; + } + if (window.navigator.platform.toLowerCase().includes('mac')) { + return '(\u2318+K)'; + } + return '(Ctrl+K)'; +})(); + const SearchSection = () => { const t = useTranslation(); const user = useUser(); @@ -68,11 +94,13 @@ const SearchSection = () => { }; }, [handleEscSearch, setFocus]); + const placeholder = [t('Search'), shortcut].filter(Boolean).join(' '); + return ( Date: Tue, 17 Sep 2024 15:28:11 -0300 Subject: [PATCH 10/14] regression: SIDE-171 --- packages/i18n/src/locales/en.i18n.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 796e9d0519ff..728ca256952d 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -6541,8 +6541,8 @@ "Incoming_Calls": "Incoming calls", "Advanced_settings": "Advanced settings", "Security_and_permissions": "Security and permissions", - "Sidepanel_navigation": "Sidepanel navigation for teams", - "Sidepanel_navigation_description": "Option to open a sidepanel with channels and/or discussions associated with the team. This allows team owners to customize communication methods to best meet their team’s needs. This feature is only available when Enhanced navigation experience is enabled.", + "Sidepanel_navigation": "Secondary navigation for teams", + "Sidepanel_navigation_description": "Display channels and/or discussions associated with teams by default. This allows team owners to customize communication methods to best meet their team’s needs. This is currently in feature preview and will be a premium capability once fully released.", "Show_channels_description": "Show team channels in second sidebar", "Show_discussions_description": "Show team discussions in second sidebar" } From e9a06e6451d5cc6ab6d20698e0009ec68af9e50f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 18 Sep 2024 12:44:34 -0300 Subject: [PATCH 11/14] feat: disable feature preview component by flag --- .../src/components/FeaturePreview/FeaturePreview.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/ui-client/src/components/FeaturePreview/FeaturePreview.tsx b/packages/ui-client/src/components/FeaturePreview/FeaturePreview.tsx index 09ec0700cb79..c297cc839abc 100644 --- a/packages/ui-client/src/components/FeaturePreview/FeaturePreview.tsx +++ b/packages/ui-client/src/components/FeaturePreview/FeaturePreview.tsx @@ -5,8 +5,16 @@ import { Children, Suspense, cloneElement } from 'react'; import { useFeaturePreview } from '../../hooks/useFeaturePreview'; import { FeaturesAvailable } from '../../hooks/useFeaturePreviewList'; -export const FeaturePreview = ({ feature, children }: { feature: FeaturesAvailable; children: ReactElement[] }) => { - const featureToggleEnabled = useFeaturePreview(feature); +export const FeaturePreview = ({ + feature, + disabled = false, + children, +}: { + disabled?: boolean; + feature: FeaturesAvailable; + children: ReactElement[]; +}) => { + const featureToggleEnabled = useFeaturePreview(feature) && !disabled; const toggledChildren = Children.map(children, (child) => cloneElement(child, { From 54a6b8a05c90c93a07fb0f94c74e7ce0a6090565 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 18 Sep 2024 12:45:40 -0300 Subject: [PATCH 12/14] regression: SIDE-174 --- .../FeaturePreviewSidePanelNavigation.tsx | 10 ++++++++++ apps/meteor/client/hooks/useSidePanelNavigation.ts | 14 ++++++++++++++ apps/meteor/client/views/room/RoomOpener.tsx | 7 ++++--- .../client/views/room/providers/RoomProvider.tsx | 4 ++-- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 apps/meteor/client/components/FeaturePreviewSidePanelNavigation.tsx create mode 100644 apps/meteor/client/hooks/useSidePanelNavigation.ts diff --git a/apps/meteor/client/components/FeaturePreviewSidePanelNavigation.tsx b/apps/meteor/client/components/FeaturePreviewSidePanelNavigation.tsx new file mode 100644 index 000000000000..f5d658ccb2f2 --- /dev/null +++ b/apps/meteor/client/components/FeaturePreviewSidePanelNavigation.tsx @@ -0,0 +1,10 @@ +import { FeaturePreview } from '@rocket.chat/ui-client'; +import type { ReactElement } from 'react'; +import React from 'react'; + +import { useSidePanelNavigationScreenSize } from '../hooks/useSidePanelNavigation'; + +export const FeaturePreviewSidePanelNavigation = ({ children }: { children: ReactElement[] }) => { + const disabled = !useSidePanelNavigationScreenSize(); + return ; +}; diff --git a/apps/meteor/client/hooks/useSidePanelNavigation.ts b/apps/meteor/client/hooks/useSidePanelNavigation.ts new file mode 100644 index 000000000000..f9714580cf06 --- /dev/null +++ b/apps/meteor/client/hooks/useSidePanelNavigation.ts @@ -0,0 +1,14 @@ +import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; +import { useFeaturePreview } from '@rocket.chat/ui-client'; + +export const useSidePanelNavigation = () => { + const isSidepanelFeatureEnabled = useFeaturePreview('sidepanelNavigation'); + // ["xs", "sm", "md", "lg", "xl", xxl"] + return useSidePanelNavigationScreenSize() && isSidepanelFeatureEnabled; +}; + +export const useSidePanelNavigationScreenSize = () => { + const breakpoints = useBreakpoints(); + // ["xs", "sm", "md", "lg", "xl", xxl"] + return breakpoints.includes('lg'); +}; diff --git a/apps/meteor/client/views/room/RoomOpener.tsx b/apps/meteor/client/views/room/RoomOpener.tsx index f51c4ca04754..f87339a67a68 100644 --- a/apps/meteor/client/views/room/RoomOpener.tsx +++ b/apps/meteor/client/views/room/RoomOpener.tsx @@ -1,10 +1,11 @@ import type { RoomType } from '@rocket.chat/core-typings'; import { Box, States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; -import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; +import { FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; import type { ReactElement } from 'react'; import React, { lazy, Suspense } from 'react'; import { useTranslation } from 'react-i18next'; +import { FeaturePreviewSidePanelNavigation } from '../../components/FeaturePreviewSidePanelNavigation'; import { Header } from '../../components/Header'; import { getErrorMessage } from '../../lib/errorHandling'; import { NotAuthorizedError } from '../../lib/errors/NotAuthorizedError'; @@ -34,12 +35,12 @@ const RoomOpener = ({ type, reference }: RoomOpenerProps): ReactElement => { return ( {!isDirectOrOmnichannelRoom(type) && ( - + {null} - + )} }> diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index 5fa257174199..d67c8566e3c6 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -1,5 +1,4 @@ import type { IRoom } from '@rocket.chat/core-typings'; -import { useFeaturePreview } from '@rocket.chat/ui-client'; import { useRouter } from '@rocket.chat/ui-contexts'; import type { ReactNode, ContextType, ReactElement } from 'react'; import React, { useMemo, memo, useEffect, useCallback } from 'react'; @@ -10,6 +9,7 @@ import { UserAction } from '../../../../app/ui/client/lib/UserAction'; import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { useReactiveValue } from '../../../hooks/useReactiveValue'; import { useRoomInfoEndpoint } from '../../../hooks/useRoomInfoEndpoint'; +import { useSidePanelNavigation } from '../../../hooks/useSidePanelNavigation'; import { RoomManager } from '../../../lib/RoomManager'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import ImageGalleryProvider from '../../../providers/ImageGalleryProvider'; @@ -92,7 +92,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }; }, [hasMoreNextMessages, hasMorePreviousMessages, isLoadingMoreMessages, pseudoRoom, rid, subscriptionQuery.data]); - const isSidepanelFeatureEnabled = useFeaturePreview('sidepanelNavigation'); + const isSidepanelFeatureEnabled = useSidePanelNavigation(); useEffect(() => { if (isSidepanelFeatureEnabled) { From fd0c2cc476672cf4e987446888cc2741e8b7d900 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 18 Sep 2024 18:26:29 -0300 Subject: [PATCH 13/14] temp: fix reactivity item --- .../room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx index 459cd5287a3e..67ef4c3c7d5d 100644 --- a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx +++ b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx @@ -17,13 +17,13 @@ const RoomSidepanelItem = ({ room, openedRoom, viewMode }: RoomSidepanelItemProp const SidepanelItem = useTemplateByViewMode(); const subscription = useUserSubscription(room._id); - const itemData = useItemData({ ...subscription, ...room } as any, { viewMode, openedRoom }); // as any because of divergent and overlaping timestamp types in subs and room (type Date vs type string) + const itemData = useItemData({ ...room, ...subscription } as any, { viewMode, openedRoom }); // as any because of divergent and overlaping timestamp types in subs and room (type Date vs type string) if (!subscription) { - return ; + return ; } - return ; + return ; }; export default memo(RoomSidepanelItem); From e3f2772ad9f0bfbf52886bddc293a0a0c75ab275 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 18 Sep 2024 23:29:16 -0300 Subject: [PATCH 14/14] regression: SIDE-163 --- .../views/room/Sidepanel/RoomSidepanel.tsx | 24 ++-- .../SidepanelItem/RoomSidepanelItem.tsx | 4 +- .../room/Sidepanel/hooks/useItemData.tsx | 2 +- .../Sidepanel/hooks/useTeamslistChildren.ts | 106 ++++++++++++++++++ 4 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 apps/meteor/client/views/room/Sidepanel/hooks/useTeamslistChildren.ts diff --git a/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx b/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx index e9f243de736a..27c45e2774e8 100644 --- a/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx +++ b/apps/meteor/client/views/room/Sidepanel/RoomSidepanel.tsx @@ -1,7 +1,6 @@ /* eslint-disable react/no-multi-comp */ import { Box, Sidepanel, SidepanelListItem } from '@rocket.chat/fuselage'; -import { useEndpoint, useUserPreference } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useUserPreference } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; import { Virtuoso } from 'react-virtuoso'; @@ -11,6 +10,7 @@ import { useOpenedRoom, useSecondLevelOpenedRoom } from '../../../lib/RoomManage import RoomSidepanelListWrapper from './RoomSidepanelListWrapper'; import RoomSidepanelLoading from './RoomSidepanelLoading'; import RoomSidepanelItem from './SidepanelItem'; +import { useTeamsListChildrenUpdate } from './hooks/useTeamslistChildren'; const RoomSidepanel = () => { const parentRid = useOpenedRoom(); @@ -29,20 +29,12 @@ const RoomSidepanelWithData = ({ parentRid, openedRoom }: { parentRid: string; o const roomInfo = useRoomInfoEndpoint(parentRid); const sidepanelItems = roomInfo.data?.room?.sidepanel?.items || roomInfo.data?.parent?.sidepanel?.items; - const listRoomsAndDiscussions = useEndpoint('GET', '/v1/teams.listChildren'); - const result = useQuery({ - queryKey: ['sidepanel', 'list', parentRid, sidepanelItems], - queryFn: () => - listRoomsAndDiscussions({ - roomId: parentRid, - sort: JSON.stringify({ lm: -1 }), - type: sidepanelItems?.length === 1 ? sidepanelItems[0] : undefined, - }), - enabled: roomInfo.isFetched && !!sidepanelItems, - staleTime: Infinity, - keepPreviousData: true, - }); - + const result = useTeamsListChildrenUpdate( + parentRid, + !roomInfo.data ? null : roomInfo.data.room?.teamId, + // eslint-disable-next-line no-nested-ternary + !sidepanelItems ? null : sidepanelItems?.length === 1 ? sidepanelItems[0] : undefined, + ); if (roomInfo.isSuccess && !roomInfo.data.room?.sidepanel && !roomInfo.data.parent?.sidepanel) { return null; } diff --git a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx index 67ef4c3c7d5d..dceb69e1aba3 100644 --- a/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx +++ b/apps/meteor/client/views/room/Sidepanel/SidepanelItem/RoomSidepanelItem.tsx @@ -1,4 +1,4 @@ -import type { IRoom, Serialized } from '@rocket.chat/core-typings'; +import type { IRoom, ISubscription, Serialized } from '@rocket.chat/core-typings'; import { useUserSubscription } from '@rocket.chat/ui-contexts'; import React, { memo } from 'react'; @@ -17,7 +17,7 @@ const RoomSidepanelItem = ({ room, openedRoom, viewMode }: RoomSidepanelItemProp const SidepanelItem = useTemplateByViewMode(); const subscription = useUserSubscription(room._id); - const itemData = useItemData({ ...room, ...subscription } as any, { viewMode, openedRoom }); // as any because of divergent and overlaping timestamp types in subs and room (type Date vs type string) + const itemData = useItemData({ ...room, ...subscription } as ISubscription & IRoom, { viewMode, openedRoom }); // as any because of divergent and overlaping timestamp types in subs and room (type Date vs type string) if (!subscription) { return ; diff --git a/apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx b/apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx index f5a89b9df25d..a6de01e22084 100644 --- a/apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx +++ b/apps/meteor/client/views/room/Sidepanel/hooks/useItemData.tsx @@ -51,7 +51,7 @@ export const useItemData = ( const itemData = useMemo( () => ({ unread: highlighted, - selected: room._id === openedRoom, + selected: room.rid === openedRoom, t, href: roomCoordinator.getRouteLink(room.t, room) || '', title: roomCoordinator.getRoomName(room.t, room) || '', diff --git a/apps/meteor/client/views/room/Sidepanel/hooks/useTeamslistChildren.ts b/apps/meteor/client/views/room/Sidepanel/hooks/useTeamslistChildren.ts new file mode 100644 index 000000000000..5791a6e5d547 --- /dev/null +++ b/apps/meteor/client/views/room/Sidepanel/hooks/useTeamslistChildren.ts @@ -0,0 +1,106 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useEffect, useMemo } from 'react'; + +import { ChatRoom } from '../../../../../app/models/client'; + +const sortRoomByLastMessage = (a: IRoom, b: IRoom) => { + if (!a.lm) { + return 1; + } + if (!b.lm) { + return -1; + } + return new Date(b.lm).toUTCString().localeCompare(new Date(a.lm).toUTCString()); +}; + +export const useTeamsListChildrenUpdate = ( + parentRid: string, + teamId?: string | null, + sidepanelItems?: 'channels' | 'discussions' | null, +) => { + const queryClient = useQueryClient(); + + const query = useMemo(() => { + const query: Parameters[0] = { + $or: [ + { + _id: parentRid, + }, + { + prid: parentRid, + }, + ], + }; + + if (teamId && query.$or) { + query.$or.push({ + teamId, + }); + } + return query; + }, [parentRid, teamId]); + + const teamList = useEndpoint('GET', '/v1/teams.listChildren'); + + const listRoomsAndDiscussions = useEndpoint('GET', '/v1/teams.listChildren'); + const result = useQuery({ + queryKey: ['sidepanel', 'list', parentRid, sidepanelItems], + queryFn: () => + listRoomsAndDiscussions({ + roomId: parentRid, + sort: JSON.stringify({ lm: -1 }), + type: sidepanelItems || undefined, + }), + enabled: sidepanelItems !== null && teamId !== null, + refetchInterval: 5 * 60 * 1000, + keepPreviousData: true, + }); + + const { mutate: update } = useMutation({ + mutationFn: async (params?: { action: 'add' | 'remove' | 'update'; data: IRoom }) => { + queryClient.setQueryData(['sidepanel', 'list', parentRid, sidepanelItems], (data: Awaited> | void) => { + if (!data) { + return; + } + + if (params?.action === 'add') { + data.data = [JSON.parse(JSON.stringify(params.data)), ...data.data].sort(sortRoomByLastMessage); + } + + if (params?.action === 'remove') { + data.data = data.data.filter((item) => item._id !== params.data?._id); + } + + if (params?.action === 'update') { + data.data = data.data + .map((item) => (item._id === params.data?._id ? JSON.parse(JSON.stringify(params.data)) : item)) + .sort(sortRoomByLastMessage); + } + + return { ...data }; + }); + }, + }); + + useEffect(() => { + const liveQueryHandle = ChatRoom.find(query).observe({ + added: (item) => { + queueMicrotask(() => update({ action: 'add', data: item })); + }, + changed: (item) => { + queueMicrotask(() => update({ action: 'update', data: item })); + }, + removed: (item) => { + queueMicrotask(() => update({ action: 'remove', data: item })); + }, + }); + + return () => { + liveQueryHandle.stop(); + }; + }, [update, query]); + + return result; +};