From ee13dd7b6c35de26724cead7cebb4f935912452e Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 11 Jan 2016 17:24:04 +0100 Subject: [PATCH] PushRules settings: Added a dedicated component to display them --- src/component-index.js | 7 +- .../views/settings/Notifications.js | 291 ++++++++++++++++++ .../structures/UserSettings.css | 24 +- .../views/settings/Notifications.css | 53 ++++ 4 files changed, 356 insertions(+), 19 deletions(-) create mode 100644 src/components/views/settings/Notifications.js create mode 100644 src/skins/vector/css/vector-web/views/settings/Notifications.css diff --git a/src/component-index.js b/src/component-index.js index e7d9b560f4..3d814035e8 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -33,9 +33,9 @@ module.exports.components['structures.ViewSource'] = require('./components/struc module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView'); module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner'); module.exports.components['views.globals.MatrixToolbar'] = require('./components/views/globals/MatrixToolbar'); -module.exports.components['views.login.CustomServerDialog'] = require('./components/views/login/VectorCustomServerDialog'); -module.exports.components['views.login.LoginFooter'] = require('./components/views/login/VectorLoginFooter'); -module.exports.components['views.login.LoginHeader'] = require('./components/views/login/VectorLoginHeader'); +module.exports.components['views.login.VectorCustomServerDialog'] = require('./components/views/login/VectorCustomServerDialog'); +module.exports.components['views.login.VectorLoginFooter'] = require('./components/views/login/VectorLoginFooter'); +module.exports.components['views.login.VectorLoginHeader'] = require('./components/views/login/VectorLoginHeader'); module.exports.components['views.messages.DateSeparator'] = require('./components/views/messages/DateSeparator'); module.exports.components['views.messages.MessageTimestamp'] = require('./components/views/messages/MessageTimestamp'); module.exports.components['views.messages.SenderProfile'] = require('./components/views/messages/SenderProfile'); @@ -45,3 +45,4 @@ module.exports.components['views.rooms.RoomDNDView'] = require('./components/vie module.exports.components['views.rooms.RoomDropTarget'] = require('./components/views/rooms/RoomDropTarget'); module.exports.components['views.rooms.RoomTooltip'] = require('./components/views/rooms/RoomTooltip'); module.exports.components['views.rooms.SearchBar'] = require('./components/views/rooms/SearchBar'); +module.exports.components['views.settings.Notifications'] = require('./components/views/settings/Notifications'); diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js new file mode 100644 index 0000000000..8fc992e3fd --- /dev/null +++ b/src/components/views/settings/Notifications.js @@ -0,0 +1,291 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +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. +*/ + +'use strict'; +var React = require('react'); +var sdk = require('matrix-react-sdk'); +var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore'); + +/** + * Enum for state of a push rule as defined by the Vector UI. + * @readonly + * @enum {string} + */ +var PushRuleState = { + /** The user will receive push notification for this rule */ + ON: "on", + /** The user will receive push notification for this rule with sound and + highlight if this is legitimate */ + STRONG: "strong", + /** The push rule is disabled */ + OFF: "off" +}; + +module.exports = React.createClass({ + displayName: 'Notififications', + + phases: { + LOADING: "LOADING", // The component is loading or sending data to the hs + DISPLAY: "DISPLAY", // The component is ready and display data + ERROR: "ERROR" // There was an error + }, + + getInitialState: function() { + return { + phase: this.phases.LOADING, + vectorPushRules: [] + }; + }, + + componentWillMount: function() { + this._refreshFromServer(); + }, + + onEnableNotificationsChange: function(event) { + UserSettingsStore.setEnableNotifications(event.target.checked); + }, + + onNotifStateButtonClicked: function(event) { + var vectorRuleId = event.target.className.split("-")[0]; + var newPushRuleState = event.target.className.split("-")[1]; + + var rule = this.getRule(vectorRuleId); + + // For now, we support only enabled/disabled. + // Translate ON, STRONG, OFF to one of the 2. + if (rule && rule.state !== newPushRuleState) { + + this.setState({ + phase: this.phases.LOADING + }); + + var self = this; + MatrixClientPeg.get().setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, (newPushRuleState !== PushRuleState.OFF)).done(function() { + + self._refreshFromServer(); + self.forceUpdate(); + }); + } + }, + + getRule: function(vectorRuleId) { + for (var i in this.state.vectorPushRules) { + var rule = this.state.vectorPushRules[i]; + if (rule.vectorRuleId === vectorRuleId) { + return rule; + } + } + }, + + _refreshFromServer: function() { + var self = this; + MatrixClientPeg.get().getPushRules().done(function(rulesets) { + MatrixClientPeg.get().pushRules = rulesets; + + // Get homeserver default rules expected by Vector + var rule_categories = { + '.m.rule.master': 'master', + + '.m.rule.contains_display_name': 'vector', + '.m.rule.room_one_to_one': 'vector', + '.m.rule.invite_for_me': 'vector', + '.m.rule.member_event': 'vector', + '.m.rule.call': 'vector', + }; + + var defaultRules = {master: [], vector: {}, additional: [], fallthrough: [], suppression: []}; + for (var kind in rulesets.global) { + for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) { + var r = rulesets.global[kind][i]; + var cat = rule_categories[r.rule_id]; + r.kind = kind; + if (r.rule_id[0] === '.') { + if (cat) { + if (cat === 'vector') + { + defaultRules.vector[r.rule_id] = r; + } + else + { + defaultRules[cat].push(r); + } + } else { + defaultRules.additional.push(r); + } + } + } + } + + // Build the rules displayed by Vector UI + self.state.vectorPushRules = []; + var rule, state; + + rule = defaultRules.vector['.m.rule.contains_display_name']; + state = (rule && rule.enabled) ? PushRuleState.STRONG : PushRuleState.OFF; + self.state.vectorPushRules.push({ + "vectorRuleId": "contains_display_name", + "description" : "Messages containing my name", + "rule": rule, + "state": state, + "disabled": PushRuleState.ON + }); + + // TODO: Merge contains_user_name + + // TODO: Add "Messages containing keywords" + + rule = defaultRules.vector['.m.rule.room_one_to_one']; + state = (rule && rule.enabled) ? PushRuleState.STRONG : PushRuleState.OFF; + self.state.vectorPushRules.push({ + "vectorRuleId": "room_one_to_one", + "description" : "Messages just sent to me", + "rule": rule, + "state": state, + "disabled": PushRuleState.ON + }); + + rule = defaultRules.vector['.m.rule.invite_for_me']; + state = (rule && rule.enabled) ? PushRuleState.STRONG : PushRuleState.OFF; + self.state.vectorPushRules.push({ + "vectorRuleId": "invite_for_me", + "description" : "When I'm invited to a room", + "rule": rule, + "state": state, + "disabled": PushRuleState.ON + }); + + rule = defaultRules.vector['.m.rule.member_event']; + state = (rule && rule.enabled) ? PushRuleState.ON : PushRuleState.OFF; + self.state.vectorPushRules.push({ + "vectorRuleId": "member_event", + "description" : "When people join or leave a room", + "rule": rule, + "state": state, + "disabled": PushRuleState.STRONG + }); + + rule = defaultRules.vector['.m.rule.call']; + state = (rule && rule.enabled) ? PushRuleState.STRONG : PushRuleState.OFF; + self.state.vectorPushRules.push({ + "vectorRuleId": "call", + "description" : "Call invitation", + "rule": rule, + "state": state, + "disabled": PushRuleState.ON + }); + + self.setState({ + phase: self.phases.DISPLAY + }); + + self.forceUpdate(); + }); + }, + + renderNotifRulesTableRow: function(title, className, pushRuleState, disabled) { + return ( + + {title} + + + + + + + + + + + + + + ); + }, + + renderNotifRulesTableRows: function() { + var rows = []; + for (var i in this.state.vectorPushRules) { + var rule = this.state.vectorPushRules[i]; + rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.state, rule.disabled)); + } + return rows; + }, + + render: function() { + if (this.state.phase === this.phases.LOADING) { + var Loader = sdk.getComponent("elements.Spinner"); + return ( +
+ +
+ ); + } + + return ( +
+ +
+
+ +
+
+ +
+
+ +

General use

+ +
+ + + + + + + + + + + + { this.renderNotifRulesTableRows() } + + +
NormalStrongOff
+
+ +
+ ); + } +}); diff --git a/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css b/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css index a0e9052c87..fac8bf91d5 100644 --- a/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css +++ b/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css @@ -56,6 +56,13 @@ limitations under the License. border-bottom: 1px solid #eee; } +.mx_UserSettings h3 { + font-weight: bold; + font-size: 15px; + margin-top: 4px; + margin-bottom: 4px; +} + .mx_UserSettings_section { margin-left: 63px; margin-top: 28px; @@ -74,8 +81,7 @@ limitations under the License. float: left; } -.mx_UserSettings_profileTableRow, -.mx_UserSettings_notifTableRow +.mx_UserSettings_profileTableRow { display: table-row; } @@ -106,20 +112,6 @@ limitations under the License. font-size: 16px; } -.mx_UserSettings_notifInputCell { - display: table-cell; - padding-bottom: 21px; - padding-right: 8px; - width: 16px; -} - -.mx_UserSettings_notifLabelCell -{ - padding-bottom: 21px; - width: 270px; - display: table-cell; -} - .mx_UserSettings_logout { margin-right: 24px; margin-bottom: 24px; diff --git a/src/skins/vector/css/vector-web/views/settings/Notifications.css b/src/skins/vector/css/vector-web/views/settings/Notifications.css new file mode 100644 index 0000000000..962af30ed9 --- /dev/null +++ b/src/skins/vector/css/vector-web/views/settings/Notifications.css @@ -0,0 +1,53 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +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_UserNotifSettings_tableRow +{ + display: table-row; +} + +.mx_UserNotifSettings_inputCell { + display: table-cell; + padding-bottom: 21px; + padding-right: 8px; + width: 16px; +} + +.mx_UserNotifSettings_labelCell +{ + padding-bottom: 21px; + width: 270px; + display: table-cell; +} + +.mx_UserNotifSettings_pushRulesTable { + width: 100%; + table-layout: fixed; +} + +.mx_UserNotifSettings_pushRulesTable thead { + font-weight: bold; + font-size: 15px; +} + +.mx_UserNotifSettings_pushRulesTable tbody th { + font-weight: 400; + font-size: 15px; +} + +.mx_UserNotifSettings_pushRulesTable tbody th:first-child { + text-align: left; +} \ No newline at end of file