Merge pull request #2861 from matrix-org/travis/breadcrumbs/badges

Add badges to breadcrumb rooms
This commit is contained in:
Travis Ralston 2019-04-04 08:24:41 -06:00 committed by GitHub
commit 5066d68875
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 120 additions and 38 deletions

View file

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

View file

@ -144,11 +144,14 @@ limitations under the License.
font-size: 12px; 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; 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; background-color: $warning-color;
} }

View file

@ -23,6 +23,8 @@ export const ALL_MESSAGES = 'all_messages';
export const MENTIONS_ONLY = 'mentions_only'; export const MENTIONS_ONLY = 'mentions_only';
export const MUTE = 'mute'; 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) { function _shouldShowNotifBadge(roomNotifState) {
const showBadgeInStates = [ALL_MESSAGES, ALL_MESSAGES_LOUD]; 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) { function setRoomNotifsStateMuted(roomId) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const promises = []; const promises = [];
@ -204,4 +228,3 @@ function isRuleForRoom(roomId, rule) {
function isMuteRule(rule) { function isMuteRule(rule) {
return (rule.actions.length === 1 && rule.actions[0] === 'dont_notify'); return (rule.actions.length === 1 && rule.actions[0] === 'dont_notify');
} }

View file

@ -1,7 +1,7 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 PropTypes from 'prop-types';
import RoomTile from "../views/rooms/RoomTile"; import RoomTile from "../views/rooms/RoomTile";
import LazyRenderList from "../views/elements/LazyRenderList"; import LazyRenderList from "../views/elements/LazyRenderList";
import MatrixClientPeg from "../../MatrixClientPeg";
// turn this on for drop & drag console debugging galore // turn this on for drop & drag console debugging galore
const debug = false; const debug = false;
@ -139,28 +138,6 @@ const RoomSubList = React.createClass({
this.setState(this.state); 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) { makeRoomTile: function(room) {
return <RoomTile return <RoomTile
room={room} room={room}
@ -169,8 +146,8 @@ const RoomSubList = React.createClass({
key={room.roomId} key={room.roomId}
collapsed={this.props.collapsed || false} collapsed={this.props.collapsed || false}
unread={Unread.doesRoomHaveUnreadMessages(room)} unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={this.props.isInvite || this.getUnreadNotificationCount(room, 'highlight') > 0} highlight={this.props.isInvite || RoomNotifs.getUnreadNotificationCount(room, 'highlight') > 0}
notificationCount={this.getUnreadNotificationCount(room)} notificationCount={RoomNotifs.getUnreadNotificationCount(room)}
isInvite={this.props.isInvite} isInvite={this.props.isInvite}
refreshSubList={this._updateSubListCount} refreshSubList={this._updateSubListCount}
incomingCall={null} incomingCall={null}

View file

@ -22,6 +22,8 @@ import AccessibleButton from '../elements/AccessibleButton';
import RoomAvatar from '../avatars/RoomAvatar'; import RoomAvatar from '../avatars/RoomAvatar';
import classNames from 'classnames'; import classNames from 'classnames';
import sdk from "../../../index"; import sdk from "../../../index";
import * as RoomNotifs from '../../../RoomNotifs';
import * as FormattingUtils from "../../../utils/FormattingUtils";
const MAX_ROOMS = 20; 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.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() { componentWillUnmount() {
dis.unregister(this._dispatcherRef); dis.unregister(this._dispatcherRef);
const client = MatrixClientPeg.get(); 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() { componentDidUpdate() {
@ -97,6 +107,57 @@ export default class RoomBreadcrumbs extends React.Component {
} }
}; };
onRoomReceipt = (event, room) => {
if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) {
this._calculateRoomBadges(room);
}
};
onRoomTimeline = (event, room) => {
if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) {
this._calculateRoomBadges(room);
}
};
onEventDecrypted = (event) => {
if (this.state.rooms.map(r => r.room.roomId).includes(event.getRoomId())) {
this._calculateRoomBadges(MatrixClientPeg.get().getRoom(event.getRoomId()));
}
};
_calculateRoomBadges(room) {
if (!room) return;
const rooms = this.state.rooms.slice();
const roomModel = rooms.find((r) => r.room.roomId === room.roomId);
if (!roomModel) return; // No applicable room, so don't do math on it
// Reset the notification variables for simplicity
roomModel.redBadge = false;
roomModel.formattedCount = "0";
roomModel.showCount = false;
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);
roomModel.redBadge = redBadge;
roomModel.formattedCount = limitedCount;
roomModel.showCount = true;
}
}
this.setState({rooms});
}
_appendRoomId(roomId) { _appendRoomId(roomId) {
const room = MatrixClientPeg.get().getRoom(roomId); const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) { if (!room) {
@ -138,13 +199,12 @@ export default class RoomBreadcrumbs extends React.Component {
const Tooltip = sdk.getComponent('elements.Tooltip'); const Tooltip = sdk.getComponent('elements.Tooltip');
const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar'); const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar');
// check for collapsed here and // check for collapsed here and not at parent so we keep rooms in our state
// not at parent so we keep
// rooms in our state
// when collapsing and expanding // when collapsing and expanding
if (this.props.collapsed) { if (this.props.collapsed) {
return null; return null;
} }
const rooms = this.state.rooms; const rooms = this.state.rooms;
const avatars = rooms.map((r, i) => { const avatars = rooms.map((r, i) => {
const isFirst = i === 0; const isFirst = i === 0;
@ -160,10 +220,23 @@ export default class RoomBreadcrumbs extends React.Component {
tooltip = <Tooltip label={r.room.name} />; tooltip = <Tooltip label={r.room.name} />;
} }
let badge;
if (r.showCount) {
const badgeClasses = classNames({
'mx_RoomTile_badge': true,
'mx_RoomTile_badgeButton': true,
'mx_RoomTile_badgeRed': r.redBadge,
'mx_RoomTile_badgeUnread': !r.redBadge,
});
badge = <div className={badgeClasses}>{r.formattedCount}</div>;
}
return ( return (
<AccessibleButton className={classes} key={r.room.roomId} onClick={() => this._viewRoom(r.room)} <AccessibleButton className={classes} key={r.room.roomId} onClick={() => this._viewRoom(r.room)}
onMouseEnter={() => this._onMouseEnter(r.room)} onMouseLeave={() => this._onMouseLeave(r.room)}> onMouseEnter={() => this._onMouseEnter(r.room)} onMouseLeave={() => this._onMouseLeave(r.room)}>
<RoomAvatar room={r.room} width={32} height={32} /> <RoomAvatar room={r.room} width={32} height={32} />
{badge}
{tooltip} {tooltip}
</AccessibleButton> </AccessibleButton>
); );

View file

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