diff --git a/res/css/_components.scss b/res/css/_components.scss index afc40ca0d6..8288cf34f6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -49,6 +49,7 @@ @import "./views/auth/_ServerTypeSelector.scss"; @import "./views/auth/_Welcome.scss"; @import "./views/avatars/_BaseAvatar.scss"; +@import "./views/avatars/_DecoratedRoomAvatar.scss"; @import "./views/avatars/_MemberStatusMessageAvatar.scss"; @import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_RoomTileContextMenu.scss"; diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 40babaa9ca..bdaada0d15 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -70,7 +70,8 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations .mx_LeftPanel2_breadcrumbsContainer { width: 100%; - overflow: hidden; + overflow-y: hidden; + overflow-x: scroll; margin-top: 8px; } } diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss new file mode 100644 index 0000000000..984fa0ce9a --- /dev/null +++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss @@ -0,0 +1,33 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_DecoratedRoomAvatar { + position: relative; + + .mx_RoomTileIcon { + position: absolute; + bottom: 0; + right: 0; + } + + .mx_NotificationBadge { + position: absolute; + top: 0; + right: 0; + height: 18px; + width: 18px; + } +} diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 6241b3d0ba..b11eb47d1c 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -29,15 +29,8 @@ limitations under the License. border-radius: 32px; } - .mx_RoomTile2_avatarContainer { + .mx_DecoratedRoomAvatar { margin-right: 8px; - position: relative; - - .mx_RoomTileIcon { - position: absolute; - bottom: 0; - right: 0; - } } .mx_RoomTile2_nameContainer { @@ -155,16 +148,9 @@ limitations under the License. align-items: center; position: relative; - .mx_RoomTile2_avatarContainer { + .mx_DecoratedRoomAvatar { margin-right: 0; } - - .mx_RoomTile2_badgeContainer { - position: absolute; - top: 0; - right: 0; - height: 18px; - } } } diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index ab117d55ed..83d5b9e138 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -30,6 +30,7 @@ import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import ResizeNotifier from "../../utils/ResizeNotifier"; import SettingsStore from "../../settings/SettingsStore"; +import RoomListStore, { RoomListStore2, LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2"; // TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 @@ -69,6 +70,7 @@ export default class LeftPanel2 extends React.Component { }; BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); + RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); this.tagPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => { this.setState({showTagPanel: SettingsStore.getValue("TagPanel.enableTagPanel")}); }); @@ -81,6 +83,7 @@ export default class LeftPanel2 extends React.Component { public componentWillUnmount() { SettingsStore.unwatchSetting(this.tagPanelWatcherRef); BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); + RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); } @@ -151,7 +154,7 @@ export default class LeftPanel2 extends React.Component { let breadcrumbs; if (this.state.showBreadcrumbs) { breadcrumbs = ( -
+
{this.props.isMinimized ? null : }
); diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx new file mode 100644 index 0000000000..1156c80313 --- /dev/null +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -0,0 +1,63 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { Room } from "matrix-js-sdk/src/models/room"; + +import { TagID } from '../../../stores/room-list/models'; +import RoomAvatar from "./RoomAvatar"; +import RoomTileIcon from "../rooms/RoomTileIcon"; +import NotificationBadge, { INotificationState, TagSpecificNotificationState } from '../rooms/NotificationBadge'; + +interface IProps { + room: Room; + avatarSize: number; + tag: TagID; + displayBadge?: boolean; + forceCount?: boolean; +} + +interface IState { + notificationState?: INotificationState; +} + +export default class DecoratedRoomAvatar extends React.PureComponent { + + constructor(props: IProps) { + super(props); + + this.state = { + notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag), + }; + } + + public render(): React.ReactNode { + let badge: React.ReactNode; + if (this.props.displayBadge) { + badge = ; + } + + return
+ + + {badge} +
; + } +} \ No newline at end of file diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index bd12ced6ee..c0417fc592 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -17,13 +17,15 @@ limitations under the License. import React from "react"; import { BreadcrumbsStore } from "../../../stores/BreadcrumbsStore"; import AccessibleButton from "../elements/AccessibleButton"; -import RoomAvatar from "../avatars/RoomAvatar"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import { _t } from "../../../languageHandler"; import { Room } from "matrix-js-sdk/src/models/room"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import Analytics from "../../../Analytics"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { CSSTransition } from "react-transition-group"; +import RoomListStore from "../../../stores/room-list/RoomListStore2"; +import { DefaultTagID } from "../../../stores/room-list/models"; // TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 @@ -93,6 +95,8 @@ export default class RoomBreadcrumbs2 extends React.PureComponent { + const roomTags = RoomListStore.instance.getTagsForRoom(r); + const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0]; return ( this.viewRoom(r, i)} aria-label={_t("Room %(name)s", {name: r.name})} > - + ); }); diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 7a07027913..499edaa0d7 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -22,7 +22,6 @@ import { Room } from "matrix-js-sdk/src/models/room"; import classNames from "classnames"; import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; -import RoomAvatar from "../../views/avatars/RoomAvatar"; import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import ActiveRoomObserver from "../../../ActiveRoomObserver"; @@ -35,6 +34,7 @@ import { _t } from "../../../languageHandler"; import { ContextMenu, ContextMenuButton, MenuItemRadio } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import RoomTileIcon from "./RoomTileIcon"; import { getRoomNotifsState, ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -361,13 +361,21 @@ export default class RoomTile2 extends React.Component { 'mx_RoomTile2_minimized': this.props.isMinimized, }); - const badge = ( - ; + + let badge: React.ReactNode; + if (!this.props.isMinimized) { + badge = - ); + />; + } // TODO: the original RoomTile uses state for the room name. Do we need to? let name = this.props.room.name; @@ -405,7 +413,6 @@ export default class RoomTile2 extends React.Component { ); if (this.props.isMinimized) nameContainer = null; - const avatarSize = 32; return ( @@ -421,10 +428,7 @@ export default class RoomTile2 extends React.Component { role="treeitem" onContextMenu={this.onContextMenu} > -
- - -
+ {roomAvatar} {nameContainer}
{badge} diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 1c47075cbb..c78f15c3b4 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -51,7 +51,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } public get visible(): boolean { - return this.state.enabled; + return this.state.enabled && this.matrixClient.getVisibleRooms().length >= 20; } protected async onAction(payload: ActionPayload) { diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 6de555f234..e5205f6051 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -17,7 +17,7 @@ limitations under the License. import { MatrixClient } from "matrix-js-sdk/src/client"; import SettingsStore from "../../settings/SettingsStore"; -import { OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models"; +import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models"; import TagOrderStore from "../TagOrderStore"; import { AsyncStore } from "../AsyncStore"; import { Room } from "matrix-js-sdk/src/models/room"; @@ -186,7 +186,8 @@ export class RoomListStore2 extends AsyncStore { const room = this.matrixClient.getRoom(roomId); const tryUpdate = async (updatedRoom: Room) => { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 - console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()} in ${updatedRoom.roomId}`); + console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` + + ` in ${updatedRoom.roomId}`); if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); @@ -427,6 +428,19 @@ export class RoomListStore2 extends AsyncStore { } } } + + /** + * Gets the tags for a room identified by the store. The returned set + * should never be empty, and will contain DefaultTagID.Untagged if + * the store is not aware of any tags. + * @param room The room to get the tags for. + * @returns The tags for the room. + */ + public getTagsForRoom(room: Room): TagID[] { + const algorithmTags = this.algorithm.getTagsForRoom(room); + if (!algorithmTags) return [DefaultTagID.Untagged]; + return algorithmTags; + } } export default class RoomListStore { diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 8215d2ef57..36abf86975 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -524,7 +524,7 @@ export class Algorithm extends EventEmitter { } } - private getTagsForRoom(room: Room): TagID[] { + public getTagsForRoom(room: Room): TagID[] { // XXX: This duplicates a lot of logic from setKnownRooms above, but has a slightly // different use case and therefore different performance curve