Merge pull request #2336 from matrix-org/travis/notif-button
Show the number of unread notifications above the bell on the right
This commit is contained in:
commit
96300b45b7
5 changed files with 65 additions and 11 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;
|
||||||
|
|
|
@ -289,6 +289,11 @@ const Notifier = {
|
||||||
const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
|
||||||
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||||
if (actions && actions.notify) {
|
if (actions && actions.notify) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "event_notification",
|
||||||
|
event: ev,
|
||||||
|
room: room,
|
||||||
|
});
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
this._displayPopupNotification(ev, room);
|
this._displayPopupNotification(ev, room);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,21 @@ 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) &&
|
// We use a different attribute from the one we're setting
|
||||||
tag.getAttribute(attr).toUpperCase() === this.keyHex[l]) {
|
// because we may also be using forceColors. If we were to
|
||||||
|
// check the keyHex against a forceColors value, it may not
|
||||||
|
// match and therefore not change when we need it to.
|
||||||
|
const valAttrName = "mx-val-" + attr;
|
||||||
|
let attribute = tag.getAttribute(valAttrName);
|
||||||
|
if (!attribute) attribute = tag.getAttribute(attr); // fall back to the original
|
||||||
|
if (attribute && (attribute.toUpperCase() === this.keyHex[m] || attribute.toLowerCase() === this.keyRgb[m])) {
|
||||||
fixups.push({
|
fixups.push({
|
||||||
node: tag,
|
node: tag,
|
||||||
attr: attr,
|
attr: attr,
|
||||||
index: l,
|
refAttr: valAttrName,
|
||||||
|
index: m,
|
||||||
|
forceColors: forceColors,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,7 +448,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;
|
||||||
|
svgFixup.node.setAttribute(svgFixup.attr, forcedColor ? forcedColor : this.colors[svgFixup.index]);
|
||||||
|
svgFixup.node.setAttribute(svgFixup.refAttr, 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,
|
||||||
|
|
||||||
|
@ -205,7 +216,10 @@ module.exports = React.createClass({
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
if (payload.action === "view_user") {
|
if (payload.action === "event_notification") {
|
||||||
|
// Try and re-caclulate any badge counts we might have
|
||||||
|
this.forceUpdate();
|
||||||
|
} else if (payload.action === "view_user") {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'show_right_panel',
|
action: 'show_right_panel',
|
||||||
});
|
});
|
||||||
|
@ -308,6 +322,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 +344,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: {
|
||||||
|
@ -50,6 +51,12 @@ var TintableSvg = React.createClass({
|
||||||
delete TintableSvg.mounts[this.id];
|
delete TintableSvg.mounts[this.id];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidUpdate: function(prevProps, prevState) {
|
||||||
|
if (prevProps.forceColors !== this.props.forceColors) {
|
||||||
|
this.calcAndApplyFixups(this.refs.svgContainer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
tint: function() {
|
tint: function() {
|
||||||
// TODO: only bother running this if the global tint settings have changed
|
// TODO: only bother running this if the global tint settings have changed
|
||||||
// since we loaded!
|
// since we loaded!
|
||||||
|
@ -57,8 +64,13 @@ var TintableSvg = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad: function(event) {
|
onLoad: function(event) {
|
||||||
// console.log("TintableSvg.onLoad for " + this.props.src);
|
this.calcAndApplyFixups(event.target);
|
||||||
this.fixups = Tinter.calcSvgFixups([event.target]);
|
},
|
||||||
|
|
||||||
|
calcAndApplyFixups: function(target) {
|
||||||
|
if (!target) return;
|
||||||
|
// console.log("TintableSvg.calcAndApplyFixups for " + this.props.src);
|
||||||
|
this.fixups = Tinter.calcSvgFixups([target], this.props.forceColors);
|
||||||
Tinter.applySvgFixups(this.fixups);
|
Tinter.applySvgFixups(this.fixups);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -71,6 +83,7 @@ var TintableSvg = React.createClass({
|
||||||
height={this.props.height}
|
height={this.props.height}
|
||||||
onLoad={this.onLoad}
|
onLoad={this.onLoad}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
|
ref="svgContainer"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue