Styling to match the other tabs a bit closer

Introduces a new component to reduce code duplication
This commit is contained in:
Travis Ralston 2019-01-23 20:13:43 -07:00
parent d267f232bc
commit 06a9ab3a70
4 changed files with 88 additions and 118 deletions

View file

@ -0,0 +1,42 @@
/*
Copyright 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.
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.
*/
import React from 'react';
import PropTypes from "prop-types";
import ToggleSwitch from "./ToggleSwitch";
export default class LabelledToggleSwitch extends React.Component {
static propTypes = {
// The value for the toggle switch
value: PropTypes.bool.isRequired,
// The function to call when the value changes
onChange: PropTypes.func.isRequired,
// The translated label for the switch
label: PropTypes.string.isRequired,
};
render() {
// This is a minimal version of a SettingsFlag
return (
<div className="mx_SettingsFlag">
<span className="mx_SettingsFlag_label">{this.props.label}</span>
<ToggleSwitch checked={this.props.value} onChange={this.props.onChange} />
</div>
);
}
}

View file

@ -29,6 +29,7 @@ import {
ContentRules, ContentRules,
} from '../../../notifications'; } from '../../../notifications';
import * as SdkConfig from "../../../SdkConfig"; import * as SdkConfig from "../../../SdkConfig";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
// TODO: this "view" component still has far too much application logic in it, // TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files. // which should be factored out to other files.
@ -71,17 +72,6 @@ module.exports = React.createClass({
ERROR: "ERROR", // There was an error ERROR: "ERROR", // There was an error
}, },
propTypes: {
// The array of threepids from the JS SDK (required for email notifications)
threepids: React.PropTypes.array.isRequired,
},
getDefaultProps: function() {
return {
threepids: [],
};
},
getInitialState: function() { getInitialState: function() {
return { return {
phase: this.phases.LOADING, phase: this.phases.LOADING,
@ -93,6 +83,7 @@ module.exports = React.createClass({
}, },
externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
externalContentRules: [], // Keyword push rules that have been defined outside Vector UI externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
threepids: [], // used for email notifications
}; };
}, },
@ -100,42 +91,42 @@ module.exports = React.createClass({
this._refreshFromServer(); this._refreshFromServer();
}, },
onEnableNotificationsChange: function(event) { onEnableNotificationsChange: function(checked) {
const self = this; const self = this;
this.setState({ this.setState({
phase: this.phases.LOADING, phase: this.phases.LOADING,
}); });
MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !event.target.checked).done(function() { MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).done(function() {
self._refreshFromServer(); self._refreshFromServer();
}); });
}, },
onEnableDesktopNotificationsChange: function(event) { onEnableDesktopNotificationsChange: function(checked) {
SettingsStore.setValue( SettingsStore.setValue(
"notificationsEnabled", null, "notificationsEnabled", null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
event.target.checked, checked,
).finally(() => { ).finally(() => {
this.forceUpdate(); this.forceUpdate();
}); });
}, },
onEnableDesktopNotificationBodyChange: function(event) { onEnableDesktopNotificationBodyChange: function(checked) {
SettingsStore.setValue( SettingsStore.setValue(
"notificationBodyEnabled", null, "notificationBodyEnabled", null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
event.target.checked, checked,
).finally(() => { ).finally(() => {
this.forceUpdate(); this.forceUpdate();
}); });
}, },
onEnableAudioNotificationsChange: function(event) { onEnableAudioNotificationsChange: function(checked) {
SettingsStore.setValue( SettingsStore.setValue(
"audioNotificationsEnabled", null, "audioNotificationsEnabled", null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
event.target.checked, checked,
).finally(() => { ).finally(() => {
this.forceUpdate(); this.forceUpdate();
}); });
@ -433,7 +424,6 @@ module.exports = React.createClass({
// Check if any legacy im.vector rules need to be ported to the new API // Check if any legacy im.vector rules need to be ported to the new API
// for overriding the actions of default rules. // for overriding the actions of default rules.
_portRulesToNewAPI: function(rulesets) { _portRulesToNewAPI: function(rulesets) {
const self = this;
const needsUpdate = []; const needsUpdate = [];
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -632,6 +622,8 @@ module.exports = React.createClass({
externalPushRules: self.state.externalPushRules, externalPushRules: self.state.externalPushRules,
}); });
}).done(); }).done();
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
}, },
_updatePushRuleActions: function(rule, actions, enabled) { _updatePushRuleActions: function(rule, actions, enabled) {
@ -690,27 +682,25 @@ module.exports = React.createClass({
return rows; return rows;
}, },
hasEmailPusher: function(pushers, address) {
if (pushers === undefined) {
return false;
}
for (let i = 0; i < pushers.length; ++i) {
if (pushers[i].kind === 'email' && pushers[i].pushkey === address) {
return true;
}
}
return false;
},
emailNotificationsRow: function(address, label) { emailNotificationsRow: function(address, label) {
return (<div className="mx_UserNotifSettings_tableRow"> return <LabelledToggleSwitch value={this.hasEmailPusher(this.state.pushers, address)}
<div className="mx_UserNotifSettings_inputCell"> onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
<input id="enableEmailNotifications_{address}" label={label} />;
ref="enableEmailNotifications_{address}"
type="checkbox"
checked={ UserSettingsStore.hasEmailPusher(this.state.pushers, address) }
onChange={ this.onEnableEmailNotificationsChange.bind(this, address) }
/>
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableEmailNotifications_{address}">
{label}
</label>
</div>
</div>);
}, },
render: function() { render: function() {
const self = this;
let spinner; let spinner;
if (this.state.phase === this.phases.LOADING) { if (this.state.phase === this.phases.LOADING) {
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
@ -719,23 +709,9 @@ module.exports = React.createClass({
let masterPushRuleDiv; let masterPushRuleDiv;
if (this.state.masterPushRule) { if (this.state.masterPushRule) {
masterPushRuleDiv = ( masterPushRuleDiv = <LabelledToggleSwitch value={!this.state.masterPushRule.enabled}
<div className="mx_UserNotifSettings_tableRow"> onChange={this.onEnableNotificationsChange}
<div className="mx_UserNotifSettings_inputCell"> label={_t('Enable notifications for this account')}/>;
<input id="enableNotifications"
ref="enableNotifications"
type="checkbox"
checked={ !this.state.masterPushRule.enabled }
onChange={ this.onEnableNotificationsChange }
/>
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableNotifications">
{ _t('Enable notifications for this account') }
</label>
</div>
</div>
);
} }
// When enabled, the master rule inhibits all existing rules // When enabled, the master rule inhibits all existing rules
@ -746,17 +722,17 @@ module.exports = React.createClass({
{masterPushRuleDiv} {masterPushRuleDiv}
<div className="mx_UserSettings_notifTable"> <div className="mx_UserSettings_notifTable">
{ _t('All notifications are currently disabled for all targets.') }. { _t('All notifications are currently disabled for all targets.') }
</div> </div>
</div> </div>
); );
} }
const emailThreepids = this.props.threepids.filter((tp) => tp.medium === "email"); const emailThreepids = this.state.threepids.filter((tp) => tp.medium === "email");
let emailNotificationsRow; let emailNotificationsRow;
if (emailThreepids.length === 0) { if (emailThreepids.length === 0) {
emailNotificationsRow = <div> emailNotificationsRow = <div>
{ _t('Add an email address above to configure email notifications') } { _t('Add an email address to configure email notifications') }
</div>; </div>;
} else { } else {
// This only supports the first email address in your profile for now // This only supports the first email address in your profile for now
@ -787,7 +763,7 @@ module.exports = React.createClass({
let devicesSection; let devicesSection;
if (this.state.pushers === undefined) { if (this.state.pushers === undefined) {
devicesSection = <div className="error">{ _t('Unable to fetch notification target list') }</div>; devicesSection = <div className="error">{ _t('Unable to fetch notification target list') }</div>;
} else if (this.state.pushers.length == 0) { } else if (this.state.pushers.length === 0) {
devicesSection = null; devicesSection = null;
} else { } else {
// TODO: It would be great to be able to delete pushers from here too, // TODO: It would be great to be able to delete pushers from here too,
@ -835,50 +811,17 @@ module.exports = React.createClass({
{ spinner } { spinner }
<div className="mx_UserNotifSettings_tableRow"> <LabelledToggleSwitch value={SettingsStore.getValue("notificationsEnabled")}
<div className="mx_UserNotifSettings_inputCell"> onChange={this.onEnableDesktopNotificationsChange}
<input id="enableDesktopNotifications" label={_t('Enable desktop notifications')} />
ref="enableDesktopNotifications"
type="checkbox"
checked={ SettingsStore.getValue("notificationsEnabled") }
onChange={ this.onEnableDesktopNotificationsChange } />
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableDesktopNotifications">
{ _t('Enable desktop notifications') }
</label>
</div>
</div>
<div className="mx_UserNotifSettings_tableRow"> <LabelledToggleSwitch value={SettingsStore.getValue("notificationBodyEnabled")}
<div className="mx_UserNotifSettings_inputCell"> onChange={this.onEnableDesktopNotificationBodyChange}
<input id="enableDesktopNotificationBody" label={_t('Show message in desktop notification')} />
ref="enableDesktopNotificationBody"
type="checkbox"
checked={ SettingsStore.getValue("notificationBodyEnabled") }
onChange={ this.onEnableDesktopNotificationBodyChange } />
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableDesktopNotificationBody">
{ _t('Show message in desktop notification') }
</label>
</div>
</div>
<div className="mx_UserNotifSettings_tableRow"> <LabelledToggleSwitch value={SettingsStore.getValue("audioNotificationsEnabled")}
<div className="mx_UserNotifSettings_inputCell"> onChange={this.onEnableAudioNotificationsChange}
<input id="enableDesktopAudioNotifications" label={_t('Enable audible notifications in web client')} />
ref="enableDesktopAudioNotifications"
type="checkbox"
checked={ SettingsStore.getValue("audioNotificationsEnabled") }
onChange={ this.onEnableAudioNotificationsChange } />
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableDesktopAudioNotifications">
{ _t('Enable audible notifications in web client') }
</label>
</div>
</div>
{ emailNotificationsRow } { emailNotificationsRow }

View file

@ -18,8 +18,8 @@ import React from 'react';
import {_t} from "../../../../languageHandler"; import {_t} from "../../../../languageHandler";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import SettingsStore from "../../../../settings/SettingsStore"; import SettingsStore from "../../../../settings/SettingsStore";
import ToggleSwitch from "../../elements/ToggleSwitch";
import MatrixClientPeg from "../../../../MatrixClientPeg"; import MatrixClientPeg from "../../../../MatrixClientPeg";
import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch";
const Modal = require("../../../../Modal"); const Modal = require("../../../../Modal");
const sdk = require("../../../../index"); const sdk = require("../../../../index");
@ -65,15 +65,9 @@ export class LabsSettingToggle extends React.Component {
}; };
render() { render() {
// This is a minimal version of a SettingsFlag
const label = _t(SettingsStore.getDisplayName(this.props.featureId)); const label = _t(SettingsStore.getDisplayName(this.props.featureId));
const value = SettingsStore.isFeatureEnabled(this.props.featureId); const value = SettingsStore.isFeatureEnabled(this.props.featureId);
return ( return <LabelledToggleSwitch value={value} label={label} onChange={this._onChange} />
<div className="mx_SettingsFlag">
<span className="mx_SettingsFlag_label">{label}</span>
<ToggleSwitch checked={value} onChange={this._onChange} />
</div>
);
} }
} }

View file

@ -16,20 +16,11 @@ limitations under the License.
import React from 'react'; import React from 'react';
import {_t} from "../../../../languageHandler"; import {_t} from "../../../../languageHandler";
import MatrixClientPeg from "../../../../MatrixClientPeg";
const sdk = require("../../../../index"); const sdk = require("../../../../index");
export default class NotificationSettingsTab extends React.Component { export default class NotificationSettingsTab extends React.Component {
constructor() { constructor() {
super(); super();
this.state = {
threepids: [],
};
}
async componentWillMount(): void {
MatrixClientPeg.get().getThreePids().then(r => this.setState({threepids: r.threepids}));
} }
render() { render() {
@ -38,7 +29,7 @@ export default class NotificationSettingsTab extends React.Component {
<div className="mx_SettingsTab"> <div className="mx_SettingsTab">
<div className="mx_SettingsTab_heading">{_t("Notifications")}</div> <div className="mx_SettingsTab_heading">{_t("Notifications")}</div>
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<Notifications threepids={this.state.threepids} /> <Notifications />
</div> </div>
</div> </div>
); );