mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
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:
parent
97666d39bc
commit
3f897468a6
10 changed files with 175 additions and 49 deletions
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
51
res/css/views/elements/_ToggleSwitch.scss
Normal file
51
res/css/views/elements/_ToggleSwitch.scss
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
66
src/components/views/elements/ToggleSwitch.js
Normal file
66
src/components/views/elements/ToggleSwitch.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue