Sort settings tabs into a logical structure
Fixes https://github.com/vector-im/riot-web/issues/8864
This commit is contained in:
parent
150c941340
commit
bd54a401bc
25 changed files with 190 additions and 190 deletions
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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 {DragDropContext} from "react-beautiful-dnd";
|
||||
import GroupUserSettings from "../../../groups/GroupUserSettings";
|
||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
||||
import PropTypes from "prop-types";
|
||||
import {MatrixClient} from "matrix-js-sdk";
|
||||
|
||||
export default class FlairUserSettingsTab extends React.Component {
|
||||
static childContextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<span className="mx_SettingsTab_heading">{_t("Flair")}</span>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<DragDropContext>
|
||||
<GroupUserSettings />
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
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 ProfileSettings from "../../ProfileSettings";
|
||||
import EmailAddresses from "../../EmailAddresses";
|
||||
import PhoneNumbers from "../../PhoneNumbers";
|
||||
import Field from "../../../elements/Field";
|
||||
import * as languageHandler from "../../../../../languageHandler";
|
||||
import {SettingLevel} from "../../../../../settings/SettingsStore";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import LanguageDropdown from "../../../elements/LanguageDropdown";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
||||
const PlatformPeg = require("../../../../../PlatformPeg");
|
||||
const sdk = require('../../../../..');
|
||||
const Modal = require("../../../../../Modal");
|
||||
const dis = require("../../../../../dispatcher");
|
||||
|
||||
export default class GeneralUserSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
language: languageHandler.getCurrentLanguage(),
|
||||
theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"),
|
||||
};
|
||||
}
|
||||
|
||||
_onLanguageChange = (newLanguage) => {
|
||||
if (this.state.language === newLanguage) return;
|
||||
|
||||
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage);
|
||||
this.setState({language: newLanguage});
|
||||
PlatformPeg.get().reload();
|
||||
};
|
||||
|
||||
_onThemeChange = (e) => {
|
||||
const newTheme = e.target.value;
|
||||
if (this.state.theme === newTheme) return;
|
||||
|
||||
SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme);
|
||||
this.setState({theme: newTheme});
|
||||
dis.dispatch({action: 'set_theme', value: newTheme});
|
||||
};
|
||||
|
||||
_onPasswordChangeError = (err) => {
|
||||
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||
let errMsg = err.error || "";
|
||||
if (err.httpStatus === 403) {
|
||||
errMsg = _t("Failed to change password. Is your password correct?");
|
||||
} else if (err.httpStatus) {
|
||||
errMsg += ` (HTTP status ${err.httpStatus})`;
|
||||
}
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to change password: " + errMsg);
|
||||
Modal.createTrackedDialog('Failed to change password', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: errMsg,
|
||||
});
|
||||
};
|
||||
|
||||
_onPasswordChanged = () => {
|
||||
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Password changed', '', ErrorDialog, {
|
||||
title: _t("Success"),
|
||||
description: _t(
|
||||
"Your password was successfully changed. You will not receive " +
|
||||
"push notifications on other devices until you log back in to them",
|
||||
) + ".",
|
||||
});
|
||||
};
|
||||
|
||||
_onDeactivateClicked = () => {
|
||||
Modal.createTrackedDialog('Deactivate Account', '', DeactivateAccountDialog, {});
|
||||
};
|
||||
|
||||
_renderProfileSection() {
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
|
||||
<ProfileSettings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderAccountSection() {
|
||||
const ChangePassword = sdk.getComponent("views.settings.ChangePassword");
|
||||
const passwordChangeForm = (
|
||||
<ChangePassword
|
||||
className="mx_GeneralUserSettingsTab_changePassword"
|
||||
rowClassName=""
|
||||
buttonKind="primary"
|
||||
onError={this._onPasswordChangeError}
|
||||
onFinished={this._onPasswordChanged} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_accountSection">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Account")}</span>
|
||||
<p className="mx_SettingsTab_subsectionText">
|
||||
{_t("Set a new account password...")}
|
||||
</p>
|
||||
{passwordChangeForm}
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
|
||||
<EmailAddresses />
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Phone numbers")}</span>
|
||||
<PhoneNumbers />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderLanguageSection() {
|
||||
// TODO: Convert to new-styled Field
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Language and region")}</span>
|
||||
<LanguageDropdown className="mx_GeneralUserSettingsTab_languageInput"
|
||||
onOptionChange={this._onLanguageChange} value={this.state.language} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderThemeSection() {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
return (
|
||||
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
|
||||
<Field id="theme" label={_t("Theme")} element="select"
|
||||
value={this.state.theme} onChange={this._onThemeChange}>
|
||||
<option value="light">{_t("Light theme")}</option>
|
||||
<option value="dark">{_t("Dark theme")}</option>
|
||||
</Field>
|
||||
<SettingsFlag name="useCompactLayout" level={SettingLevel.ACCOUNT} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderManagementSection() {
|
||||
// TODO: Improve warning text for account deactivation
|
||||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Account management")}</span>
|
||||
<span className="mx_SettingsTab_subsectionText">
|
||||
{_t("Deactivating your account is a permanent action - be careful!")}
|
||||
</span>
|
||||
<AccessibleButton onClick={this._onDeactivateClicked} kind="danger">
|
||||
{_t("Deactivate Account")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||
{this._renderProfileSection()}
|
||||
{this._renderAccountSection()}
|
||||
{this._renderLanguageSection()}
|
||||
{this._renderThemeSection()}
|
||||
{this._renderManagementSection()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
250
src/components/views/settings/tabs/user/HelpUserSettingsTab.js
Normal file
250
src/components/views/settings/tabs/user/HelpUserSettingsTab.js
Normal file
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
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 {_t, getCurrentLanguage} from "../../../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import createRoom from "../../../../../createRoom";
|
||||
const packageJson = require('../../../../../../package.json');
|
||||
const Modal = require("../../../../../Modal");
|
||||
const sdk = require("../../../../..");
|
||||
const PlatformPeg = require("../../../../../PlatformPeg");
|
||||
|
||||
// if this looks like a release, use the 'version' from package.json; else use
|
||||
// the git sha. Prepend version with v, to look like riot-web version
|
||||
const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJson.gitHead || '<local>';
|
||||
|
||||
// Simple method to help prettify GH Release Tags and Commit Hashes.
|
||||
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
|
||||
const ghVersionLabel = function(repo, token='') {
|
||||
const match = token.match(semVerRegex);
|
||||
let url;
|
||||
if (match && match[1]) { // basic semVer string possibly with commit hash
|
||||
url = (match.length > 1 && match[2])
|
||||
? `https://github.com/${repo}/commit/${match[2]}`
|
||||
: `https://github.com/${repo}/releases/tag/v${match[1]}`;
|
||||
} else {
|
||||
url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
|
||||
}
|
||||
return <a target="_blank" rel="noopener" href={url}>{ token }</a>;
|
||||
};
|
||||
|
||||
export default class HelpUserSettingsTab extends React.Component {
|
||||
static propTypes = {
|
||||
closeSettingsFn: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
vectorVersion: null,
|
||||
canUpdate: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
PlatformPeg.get().getAppVersion().then((ver) => this.setState({vectorVersion: ver})).catch((e) => {
|
||||
console.error("Error getting vector version: ", e);
|
||||
});
|
||||
PlatformPeg.get().canSelfUpdate().then((v) => this.setState({canUpdate: v})).catch((e) => {
|
||||
console.error("Error getting self updatability: ", e);
|
||||
});
|
||||
}
|
||||
|
||||
_onClearCacheAndReload = (e) => {
|
||||
if (!PlatformPeg.get()) return;
|
||||
|
||||
MatrixClientPeg.get().stopClient();
|
||||
MatrixClientPeg.get().store.deleteAllData().done(() => {
|
||||
PlatformPeg.get().reload();
|
||||
});
|
||||
};
|
||||
|
||||
_onBugReport = (e) => {
|
||||
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
||||
if (!BugReportDialog) {
|
||||
return;
|
||||
}
|
||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
||||
};
|
||||
|
||||
_onStartBotChat = (e) => {
|
||||
this.props.closeSettingsFn();
|
||||
createRoom({
|
||||
dmUserId: SdkConfig.get().welcomeUserId,
|
||||
andView: true,
|
||||
});
|
||||
};
|
||||
|
||||
_showSpoiler = (event) => {
|
||||
const target = event.target;
|
||||
target.innerHTML = target.getAttribute('data-spoiler');
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(target);
|
||||
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
};
|
||||
|
||||
_renderLegal() {
|
||||
const tocLinks = SdkConfig.get().terms_and_conditions_links;
|
||||
if (!tocLinks) return null;
|
||||
|
||||
const legalLinks = [];
|
||||
for (const tocEntry of SdkConfig.get().terms_and_conditions_links) {
|
||||
legalLinks.push(<div key={tocEntry.url}>
|
||||
<a href={tocEntry.url} rel="noopener" target="_blank">{tocEntry.text}</a>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Legal")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{legalLinks}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderCredits() {
|
||||
// Note: This is not translated because it is legal text.
|
||||
// Also, is ugly but necessary.
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Credits")}</span>
|
||||
<ul>
|
||||
<li>
|
||||
The <a href="themes/riot/img/backgrounds/valley.jpg" rel="noopener" target="_blank">
|
||||
default cover photo</a> is (C)
|
||||
<a href="https://www.flickr.com/golan" rel="noopener" target="_blank">Jesús Roncero</a>
|
||||
used under the terms of
|
||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noopener" target="_blank">
|
||||
CC-BY-SA 4.0</a>. No warranties are given.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let faqText = _t('For help with using Riot, click <a>here</a>.', {}, {
|
||||
'a': (sub) => <a href="https://about.riot.im/need-help/" rel='noopener' target='_blank'>{sub}</a>,
|
||||
});
|
||||
if (SdkConfig.get().welcomeUserId && getCurrentLanguage().startsWith('en')) {
|
||||
faqText = (
|
||||
<div>
|
||||
{
|
||||
_t('For help with using Riot, click <a>here</a> or start a chat with our ' +
|
||||
'bot using the button below.', {}, {
|
||||
'a': (sub) => <a href="https://about.riot.im/need-help/" rel='noopener'
|
||||
target='_blank'>{sub}</a>,
|
||||
})
|
||||
}
|
||||
<div>
|
||||
<AccessibleButton onClick={this._onStartBotChat} kind='primary'>
|
||||
{_t("Chat with Riot Bot")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const reactSdkVersion = REACT_SDK_VERSION !== '<local>'
|
||||
? ghVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
|
||||
: REACT_SDK_VERSION;
|
||||
const vectorVersion = this.state.vectorVersion
|
||||
? ghVersionLabel('vector-im/riot-web', this.state.vectorVersion)
|
||||
: 'unknown';
|
||||
|
||||
let olmVersion = MatrixClientPeg.get().olmVersion;
|
||||
olmVersion = olmVersion ? `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}` : '<not-enabled>';
|
||||
|
||||
let updateButton = null;
|
||||
if (this.state.canUpdate) {
|
||||
const platform = PlatformPeg.get();
|
||||
updateButton = (
|
||||
<AccessibleButton onClick={platform.startUpdateCheck} kind='primary'>
|
||||
{_t('Check for update')}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_HelpUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Help & About")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Bug reporting')}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{
|
||||
_t( "If you've submitted a bug via GitHub, debug logs can help " +
|
||||
"us track down the problem. Debug logs contain application " +
|
||||
"usage data including your username, the IDs or aliases of " +
|
||||
"the rooms or groups you have visited and the usernames of " +
|
||||
"other users. They do not contain messages.",
|
||||
)
|
||||
}
|
||||
<div className='mx_HelpUserSettingsTab_debugButton'>
|
||||
<AccessibleButton onClick={this._onBugReport} kind='primary'>
|
||||
{_t("Submit debug logs")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div className='mx_HelpUserSettingsTab_debugButton'>
|
||||
<AccessibleButton onClick={this._onClearCacheAndReload} kind='danger'>
|
||||
{_t("Clear Cache and Reload")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("FAQ")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{faqText}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Versions")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("matrix-react-sdk version:")} {reactSdkVersion}<br />
|
||||
{_t("riot-web version:")} {vectorVersion}<br />
|
||||
{_t("olm version:")} {olmVersion}<br />
|
||||
{updateButton}
|
||||
</div>
|
||||
</div>
|
||||
{this._renderLegal()}
|
||||
{this._renderCredits()}
|
||||
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Advanced")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("Homeserver is")} {MatrixClientPeg.get().getHomeserverUrl()}<br />
|
||||
{_t("Identity Server is")} {MatrixClientPeg.get().getIdentityServerUrl()}<br />
|
||||
{_t("Access Token:") + ' '}
|
||||
<AccessibleButton element="span" onClick={this._showSpoiler}
|
||||
data-spoiler={MatrixClientPeg.get().getAccessToken()}>
|
||||
<{ _t("click to reveal") }>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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 PropTypes from "prop-types";
|
||||
import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
const sdk = require("../../../../..");
|
||||
|
||||
export class LabsSettingToggle extends React.Component {
|
||||
static propTypes = {
|
||||
featureId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
_onChange = async (checked) => {
|
||||
await SettingsStore.setFeatureEnabled(this.props.featureId, checked);
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
render() {
|
||||
const label = _t(SettingsStore.getDisplayName(this.props.featureId));
|
||||
const value = SettingsStore.isFeatureEnabled(this.props.featureId);
|
||||
return <LabelledToggleSwitch value={value} label={label} onChange={this._onChange} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default class LabsUserSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
render() {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
const flags = SettingsStore.getLabsFeatures().map(f => <LabsSettingToggle featureId={f} key={f} />);
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Labs")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{flags}
|
||||
<SettingsFlag name={"enableWidgetScreenshots"} level={SettingLevel.ACCOUNT} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
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";
|
||||
const sdk = require("../../../../..");
|
||||
|
||||
export default class NotificationUserSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
render() {
|
||||
const Notifications = sdk.getComponent("views.settings.Notifications");
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_NotificationUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Notifications")}</div>
|
||||
<div className="mx_SettingsTab_section mx_SettingsTab_subsectionText">
|
||||
<Notifications />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
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 {SettingLevel} from "../../../../../settings/SettingsStore";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import Field from "../../../elements/Field";
|
||||
const sdk = require("../../../../..");
|
||||
const PlatformPeg = require("../../../../../PlatformPeg");
|
||||
|
||||
export default class PreferencesUserSettingsTab extends React.Component {
|
||||
static COMPOSER_SETTINGS = [
|
||||
'MessageComposerInput.autoReplaceEmoji',
|
||||
'MessageComposerInput.suggestEmoji',
|
||||
'sendTypingNotifications',
|
||||
];
|
||||
|
||||
static TIMELINE_SETTINGS = [
|
||||
'autoplayGifsAndVideos',
|
||||
'urlPreviewsEnabled',
|
||||
'TextualBody.enableBigEmoji',
|
||||
'showReadReceipts',
|
||||
'showTwelveHourTimestamps',
|
||||
'alwaysShowTimestamps',
|
||||
'showRedactions',
|
||||
'enableSyntaxHighlightLanguageDetection',
|
||||
'showJoinLeaves',
|
||||
'showAvatarChanges',
|
||||
'showDisplaynameChanges',
|
||||
];
|
||||
|
||||
static ADVANCED_SETTINGS = [
|
||||
'alwaysShowEncryptionIcons',
|
||||
'Pill.shouldShowPillAvatar',
|
||||
'TagPanel.enableTagPanel',
|
||||
'promptBeforeInviteUnknownUsers',
|
||||
// Start automatically after startup (electron-only)
|
||||
// Autocomplete delay (niche text box)
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
autoLaunch: false,
|
||||
autoLaunchSupported: false,
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount(): void {
|
||||
const autoLaunchSupported = await PlatformPeg.get().supportsAutoLaunch();
|
||||
let autoLaunch = false;
|
||||
|
||||
if (autoLaunchSupported) {
|
||||
autoLaunch = await PlatformPeg.get().getAutoLaunchEnabled();
|
||||
}
|
||||
|
||||
this.setState({autoLaunch, autoLaunchSupported});
|
||||
}
|
||||
|
||||
_onAutoLaunchChange = (checked) => {
|
||||
PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked}));
|
||||
};
|
||||
|
||||
_onAutocompleteDelayChange = (e) => {
|
||||
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
|
||||
};
|
||||
|
||||
_renderGroup(settingIds) {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
return settingIds.map(i => <SettingsFlag key={i} name={i} level={SettingLevel.ACCOUNT} />);
|
||||
}
|
||||
|
||||
render() {
|
||||
let autoLaunchOption = null;
|
||||
if (this.state.autoLaunchSupported) {
|
||||
autoLaunchOption = <LabelledToggleSwitch value={this.state.autoLaunch}
|
||||
onChange={this._onAutoLaunchChange}
|
||||
label={_t('Start automatically after system login')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_PreferencesUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Preferences")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Composer")}</span>
|
||||
{this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)}
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Timeline")}</span>
|
||||
{this._renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Advanced")}</span>
|
||||
{this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)}
|
||||
{autoLaunchOption}
|
||||
<Field id={"autocompleteDelay"} label={_t('Autocomplete delay (ms)')} type='number'
|
||||
value={SettingsStore.getValueAt(SettingLevel.DEVICE, 'autocompleteDelay')}
|
||||
onChange={this._onAutocompleteDelayChange} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
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 {_t} from "../../../../../languageHandler";
|
||||
import {SettingLevel} from "../../../../../settings/SettingsStore";
|
||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
||||
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import Analytics from "../../../../../Analytics";
|
||||
import Promise from "bluebird";
|
||||
import Modal from "../../../../../Modal";
|
||||
import sdk from "../../../../..";
|
||||
|
||||
export class IgnoredUser extends React.Component {
|
||||
static propTypes = {
|
||||
userId: PropTypes.string.isRequired,
|
||||
onUnignored: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
_onUnignoreClicked = (e) => {
|
||||
this.props.onUnignored(this.props.userId);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='mx_SecurityUserSettingsTab_ignoredUser'>
|
||||
<AccessibleButton onClick={this._onUnignoreClicked} kind='primary_sm'>
|
||||
{_t('Unignore')}
|
||||
</AccessibleButton>
|
||||
<span>{this.props.userId}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class SecurityUserSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
ignoredUserIds: MatrixClientPeg.get().getIgnoredUsers(),
|
||||
rejectingInvites: false,
|
||||
};
|
||||
}
|
||||
|
||||
_updateBlacklistDevicesFlag = (checked) => {
|
||||
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
|
||||
};
|
||||
|
||||
_updateAnalytics = (checked) => {
|
||||
checked ? Analytics.enable() : Analytics.disable();
|
||||
};
|
||||
|
||||
_onExportE2eKeysClicked = () => {
|
||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||
import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
||||
{matrixClient: MatrixClientPeg.get()},
|
||||
);
|
||||
};
|
||||
|
||||
_onImportE2eKeysClicked = () => {
|
||||
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
||||
import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'),
|
||||
{matrixClient: MatrixClientPeg.get()},
|
||||
);
|
||||
};
|
||||
|
||||
_onUserUnignored = async (userId) => {
|
||||
// Don't use this.state to get the ignored user list as it might be
|
||||
// ever so slightly outdated. Instead, prefer to get a fresh list and
|
||||
// update that.
|
||||
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
|
||||
const index = ignoredUsers.indexOf(userId);
|
||||
if (index !== -1) {
|
||||
ignoredUsers.splice(index, 1);
|
||||
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers);
|
||||
}
|
||||
this.setState({ignoredUsers});
|
||||
};
|
||||
|
||||
_onRejectAllInvitesClicked = (rooms, ev) => {
|
||||
this.setState({
|
||||
rejectingInvites: true,
|
||||
});
|
||||
// reject the invites
|
||||
const promises = rooms.map((room) => {
|
||||
return MatrixClientPeg.get().leave(room.roomId).catch((e) => {
|
||||
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
|
||||
// after trying to reject all the invites.
|
||||
});
|
||||
});
|
||||
Promise.all(promises).then(() => {
|
||||
this.setState({
|
||||
rejectingInvites: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_renderCurrentDeviceInfo() {
|
||||
const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const deviceId = client.deviceId;
|
||||
let identityKey = client.getDeviceEd25519Key();
|
||||
if (!identityKey) {
|
||||
identityKey = _t("<not supported>");
|
||||
} else {
|
||||
identityKey = FormattingUtils.formatCryptoKey(identityKey);
|
||||
}
|
||||
|
||||
let importExportButtons = null;
|
||||
if (client.isCryptoEnabled()) {
|
||||
importExportButtons = (
|
||||
<div className='mx_SecurityUserSettingsTab_importExportButtons'>
|
||||
<AccessibleButton kind='primary' onClick={this._onExportE2eKeysClicked}>
|
||||
{_t("Export E2E room keys")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind='primary' onClick={this._onImportE2eKeysClicked}>
|
||||
{_t("Import E2E room keys")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Cryptography")}</span>
|
||||
<ul className='mx_SettingsTab_subsectionText mx_SecurityUserSettingsTab_deviceInfo'>
|
||||
<li>
|
||||
<label>{_t("Device ID:")}</label>
|
||||
<span><code>{deviceId}</code></span>
|
||||
</li>
|
||||
<li>
|
||||
<label>{_t("Device key:")}</label>
|
||||
<span><code><b>{identityKey}</b></code></span>
|
||||
</li>
|
||||
</ul>
|
||||
{importExportButtons}
|
||||
<SettingsFlag name='blacklistUnverifiedDevices' level={SettingLevel.DEVICE}
|
||||
onChange={this._updateBlacklistDevicesFlag} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderIgnoredUsers() {
|
||||
if (!this.state.ignoredUserIds || this.state.ignoredUserIds.length === 0) return null;
|
||||
|
||||
const userIds = this.state.ignoredUserIds
|
||||
.map((u) => <IgnoredUser userId={u} onUnignored={this._onUserUnignored} key={u} />);
|
||||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Ignored users')}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{userIds}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRejectInvites() {
|
||||
const invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
|
||||
return r.hasMembershipState(MatrixClientPeg.get().getUserId(), "invite");
|
||||
});
|
||||
if (invitedRooms.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onClick = this._onRejectAllInvitesClicked.bind(this, invitedRooms);
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Bulk options')}</span>
|
||||
<AccessibleButton onClick={onClick} kind='danger' disabled={this.state.rejectingInvites}>
|
||||
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const DevicesPanel = sdk.getComponent('views.settings.DevicesPanel');
|
||||
const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
|
||||
|
||||
const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel');
|
||||
const keyBackup = (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Key backup")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<KeyBackupPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Devices")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<DevicesPanel />
|
||||
</div>
|
||||
</div>
|
||||
{keyBackup}
|
||||
{this._renderCurrentDeviceInfo()}
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Analytics")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("Riot collects anonymous analytics to allow us to improve the application.")}
|
||||
|
||||
{_t("Privacy is important to us, so we don't collect any personal or " +
|
||||
"identifiable data for our analytics.")}
|
||||
<AccessibleButton className="mx_SettingsTab_linkBtn" onClick={Analytics.showDetailsModal}>
|
||||
{_t("Learn more about how we use analytics.")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<SettingsFlag name='analyticsOptIn' level={SettingLevel.DEVICE}
|
||||
onChange={this._updateAnalytics} />
|
||||
</div>
|
||||
{this._renderIgnoredUsers()}
|
||||
{this._renderRejectInvites()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
182
src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
Normal file
182
src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
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 CallMediaHandler from "../../../../../CallMediaHandler";
|
||||
import Field from "../../../elements/Field";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import {SettingLevel} from "../../../../../settings/SettingsStore";
|
||||
const Modal = require("../../../../../Modal");
|
||||
const sdk = require("../../../../..");
|
||||
const MatrixClientPeg = require("../../../../../MatrixClientPeg");
|
||||
|
||||
export default class VoiceUserSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
mediaDevices: null,
|
||||
activeAudioOutput: null,
|
||||
activeAudioInput: null,
|
||||
activeVideoInput: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
this._refreshMediaDevices();
|
||||
}
|
||||
|
||||
_refreshMediaDevices = async (stream) => {
|
||||
if (stream) {
|
||||
// kill stream so that we don't leave it lingering around with webcam enabled etc
|
||||
// as here we called gUM to ask user for permission to their device names only
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
|
||||
this.setState({
|
||||
mediaDevices: await CallMediaHandler.getDevices(),
|
||||
activeAudioOutput: CallMediaHandler.getAudioOutput(),
|
||||
activeAudioInput: CallMediaHandler.getAudioInput(),
|
||||
activeVideoInput: CallMediaHandler.getVideoInput(),
|
||||
});
|
||||
};
|
||||
|
||||
_requestMediaPermissions = () => {
|
||||
const getUserMedia = (
|
||||
window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia
|
||||
);
|
||||
if (getUserMedia) {
|
||||
return getUserMedia.apply(window.navigator, [
|
||||
{ video: true, audio: true },
|
||||
this._refreshMediaDevices,
|
||||
function() {
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
Modal.createTrackedDialog('No media permissions', '', ErrorDialog, {
|
||||
title: _t('No media permissions'),
|
||||
description: _t('You may need to manually permit Riot to access your microphone/webcam'),
|
||||
});
|
||||
},
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
_setAudioOutput = (e) => {
|
||||
CallMediaHandler.setAudioOutput(e.target.value);
|
||||
};
|
||||
|
||||
_setAudioInput = (e) => {
|
||||
CallMediaHandler.setAudioInput(e.target.value);
|
||||
};
|
||||
|
||||
_setVideoInput = (e) => {
|
||||
CallMediaHandler.setVideoInput(e.target.value);
|
||||
};
|
||||
|
||||
_changeWebRtcMethod = (p2p) => {
|
||||
MatrixClientPeg.get().setForceTURN(!p2p);
|
||||
};
|
||||
|
||||
_renderDeviceOptions(devices, category) {
|
||||
return devices.map((d) => <option key={`${category}-${d.deviceId}`} value={d.deviceId}>{d.label}</option>);
|
||||
}
|
||||
|
||||
render() {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
|
||||
let requestButton = null;
|
||||
let speakerDropdown = null;
|
||||
let microphoneDropdown = null;
|
||||
let webcamDropdown = null;
|
||||
if (this.state.mediaDevices === false) {
|
||||
requestButton = (
|
||||
<div className='mx_VoiceUserSettingsTab_missingMediaPermissions'>
|
||||
<p>{_t("Missing media permissions, click the button below to request.")}</p>
|
||||
<AccessibleButton onClick={this._requestMediaPermissions} kind="primary">
|
||||
{_t("Request media permissions")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.mediaDevices) {
|
||||
speakerDropdown = <p>{ _t('No Audio Outputs detected') }</p>;
|
||||
microphoneDropdown = <p>{ _t('No Microphones detected') }</p>;
|
||||
webcamDropdown = <p>{ _t('No Webcams detected') }</p>;
|
||||
|
||||
const defaultOption = {
|
||||
deviceId: '',
|
||||
label: _t('Default Device'),
|
||||
};
|
||||
const getDefaultDevice = (devices) => {
|
||||
if (!devices.some((i) => i.deviceId === 'default')) {
|
||||
devices.unshift(defaultOption);
|
||||
return '';
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
const audioOutputs = this.state.mediaDevices.audiooutput.slice(0);
|
||||
if (audioOutputs.length > 0) {
|
||||
const defaultDevice = getDefaultDevice(audioOutputs);
|
||||
speakerDropdown = (
|
||||
<Field element="select" label={_t("Audio Output")} id="audioOutput"
|
||||
value={this.state.activeAudioOutput || defaultDevice}
|
||||
onChange={this._setAudioOutput}>
|
||||
{this._renderDeviceOptions(audioOutputs, 'audioOutput')}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
const audioInputs = this.state.mediaDevices.audioinput.slice(0);
|
||||
if (audioInputs.length > 0) {
|
||||
const defaultDevice = getDefaultDevice(audioInputs);
|
||||
microphoneDropdown = (
|
||||
<Field element="select" label={_t("Microphone")} id="audioInput"
|
||||
value={this.state.activeAudioInput || defaultDevice}
|
||||
onChange={this._setAudioInput}>
|
||||
{this._renderDeviceOptions(audioInputs, 'audioInput')}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
const videoInputs = this.state.mediaDevices.videoinput.slice(0);
|
||||
if (videoInputs.length > 0) {
|
||||
const defaultDevice = getDefaultDevice(videoInputs);
|
||||
webcamDropdown = (
|
||||
<Field element="select" label={_t("Camera")} id="videoInput"
|
||||
value={this.state.activeVideoInput || defaultDevice}
|
||||
onChange={this._setVideoInput}>
|
||||
{this._renderDeviceOptions(videoInputs, 'videoInput')}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_VoiceUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Voice & Video")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{requestButton}
|
||||
{speakerDropdown}
|
||||
{microphoneDropdown}
|
||||
{webcamDropdown}
|
||||
<SettingsFlag name='VideoView.flipVideoHorizontally' level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag name='webRtcAllowPeerToPeer' level={SettingLevel.DEVICE} onChange={this._changeWebRtcMethod} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue