From dcd51b5be3369cf87a89d937c5eddb94669b2d58 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Tue, 30 Jun 2020 23:24:46 +0100 Subject: [PATCH] Implement breadcrumb notifications and scrolling --- res/css/_components.scss | 1 + res/css/structures/_LeftPanel2.scss | 3 +- .../views/avatars/_DecoratedRoomAvatar.scss | 33 ++++++++++ res/css/views/rooms/_RoomTile2.scss | 17 +---- src/components/structures/LeftPanel2.tsx | 2 +- .../views/avatars/DecoratedRoomAvatar.tsx | 65 +++++++++++++++++++ .../views/rooms/RoomBreadcrumbs2.tsx | 14 +++- src/components/views/rooms/RoomTile2.tsx | 24 ++++--- src/stores/room-list/RoomListStore2.ts | 18 ++++- src/stores/room-list/algorithms/Algorithm.ts | 11 ++++ 10 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 res/css/views/avatars/_DecoratedRoomAvatar.scss create mode 100644 src/components/views/avatars/DecoratedRoomAvatar.tsx 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 2845068de3..e4e6a3eac1 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -30,15 +30,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 { @@ -145,16 +138,10 @@ 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 0f614435e5..b4ec897561 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -151,7 +151,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..af1cdc779c --- /dev/null +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -0,0 +1,65 @@ +/* +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 { + console.log({tag: this.props.tag}) + + 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..2f2b815002 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 1284728855..f8c46ee85a 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,7 +34,7 @@ import { _t } from "../../../languageHandler"; import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; -import RoomTileIcon from "./RoomTileIcon"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; // 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 @@ -233,13 +232,22 @@ 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; @@ -277,7 +285,6 @@ export default class RoomTile2 extends React.Component { ); if (this.props.isMinimized) nameContainer = null; - const avatarSize = 32; return ( @@ -292,10 +299,7 @@ export default class RoomTile2 extends React.Component { onClick={this.onTileClick} role="treeitem" > -
- - -
+ {roomAvatar} {nameContainer}
{badge} diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 497b8e5530..b4d96becc4 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"; @@ -187,7 +187,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`); @@ -422,6 +423,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..d4615356da 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -670,4 +670,15 @@ export class Algorithm extends EventEmitter { return true; } + + /** + * Returns the tags for a given room as known by the algorithm. May be null or + * empty. + * @param room The room to get known tags for. + * @returns The known tags for the room. + */ + public getTagsForRoom(room: Room): TagID[] { + if (!room) throw new Error("A room is required"); + return this.roomIdsToTags[room.roomId]; + } }