diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index 92a475694e..a0a40c0239 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -18,6 +18,10 @@ limitations under the License. margin-left: 8px; margin-bottom: 4px; + &.mx_RoomSublist_hidden { + display: none; + } + .mx_RoomSublist_headerContainer { // Create a flexbox to make alignment easy display: flex; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 963e94ebbb..8ac706fc15 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -289,12 +289,11 @@ export default class RoomList extends React.PureComponent { // shallow-copy from the template as we need to make modifications to it this.tagAesthetics = objectShallowClone(TAG_AESTHETICS); this.updateDmAddRoomAction(); - - this.dispatcherRef = defaultDispatcher.register(this.onAction); - this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); } public componentDidMount(): void { + this.dispatcherRef = defaultDispatcher.register(this.onAction); + this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); SpaceStore.instance.on(SUGGESTED_ROOMS, this.updateSuggestedRooms); RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists); this.customTagStoreRef = CustomRoomTagStore.addListener(this.updateLists); @@ -502,59 +501,50 @@ export default class RoomList extends React.PureComponent { } private renderSublists(): React.ReactElement[] { - const components: React.ReactElement[] = []; - - const tagOrder = TAG_ORDER.reduce((p, c) => { - if (c === CUSTOM_TAGS_BEFORE_TAG) { - const customTags = Object.keys(this.state.sublists) - .filter(t => isCustomTag(t)); - p.push(...customTags); - } - p.push(c); - return p; - }, [] as TagID[]); - // show a skeleton UI if the user is in no rooms and they are not filtering const showSkeleton = !this.state.isNameFiltering && Object.values(RoomListStore.instance.unfilteredLists).every(list => !list?.length); - for (const orderedTagId of tagOrder) { - const orderedRooms = this.state.sublists[orderedTagId] || []; - - let extraTiles = null; - if (orderedTagId === DefaultTagID.Invite) { - extraTiles = this.renderCommunityInvites(); - } else if (orderedTagId === DefaultTagID.Suggested) { - extraTiles = this.renderSuggestedRooms(); + return TAG_ORDER.reduce((tags, tagId) => { + if (tagId === CUSTOM_TAGS_BEFORE_TAG) { + const customTags = Object.keys(this.state.sublists) + .filter(tagId => isCustomTag(tagId)); + tags.push(...customTags); } + tags.push(tagId); + return tags; + }, [] as TagID[]) + .map(orderedTagId => { + let extraTiles = null; + if (orderedTagId === DefaultTagID.Invite) { + extraTiles = this.renderCommunityInvites(); + } else if (orderedTagId === DefaultTagID.Suggested) { + extraTiles = this.renderSuggestedRooms(); + } - const totalTiles = orderedRooms.length + (extraTiles ? extraTiles.length : 0); - if (totalTiles === 0 && !ALWAYS_VISIBLE_TAGS.includes(orderedTagId)) { - continue; // skip tag - not needed - } + const aesthetics: ITagAesthetics = isCustomTag(orderedTagId) + ? customTagAesthetics(orderedTagId) + : this.tagAesthetics[orderedTagId]; + if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); - const aesthetics: ITagAesthetics = isCustomTag(orderedTagId) - ? customTagAesthetics(orderedTagId) - : this.tagAesthetics[orderedTagId]; - if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); - - components.push(); - } - - return components; + // The cost of mounting/unmounting this component offsets the cost + // of keeping it in the DOM and hiding it when it is not required + return + }); } public render() { diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index 74052e8ba1..a726ab99fc 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -74,6 +74,7 @@ interface IProps { tagId: TagID; onResize: () => void; showSkeleton?: boolean; + alwaysVisible?: boolean; extraTiles?: ReactComponentElement[]; @@ -125,8 +126,6 @@ export default class RoomSublist extends React.Component { }; // Why Object.assign() and not this.state.height? Because TypeScript says no. this.state = Object.assign(this.state, {height: this.calculateInitialHeight()}); - this.dispatcherRef = defaultDispatcher.register(this.onAction); - RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onListsUpdated); } private calculateInitialHeight() { @@ -242,6 +241,11 @@ export default class RoomSublist extends React.Component { return false; } + public componentDidMount() { + this.dispatcherRef = defaultDispatcher.register(this.onAction); + RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onListsUpdated); + } + public componentWillUnmount() { defaultDispatcher.unregister(this.dispatcherRef); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated); @@ -759,6 +763,7 @@ export default class RoomSublist extends React.Component { 'mx_RoomSublist': true, 'mx_RoomSublist_hasMenuOpen': !!this.state.contextMenuPosition, 'mx_RoomSublist_minimized': this.props.isMinimized, + 'mx_RoomSublist_hidden': !this.state.rooms.length && this.props.alwaysVisible !== true, }); let content = null; diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index a32fc46a80..b2a07d7e06 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -97,22 +97,8 @@ export default class RoomTile extends React.PureComponent { // generatePreview() will return nothing if the user has previews disabled messagePreview: this.generatePreview(), }; - - ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate); - this.dispatcherRef = defaultDispatcher.register(this.onAction); - MessagePreviewStore.instance.on( - MessagePreviewStore.getPreviewChangedEventName(this.props.room), - this.onRoomPreviewChanged, - ); this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); - this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); this.roomProps = EchoChamber.forRoom(this.props.room); - this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate); - CommunityPrototypeStore.instance.on( - CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId), - this.onCommunityUpdate, - ); - this.props.room.on("Room.name", this.onRoomNameUpdate); } private onRoomNameUpdate = (room) => { @@ -167,6 +153,20 @@ export default class RoomTile extends React.PureComponent { if (this.state.selected) { this.scrollIntoView(); } + + ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate); + this.dispatcherRef = defaultDispatcher.register(this.onAction); + MessagePreviewStore.instance.on( + MessagePreviewStore.getPreviewChangedEventName(this.props.room), + this.onRoomPreviewChanged, + ); + this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); + this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate); + this.roomProps.on("Room.name", this.onRoomNameUpdate); + CommunityPrototypeStore.instance.on( + CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId), + this.onCommunityUpdate, + ); } public componentWillUnmount() { @@ -182,8 +182,15 @@ export default class RoomTile extends React.PureComponent { ); this.props.room.off("Room.name", this.onRoomNameUpdate); } + ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate); defaultDispatcher.unregister(this.dispatcherRef); this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); + this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate); + this.roomProps.off("Room.name", this.onRoomNameUpdate); + CommunityPrototypeStore.instance.off( + CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId), + this.onCommunityUpdate, + ); } private onAction = (payload: ActionPayload) => { @@ -547,7 +554,7 @@ export default class RoomTile extends React.PureComponent { />; let badge: React.ReactNode; - if (!this.props.isMinimized) { + if (!this.props.isMinimized && this.notificationState) { // aria-hidden because we summarise the unread count/highlight status in a manual aria-label below badge = (