Add a ToggleSwitch and use it for SettingsFlag

Also bring in the compact timeline option.

Without minor CSS changes, the old user settings are completely unusable with this change. As such, minimal effort has been put in to have it be useful. Similarly, the changes drop the use of radio groups and the old theme selector was the only one that used it. See the comments for more details on how/why this was mitigated the way it was.
This commit is contained in:
Travis Ralston 2019-01-23 15:50:41 -07:00
parent 97666d39bc
commit 3f897468a6
10 changed files with 175 additions and 49 deletions

View file

@ -79,6 +79,7 @@
@import "./views/elements/_RoleButton.scss"; @import "./views/elements/_RoleButton.scss";
@import "./views/elements/_Spinner.scss"; @import "./views/elements/_Spinner.scss";
@import "./views/elements/_SyntaxHighlight.scss"; @import "./views/elements/_SyntaxHighlight.scss";
@import "./views/elements/_ToggleSwitch.scss";
@import "./views/elements/_ToolTipButton.scss"; @import "./views/elements/_ToolTipButton.scss";
@import "./views/globals/_MatrixToolbar.scss"; @import "./views/globals/_MatrixToolbar.scss";
@import "./views/groups/_GroupPublicityToggle.scss"; @import "./views/groups/_GroupPublicityToggle.scss";

View file

@ -255,3 +255,15 @@ input.mx_UserSettings_phoneNumberField {
.mx_UserSettings_analyticsModal table { .mx_UserSettings_analyticsModal table {
margin: 10px 0px; margin: 10px 0px;
} }
// Temp styles to keep the layout moderately usable. Not perfect, but better
// than 30 options being impossible to understand.
.mx_UserSettings .mx_SettingsFlag {
height: 30px;
}
.mx_UserSettings .mx_SettingsFlag .mx_ToggleSwitch {
float: left;
margin-right: 5px;
}

View file

@ -0,0 +1,51 @@
/*
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.
*/
// TODO: Fancy transitions
.mx_ToggleSwitch {
width: 48px;
height: 24px;
border-radius: 14px;
background-color: $togglesw-off-color;
position: relative;
}
.mx_ToggleSwitch_enabled {
cursor: pointer;
}
.mx_ToggleSwitch.mx_ToggleSwitch_on {
background-color: $togglesw-on-color;
}
.mx_ToggleSwitch_ball {
margin: 2px;
width: 20px;
height: 20px;
border-radius: 20px;
background-color: $togglesw-ball-color;
position: absolute;
top: 0;
}
.mx_ToggleSwitch:not(.mx_ToggleSwitch_on) > .mx_ToggleSwitch_ball {
left: 2px;
}
.mx_ToggleSwitch_on > .mx_ToggleSwitch_ball {
right: 2px;
}

View file

@ -36,3 +36,17 @@ limitations under the License.
margin: 0; margin: 0;
display: block; display: block;
} }
.mx_SettingsTab_section .mx_SettingsFlag {
margin-right: 100px;
height: 25px;
margin-bottom: 10px;
}
.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label {
vertical-align: middle;
}
.mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch {
float: right;
}

View file

@ -216,6 +216,11 @@ $button-danger-bg-color: #f56679;
$button-danger-disabled-fg-color: #ffffff; $button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
// Toggle switch
$togglesw-off-color: #c1c9d6;
$togglesw-on-color: #7ac9a1;
$togglesw-ball-color: #fff;
// unused? // unused?
$progressbar-color: #000; $progressbar-color: #000;

View file

@ -212,6 +212,11 @@ $button-danger-bg-color: #f56679;
$button-danger-disabled-fg-color: #ffffff; $button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
// Toggle switch
$togglesw-off-color: #c1c9d6;
$togglesw-on-color: #7ac9a1;
$togglesw-ball-color: #fff;
// unused? // unused?
$progressbar-color: #000; $progressbar-color: #000;

View file

@ -637,11 +637,14 @@ module.exports = React.createClass({
// to rebind the onChange each time we render // to rebind the onChange each time we render
const onChange = (e) => const onChange = (e) =>
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
// HACK: Lack of translations for themes header. We're removing this view in the very near future,
// and the header is really only there to maintain some semblance of the UX the section once was.
return ( return (
<div> <div>
<h3>{ _t("User Interface") }</h3> <h3>{ _t("User Interface") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
{ SIMPLE_SETTINGS.map( this._renderAccountSetting ) } { SIMPLE_SETTINGS.map( this._renderAccountSetting ) }
<div><b>Themes</b></div>
{ THEMES.map( this._renderThemeOption ) } { THEMES.map( this._renderThemeOption ) }
<table> <table>
<tbody> <tbody>
@ -676,18 +679,12 @@ module.exports = React.createClass({
}, },
_renderThemeOption: function(setting) { _renderThemeOption: function(setting) {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag"); // HACK: Temporary disablement of theme selection.
const onChange = (v) => dis.dispatch({action: 'set_theme', value: setting.value}); // We don't support changing themes on experimental anyways, and radio groups aren't
return ( // a thing anymore for setting flags. We're also dropping this view in the very near
<div className="mx_UserSettings_toggle" key={setting.id + '_' + setting.value}> // future, so just replace the theme selection with placeholder text.
<SettingsFlag name="theme" const currentTheme = SettingsStore.getValue("theme");
label={setting.label} return <div>{_t(setting.label)} {currentTheme === setting.value ? '(current)' : null}</div>;
level={SettingLevel.ACCOUNT}
onChange={onChange}
group="theme"
value={setting.value} />
</div>
);
}, },
_renderCryptoInfo: function() { _renderCryptoInfo: function() {

View file

@ -18,6 +18,7 @@ import React from "react";
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import ToggleSwitch from "./ToggleSwitch";
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'SettingsFlag', displayName: 'SettingsFlag',
@ -29,10 +30,6 @@ module.exports = React.createClass({
onChange: PropTypes.func, onChange: PropTypes.func,
isExplicit: PropTypes.bool, isExplicit: PropTypes.bool,
manualSave: PropTypes.bool, manualSave: PropTypes.bool,
// If group is supplied, then this will create a radio button instead.
group: PropTypes.string,
value: PropTypes.any, // the value for the radio button
}, },
getInitialState: function() { getInitialState: function() {
@ -46,13 +43,12 @@ module.exports = React.createClass({
}; };
}, },
onChange: function(e) { onChange: function(checked) {
if (this.props.group && !e.target.checked) return; if (this.props.group && !checked) return;
const newState = this.props.group ? this.props.value : e.target.checked; if (!this.props.manualSave) this.save(checked);
if (!this.props.manualSave) this.save(newState); else this.setState({ value: checked });
else this.setState({ value: newState }); if (this.props.onChange) this.props.onChange(checked);
if (this.props.onChange) this.props.onChange(newState);
}, },
save: function(val = undefined) { save: function(val = undefined) {
@ -78,34 +74,11 @@ module.exports = React.createClass({
if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level); if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level);
else label = _t(label); else label = _t(label);
// We generate a relatively complex ID to avoid conflicts
const id = this.props.name + "_" + this.props.group + "_" + this.props.value + "_" + this.props.level;
let checkbox = (
<input id={id}
type="checkbox"
defaultChecked={value}
onChange={this.onChange}
disabled={!canChange}
/>
);
if (this.props.group) {
checkbox = (
<input id={id}
type="radio"
name={this.props.group}
value={this.props.value}
checked={value === this.props.value}
onChange={this.onChange}
disabled={!canChange}
/>
);
}
return ( return (
<label> <div className="mx_SettingsFlag">
{ checkbox } <span className="mx_SettingsFlag_label">{label}</span>
{ label } <ToggleSwitch checked={value} onChange={this.onChange} disabled={!canChange} />
</label> </div>
); );
}, },
}); });

View file

@ -0,0 +1,66 @@
/*
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 classNames from "classnames";
export default class ToggleSwitch extends React.Component {
static propTypes = {
// Whether or not this toggle is in the 'on' position. Default false (off).
checked: PropTypes.bool,
// Whether or not the user can interact with the switch
disabled: PropTypes.bool,
// Called when the checked state changes. First argument will be the new state.
onChange: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
checked: props.checked || false, // default false
};
}
_onClick = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.disabled) return;
const newState = !this.state.checked;
this.setState({checked: newState});
if (this.props.onChange) {
this.props.onChange(newState);
}
};
render() {
const classes = classNames({
"mx_ToggleSwitch": true,
"mx_ToggleSwitch_on": this.state.checked,
"mx_ToggleSwitch_enabled": !this.props.disabled,
});
return (
<div className={classes} onClick={this._onClick}>
<div className="mx_ToggleSwitch_ball" />
</div>
)
}
}

View file

@ -154,6 +154,7 @@ export default class GeneralSettingsTab extends React.Component {
_renderThemeSection() { _renderThemeSection() {
// TODO: Re-enable theme selection once the themes actually work // TODO: Re-enable theme selection once the themes actually work
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
return ( return (
<div className="mx_SettingsTab_section mx_GeneralSettingsTab_themeSection"> <div className="mx_SettingsTab_section mx_GeneralSettingsTab_themeSection">
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span> <span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
@ -164,6 +165,7 @@ export default class GeneralSettingsTab extends React.Component {
<option value="dharma">{_t("2018 theme")}</option> <option value="dharma">{_t("2018 theme")}</option>
<option value="status">{_t("Status.im theme")}</option> <option value="status">{_t("Status.im theme")}</option>
</Field> </Field>
<SettingsFlag name="useCompactLayout" level={SettingLevel.ACCOUNT} />
</div> </div>
); );
} }