Add badges to breadcrumb rooms

Fixes https://github.com/vector-im/riot-web/issues/8606
This commit is contained in:
Travis Ralston 2019-04-01 16:06:33 -06:00
parent c3d3dd1fd7
commit f5600fd4d7
6 changed files with 100 additions and 38 deletions

View file

@ -17,8 +17,8 @@ limitations under the License.
.mx_RoomBreadcrumbs {
position: relative;
height: 42px;
margin: 8px;
margin-bottom: 0;
padding: 8px;
padding-bottom: 0;
overflow-x: visible;
display: flex;
flex-direction: row;
@ -34,6 +34,13 @@ limitations under the License.
height: 32px;
display: inline-block;
transition: transform 0.3s, width 0.3s;
position: relative;
.mx_RoomTile_badge {
position: absolute;
top: -3px;
right: -4px;
}
}
.mx_RoomBreadcrumbs_animate {

View file

@ -144,11 +144,14 @@ limitations under the License.
font-size: 12px;
}
.mx_RoomTile_unreadNotify .mx_RoomTile_badge {
.mx_RoomTile_unreadNotify .mx_RoomTile_badge,
.mx_RoomTile_badge.mx_RoomTile_badgeUnread {
background-color: $roomtile-name-color;
}
.mx_RoomTile_highlight .mx_RoomTile_badge {
.mx_RoomTile_highlight .mx_RoomTile_badge,
.mx_RoomTile_badge.mx_RoomTile_badgeRed
{
background-color: $warning-color;
}

View file

@ -23,6 +23,8 @@ export const ALL_MESSAGES = 'all_messages';
export const MENTIONS_ONLY = 'mentions_only';
export const MUTE = 'mute';
export const BADGE_STATES = [ALL_MESSAGES, ALL_MESSAGES_LOUD];
export const MENTION_BADGE_STATES = [...BADGE_STATES, MENTIONS_ONLY];
function _shouldShowNotifBadge(roomNotifState) {
const showBadgeInStates = [ALL_MESSAGES, ALL_MESSAGES_LOUD];
@ -107,6 +109,28 @@ export function setRoomNotifsState(roomId, newState) {
}
}
export function getUnreadNotificationCount(room, type=null) {
let notificationCount = room.getUnreadNotificationCount(type);
// Check notification counts in the old room just in case there's some lost
// there. We only go one level down to avoid performance issues, and theory
// is that 1st generation rooms will have already been read by the 3rd generation.
const createEvent = room.currentState.getStateEvents("m.room.create", "");
if (createEvent && createEvent.getContent()['predecessor']) {
const oldRoomId = createEvent.getContent()['predecessor']['room_id'];
const oldRoom = MatrixClientPeg.get().getRoom(oldRoomId);
if (oldRoom) {
// We only ever care if there's highlights in the old room. No point in
// notifying the user for unread messages because they would have extreme
// difficulty changing their notification preferences away from "All Messages"
// and "Noisy".
notificationCount += oldRoom.getUnreadNotificationCount("highlight");
}
}
return notificationCount;
}
function setRoomNotifsStateMuted(roomId) {
const cli = MatrixClientPeg.get();
const promises = [];
@ -204,4 +228,3 @@ function isRuleForRoom(roomId, rule) {
function isMuteRule(rule) {
return (rule.actions.length === 1 && rule.actions[0] === 'dont_notify');
}

View file

@ -1,7 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2018, 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -29,7 +29,6 @@ import { Group } from 'matrix-js-sdk';
import PropTypes from 'prop-types';
import RoomTile from "../views/rooms/RoomTile";
import LazyRenderList from "../views/elements/LazyRenderList";
import MatrixClientPeg from "../../MatrixClientPeg";
// turn this on for drop & drag console debugging galore
const debug = false;
@ -139,28 +138,6 @@ const RoomSubList = React.createClass({
this.setState(this.state);
},
getUnreadNotificationCount: function(room, type=null) {
let notificationCount = room.getUnreadNotificationCount(type);
// Check notification counts in the old room just in case there's some lost
// there. We only go one level down to avoid performance issues, and theory
// is that 1st generation rooms will have already been read by the 3rd generation.
const createEvent = room.currentState.getStateEvents("m.room.create", "");
if (createEvent && createEvent.getContent()['predecessor']) {
const oldRoomId = createEvent.getContent()['predecessor']['room_id'];
const oldRoom = MatrixClientPeg.get().getRoom(oldRoomId);
if (oldRoom) {
// We only ever care if there's highlights in the old room. No point in
// notifying the user for unread messages because they would have extreme
// difficulty changing their notification preferences away from "All Messages"
// and "Noisy".
notificationCount += oldRoom.getUnreadNotificationCount("highlight");
}
}
return notificationCount;
},
makeRoomTile: function(room) {
return <RoomTile
room={room}
@ -169,8 +146,8 @@ const RoomSubList = React.createClass({
key={room.roomId}
collapsed={this.props.collapsed || false}
unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={this.props.isInvite || this.getUnreadNotificationCount(room, 'highlight') > 0}
notificationCount={this.getUnreadNotificationCount(room)}
highlight={this.props.isInvite || RoomNotifs.getUnreadNotificationCount(room, 'highlight') > 0}
notificationCount={RoomNotifs.getUnreadNotificationCount(room)}
isInvite={this.props.isInvite}
refreshSubList={this._updateSubListCount}
incomingCall={null}

View file

@ -22,6 +22,8 @@ import AccessibleButton from '../elements/AccessibleButton';
import RoomAvatar from '../avatars/RoomAvatar';
import classNames from 'classnames';
import sdk from "../../../index";
import * as RoomNotifs from '../../../RoomNotifs';
import * as FormattingUtils from "../../../utils/FormattingUtils";
const MAX_ROOMS = 20;
@ -54,13 +56,21 @@ export default class RoomBreadcrumbs extends React.Component {
}
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Event.decrypted", this.onEventDecrypted);
}
componentWillUnmount() {
dis.unregister(this._dispatcherRef);
const client = MatrixClientPeg.get();
if (client) client.removeListener("Room.myMembership", this.onMyMembership);
if (client) {
client.removeListener("Room.myMembership", this.onMyMembership);
client.removeListener("Room.receipt", this.onRoomReceipt);
client.removeListener("Room.timeline", this.onRoomTimeline);
client.removeListener("Event.decrypted", this.onEventDecrypted);
}
}
componentDidUpdate() {
@ -97,6 +107,24 @@ export default class RoomBreadcrumbs extends React.Component {
}
};
onRoomReceipt = (event, room) => {
if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) {
this.forceUpdate();
}
};
onRoomTimeline = (event, room) => {
if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) {
this.forceUpdate();
}
};
onEventDecrypted = (event) => {
if (this.state.rooms.map(r => r.room.roomId).includes(event.getRoomId())) {
this.forceUpdate();
}
};
_appendRoomId(roomId) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
@ -138,13 +166,12 @@ export default class RoomBreadcrumbs extends React.Component {
const Tooltip = sdk.getComponent('elements.Tooltip');
const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar');
// check for collapsed here and
// not at parent so we keep
// rooms in our state
// check for collapsed here and not at parent so we keep rooms in our state
// when collapsing and expanding
if (this.props.collapsed) {
return null;
}
const rooms = this.state.rooms;
const avatars = rooms.map((r, i) => {
const isFirst = i === 0;
@ -160,10 +187,36 @@ export default class RoomBreadcrumbs extends React.Component {
tooltip = <Tooltip label={r.room.name} />;
}
let badge;
const notifState = RoomNotifs.getRoomNotifsState(room.roomId);
if (RoomNotifs.MENTION_BADGE_STATES.includes(notifState)) {
const highlightNotifs = RoomNotifs.getUnreadNotificationCount(room, 'highlight');
const unreadNotifs = RoomNotifs.getUnreadNotificationCount(room);
const redBadge = highlightNotifs > 0;
const greyBadge = redBadge || (unreadNotifs > 0 && RoomNotifs.BADGE_STATES.includes(notifState));
if (redBadge || greyBadge) {
const notifCount = redBadge ? highlightNotifs : unreadNotifs;
const limitedCount = FormattingUtils.formatCount(notifCount);
// HACK: We are abusing the RoomTile badge styles here
const badgeClasses = classNames({
'mx_RoomTile_badge': true,
'mx_RoomTile_badgeButton': true,
'mx_RoomTile_badgeRed': redBadge,
'mx_RoomTile_badgeUnread': !redBadge,
});
badge = <div className={badgeClasses}>{limitedCount}</div>;
}
}
return (
<AccessibleButton className={classes} key={r.room.roomId} onClick={() => this._viewRoom(r.room)}
onMouseEnter={() => this._onMouseEnter(r.room)} onMouseLeave={() => this._onMouseLeave(r.room)}>
<RoomAvatar room={r.room} width={32} height={32} />
{badge}
{tooltip}
</AccessibleButton>
);

View file

@ -68,12 +68,11 @@ module.exports = React.createClass({
},
_shouldShowNotifBadge: function() {
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
return showBadgeInStates.indexOf(this.state.notifState) > -1;
return RoomNotifs.BADGE_STATES.includes(this.state.notifState);
},
_shouldShowMentionBadge: function() {
return this.state.notifState !== RoomNotifs.MUTE;
return RoomNotifs.MENTION_BADGE_STATES.includes(this.state.notifState);
},
_isDirectMessageRoom: function(roomId) {