Show the number of unread notifications above the bell on the right
Fixes https://github.com/vector-im/riot-web/issues/3383 This achieves the result by counting up the number of highlights across all rooms and setting that as the badge above the icon. If there are no highlights, nothing is displayed. The red highlight on the bell is done by abusing how the Tinter works: because it has access to the properties of the SVG that we'd need to override it, we give it a collection of colors it should use instead of the theme/tint it is trying to apply. This results in the Tinter using our warning color instead of whatever it was going to apply. The RightPanel now listens for events to update the count too, otherwise when the user receives a ping they'd have to switch rooms to see the change.
This commit is contained in:
parent
31b7a0ddcb
commit
173669b375
4 changed files with 40 additions and 8 deletions
|
@ -55,6 +55,10 @@ limitations under the License.
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RightPanel_headerButton_badgeHighlight .mx_RightPanel_headerButton_badge {
|
||||||
|
color: $warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RightPanel_headerButton_highlight {
|
.mx_RightPanel_headerButton_highlight {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
|
|
|
@ -390,7 +390,7 @@ class Tinter {
|
||||||
// XXX: we could just move this all into TintableSvg, but as it's so similar
|
// XXX: we could just move this all into TintableSvg, but as it's so similar
|
||||||
// to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
|
// to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
|
||||||
// keeping it here for now.
|
// keeping it here for now.
|
||||||
calcSvgFixups(svgs) {
|
calcSvgFixups(svgs, forceColors) {
|
||||||
// go through manually fixing up SVG colours.
|
// go through manually fixing up SVG colours.
|
||||||
// we could do this by stylesheets, but keeping the stylesheets
|
// we could do this by stylesheets, but keeping the stylesheets
|
||||||
// updated would be a PITA, so just brute-force search for the
|
// updated would be a PITA, so just brute-force search for the
|
||||||
|
@ -418,13 +418,14 @@ class Tinter {
|
||||||
const tag = tags[j];
|
const tag = tags[j];
|
||||||
for (let k = 0; k < this.svgAttrs.length; k++) {
|
for (let k = 0; k < this.svgAttrs.length; k++) {
|
||||||
const attr = this.svgAttrs[k];
|
const attr = this.svgAttrs[k];
|
||||||
for (let l = 0; l < this.keyHex.length; l++) {
|
for (let m = 0; m < this.keyHex.length; m++) { // dev note: don't use L please.
|
||||||
if (tag.getAttribute(attr) &&
|
if (tag.getAttribute(attr) &&
|
||||||
tag.getAttribute(attr).toUpperCase() === this.keyHex[l]) {
|
tag.getAttribute(attr).toUpperCase() === this.keyHex[m]) {
|
||||||
fixups.push({
|
fixups.push({
|
||||||
node: tag,
|
node: tag,
|
||||||
attr: attr,
|
attr: attr,
|
||||||
index: l,
|
index: m,
|
||||||
|
forceColors: forceColors,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,7 +441,9 @@ class Tinter {
|
||||||
if (DEBUG) console.log("applySvgFixups start for " + fixups);
|
if (DEBUG) console.log("applySvgFixups start for " + fixups);
|
||||||
for (let i = 0; i < fixups.length; i++) {
|
for (let i = 0; i < fixups.length; i++) {
|
||||||
const svgFixup = fixups[i];
|
const svgFixup = fixups[i];
|
||||||
svgFixup.node.setAttribute(svgFixup.attr, this.colors[svgFixup.index]);
|
const forcedColor = svgFixup.forceColors ? svgFixup.forceColors[svgFixup.index] : null;
|
||||||
|
if (forcedColor) console.log(forcedColor);
|
||||||
|
svgFixup.node.setAttribute(svgFixup.attr, forcedColor ? forcedColor : this.colors[svgFixup.index]);
|
||||||
}
|
}
|
||||||
if (DEBUG) console.log("applySvgFixups end");
|
if (DEBUG) console.log("applySvgFixups end");
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddres
|
||||||
import GroupStore from '../../stores/GroupStore';
|
import GroupStore from '../../stores/GroupStore';
|
||||||
|
|
||||||
import { formatCount } from '../../utils/FormattingUtils';
|
import { formatCount } from '../../utils/FormattingUtils';
|
||||||
|
import MatrixClientPeg from "../../MatrixClientPeg";
|
||||||
|
|
||||||
class HeaderButton extends React.Component {
|
class HeaderButton extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -49,17 +50,26 @@ class HeaderButton extends React.Component {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
|
||||||
|
// XXX: We really shouldn't be hardcoding colors here, but the way TintableSvg
|
||||||
|
// works kinda prevents us from using normal CSS tactics. We use $warning-color
|
||||||
|
// here.
|
||||||
|
// Note: This array gets passed along to the Tinter's forceColors eventually.
|
||||||
|
const tintableColors = this.props.badgeHighlight ? ["#ff0064"] : null;
|
||||||
|
|
||||||
|
const classNames = ["mx_RightPanel_headerButton"];
|
||||||
|
if (this.props.badgeHighlight) classNames.push("mx_RightPanel_headerButton_badgeHighlight");
|
||||||
|
|
||||||
return <AccessibleButton
|
return <AccessibleButton
|
||||||
aria-label={this.props.title}
|
aria-label={this.props.title}
|
||||||
aria-expanded={this.props.isHighlighted}
|
aria-expanded={this.props.isHighlighted}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
className="mx_RightPanel_headerButton"
|
className={classNames.join(" ")}
|
||||||
onClick={this.onClick} >
|
onClick={this.onClick} >
|
||||||
|
|
||||||
<div className="mx_RightPanel_headerButton_badge">
|
<div className="mx_RightPanel_headerButton_badge">
|
||||||
{ this.props.badge ? this.props.badge : <span> </span> }
|
{ this.props.badge ? this.props.badge : <span> </span> }
|
||||||
</div>
|
</div>
|
||||||
<TintableSvg src={this.props.iconSrc} width="25" height="25" />
|
<TintableSvg src={this.props.iconSrc} width="25" height="25" forceColors={tintableColors} />
|
||||||
{ this.props.isHighlighted ? <div className="mx_RightPanel_headerButton_highlight" /> : <div /> }
|
{ this.props.isHighlighted ? <div className="mx_RightPanel_headerButton_highlight" /> : <div /> }
|
||||||
|
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
|
@ -76,6 +86,7 @@ HeaderButton.propTypes = {
|
||||||
|
|
||||||
// The badge to display above the icon
|
// The badge to display above the icon
|
||||||
badge: PropTypes.node,
|
badge: PropTypes.node,
|
||||||
|
badgeHighlight: PropTypes.bool,
|
||||||
// The parameters to track the click event
|
// The parameters to track the click event
|
||||||
analytics: PropTypes.arrayOf(PropTypes.string).isRequired,
|
analytics: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
|
||||||
|
@ -113,6 +124,7 @@ module.exports = React.createClass({
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
const cli = this.context.matrixClient;
|
const cli = this.context.matrixClient;
|
||||||
cli.on("RoomState.members", this.onRoomStateMember);
|
cli.on("RoomState.members", this.onRoomStateMember);
|
||||||
|
cli.on("Room.notificationCounts", this.onRoomNotifications);
|
||||||
this._initGroupStore(this.props.groupId);
|
this._initGroupStore(this.props.groupId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -200,6 +212,10 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onRoomNotifications: function(room, type, count) {
|
||||||
|
if (type === "highlight") this.forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
_delayedUpdate: new RateLimitedFunc(function() {
|
_delayedUpdate: new RateLimitedFunc(function() {
|
||||||
this.forceUpdate(); // eslint-disable-line babel/no-invalid-this
|
this.forceUpdate(); // eslint-disable-line babel/no-invalid-this
|
||||||
}, 500),
|
}, 500),
|
||||||
|
@ -308,6 +324,13 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let headerButtons = [];
|
let headerButtons = [];
|
||||||
if (this.props.roomId) {
|
if (this.props.roomId) {
|
||||||
|
let notifCountBadge;
|
||||||
|
let notifCount = 0;
|
||||||
|
MatrixClientPeg.get().getRooms().forEach(r => notifCount += (r.getUnreadNotificationCount('highlight') || 0));
|
||||||
|
if (notifCount > 0) {
|
||||||
|
notifCountBadge = <div title={_t("%counts Notifications")}>{ formatCount(notifCount) }</div>;
|
||||||
|
}
|
||||||
|
|
||||||
headerButtons = [
|
headerButtons = [
|
||||||
<HeaderButton key="_membersButton" title={membersTitle} iconSrc="img/icons-people.svg"
|
<HeaderButton key="_membersButton" title={membersTitle} iconSrc="img/icons-people.svg"
|
||||||
isHighlighted={[this.Phase.RoomMemberList, this.Phase.RoomMemberInfo].includes(this.state.phase)}
|
isHighlighted={[this.Phase.RoomMemberList, this.Phase.RoomMemberInfo].includes(this.state.phase)}
|
||||||
|
@ -323,6 +346,7 @@ module.exports = React.createClass({
|
||||||
<HeaderButton key="_notifsButton" title={_t('Notifications')} iconSrc="img/icons-notifications.svg"
|
<HeaderButton key="_notifsButton" title={_t('Notifications')} iconSrc="img/icons-notifications.svg"
|
||||||
isHighlighted={this.state.phase === this.Phase.NotificationPanel}
|
isHighlighted={this.state.phase === this.Phase.NotificationPanel}
|
||||||
clickPhase={this.Phase.NotificationPanel}
|
clickPhase={this.Phase.NotificationPanel}
|
||||||
|
badge={notifCountBadge} badgeHighlight={notifCount > 0}
|
||||||
analytics={['Right Panel', 'Notification List Button', 'click']}
|
analytics={['Right Panel', 'Notification List Button', 'click']}
|
||||||
/>,
|
/>,
|
||||||
];
|
];
|
||||||
|
|
|
@ -29,6 +29,7 @@ var TintableSvg = React.createClass({
|
||||||
width: PropTypes.string.isRequired,
|
width: PropTypes.string.isRequired,
|
||||||
height: PropTypes.string.isRequired,
|
height: PropTypes.string.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
forceColors: PropTypes.arrayOf(PropTypes.string),
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
|
@ -58,7 +59,7 @@ var TintableSvg = React.createClass({
|
||||||
|
|
||||||
onLoad: function(event) {
|
onLoad: function(event) {
|
||||||
// console.log("TintableSvg.onLoad for " + this.props.src);
|
// console.log("TintableSvg.onLoad for " + this.props.src);
|
||||||
this.fixups = Tinter.calcSvgFixups([event.target]);
|
this.fixups = Tinter.calcSvgFixups([event.target], this.props.forceColors);
|
||||||
Tinter.applySvgFixups(this.fixups);
|
Tinter.applySvgFixups(this.fixups);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue