diff --git a/app/e2e/client/tabbar.ts b/app/e2e/client/tabbar.ts index e3d23015dd3ea..5b4397d852f23 100644 --- a/app/e2e/client/tabbar.ts +++ b/app/e2e/client/tabbar.ts @@ -10,7 +10,7 @@ import { e2e } from './rocketchat.e2e'; addAction('e2e', ({ room }) => { const e2eEnabled = useSetting('E2E_Enable'); const e2eReady = e2e.isReady() || room.encrypted; - const e2ePermission = room.t === 'd' || usePermission('toggle-room-e2e-encryption', room._id); + const e2ePermission = usePermission('toggle-room-e2e-encryption', room._id) || room.t === 'd'; const hasPermission = usePermission('edit-room', room._id) && e2ePermission && e2eReady; const toggleE2E = useMethod('saveRoomSettings'); diff --git a/app/ui-message/client/message.js b/app/ui-message/client/message.js index 3b71f96fd9614..d5f4ed0a2ac40 100644 --- a/app/ui-message/client/message.js +++ b/app/ui-message/client/message.js @@ -551,6 +551,11 @@ const processSequentials = ({ index, currentNode, settings, forceDate, showDateS const previousNode = (index === undefined || index > 0) && getPreviousSentMessage(currentNode); const nextNode = currentNode.nextElementSibling; + if (!previousNode) { + setTimeout(() => { + currentNode.dispatchEvent(new CustomEvent('MessageGroup', { bubbles: true })); + }, 100); + } if (isSequential(currentNode, previousNode, forceDate, settings.Message_GroupingPeriod, showDateSeparator, shouldCollapseReplies)) { currentNode.classList.add('sequential'); } else { diff --git a/app/ui-utils/client/lib/RoomManager.js b/app/ui-utils/client/lib/RoomManager.js index b5506be1b4c0f..ed01f1766fd5c 100644 --- a/app/ui-utils/client/lib/RoomManager.js +++ b/app/ui-utils/client/lib/RoomManager.js @@ -4,7 +4,6 @@ import { Session } from 'meteor/session'; import { Tracker } from 'meteor/tracker'; import { Blaze } from 'meteor/blaze'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import { Template } from 'meteor/templating'; import _ from 'underscore'; import { fireGlobalEvent } from './fireGlobalEvent'; @@ -19,7 +18,7 @@ import { CachedCollectionManager } from '../../../ui-cached-collection'; import { getConfig } from '../config'; import { ROOM_DATA_STREAM } from '../../../utils/stream/constants'; import { call } from '..'; - +import { RoomManager as NewRoomManager } from '../../../../client/lib/RoomManager'; const maxRoomsOpen = parseInt(getConfig('maxRoomsOpen')) || 5; @@ -143,23 +142,6 @@ export const RoomManager = new function() { return Object.keys(openedRooms).map((typeName) => openedRooms[typeName]).find((openedRoom) => openedRoom.rid === rid); } - getDomOfRoom(typeName, rid, templateName) { - const room = openedRooms[typeName]; - if (room == null) { - return; - } - - if ((room.dom == null) && (rid != null)) { - room.dom = document.createElement('div'); - room.dom.classList.add('room-container'); - const contentAsFunc = (content) => () => content; - - room.template = Blaze._TemplateWith({ _id: rid }, contentAsFunc(Template[templateName || 'room'])); - Blaze.render(room.template, room.dom); // , nextNode, parentView - } - - return room.dom; - } close(typeName) { if (openedRooms[typeName]) { @@ -185,6 +167,7 @@ export const RoomManager = new function() { delete openedRooms[typeName]; if (rid != null) { + NewRoomManager.close(rid); return RoomHistoryManager.clear(rid); } } @@ -265,34 +248,29 @@ export const RoomManager = new function() { return onlineUsers.set(onlineUsersValue); } - updateMentionsMarksOfRoom(typeName) { - const dom = this.getDomOfRoom(typeName); - if (!dom) { - return; - } - - const [ticksBar] = dom.getElementsByClassName('ticks-bar'); - const [messagesBox] = dom.getElementsByClassName('messages-box'); - const scrollTop = $('> .wrapper', messagesBox).scrollTop() - 50; - const totalHeight = $(' > .wrapper > ul', messagesBox).height() + 40; - if (!ticksBar) { - return; - } - - // TODO: thread quotes should NOT have mention links at all - const mentionsSelector = '.message .body .mention-link--me, .message .body .mention-link--group'; - ticksBar.innerHTML = Array.from(messagesBox?.querySelectorAll(mentionsSelector) || []) - .map((mentionLink) => { - const topOffset = $(mentionLink).offset().top + scrollTop; - const percent = (100 / totalHeight) * topOffset; - const className = [ - 'tick', - mentionLink.classList.contains('mention-link--me') && 'tick--me', - mentionLink.classList.contains('mention-link--group') && 'tick--group', - ].filter(Boolean).join(' '); - return `
`; - }) - .join(''); + updateMentionsMarksOfRoom(/* typeName */) { + // const [ticksBar] = dom.getElementsByClassName('ticks-bar'); + // const [messagesBox] = dom.getElementsByClassName('messages-box'); + // const scrollTop = $('> .wrapper', messagesBox).scrollTop() - 50; + // const totalHeight = $(' > .wrapper > ul', messagesBox).height() + 40; + // if (!ticksBar) { + // return; + // } + + // // TODO: thread quotes should NOT have mention links at all + // const mentionsSelector = '.message .body .mention-link--me, .message .body .mention-link--group'; + // ticksBar.innerHTML = Array.from(messagesBox?.querySelectorAll(mentionsSelector) || []) + // .map((mentionLink) => { + // const topOffset = $(mentionLink).offset().top + scrollTop; + // const percent = (100 / totalHeight) * topOffset; + // const className = [ + // 'tick', + // mentionLink.classList.contains('mention-link--me') && 'tick--me', + // mentionLink.classList.contains('mention-link--group') && 'tick--group', + // ].filter(Boolean).join(' '); + // return `
`; + // }) + // .join(''); } }; Cls.initClass(); diff --git a/app/ui-utils/client/lib/messageContext.js b/app/ui-utils/client/lib/messageContext.js index 62342c8d93b66..168b415e3ce91 100644 --- a/app/ui-utils/client/lib/messageContext.js +++ b/app/ui-utils/client/lib/messageContext.js @@ -31,6 +31,8 @@ export function messageContext({ rid } = Template.instance()) { }, { jump: tmid && tmid !== mid && mid && mid, }); + e.preventDefault(); + e.stopPropagation(); }; const runAction = Layout.isEmbedded() ? (msg, e) => { @@ -51,6 +53,7 @@ export function messageContext({ rid } = Template.instance()) { const openDiscussion = (e) => { e.preventDefault(); + e.stopPropagation(); const { drid } = e.currentTarget.dataset; goToRoomById(drid); }; diff --git a/app/ui-utils/client/lib/openRoom.js b/app/ui-utils/client/lib/openRoom.js index 29b80c19fdbb9..eec44400bc5c1 100644 --- a/app/ui-utils/client/lib/openRoom.js +++ b/app/ui-utils/client/lib/openRoom.js @@ -3,35 +3,22 @@ import { Tracker } from 'meteor/tracker'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Session } from 'meteor/session'; import _ from 'underscore'; -import { Random } from 'meteor/random'; import { appLayout } from '../../../../client/lib/appLayout'; -import { Messages, ChatSubscription, Rooms } from '../../../models'; +import { Messages, ChatSubscription } from '../../../models'; import { settings } from '../../../settings'; import { callbacks } from '../../../callbacks'; import { roomTypes } from '../../../utils'; import { call, callMethod } from './callMethod'; import { RoomManager, fireGlobalEvent, RoomHistoryManager } from '..'; -import { waitUntilWrapperExists } from './RoomHistoryManager'; -import { createTemplateForComponent } from '../../../../client/lib/portals/createTemplateForComponent'; +import { RoomManager as NewRoomManager } from '../../../../client/lib/RoomManager'; +import { Rooms } from '../../../models/client'; window.currentTracker = undefined; // cleanup session when hot reloading Session.set('openedRoom', null); -const replaceCenterDomBy = (dom) => { - const roomNode = dom(); - - const center = createTemplateForComponent(Random.id(), () => import('../../../../client/views/root/DomNode'), { - attachment: 'at-parent', - props: () => ({ node: roomNode }), - }); - - appLayout.render('main', { center }); - - return roomNode; -}; const waitUntilRoomBeInserted = async (type, rid) => new Promise((resolve) => { Tracker.autorun((c) => { @@ -43,6 +30,12 @@ const waitUntilRoomBeInserted = async (type, rid) => new Promise((resolve) => { }); }); + +NewRoomManager.on('changed', (rid) => { + Session.set('openedRoom', rid); + RoomManager.openedRoom = rid; +}); + export const openRoom = async function(type, name) { window.currentTracker && window.currentTracker.stop(); window.currentTracker = Tracker.autorun(async function(c) { @@ -61,20 +54,15 @@ export const openRoom = async function(type, name) { return FlowRouter.go('direct', { rid: room._id }, FlowRouter.current().queryParams); } + if (room._id === Session.get('openedRoom') && !FlowRouter.getQueryParam('msg')) { return; } - if (RoomManager.open(type + name).ready() !== true) { - if (settings.get('Accounts_AllowAnonymousRead')) { - appLayout.render('main'); - } + RoomManager.open(type + name); - appLayout.render('main', { center: 'loading' }); - return; - } + appLayout.render('main', { center: 'room' }); - appLayout.render('main', { center: 'loading' }); c.stop(); @@ -82,13 +70,7 @@ export const openRoom = async function(type, name) { window.currentTracker = undefined; } - const roomDom = replaceCenterDomBy(() => RoomManager.getDomOfRoom(type + name, room._id, roomTypes.getConfig(type).mainTemplate)); - - const selector = await waitUntilWrapperExists('.messages-box .wrapper'); - selector.scrollTop = roomDom.oldScrollTop; - - Session.set('openedRoom', room._id); - RoomManager.openedRoom = room._id; + NewRoomManager.open(room._id); fireGlobalEvent('room-opened', _.omit(room, 'usernames')); diff --git a/app/ui/client/views/app/room.js b/app/ui/client/views/app/room.js index 87614468e85b7..a326e2391af0b 100644 --- a/app/ui/client/views/app/room.js +++ b/app/ui/client/views/app/room.js @@ -29,6 +29,7 @@ import { ChatMessages } from '../../lib/chatMessages'; import { fileUpload } from '../../lib/fileUpload'; import './room.html'; import { getCommonRoomEvents } from './lib/getCommonRoomEvents'; +import { RoomManager as NewRoomManager } from '../../../../../client/lib/RoomManager'; export const chatMessages = {}; @@ -787,10 +788,30 @@ Meteor.startup(() => { if (!chatMessages[rid]) { chatMessages[rid] = new ChatMessages(); } + + const wrapper = this.find('.wrapper'); + + const store = NewRoomManager.getStore(rid); + + const afterMessageGroup = () => { + if (store.scroll) { + wrapper.scrollTop = store.scroll; + } else { + this.sendToBottom(); + } + wrapper.removeEventListener('MessageGroup', afterMessageGroup); + + wrapper.addEventListener('scroll', _.throttle(() => { + store.update({ scroll: wrapper.scrollTop }); + }, 100)); + }; + + wrapper.addEventListener('MessageGroup', afterMessageGroup); + chatMessages[rid].initializeWrapper(this.find('.wrapper')); chatMessages[rid].initializeInput(this.find('.js-input-message'), { rid }); - const wrapper = this.find('.wrapper'); + const wrapperUl = this.find('.wrapper > ul'); const newMessage = this.find('.new-message'); diff --git a/app/utils/lib/RoomTypesCommon.js b/app/utils/lib/RoomTypesCommon.js index 887ae2fae62aa..c34500a590401 100644 --- a/app/utils/lib/RoomTypesCommon.js +++ b/app/utils/lib/RoomTypesCommon.js @@ -1,5 +1,5 @@ import { Meteor } from 'meteor/meteor'; -import { Session } from 'meteor/session'; +// import { Session } from 'meteor/session'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { RoomTypeConfig } from './RoomTypeConfig'; @@ -47,7 +47,7 @@ export class RoomTypesCommon { const routeConfig = { name: roomConfig.route.name, action: roomConfig.route.action, - triggersExit: [() => Session.set('openedRoom', '')], + // triggersExit: [() => Session.set('openedRoom', '')], }; if (Meteor.isClient) { diff --git a/app/utils/lib/roomExit.js b/app/utils/lib/roomExit.js index 35caa34a4e3c5..a7c3632b2c9c7 100644 --- a/app/utils/lib/roomExit.js +++ b/app/utils/lib/roomExit.js @@ -1,5 +1,5 @@ import { Blaze } from 'meteor/blaze'; -import { Session } from 'meteor/session'; +// import { Session } from 'meteor/session'; import { Tracker } from 'meteor/tracker'; import { FlowRouter } from 'meteor/kadira:flow-router'; @@ -25,8 +25,8 @@ export const roomExit = function() { } callbacks.run('roomExit'); - Session.set('lastOpenedRoom', Session.get('openedRoom')); - Session.set('openedRoom', null); + // Session.set('lastOpenedRoom', Session.get('openedRoom')); + // Session.set('openedRoom', null); RoomManager.openedRoom = null; const mainNode = document.querySelector('.main-content'); diff --git a/client/components/Message/Attachments/components/Image.tsx b/client/components/Message/Attachments/components/Image.tsx index 96af49b9f7a20..9c46a9e680399 100644 --- a/client/components/Message/Attachments/components/Image.tsx +++ b/client/components/Message/Attachments/components/Image.tsx @@ -15,17 +15,20 @@ type ImageProps = { ({ loadImage: true } | { loadImage: false; setLoadImage: () => void }); const getDimensions = ( - width: Dimensions['width'], - height: Dimensions['height'], + originalWidth: Dimensions['width'], + originalHeight: Dimensions['height'], limits: { width: number; height: number }, -): { width: 'auto' | number; height: 'auto' | number } => { - const ratio = height / width; +): { width: number; height: number } => { + const widthRatio = originalWidth / (limits.width - 4); + const heightRatio = originalHeight / limits.height; - if (height >= width || Math.min(width, limits.width) * ratio > limits.height) { - return { width: (width * Math.min(height, limits.height)) / height, height: 'auto' }; + if (widthRatio > heightRatio) { + const width = Math.min(originalWidth, limits.width - 4); + return { width, height: (width / originalWidth) * originalHeight }; } - return { width: Math.min(width, limits.width), height: 'auto' }; + const height = Math.min(originalHeight, limits.height); + return { width: (height / originalHeight) * originalWidth, height }; }; const Image: FC = ({ previewUrl, loadImage = true, setLoadImage, src, ...size }) => { @@ -46,22 +49,23 @@ const Image: FC = ({ previewUrl, loadImage = true, setLoadImage, src const background = previewUrl && `url(${previewUrl}) center center / cover no-repeat fixed`; if (!loadImage) { - return ; + return ; } if (error) { - return ; + return ; } return ( + is='picture' + > + + ); }; diff --git a/client/lib/RoomManager.ts b/client/lib/RoomManager.ts new file mode 100644 index 0000000000000..72a59826b5e4b --- /dev/null +++ b/client/lib/RoomManager.ts @@ -0,0 +1,173 @@ +import { Emitter } from '@rocket.chat/emitter'; +import { useEffect, useMemo } from 'react'; +import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; + +import { getConfig } from '../../app/ui-utils/client/config'; +import { IRoom } from '../../definition/IRoom'; +import { useUserId, useUserRoom, useUserSubscription } from '../contexts/UserContext'; +import { useAsyncState } from '../hooks/useAsyncState'; +import { AsyncState } from './asyncState'; + +const debug = !!(getConfig('debug') || getConfig('debug-RoomStore')); + +export class RoomStore extends Emitter<{ + changed: undefined; +}> { + lastTime?: Date; + + scroll?: number; + + constructor(readonly rid: string) { + super(); + + debug && this.on('changed', () => console.log(`RoomStore ${this.rid} changed`, this)); + } + + update({ scroll, lastTime }: { scroll?: number; lastTime?: Date }): void { + if (scroll !== undefined) { + this.scroll = scroll; + } + if (lastTime !== undefined) { + this.lastTime = lastTime; + } + if (scroll || lastTime) { + this.emit('changed'); + } + } +} + +const debugRoomManager = !!(getConfig('debug') || getConfig('debug-RoomManager')); +export const RoomManager = new (class RoomManager extends Emitter<{ + changed: IRoom['_id'] | undefined; + opened: IRoom['_id']; + closed: IRoom['_id']; + back: IRoom['_id']; + removed: IRoom['_id']; +}> { + private rid: IRoom['_id'] | undefined; + + private lastRid: IRoom['_id'] | undefined; + + private rooms: Map = new Map(); + + constructor() { + super(); + debugRoomManager && + this.on('opened', (rid) => { + console.log('room opened ->', rid); + }); + + debugRoomManager && + this.on('back', (rid) => { + console.log('room moved to back ->', rid); + }); + + debugRoomManager && + this.on('closed', (rid) => { + console.log('room close ->', rid); + }); + } + + get lastOpened(): IRoom['_id'] | undefined { + return this.lastRid; + } + + get opened(): IRoom['_id'] | undefined { + return this.rid; + } + + visitedRooms(): IRoom['_id'][] { + return [...this.rooms.keys()]; + } + + back(rid: IRoom['_id']): void { + if (rid === this.rid) { + this.lastRid = rid; + this.rid = undefined; + this.emit('back', rid); + } + } + + close(rid: IRoom['_id']): void { + if (!this.rooms.has(rid)) { + this.rooms.delete(rid); + this.emit('closed', rid); + } + this.emit('changed', this.rid); + } + + open(rid: 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.emit('opened', rid); + this.emit('changed', this.rid); + } + + getStore(rid: IRoom['_id']): RoomStore | undefined { + return this.rooms.get(rid); + } +})(); + +const subscribeVistedRooms: Subscription = { + getCurrentValue: () => RoomManager.visitedRooms(), + subscribe(callback) { + return RoomManager.on('changed', callback); + }, +}; + +const subscribeOpenedRoom: Subscription = { + getCurrentValue: () => RoomManager.opened, + subscribe(callback) { + return RoomManager.on('opened', callback); + }, +}; + +const fields = {}; + +export const useHandleRoom = (rid: IRoom['_id']): AsyncState => { + const { resolve, update, ...state } = useAsyncState(); + const uid = useUserId(); + const subscription = (useUserSubscription(rid, fields) as unknown) as IRoom; + const _room = (useUserRoom(rid, fields) as unknown) as IRoom; + + const room = uid ? subscription || _room : _room; + + useEffect(() => { + if (room) { + update(); + resolve(room); + } + }, [resolve, update, room]); + + return state; +}; + +export const useVisitedRooms = (): IRoom['_id'][] => useSubscription(subscribeVistedRooms); + +export const useOpenedRoom = (): IRoom['_id'] | undefined => useSubscription(subscribeOpenedRoom); + +export const useRoomStore = (rid: IRoom['_id']): RoomStore => { + const subscribeStore: Subscription = useMemo( + () => ({ + getCurrentValue: (): RoomStore | undefined => RoomManager.getStore(rid), + subscribe(callback): Unsubscribe { + return RoomManager.on('changed', callback); + }, + }), + [rid], + ); + + const store = useSubscription(subscribeStore); + + if (!store) { + throw new Error('Something wrong'); + } + return store; +}; diff --git a/client/sidebar/search/Row.js b/client/sidebar/search/Row.js index 94c89abf8e1d6..c1134cd5e45e3 100644 --- a/client/sidebar/search/Row.js +++ b/client/sidebar/search/Row.js @@ -4,7 +4,7 @@ import SideBarItemTemplateWithData from '../RoomList/SideBarItemTemplateWithData import UserItem from './UserItem'; const Row = ({ item, data }) => { - const { t, SideBarItemTemplate, AvatarTemplate, useRealName, extended } = data; + const { t, SideBarItemTemplate, avatarTemplate: AvatarTemplate, useRealName, extended } = data; if (item.t === 'd' && !item.u) { return ( diff --git a/client/templates.ts b/client/templates.ts index 6057830e9abf3..52f2752000cbd 100644 --- a/client/templates.ts +++ b/client/templates.ts @@ -6,11 +6,22 @@ createTemplateForComponent('MessageActions', () => import('./components/Message/ createTemplateForComponent('reactAttachments', () => import('./components/Message/Attachments')); -createTemplateForComponent('ThreadMetric', () => import('./components/Message/Metrics/Thread')); +createTemplateForComponent('ThreadMetric', () => import('./components/Message/Metrics/Thread'), { + renderContainerView: () => + HTML.DIV({ + style: 'min-height: 36px;', + }), +}); createTemplateForComponent( 'DiscussionMetric', () => import('./components/Message/Metrics/Discussion'), + { + renderContainerView: () => + HTML.DIV({ + style: 'min-height: 36px;', + }), + }, ); createTemplateForComponent( diff --git a/client/views/room/Room.stories.js b/client/views/room/Room.stories.js index 83b51165f0181..2e2dc933a4851 100644 --- a/client/views/room/Room.stories.js +++ b/client/views/room/Room.stories.js @@ -1,6 +1,6 @@ import React from 'react'; -import { RoomTemplate } from './components/RoomTemplate'; +import { RoomTemplate } from './components/RoomTemplate/RoomTemplate'; export default { title: 'views/Room', diff --git a/client/views/room/Room/Room.js b/client/views/room/Room/Room.js index fda74cccaae6b..082f93b7fc6f3 100644 --- a/client/views/room/Room/Room.js +++ b/client/views/room/Room/Room.js @@ -5,7 +5,7 @@ import { useTranslation } from '../../../contexts/TranslationContext'; import { useUserPreference } from '../../../contexts/UserContext'; import Header from '../Header'; import BlazeTemplate from '../components/BlazeTemplate'; -import { RoomTemplate } from '../components/RoomTemplate'; +import { RoomTemplate } from '../components/RoomTemplate/RoomTemplate'; import VerticalBarOldActions from '../components/VerticalBarOldActions'; import { useRoom } from '../providers/RoomProvider'; import { diff --git a/client/views/room/Room/RoomWithData.js b/client/views/room/Room/RoomWithData.js index 7361055ccb8bd..11280e17b6899 100644 --- a/client/views/room/Room/RoomWithData.js +++ b/client/views/room/Room/RoomWithData.js @@ -1,12 +1,16 @@ import React from 'react'; +import { useOpenedRoom } from '../../../lib/RoomManager'; import RoomProvider from '../providers/RoomProvider'; import Room from './Room'; -const RoomWithData = ({ _id }) => ( - - - -); +const RoomWithData = () => { + const rid = useOpenedRoom(); + return rid ? ( + + + + ) : null; +}; export default RoomWithData; diff --git a/client/views/room/Room/Skeleton.tsx b/client/views/room/Room/Skeleton.tsx new file mode 100644 index 0000000000000..b6582de50cd42 --- /dev/null +++ b/client/views/room/Room/Skeleton.tsx @@ -0,0 +1,55 @@ +import { Skeleton, Box, InputBox } from '@rocket.chat/fuselage'; +import React, { FC, memo } from 'react'; + +import Header from '../../../components/Header'; +import VerticalBarSkeleton from '../../../components/VerticalBar/VerticalBarSkeleton'; +import { RoomTemplate } from '../components/RoomTemplate/RoomTemplate'; + +const RoomSkeleton: FC = () => ( + + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+); +export default memo(RoomSkeleton); diff --git a/client/views/room/components/BlazeTemplate.js b/client/views/room/components/BlazeTemplate.js index e80b6335f9f42..40b4c480e7cfc 100644 --- a/client/views/room/components/BlazeTemplate.js +++ b/client/views/room/components/BlazeTemplate.js @@ -21,7 +21,7 @@ const BlazeTemplate = ({ name, flexShrink, overflow, onClick, children, ...props view && Blaze.remove(view); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [name]); + }, [name, JSON.stringify(props)]); return ( { - const c = flattenChildren(children); - const header = c.filter((child) => child.type === RoomTemplate.Header); - const body = c.filter((child) => child.type === RoomTemplate.Body); - const footer = c.filter((child) => child.type === RoomTemplate.Footer); - const aside = c.filter((child) => child.type === RoomTemplate.Aside); - - return ( - - {header.length > 0 && header} - - - - {body} - - {footer.length > 0 && {footer}} - - {aside.length > 0 && {aside}} - - - ); -}; - -RoomTemplate.Header = function Header({ children }) { - return children; -}; -RoomTemplate.Body = function Body({ children }) { - return children; -}; -RoomTemplate.Footer = function Footer({ children }) { - return children; -}; -RoomTemplate.Aside = function Aside({ children }) { - return children; -}; - -export default memo(RoomTemplate); diff --git a/client/views/room/components/RoomTemplate/RoomTemplate.tsx b/client/views/room/components/RoomTemplate/RoomTemplate.tsx new file mode 100644 index 0000000000000..682a78c55d67a --- /dev/null +++ b/client/views/room/components/RoomTemplate/RoomTemplate.tsx @@ -0,0 +1,44 @@ +import { Box } from '@rocket.chat/fuselage'; +import React, { FC } from 'react'; +import flattenChildren from 'react-keyed-flatten-children'; + +import VerticalBar from '../../../../components/VerticalBar/VerticalBar'; +import { Aside } from './slots/Aside'; +import { Body } from './slots/Body'; +import { Footer } from './slots/Footer'; +import { Header } from './slots/Header'; + +export const RoomTemplate: FC & { + Header: FC; + Body: FC; + Footer: FC; + Aside: FC; +} = ({ children, ...props }) => { + const c = flattenChildren(children); + const header = c.filter((child) => (child as any).type === RoomTemplate.Header); + const body = c.filter((child) => (child as any).type === RoomTemplate.Body); + const footer = c.filter((child) => (child as any).type === RoomTemplate.Footer); + const aside = c.filter((child) => (child as any).type === RoomTemplate.Aside); + + return ( + + {header.length > 0 && header} + + + + {body} + + {footer.length > 0 && {footer}} + + {aside.length > 0 && {aside}} + + + ); +}; + +RoomTemplate.Header = Header; +RoomTemplate.Body = Body; +RoomTemplate.Footer = Footer; +RoomTemplate.Aside = Aside; + +export default RoomTemplate; diff --git a/client/views/room/components/RoomTemplate/slots/Aside.tsx b/client/views/room/components/RoomTemplate/slots/Aside.tsx new file mode 100644 index 0000000000000..386fe26382b9f --- /dev/null +++ b/client/views/room/components/RoomTemplate/slots/Aside.tsx @@ -0,0 +1,5 @@ +import React, { FC } from 'react'; + +export const Aside: FC = function Aside({ children }) { + return <>{children}; +}; diff --git a/client/views/room/components/RoomTemplate/slots/Body.tsx b/client/views/room/components/RoomTemplate/slots/Body.tsx new file mode 100644 index 0000000000000..7c08bb05d3e50 --- /dev/null +++ b/client/views/room/components/RoomTemplate/slots/Body.tsx @@ -0,0 +1,5 @@ +import React, { FC } from 'react'; + +export const Body: FC = function Body({ children }) { + return <>{children}; +}; diff --git a/client/views/room/components/RoomTemplate/slots/Footer.tsx b/client/views/room/components/RoomTemplate/slots/Footer.tsx new file mode 100644 index 0000000000000..a5ee444acafa3 --- /dev/null +++ b/client/views/room/components/RoomTemplate/slots/Footer.tsx @@ -0,0 +1,5 @@ +import React, { FC } from 'react'; + +export const Footer: FC = function Footer({ children }) { + return <>{children}; +}; diff --git a/client/views/room/components/RoomTemplate/slots/Header.tsx b/client/views/room/components/RoomTemplate/slots/Header.tsx new file mode 100644 index 0000000000000..f2082df2ac82f --- /dev/null +++ b/client/views/room/components/RoomTemplate/slots/Header.tsx @@ -0,0 +1,5 @@ +import React, { FC } from 'react'; + +export const Header: FC = function Header({ children }) { + return <>{children}; +}; diff --git a/client/views/room/providers/RoomProvider.tsx b/client/views/room/providers/RoomProvider.tsx index 957ad7d73287c..d6b7268812936 100644 --- a/client/views/room/providers/RoomProvider.tsx +++ b/client/views/room/providers/RoomProvider.tsx @@ -1,25 +1,20 @@ -import React, { ReactNode, useContext, useMemo } from 'react'; +import React, { ReactNode, useContext, useMemo, memo } from 'react'; import { roomTypes } from '../../../../app/utils/client'; import { IRoom } from '../../../../definition/IRoom'; -import { useUserId, useUserSubscription } from '../../../contexts/UserContext'; +import { useHandleRoom } from '../../../lib/RoomManager'; +import { AsyncStatePhase } from '../../../lib/asyncState'; +import Skeleton from '../Room/Skeleton'; import { RoomContext } from '../contexts/RoomContext'; -import { useUserRoom } from '../hooks/useUserRoom'; import ToolboxProvider from './ToolboxProvider'; -const fields = {}; - export type Props = { children: ReactNode; rid: IRoom['_id']; }; const RoomProvider = ({ rid, children }: Props): JSX.Element => { - const uid = useUserId(); - const subscription = (useUserSubscription(rid, fields) as unknown) as IRoom; - const _room = (useUserRoom(rid, fields) as unknown) as IRoom; - - const room = uid ? subscription || _room : _room; + const { phase, value: room } = useHandleRoom(rid); const context = useMemo(() => { if (!room) { return null; @@ -31,8 +26,8 @@ const RoomProvider = ({ rid, children }: Props): JSX.Element => { }; }, [room, rid]); - if (!room) { - return <>; + if (phase === AsyncStatePhase.LOADING || !room) { + return ; } return ( @@ -43,4 +38,4 @@ const RoomProvider = ({ rid, children }: Props): JSX.Element => { }; export const useRoom = (): undefined | IRoom => useContext(RoomContext)?.room; -export default RoomProvider; +export default memo(RoomProvider); diff --git a/client/views/room/providers/ToolboxProvider.tsx b/client/views/room/providers/ToolboxProvider.tsx index 924873d71b8d1..61dbfd61ac775 100644 --- a/client/views/room/providers/ToolboxProvider.tsx +++ b/client/views/room/providers/ToolboxProvider.tsx @@ -94,16 +94,12 @@ const ToolboxProvider = ({ children, room }: { children: ReactNode; room: IRoom ); useLayoutEffect(() => { - if (!(currentRoom === room._id)) { - return; - } - if (!tab) { setActiveTabBar([undefined, undefined]); } setActiveTabBar([list.get(tab as string) as ToolboxActionConfig, context]); - }, [tab, list, currentRoom, room._id, context]); + }, [tab, list, currentRoom, context]); const contextValue = useMemo( () => ({ @@ -129,7 +125,13 @@ const ToolboxProvider = ({ children, room }: { children: ReactNode; room: IRoom (action as ToolboxActionConfig).anonymous), ) .map(([id, item]) => ( - + ))} {children} diff --git a/client/views/teams/contextualBar/channels/TeamsChannels.js b/client/views/teams/contextualBar/channels/TeamsChannels.js index 54ee393054212..6394d941f6901 100644 --- a/client/views/teams/contextualBar/channels/TeamsChannels.js +++ b/client/views/teams/contextualBar/channels/TeamsChannels.js @@ -9,7 +9,7 @@ import { roomTypes } from '../../../../../app/utils/client'; import { useSetModal } from '../../../../contexts/ModalContext'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../lib/asyncState'; -import CreateChannel from '../../../../sidebar/header/CreateChannel'; +import CreateChannelWithData from '../../../../sidebar/header/CreateChannelWithData'; import RoomInfo from '../../../room/contextualBar/Info'; import { useTabBarClose } from '../../../room/providers/ToolboxProvider'; import AddExistingModal from './AddExistingModal'; @@ -50,7 +50,7 @@ const TeamsChannels = ({ teamId }) => { }, []); const addExisting = useReactModal(AddExistingModal, { teamId, reload }); - const createNew = useReactModal(CreateChannel, { teamId, reload }); + const createNew = useReactModal(CreateChannelWithData, { teamId, reload }); const goToRoom = useCallback((room) => roomTypes.openRouteLink(room.t, room), []); const handleBack = useCallback(() => setState({}), [setState]);