mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-15 20:54:59 +08:00
Merge pull request #2476 from matrix-org/travis/modal-tab-settings
Basic structure for tabbed user settings
This commit is contained in:
commit
94b1d739fb
@ -42,9 +42,8 @@ module.exports = {
|
||||
|
||||
// bind or arrow function in props causes performance issues
|
||||
// (but we currently use them in some places)
|
||||
"react/jsx-no-bind": ["warn", {
|
||||
"ignoreRefs": true,
|
||||
}],
|
||||
// It's disabled here, but we should using it sparingly.
|
||||
"react/jsx-no-bind": "off",
|
||||
"react/jsx-key": ["error"],
|
||||
|
||||
// Components in JSX should always be defined.
|
||||
|
@ -160,7 +160,7 @@ textarea {
|
||||
padding: 0 58px 36px;
|
||||
width: 60%;
|
||||
max-width: 704px;
|
||||
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@ -171,7 +171,7 @@ textarea {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $dialog-background-bg-color;
|
||||
background-color: $dialog-backdrop-color;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
@import "./structures/_RoomSubList.scss";
|
||||
@import "./structures/_RoomView.scss";
|
||||
@import "./structures/_SearchBox.scss";
|
||||
@import "./structures/_TabbedView.scss";
|
||||
@import "./structures/_TagPanel.scss";
|
||||
@import "./structures/_TopLeftMenuButton.scss";
|
||||
@import "./structures/_UploadBar.scss";
|
||||
@ -56,6 +57,7 @@
|
||||
@import "./views/dialogs/_SetPasswordDialog.scss";
|
||||
@import "./views/dialogs/_ShareDialog.scss";
|
||||
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
||||
@import "./views/dialogs/_UserSettingsDialog.scss";
|
||||
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
||||
@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss";
|
||||
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
||||
@ -128,6 +130,8 @@
|
||||
@import "./views/settings/_IntegrationsManager.scss";
|
||||
@import "./views/settings/_KeyBackupPanel.scss";
|
||||
@import "./views/settings/_Notifications.scss";
|
||||
@import "./views/settings/tabs/_GeneralSettingsTab.scss";
|
||||
@import "./views/settings/tabs/_SettingsTab.scss";
|
||||
@import "./views/voip/_CallView.scss";
|
||||
@import "./views/voip/_IncomingCallbox.scss";
|
||||
@import "./views/voip/_VideoView.scss";
|
||||
|
92
res/css/structures/_TabbedView.scss
Normal file
92
res/css/structures/_TabbedView.scss
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright 2017 Travis Ralston
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_TabbedView {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabLabels {
|
||||
width: 136px;
|
||||
height: 100%;
|
||||
color: $tab-label-fg-color;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabLabel {
|
||||
vertical-align: text-top;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
height: 20px;
|
||||
margin-bottom: 6px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabLabel_active {
|
||||
background-color: $tab-label-active-bg-color;
|
||||
color: $tab-label-active-fg-color;
|
||||
}
|
||||
|
||||
// TODO: Remove temporary hack alongside "visit old settings" tab
|
||||
.mx_TabbedView_tabLabel_TEMP_HACK {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.mx_TabbedView_maskedIcon {;
|
||||
margin-left: 6px;
|
||||
margin-right: 9px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_TabbedView_maskedIcon:before {
|
||||
display: inline-block;
|
||||
background-color: $tab-label-icon-bg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon:before {
|
||||
background-color: $tab-label-active-icon-bg-color;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabLabel_text {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabPanel {
|
||||
width: calc(100% - 320px);
|
||||
display: inline-block;
|
||||
margin-left: 70px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabPanelContent {
|
||||
flex-grow: 1;
|
||||
min-width: 560px;
|
||||
}
|
63
res/css/views/dialogs/_UserSettingsDialog.scss
Normal file
63
res/css/views/dialogs/_UserSettingsDialog.scss
Normal file
@ -0,0 +1,63 @@
|
||||
.mx_UserSettingsDialog_header {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: $dialog-title-fg-color;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_closeIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_closeIcon:before {
|
||||
mask: url('$(res)/img/feather-icons/cancel.svg');
|
||||
background-color: $dialog-close-fg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 16px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
||||
// ICONS
|
||||
// ==========================================================
|
||||
|
||||
.mx_UserSettingsDialog_settingsIcon:before {
|
||||
mask-image: url('$(res)/img/feather-icons/settings.svg');
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_voiceIcon:before {
|
||||
mask-image: url('$(res)/img/feather-icons/phone.svg');
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_bellIcon:before {
|
||||
mask-image: url('$(res)/img/feather-icons/notifications.svg');
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_preferencesIcon:before {
|
||||
mask-image: url('$(res)/img/feather-icons/sliders.svg');
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_securityIcon:before {
|
||||
mask-image: url('$(res)/img/feather-icons/lock.svg');
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_helpIcon:before {
|
||||
mask-image: url('$(res)/img/feather-icons/help-circle.svg');
|
||||
}
|
@ -21,3 +21,24 @@ limitations under the License.
|
||||
.mx_AccessibleButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_hasKind {
|
||||
padding: 10px 25px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_primary {
|
||||
color: $button-primary-fg-color;
|
||||
background-color: $button-primary-bg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled {
|
||||
color: $button-primary-disabled-fg-color;
|
||||
background-color: $button-primary-disabled-bg-color;
|
||||
}
|
||||
|
25
res/css/views/settings/tabs/_GeneralSettingsTab.scss
Normal file
25
res/css/views/settings/tabs/_GeneralSettingsTab.scss
Normal file
@ -0,0 +1,25 @@
|
||||
.mx_GeneralSettingsTab_profile {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx_GeneralSettingsTab_profileControls {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.mx_GeneralSettingsTab_profileControls .mx_Field #profileDisplayName {
|
||||
width: calc(100% - 20px); // subtract 10px padding on left and right
|
||||
}
|
||||
|
||||
.mx_GeneralSettingsTab_profileAvatar {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
margin-left: 13px;
|
||||
}
|
||||
|
||||
.mx_GeneralSettingsTab_profileAvatar div {
|
||||
display: block;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: 4px;
|
||||
background-color: #ccc;
|
||||
}
|
17
res/css/views/settings/tabs/_SettingsTab.scss
Normal file
17
res/css/views/settings/tabs/_SettingsTab.scss
Normal file
@ -0,0 +1,17 @@
|
||||
.mx_SettingsTab_heading {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
|
||||
.mx_SettingsTab_subheading {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
font-family: $font-family-semibold;
|
||||
color: $primary-fg-color;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mx_SettingsTab_section {
|
||||
margin-top: 10px;
|
||||
}
|
10
res/img/feather-icons/cancel.svg
Normal file
10
res/img/feather-icons/cancel.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.4.2 (15857) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="#" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<path d="M9.74464309,-3.02908503 L8.14106175,-3.02908503 L8.14106175,8.19448443 L-3.03028759,8.19448443 L-3.03028759,9.7978515 L8.14106175,9.7978515 L8.14106175,20.9685098 L9.74464309,20.9685098 L9.74464309,9.7978515 L20.9697124,9.7978515 L20.9697124,8.19448443 L9.74464309,8.19448443 L9.74464309,-3.02908503" id="Fill-108" opacity="0.9" fill="#9fa9ba" sketch:type="MSShapeGroup" transform="translate(8.969712, 8.969712) rotate(-315.000000) translate(-8.969712, -8.969712) "></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
6
res/img/feather-icons/help-circle.svg
Normal file
6
res/img/feather-icons/help-circle.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||
<circle cx="6" cy="6" r="6"/>
|
||||
<path d="M4.254 4.2a1.8 1.8 0 0 1 3.498.6c0 1.2-1.8 1.8-1.8 1.8M6 8.991"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 352 B |
6
res/img/feather-icons/lock.svg
Normal file
6
res/img/feather-icons/lock.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="14" viewBox="0 0 13 14">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||
<rect width="11" height="6.6" y="5.4" rx="1.2"/>
|
||||
<path d="M2.444 5.4V3c0-1.657 1.368-3 3.056-3s3.056 1.343 3.056 3v2.4"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 369 B |
5
res/img/feather-icons/sliders.svg
Normal file
5
res/img/feather-icons/sliders.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="12" viewBox="0 0 14 12">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M2.636 11V7.111M2.636 4.889V1M7 11V6M7 3.778V1M11.364 11V8.222M11.364 6V1M1 7.111h3.273M5.364 3.778h3.272M9.727 8.222H13"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 345 B |
@ -5,6 +5,7 @@
|
||||
horizontal mess. Arial empirically gets it right, hence prioritising
|
||||
Arial here. */
|
||||
$font-family: 'Nunito', Arial, Helvetica, Sans-Serif;
|
||||
$font-family-semibold: 'Nunito SemiBold', Arial, Helvetica, Sans-Serif;
|
||||
|
||||
// typical text (dark-on-white in light skin)
|
||||
$primary-fg-color: #454545;
|
||||
@ -96,6 +97,11 @@ $avatar-bg-color: #ffffff;
|
||||
|
||||
$h3-color: #3d3b39;
|
||||
|
||||
$dialog-title-fg-color: #454545;
|
||||
$dialog-backdrop-color: rgba(46, 48, 51, 0.38);
|
||||
$dialog-shadow-color: rgba(0, 0, 0, 0.48);
|
||||
$dialog-close-fg-color: #9fa9ba;
|
||||
|
||||
$dialog-background-bg-color: #e9e9e9;
|
||||
$lightbox-background-bg-color: #000;
|
||||
|
||||
@ -186,6 +192,20 @@ $lightbox-bg-color: #454545;
|
||||
$lightbox-fg-color: #ffffff;
|
||||
$lightbox-border-color: #ffffff;
|
||||
|
||||
// Tabbed views
|
||||
$tab-label-fg-color: #45474a;
|
||||
$tab-label-active-fg-color: #ffffff;
|
||||
$tab-label-bg-color: transparent;
|
||||
$tab-label-active-bg-color: #7ac9a1;
|
||||
$tab-label-icon-bg-color: #454545;
|
||||
$tab-label-active-icon-bg-color: #ffffff;
|
||||
|
||||
// Buttons
|
||||
$button-primary-fg-color: #ffffff;
|
||||
$button-primary-bg-color: #7ac9a1;
|
||||
$button-primary-disabled-fg-color: #ffffff;
|
||||
$button-primary-disabled-bg-color: #bce4d0;
|
||||
|
||||
// unused?
|
||||
$progressbar-color: #000;
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
horizontal mess. Arial empirically gets it right, hence prioritising
|
||||
Arial here. */
|
||||
$font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
|
||||
$font-family-semibold: 'Open Sans', Arial, Helvetica, Sans-Serif;
|
||||
|
||||
// typical text (dark-on-white in light skin)
|
||||
$primary-fg-color: #454545;
|
||||
@ -66,6 +67,7 @@ $primary-hairline-color: #e5e5e5;
|
||||
|
||||
// used for the border of input text fields
|
||||
$input-border-color: #f0f0f0;
|
||||
$input-border-dark-color: #b8b8b8;
|
||||
|
||||
$input-darker-bg-color: #c1c9d6;
|
||||
$input-darker-fg-color: #9fa9ba;
|
||||
@ -92,6 +94,11 @@ $avatar-bg-color: #ffffff;
|
||||
|
||||
$h3-color: #3d3b39;
|
||||
|
||||
$dialog-title-fg-color: #454545;
|
||||
$dialog-backdrop-color: rgba(46, 48, 51, 0.38);
|
||||
$dialog-shadow-color: rgba(0, 0, 0, 0.48);
|
||||
$dialog-close-fg-color: #9fa9ba;
|
||||
|
||||
$dialog-background-bg-color: #e9e9e9;
|
||||
$lightbox-background-bg-color: #000;
|
||||
|
||||
@ -181,6 +188,20 @@ $imagebody-giflabel: rgba(0, 0, 0, 0.7);
|
||||
$imagebody-giflabel-border: rgba(0, 0, 0, 0.2);
|
||||
$imagebody-giflabel-color: rgba(255, 255, 255, 1);
|
||||
|
||||
// Tabbed views
|
||||
$tab-label-fg-color: #45474a;
|
||||
$tab-label-active-fg-color: #ffffff;
|
||||
$tab-label-bg-color: transparent;
|
||||
$tab-label-active-bg-color: #7ac9a1;
|
||||
$tab-label-icon-bg-color: #454545;
|
||||
$tab-label-active-icon-bg-color: #ffffff;
|
||||
|
||||
// Buttons
|
||||
$button-primary-fg-color: #ffffff;
|
||||
$button-primary-bg-color: #7ac9a1;
|
||||
$button-primary-disabled-fg-color: #ffffff;
|
||||
$button-primary-disabled-bg-color: #bce4d0;
|
||||
|
||||
// unused?
|
||||
$progressbar-color: #000;
|
||||
|
||||
|
@ -610,7 +610,17 @@ export default React.createClass({
|
||||
case 'view_indexed_room':
|
||||
this._viewIndexedRoom(payload.roomIndex);
|
||||
break;
|
||||
case 'view_user_settings':
|
||||
case 'view_user_settings': {
|
||||
if (SettingsStore.isFeatureEnabled("feature_tabbed_settings")) {
|
||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {});
|
||||
} else {
|
||||
this._setPage(PageTypes.UserSettings);
|
||||
this.notifyNewScreen('settings');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'view_old_user_settings':
|
||||
this._setPage(PageTypes.UserSettings);
|
||||
this.notifyNewScreen('settings');
|
||||
break;
|
||||
|
118
src/components/structures/TabbedView.js
Normal file
118
src/components/structures/TabbedView.js
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright 2017 Travis Ralston
|
||||
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 * as React from "react";
|
||||
import {_t} from '../../languageHandler';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Represents a tab for the TabbedView.
|
||||
*/
|
||||
export class Tab {
|
||||
/**
|
||||
* Creates a new tab.
|
||||
* @param {string} tabLabel The untranslated tab label.
|
||||
* @param {string} tabIconClass The class for the tab icon. This should be a simple mask.
|
||||
* @param {string} tabJsx The JSX for the tab container.
|
||||
*/
|
||||
constructor(tabLabel, tabIconClass, tabJsx) {
|
||||
this.label = tabLabel;
|
||||
this.icon = tabIconClass;
|
||||
this.body = tabJsx;
|
||||
}
|
||||
}
|
||||
|
||||
export class TabbedView extends React.Component {
|
||||
static propTypes = {
|
||||
// The tabs to show
|
||||
tabs: PropTypes.arrayOf(PropTypes.instanceOf(Tab)).isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
activeTabIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
_getActiveTabIndex() {
|
||||
if (!this.state || !this.state.activeTabIndex) return 0;
|
||||
return this.state.activeTabIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the given tab
|
||||
* @param {Tab} tab the tab to show
|
||||
* @private
|
||||
*/
|
||||
_setActiveTab(tab) {
|
||||
const idx = this.props.tabs.indexOf(tab);
|
||||
if (idx !== -1) {
|
||||
this.setState({activeTabIndex: idx});
|
||||
} else {
|
||||
console.error("Could not find tab " + tab.label + " in tabs");
|
||||
}
|
||||
}
|
||||
|
||||
_renderTabLabel(tab) {
|
||||
let classes = "mx_TabbedView_tabLabel ";
|
||||
|
||||
const idx = this.props.tabs.indexOf(tab);
|
||||
if (idx === this._getActiveTabIndex()) classes += "mx_TabbedView_tabLabel_active";
|
||||
if (tab.label === "Visit old settings") classes += "mx_TabbedView_tabLabel_TEMP_HACK";
|
||||
|
||||
let tabIcon = null;
|
||||
if (tab.icon) {
|
||||
tabIcon = <span className={`mx_TabbedView_maskedIcon ${tab.icon}`} />;
|
||||
}
|
||||
|
||||
const onClickHandler = () => this._setActiveTab(tab);
|
||||
|
||||
return (
|
||||
<span className={classes} key={"tab_label_" + tab.label}
|
||||
onClick={onClickHandler}>
|
||||
{tabIcon}
|
||||
<span className="mx_TabbedView_tabLabel_text">
|
||||
{_t(tab.label)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
_renderTabPanel(tab) {
|
||||
return (
|
||||
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
||||
{tab.body}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const labels = this.props.tabs.map(tab => this._renderTabLabel(tab));
|
||||
const panel = this._renderTabPanel(this.props.tabs[this._getActiveTabIndex()]);
|
||||
|
||||
return (
|
||||
<div className="mx_TabbedView">
|
||||
<div className="mx_TabbedView_tabLabels">
|
||||
{labels}
|
||||
</div>
|
||||
{panel}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
99
src/components/views/dialogs/UserSettingsDialog.js
Normal file
99
src/components/views/dialogs/UserSettingsDialog.js
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
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 {Tab, TabbedView} from "../../structures/TabbedView";
|
||||
import {_t, _td} from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import GeneralSettingsTab from "../settings/tabs/GeneralSettingsTab";
|
||||
import dis from '../../../dispatcher';
|
||||
|
||||
// TODO: Ditch this whole component
|
||||
export class TempTab extends React.Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
dis.dispatch({action: "view_old_user_settings"});
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default class UserSettingsDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
_getTabs() {
|
||||
return [
|
||||
new Tab(
|
||||
_td("General"),
|
||||
"mx_UserSettingsDialog_settingsIcon",
|
||||
<GeneralSettingsTab />,
|
||||
),
|
||||
new Tab(
|
||||
_td("Notifications"),
|
||||
"mx_UserSettingsDialog_bellIcon",
|
||||
<div>Notifications Test</div>,
|
||||
),
|
||||
new Tab(
|
||||
_td("Preferences"),
|
||||
"mx_UserSettingsDialog_preferencesIcon",
|
||||
<div>Preferences Test</div>,
|
||||
),
|
||||
new Tab(
|
||||
_td("Voice & Video"),
|
||||
"mx_UserSettingsDialog_voiceIcon",
|
||||
<div>Voice Test</div>,
|
||||
),
|
||||
new Tab(
|
||||
_td("Security & Privacy"),
|
||||
"mx_UserSettingsDialog_securityIcon",
|
||||
<div>Security Test</div>,
|
||||
),
|
||||
new Tab(
|
||||
_td("Help & About"),
|
||||
"mx_UserSettingsDialog_helpIcon",
|
||||
<div>Help Test</div>,
|
||||
),
|
||||
new Tab(
|
||||
_td("Visit old settings"),
|
||||
"mx_UserSettingsDialog_helpIcon",
|
||||
<TempTab onClose={this.props.onFinished} />,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="mx_UserSettingsDialog">
|
||||
<div className="mx_UserSettingsDialog_header">
|
||||
{_t("Settings")}
|
||||
<span className="mx_UserSettingsDialog_close">
|
||||
<AccessibleButton className="mx_UserSettingsDialog_closeIcon" onClick={this.props.onFinished} />
|
||||
</span>
|
||||
</div>
|
||||
<TabbedView tabs={this._getTabs()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -28,41 +28,56 @@ import { KeyCode } from '../../../Keyboard';
|
||||
* @returns {Object} rendered react
|
||||
*/
|
||||
export default function AccessibleButton(props) {
|
||||
const {element, onClick, children, ...restProps} = props;
|
||||
restProps.onClick = onClick;
|
||||
// We need to consume enter onKeyDown and space onKeyUp
|
||||
// otherwise we are risking also activating other keyboard focusable elements
|
||||
// that might receive focus as a result of the AccessibleButtonClick action
|
||||
// It's because we are using html buttons at a few places e.g. inside dialogs
|
||||
// And divs which we report as role button to assistive technologies.
|
||||
// Browsers handle space and enter keypresses differently and we are only adjusting to the
|
||||
// inconsistencies here
|
||||
restProps.onKeyDown = function(e) {
|
||||
if (e.keyCode === KeyCode.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.keyCode === KeyCode.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
restProps.onKeyUp = function(e) {
|
||||
if (e.keyCode === KeyCode.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.keyCode === KeyCode.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
const {element, onClick, children, kind, disabled, ...restProps} = props;
|
||||
|
||||
if (!disabled) {
|
||||
restProps.onClick = onClick;
|
||||
// We need to consume enter onKeyDown and space onKeyUp
|
||||
// otherwise we are risking also activating other keyboard focusable elements
|
||||
// that might receive focus as a result of the AccessibleButtonClick action
|
||||
// It's because we are using html buttons at a few places e.g. inside dialogs
|
||||
// And divs which we report as role button to assistive technologies.
|
||||
// Browsers handle space and enter keypresses differently and we are only adjusting to the
|
||||
// inconsistencies here
|
||||
restProps.onKeyDown = function(e) {
|
||||
if (e.keyCode === KeyCode.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.keyCode === KeyCode.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
restProps.onKeyUp = function(e) {
|
||||
if (e.keyCode === KeyCode.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.keyCode === KeyCode.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
restProps.tabIndex = restProps.tabIndex || "0";
|
||||
restProps.role = "button";
|
||||
restProps.className = (restProps.className ? restProps.className + " " : "") +
|
||||
"mx_AccessibleButton";
|
||||
|
||||
if (kind) {
|
||||
// We apply a hasKind class to maintain backwards compatibility with
|
||||
// buttons which might not know about kind and break
|
||||
restProps.className += " mx_AccessibleButton_hasKind mx_AccessibleButton_kind_" + kind;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
restProps.className += " mx_AccessibleButton_disabled";
|
||||
}
|
||||
|
||||
return React.createElement(element, restProps, children);
|
||||
}
|
||||
|
||||
@ -76,6 +91,12 @@ AccessibleButton.propTypes = {
|
||||
children: PropTypes.node,
|
||||
element: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
|
||||
// The kind of button, similar to how Bootstrap works.
|
||||
// See available classes for AccessibleButton for options.
|
||||
kind: PropTypes.string,
|
||||
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
AccessibleButton.defaultProps = {
|
||||
|
95
src/components/views/settings/tabs/GeneralSettingsTab.js
Normal file
95
src/components/views/settings/tabs/GeneralSettingsTab.js
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
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 {_t} from "../../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../../MatrixClientPeg";
|
||||
import Field from "../../elements/Field";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
|
||||
export default class GeneralSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
this.state = {
|
||||
userId: client.getUserId(),
|
||||
displayName: client.getUser(client.getUserId()).displayName,
|
||||
enableProfileSave: false,
|
||||
};
|
||||
}
|
||||
|
||||
_saveProfile = async (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.state.enableProfileSave) return;
|
||||
this.setState({enableProfileSave: false});
|
||||
|
||||
// TODO: What do we do about errors?
|
||||
await MatrixClientPeg.get().setDisplayName(this.state.displayName);
|
||||
|
||||
// TODO: Support avatars
|
||||
|
||||
this.setState({enableProfileSave: true});
|
||||
};
|
||||
|
||||
_onDisplayNameChanged = (e) => {
|
||||
this.setState({
|
||||
displayName: e.target.value,
|
||||
enableProfileSave: true,
|
||||
});
|
||||
};
|
||||
|
||||
_renderProfileSection() {
|
||||
// TODO: Ditch avatar placeholder and use the real thing
|
||||
const form = (
|
||||
<form onSubmit={this._saveProfile} autoComplete={false} noValidate={true}>
|
||||
<div className="mx_GeneralSettingsTab_profile">
|
||||
<div className="mx_GeneralSettingsTab_profileControls">
|
||||
<p className="mx_GeneralSettingsTab_profileUsername">{this.state.userId}</p>
|
||||
<Field id="profileDisplayName" label={_t("Display Name")}
|
||||
type="text" value={this.state.displayName} autocomplete="off"
|
||||
onChange={this._onDisplayNameChanged} />
|
||||
</div>
|
||||
<div className="mx_GeneralSettingsTab_profileAvatar">
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<AccessibleButton onClick={this._saveProfile} kind="primary"
|
||||
disabled={!this.state.enableProfileSave}>
|
||||
{_t("Save")}
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
|
||||
{form}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||
{this._renderProfileSection()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -262,6 +262,7 @@
|
||||
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
|
||||
"Failed to join room": "Failed to join room",
|
||||
"Message Pinning": "Message Pinning",
|
||||
"Tabbed settings": "Tabbed settings",
|
||||
"Custom user status messages": "Custom user status messages",
|
||||
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
|
||||
"Backup of encryption keys to server": "Backup of encryption keys to server",
|
||||
@ -401,6 +402,10 @@
|
||||
"Off": "Off",
|
||||
"On": "On",
|
||||
"Noisy": "Noisy",
|
||||
"Display Name": "Display Name",
|
||||
"Save": "Save",
|
||||
"Profile": "Profile",
|
||||
"General": "General",
|
||||
"Cannot add any more widgets": "Cannot add any more widgets",
|
||||
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
|
||||
"Add a widget": "Add a widget",
|
||||
@ -533,7 +538,6 @@
|
||||
"World readable": "World readable",
|
||||
"Guests can join": "Guests can join",
|
||||
"Failed to set avatar.": "Failed to set avatar.",
|
||||
"Save": "Save",
|
||||
"(~%(count)s results)|other": "(~%(count)s results)",
|
||||
"(~%(count)s results)|one": "(~%(count)s result)",
|
||||
"Join Room": "Join Room",
|
||||
@ -710,46 +714,6 @@
|
||||
"Removed or unknown message type": "Removed or unknown message type",
|
||||
"Message removed by %(userId)s": "Message removed by %(userId)s",
|
||||
"Message removed": "Message removed",
|
||||
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
||||
"This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot",
|
||||
"Custom Server Options": "Custom Server Options",
|
||||
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.",
|
||||
"This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.",
|
||||
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.",
|
||||
"To continue, please enter your password.": "To continue, please enter your password.",
|
||||
"Password:": "Password:",
|
||||
"Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies",
|
||||
"Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:",
|
||||
"An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s",
|
||||
"Please check your email to continue registration.": "Please check your email to continue registration.",
|
||||
"Token incorrect": "Token incorrect",
|
||||
"A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s",
|
||||
"Please enter the code it contains:": "Please enter the code it contains:",
|
||||
"Code": "Code",
|
||||
"Start authentication": "Start authentication",
|
||||
"powered by Matrix": "powered by Matrix",
|
||||
"The email field must not be blank.": "The email field must not be blank.",
|
||||
"The user name field must not be blank.": "The user name field must not be blank.",
|
||||
"The phone number field must not be blank.": "The phone number field must not be blank.",
|
||||
"The password field must not be blank.": "The password field must not be blank.",
|
||||
"Username on %(hs)s": "Username on %(hs)s",
|
||||
"User name": "User name",
|
||||
"Mobile phone number": "Mobile phone number",
|
||||
"Forgot your password?": "Forgot your password?",
|
||||
"Matrix ID": "Matrix ID",
|
||||
"%(serverName)s Matrix ID": "%(serverName)s Matrix ID",
|
||||
"Sign in with": "Sign in with",
|
||||
"Email address": "Email address",
|
||||
"Sign in": "Sign in",
|
||||
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
|
||||
"Email address (optional)": "Email address (optional)",
|
||||
"You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s",
|
||||
"Mobile phone number (optional)": "Mobile phone number (optional)",
|
||||
"Default server": "Default server",
|
||||
"Custom server": "Custom server",
|
||||
"Home server URL": "Home server URL",
|
||||
"Identity server URL": "Identity server URL",
|
||||
"What does this mean?": "What does this mean?",
|
||||
"Remove from community": "Remove from community",
|
||||
"Disinvite this user from community?": "Disinvite this user from community?",
|
||||
"Remove this user from community?": "Remove this user from community?",
|
||||
@ -886,6 +850,7 @@
|
||||
"And %(count)s more...|other": "And %(count)s more...",
|
||||
"ex. @bob:example.com": "ex. @bob:example.com",
|
||||
"Add User": "Add User",
|
||||
"Matrix ID": "Matrix ID",
|
||||
"Matrix Room ID": "Matrix Room ID",
|
||||
"email address": "email address",
|
||||
"That doesn't look like a valid email address": "That doesn't look like a valid email address",
|
||||
@ -1012,6 +977,7 @@
|
||||
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
|
||||
"Unable to add email address": "Unable to add email address",
|
||||
"Unable to verify email address.": "Unable to verify email address.",
|
||||
"Email address": "Email address",
|
||||
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
|
||||
"Skip": "Skip",
|
||||
"Only use lower case letters, numbers and '=_-./'": "Only use lower case letters, numbers and '=_-./'",
|
||||
@ -1043,6 +1009,11 @@
|
||||
"Room contains unknown devices": "Room contains unknown devices",
|
||||
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
|
||||
"Unknown devices": "Unknown devices",
|
||||
"Preferences": "Preferences",
|
||||
"Voice & Video": "Voice & Video",
|
||||
"Security & Privacy": "Security & Privacy",
|
||||
"Help & About": "Help & About",
|
||||
"Visit old settings": "Visit old settings",
|
||||
"Unable to load backup status": "Unable to load backup status",
|
||||
"Unable to restore backup": "Unable to restore backup",
|
||||
"No backup found!": "No backup found!",
|
||||
@ -1094,6 +1065,44 @@
|
||||
"Set status": "Set status",
|
||||
"Set a new status...": "Set a new status...",
|
||||
"View Community": "View Community",
|
||||
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
||||
"This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot",
|
||||
"Custom Server Options": "Custom Server Options",
|
||||
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.",
|
||||
"This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.",
|
||||
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.",
|
||||
"To continue, please enter your password.": "To continue, please enter your password.",
|
||||
"Password:": "Password:",
|
||||
"Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies",
|
||||
"Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:",
|
||||
"An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s",
|
||||
"Please check your email to continue registration.": "Please check your email to continue registration.",
|
||||
"Token incorrect": "Token incorrect",
|
||||
"A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s",
|
||||
"Please enter the code it contains:": "Please enter the code it contains:",
|
||||
"Code": "Code",
|
||||
"Start authentication": "Start authentication",
|
||||
"powered by Matrix": "powered by Matrix",
|
||||
"The email field must not be blank.": "The email field must not be blank.",
|
||||
"The user name field must not be blank.": "The user name field must not be blank.",
|
||||
"The phone number field must not be blank.": "The phone number field must not be blank.",
|
||||
"The password field must not be blank.": "The password field must not be blank.",
|
||||
"Username on %(hs)s": "Username on %(hs)s",
|
||||
"User name": "User name",
|
||||
"Mobile phone number": "Mobile phone number",
|
||||
"Forgot your password?": "Forgot your password?",
|
||||
"%(serverName)s Matrix ID": "%(serverName)s Matrix ID",
|
||||
"Sign in with": "Sign in with",
|
||||
"Sign in": "Sign in",
|
||||
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
|
||||
"Email address (optional)": "Email address (optional)",
|
||||
"You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s",
|
||||
"Mobile phone number (optional)": "Mobile phone number (optional)",
|
||||
"Default server": "Default server",
|
||||
"Custom server": "Custom server",
|
||||
"Home server URL": "Home server URL",
|
||||
"Identity server URL": "Identity server URL",
|
||||
"What does this mean?": "What does this mean?",
|
||||
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
|
||||
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.",
|
||||
"Please install <chromeLink>Chrome</chromeLink> or <firefoxLink>Firefox</firefoxLink> for the best experience.": "Please install <chromeLink>Chrome</chromeLink> or <firefoxLink>Firefox</firefoxLink> for the best experience.",
|
||||
@ -1284,7 +1293,6 @@
|
||||
"VoIP": "VoIP",
|
||||
"Email": "Email",
|
||||
"Add email address": "Add email address",
|
||||
"Profile": "Profile",
|
||||
"Display name": "Display name",
|
||||
"Account": "Account",
|
||||
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
|
||||
|
@ -84,6 +84,12 @@ export const SETTINGS = {
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_tabbed_settings": {
|
||||
isFeature: true,
|
||||
displayName: _td("Tabbed settings"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_custom_status": {
|
||||
isFeature: true,
|
||||
displayName: _td("Custom user status messages"),
|
||||
|
Loading…
Reference in New Issue
Block a user