2015-11-30 15:52:41 +00:00
/ *
2016-01-07 04:06:39 +00:00
Copyright 2015 , 2016 OpenMarket Ltd
2017-03-22 15:18:27 +00:00
Copyright 2017 Vector Creations Ltd
2015-11-30 15:52:41 +00:00
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 .
* /
2017-05-02 20:17:12 +00:00
const React = require ( 'react' ) ;
const ReactDOM = require ( 'react-dom' ) ;
const sdk = require ( '../../index' ) ;
const MatrixClientPeg = require ( "../../MatrixClientPeg" ) ;
const PlatformPeg = require ( "../../PlatformPeg" ) ;
const Modal = require ( '../../Modal' ) ;
const dis = require ( "../../dispatcher" ) ;
const q = require ( 'q' ) ;
const packageJson = require ( '../../../package.json' ) ;
const UserSettingsStore = require ( '../../UserSettingsStore' ) ;
2017-05-05 19:57:18 +00:00
const CallMediaHandler = require ( '../../CallMediaHandler' ) ;
2017-05-02 20:17:12 +00:00
const GeminiScrollbar = require ( 'react-gemini-scrollbar' ) ;
const Email = require ( '../../email' ) ;
const AddThreepid = require ( '../../AddThreepid' ) ;
const SdkConfig = require ( '../../SdkConfig' ) ;
2017-05-29 13:36:50 +00:00
import Analytics from '../../Analytics' ;
2017-01-24 22:41:52 +00:00
import AccessibleButton from '../views/elements/AccessibleButton' ;
2017-05-25 10:39:08 +00:00
import { _t } from '../../languageHandler' ;
2017-05-24 13:36:14 +00:00
import * as languageHandler from '../../languageHandler' ;
2017-05-22 11:01:09 +00:00
import * as FormattingUtils from '../../utils/FormattingUtils' ;
2015-11-30 15:52:41 +00:00
2016-06-17 16:09:52 +00:00
// if this looks like a release, use the 'version' from package.json; else use
2017-04-21 02:04:34 +00:00
// the git sha. Prepend version with v, to look like riot-web version
2017-05-02 20:17:12 +00:00
const REACT _SDK _VERSION = 'dist' in packageJson ? packageJson . version : packageJson . gitHead || '<local>' ;
2016-06-17 16:09:52 +00:00
2017-04-21 02:04:34 +00:00
// Simple method to help prettify GH Release Tags and Commit Hashes.
2017-05-04 15:22:39 +00:00
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i ;
2017-05-13 14:04:20 +00:00
const gHVersionLabel = function ( repo , token = '' ) {
2017-05-02 20:12:58 +00:00
const match = token . match ( semVerRegex ) ;
2017-05-04 15:22:39 +00:00
let url ;
2017-05-02 20:12:58 +00:00
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 ] } ` ;
}
2017-05-23 11:24:27 +00:00
return < a target = "_blank" rel = "noopener" href = { url } > { token } < / a > ;
2017-05-02 20:12:58 +00:00
} ;
2017-01-18 14:06:47 +00:00
2017-01-18 16:36:27 +00:00
// Enumerate some simple 'flip a bit' UI settings (if any).
// 'id' gives the key name in the im.vector.web.settings account data event
// 'label' is how we describe it in the UI.
2017-05-29 20:56:45 +00:00
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
2017-05-23 14:16:31 +00:00
// since they will be translated when rendered.
2017-01-18 14:06:47 +00:00
const SETTINGS _LABELS = [
2017-02-27 22:17:43 +00:00
{
id : 'autoplayGifsAndVideos' ,
label : 'Autoplay GIFs and videos' ,
} ,
2017-04-21 20:28:28 +00:00
{
id : 'hideReadReceipts' ,
2017-05-02 20:17:12 +00:00
label : 'Hide read receipts' ,
2017-04-21 20:28:28 +00:00
} ,
2017-04-21 20:50:26 +00:00
{
id : 'dontSendTypingNotifications' ,
label : "Don't send typing notifications" ,
} ,
2017-01-18 14:06:47 +00:00
{
id : 'alwaysShowTimestamps' ,
label : 'Always show message timestamps' ,
} ,
{
id : 'showTwelveHourTimestamps' ,
label : 'Show timestamps in 12 hour format (e.g. 2:30pm)' ,
} ,
{
id : 'useCompactLayout' ,
label : 'Use compact timeline layout' ,
} ,
2017-06-01 02:00:30 +00:00
/ *
2017-01-18 14:06:47 +00:00
{
id : 'useFixedWidthFont' ,
label : 'Use fixed width font' ,
} ,
* /
] ;
2017-05-29 14:11:37 +00:00
const ANALYTICS _SETTINGS _LABELS = [
{
id : 'analyticsOptOut' ,
label : 'Opt out of analytics' ,
fn : function ( checked ) {
Analytics [ checked ? 'disable' : 'enable' ] ( ) ;
} ,
} ,
] ;
2017-05-29 20:56:45 +00:00
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
2017-05-23 14:16:31 +00:00
// since they will be translated when rendered.
2017-01-21 17:39:31 +00:00
const CRYPTO _SETTINGS _LABELS = [
{
id : 'blacklistUnverifiedDevices' ,
2017-01-22 00:28:43 +00:00
label : 'Never send encrypted messages to unverified devices from this device' ,
2017-05-29 14:08:11 +00:00
fn : function ( checked ) {
MatrixClientPeg . get ( ) . setGlobalBlacklistUnverifiedDevices ( checked ) ;
} ,
2017-01-21 17:39:31 +00:00
} ,
// XXX: this is here for documentation; the actual setting is managed via RoomSettings
// {
// id: 'blacklistUnverifiedDevicesPerRoom'
// label: 'Never send encrypted messages to unverified devices in this room',
// }
] ;
2017-01-18 14:06:47 +00:00
// Enumerate the available themes, with a nice human text label.
2017-01-18 16:36:27 +00:00
// 'id' gives the key name in the im.vector.web.settings account data event
// 'value' is the value for that key in the event
// 'label' is how we describe it in the UI.
//
2017-01-18 14:06:47 +00:00
// XXX: Ideally we would have a theme manifest or something and they'd be nicely
// packaged up in a single directory, and/or located at the application layer.
// But for now for expedience we just hardcode them here.
const THEMES = [
{
id : 'theme' ,
label : 'Light theme' ,
value : 'light' ,
} ,
{
id : 'theme' ,
label : 'Dark theme' ,
value : 'dark' ,
2017-05-02 20:17:12 +00:00
} ,
2017-01-18 14:06:47 +00:00
] ;
2015-11-30 15:52:41 +00:00
module . exports = React . createClass ( {
displayName : 'UserSettings' ,
2015-12-18 00:37:56 +00:00
2015-12-23 11:47:56 +00:00
propTypes : {
2016-06-08 13:54:34 +00:00
onClose : React . PropTypes . func ,
// The brand string given when creating email pushers
brand : React . PropTypes . string ,
2016-08-05 15:13:06 +00:00
// True to show the 'labs' section of experimental features
2016-08-05 15:36:35 +00:00
enableLabs : React . PropTypes . bool ,
2016-09-13 11:18:22 +00:00
2017-01-31 15:17:43 +00:00
// The base URL to use in the referral link. Defaults to window.location.origin.
referralBaseUrl : React . PropTypes . string ,
2016-09-13 11:18:22 +00:00
// true if RightPanel is collapsed
collapsedRhs : React . PropTypes . bool ,
2017-02-16 18:00:52 +00:00
// Team token for the referral link. If falsy, the referral section will
// not appear
teamToken : React . PropTypes . string ,
2015-12-23 11:47:56 +00:00
} ,
getDefaultProps : function ( ) {
return {
2016-08-05 15:13:06 +00:00
onClose : function ( ) { } ,
enableLabs : true ,
2015-12-23 11:47:56 +00:00
} ;
2015-11-30 15:52:41 +00:00
} ,
getInitialState : function ( ) {
return {
avatarUrl : null ,
2017-05-13 14:04:20 +00:00
threepids : [ ] ,
2015-12-23 16:02:18 +00:00
phase : "UserSettings.LOADING" , // LOADING, DISPLAY
2016-01-19 16:36:54 +00:00
email _add _pending : false ,
2017-05-13 14:04:20 +00:00
vectorVersion : undefined ,
2016-12-14 16:00:50 +00:00
rejectingInvites : false ,
2017-04-28 17:21:22 +00:00
mediaDevices : null ,
2015-11-30 15:52:41 +00:00
} ;
} ,
componentWillMount : function ( ) {
2016-11-08 11:43:24 +00:00
this . _unmounted = false ;
2017-03-16 14:56:26 +00:00
this . _addThreepid = null ;
2016-11-08 11:43:24 +00:00
2016-11-08 10:45:19 +00:00
if ( PlatformPeg . get ( ) ) {
2016-11-11 10:05:53 +00:00
q ( ) . then ( ( ) => {
return PlatformPeg . get ( ) . getAppVersion ( ) ;
} ) . done ( ( appVersion ) => {
2016-11-08 11:43:24 +00:00
if ( this . _unmounted ) return ;
2016-11-08 10:45:19 +00:00
this . setState ( {
vectorVersion : appVersion ,
} ) ;
} , ( e ) => {
console . log ( "Failed to fetch app version" , e ) ;
} ) ;
}
2017-05-25 00:01:40 +00:00
this . _refreshMediaDevices ( ) ;
2017-04-28 17:21:22 +00:00
2016-12-14 16:00:50 +00:00
// Bulk rejecting invites:
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
// will still return rooms with invites. To get around this, add a listener for
// membership updates and kick the UI.
MatrixClientPeg . get ( ) . on ( "RoomMember.membership" , this . _onInviteStateChange ) ;
2016-04-15 17:30:13 +00:00
dis . dispatch ( {
action : 'ui_opacity' ,
sideOpacity : 0.3 ,
middleOpacity : 0.3 ,
} ) ;
2015-12-23 16:52:59 +00:00
this . _refreshFromServer ( ) ;
2017-01-18 14:06:47 +00:00
2017-05-02 20:17:12 +00:00
const syncedSettings = UserSettingsStore . getSyncedSettings ( ) ;
2017-01-18 14:06:47 +00:00
if ( ! syncedSettings . theme ) {
syncedSettings . theme = 'light' ;
}
this . _syncedSettings = syncedSettings ;
2017-01-21 17:39:31 +00:00
this . _localSettings = UserSettingsStore . getLocalSettings ( ) ;
2017-05-24 14:40:50 +00:00
if ( PlatformPeg . get ( ) . isElectron ( ) ) {
const { ipcRenderer } = require ( 'electron' ) ;
2017-05-24 14:55:37 +00:00
ipcRenderer . on ( 'settings' , this . _electronSettings ) ;
2017-05-24 14:40:50 +00:00
ipcRenderer . send ( 'settings_get' ) ;
}
2017-05-29 20:56:45 +00:00
2017-05-25 18:53:27 +00:00
this . setState ( {
language : languageHandler . getCurrentLanguage ( ) ,
} ) ;
2015-11-30 15:52:41 +00:00
} ,
2015-12-18 00:37:56 +00:00
componentDidMount : function ( ) {
this . dispatcherRef = dis . register ( this . onAction ) ;
2015-12-23 16:02:18 +00:00
this . _me = MatrixClientPeg . get ( ) . credentials . userId ;
2015-12-18 00:37:56 +00:00
} ,
componentWillUnmount : function ( ) {
2016-11-08 11:43:24 +00:00
this . _unmounted = true ;
2016-04-15 17:30:13 +00:00
dis . dispatch ( {
action : 'ui_opacity' ,
sideOpacity : 1.0 ,
middleOpacity : 1.0 ,
} ) ;
2015-12-18 00:37:56 +00:00
dis . unregister ( this . dispatcherRef ) ;
2017-05-02 20:17:12 +00:00
const cli = MatrixClientPeg . get ( ) ;
2016-12-15 16:13:09 +00:00
if ( cli ) {
cli . removeListener ( "RoomMember.membership" , this . _onInviteStateChange ) ;
}
2017-05-24 14:55:37 +00:00
if ( PlatformPeg . get ( ) . isElectron ( ) ) {
const { ipcRenderer } = require ( 'electron' ) ;
ipcRenderer . removeListener ( 'settings' , this . _electronSettings ) ;
}
} ,
_electronSettings : function ( ev , settings ) {
this . setState ( { electron _settings : settings } ) ;
2015-12-18 00:37:56 +00:00
} ,
2017-05-25 00:01:40 +00:00
_refreshMediaDevices : function ( ) {
q ( ) . then ( ( ) => {
return CallMediaHandler . getDevices ( ) ;
} ) . then ( ( mediaDevices ) => {
// console.log("got mediaDevices", mediaDevices, this._unmounted);
if ( this . _unmounted ) return ;
this . setState ( {
mediaDevices ,
2017-06-01 22:25:44 +00:00
activeAudioInput : this . _localSettings [ 'webrtc_audioinput' ] ,
activeVideoInput : this . _localSettings [ 'webrtc_videoinput' ] ,
2017-05-25 00:01:40 +00:00
} ) ;
} ) ;
} ,
2015-12-23 16:52:59 +00:00
_refreshFromServer : function ( ) {
2017-05-02 20:17:12 +00:00
const self = this ;
2015-12-23 16:52:59 +00:00
q . all ( [
2017-05-02 20:17:12 +00:00
UserSettingsStore . loadProfileInfo ( ) , UserSettingsStore . loadThreePids ( ) ,
2015-12-23 16:52:59 +00:00
] ) . done ( function ( resps ) {
2015-12-23 11:47:56 +00:00
self . setState ( {
2015-12-23 16:52:59 +00:00
avatarUrl : resps [ 0 ] . avatar _url ,
threepids : resps [ 1 ] . threepids ,
2015-12-23 11:47:56 +00:00
phase : "UserSettings.DISPLAY" ,
2015-12-18 00:37:56 +00:00
} ) ;
2015-12-23 11:47:56 +00:00
} , function ( error ) {
2017-05-02 20:17:12 +00:00
const ErrorDialog = sdk . getComponent ( "dialogs.ErrorDialog" ) ;
2017-03-12 22:59:41 +00:00
console . error ( "Failed to load user settings: " + error ) ;
2015-12-23 11:47:56 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Can't load user settings" ) ,
description : ( ( error && error . message ) ? error . message : _t ( "Server may be unavailable or overloaded" ) ) ,
2015-12-23 11:47:56 +00:00
} ) ;
2015-12-23 16:52:59 +00:00
} ) ;
} ,
2015-12-18 00:37:56 +00:00
onAction : function ( payload ) {
if ( payload . action === "notifier_enabled" ) {
2015-12-23 17:06:30 +00:00
this . forceUpdate ( ) ;
2015-12-18 00:37:56 +00:00
}
2015-11-30 15:52:41 +00:00
} ,
2016-01-15 12:35:30 +00:00
onAvatarPickerClick : function ( ev ) {
2016-03-15 23:55:59 +00:00
if ( MatrixClientPeg . get ( ) . isGuest ( ) ) {
2017-05-02 20:17:12 +00:00
const NeedToRegisterDialog = sdk . getComponent ( "dialogs.NeedToRegisterDialog" ) ;
2016-03-22 13:50:27 +00:00
Modal . createDialog ( NeedToRegisterDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Please Register" ) ,
2017-05-27 17:20:35 +00:00
description : _t ( "Guests can't set avatars. Please register." ) ,
2016-03-15 23:55:59 +00:00
} ) ;
return ;
}
2016-01-15 12:35:30 +00:00
if ( this . refs . file _label ) {
this . refs . file _label . click ( ) ;
}
} ,
2015-12-23 16:52:59 +00:00
onAvatarSelected : function ( ev ) {
2017-05-02 20:17:12 +00:00
const self = this ;
const changeAvatar = this . refs . changeAvatar ;
2015-12-23 16:52:59 +00:00
if ( ! changeAvatar ) {
console . error ( "No ChangeAvatar found to upload image to!" ) ;
return ;
}
2015-12-23 17:30:25 +00:00
changeAvatar . onFileSelected ( ev ) . done ( function ( ) {
// dunno if the avatar changed, re-check it.
self . _refreshFromServer ( ) ;
} , function ( err ) {
2017-05-02 20:17:12 +00:00
// const errMsg = (typeof err === "string") ? err : (err.error || "");
2017-03-12 22:59:41 +00:00
console . error ( "Failed to set avatar: " + err ) ;
2017-05-02 20:17:12 +00:00
const ErrorDialog = sdk . getComponent ( "dialogs.ErrorDialog" ) ;
2015-12-23 16:52:59 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-27 17:20:35 +00:00
title : _t ( "Failed to set avatar." ) ,
2017-05-23 14:16:31 +00:00
description : ( ( err && err . message ) ? err . message : _t ( "Operation failed" ) ) ,
2015-12-23 16:52:59 +00:00
} ) ;
2015-12-23 17:30:25 +00:00
} ) ;
2015-11-30 15:52:41 +00:00
} ,
onLogoutClicked : function ( ev ) {
2017-05-02 20:17:12 +00:00
const QuestionDialog = sdk . getComponent ( "dialogs.QuestionDialog" ) ;
2017-01-24 22:18:25 +00:00
Modal . createDialog ( QuestionDialog , {
2017-05-25 18:55:54 +00:00
title : _t ( "Sign out" ) ,
2017-01-24 22:18:25 +00:00
description :
< div >
2017-05-27 17:20:35 +00:00
{ _t ( "For security, logging out will delete any end-to-end " +
"encryption keys from this browser. If you want to be able " +
"to decrypt your conversation history from future Riot sessions, " +
"please export your room keys for safe-keeping." ) } .
2017-01-24 22:18:25 +00:00
< / d i v > ,
2017-05-23 14:16:31 +00:00
button : _t ( "Sign out" ) ,
2017-03-16 14:18:18 +00:00
extraButtons : [
2017-04-22 15:26:28 +00:00
< button key = "export" className = "mx_Dialog_primary"
2017-03-16 14:18:18 +00:00
onClick = { this . _onExportE2eKeysClicked } >
2017-05-23 14:16:31 +00:00
{ _t ( "Export E2E room keys" ) }
2017-05-02 20:17:12 +00:00
< / b u t t o n > ,
2017-03-16 14:18:18 +00:00
] ,
2017-01-24 22:18:25 +00:00
onFinished : ( confirmed ) => {
if ( confirmed ) {
dis . dispatch ( { action : 'logout' } ) ;
if ( this . props . onFinished ) {
this . props . onFinished ( ) ;
}
}
} ,
} ) ;
2015-11-30 15:52:41 +00:00
} ,
2015-12-23 15:38:28 +00:00
onPasswordChangeError : function ( err ) {
2017-05-02 20:17:12 +00:00
let errMsg = err . error || "" ;
2015-12-23 15:38:28 +00:00
if ( err . httpStatus === 403 ) {
2017-05-23 14:16:31 +00:00
errMsg = _t ( "Failed to change password. Is your password correct?" ) ;
2017-05-02 20:17:12 +00:00
} else if ( err . httpStatus ) {
2015-12-23 15:38:28 +00:00
errMsg += ` (HTTP status ${ err . httpStatus } ) ` ;
}
2017-05-02 20:17:12 +00:00
const ErrorDialog = sdk . getComponent ( "dialogs.ErrorDialog" ) ;
2017-03-12 22:59:41 +00:00
console . error ( "Failed to change password: " + errMsg ) ;
2015-12-23 15:38:28 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Error" ) ,
2017-05-02 20:17:12 +00:00
description : errMsg ,
2015-12-23 15:38:28 +00:00
} ) ;
} ,
onPasswordChanged : function ( ) {
2017-05-02 20:17:12 +00:00
const ErrorDialog = sdk . getComponent ( "dialogs.ErrorDialog" ) ;
2015-12-23 15:38:28 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-23 14:16:31 +00:00
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" ) + "." ,
2015-12-23 15:38:28 +00:00
} ) ;
} ,
2016-01-07 17:23:32 +00:00
onUpgradeClicked : function ( ) {
dis . dispatch ( {
2017-05-02 20:17:12 +00:00
action : "start_upgrade_registration" ,
2016-01-07 17:23:32 +00:00
} ) ;
} ,
2016-01-19 16:36:54 +00:00
onEnableNotificationsChange : function ( event ) {
UserSettingsStore . setEnableNotifications ( event . target . checked ) ;
} ,
2017-03-16 14:56:26 +00:00
_onAddEmailEditFinished : function ( value , shouldSubmit ) {
2016-01-19 16:36:54 +00:00
if ( ! shouldSubmit ) return ;
2017-03-16 14:56:26 +00:00
this . _addEmail ( ) ;
} ,
_addEmail : function ( ) {
2017-05-02 20:17:12 +00:00
const ErrorDialog = sdk . getComponent ( "dialogs.ErrorDialog" ) ;
const QuestionDialog = sdk . getComponent ( "dialogs.QuestionDialog" ) ;
2016-01-19 16:36:54 +00:00
2017-05-02 20:17:12 +00:00
const emailAddress = this . refs . add _email _input . value ;
if ( ! Email . looksValid ( emailAddress ) ) {
2016-01-19 16:36:54 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Invalid Email Address" ) ,
description : _t ( "This doesn't appear to be a valid email address" ) ,
2016-01-19 16:36:54 +00:00
} ) ;
return ;
}
2017-03-16 14:56:26 +00:00
this . _addThreepid = new AddThreepid ( ) ;
2016-01-19 16:36:54 +00:00
// we always bind emails when registering, so let's do the
// same here.
2017-05-02 20:17:12 +00:00
this . _addThreepid . addEmailAddress ( emailAddress , true ) . done ( ( ) => {
2016-01-19 16:36:54 +00:00
Modal . createDialog ( QuestionDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Verification Pending" ) ,
description : _t ( "Please check your email and click on the link it contains. Once this is done, click continue." ) ,
button : _t ( 'Continue' ) ,
2016-01-19 16:36:54 +00:00
onFinished : this . onEmailDialogFinished ,
} ) ;
} , ( err ) => {
2016-07-08 16:28:04 +00:00
this . setState ( { email _add _pending : false } ) ;
2017-05-02 20:17:12 +00:00
console . error ( "Unable to add email address " + emailAddress + " " + err ) ;
2016-01-19 16:36:54 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Unable to add email address" ) ,
description : ( ( err && err . message ) ? err . message : _t ( "Operation failed" ) ) ,
2016-01-19 16:36:54 +00:00
} ) ;
} ) ;
2017-03-16 14:56:26 +00:00
ReactDOM . findDOMNode ( this . refs . add _email _input ) . blur ( ) ;
2016-01-19 16:36:54 +00:00
this . setState ( { email _add _pending : true } ) ;
} ,
2016-12-21 18:49:38 +00:00
onRemoveThreepidClicked : function ( threepid ) {
const QuestionDialog = sdk . getComponent ( "dialogs.QuestionDialog" ) ;
Modal . createDialog ( QuestionDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Remove Contact Information?" ) ,
2017-05-27 17:20:35 +00:00
description : _t ( "Remove %(threePid)s?" , { threePid : threepid . address } ) ,
2017-05-23 14:16:31 +00:00
button : _t ( 'Remove' ) ,
2016-12-21 18:49:38 +00:00
onFinished : ( submit ) => {
if ( submit ) {
this . setState ( {
phase : "UserSettings.LOADING" ,
} ) ;
MatrixClientPeg . get ( ) . deleteThreePid ( threepid . medium , threepid . address ) . then ( ( ) => {
return this . _refreshFromServer ( ) ;
2016-12-22 15:26:08 +00:00
} ) . catch ( ( err ) => {
2016-12-21 18:49:38 +00:00
const ErrorDialog = sdk . getComponent ( "dialogs.ErrorDialog" ) ;
2017-03-12 22:59:41 +00:00
console . error ( "Unable to remove contact information: " + err ) ;
2016-12-21 18:49:38 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Unable to remove contact information" ) ,
description : ( ( err && err . message ) ? err . message : _t ( "Operation failed" ) ) ,
2016-12-21 18:49:38 +00:00
} ) ;
} ) . done ( ) ;
}
} ,
} ) ;
} ,
2016-01-19 16:36:54 +00:00
onEmailDialogFinished : function ( ok ) {
if ( ok ) {
this . verifyEmailAddress ( ) ;
} else {
this . setState ( { email _add _pending : false } ) ;
}
} ,
verifyEmailAddress : function ( ) {
2017-03-16 14:56:26 +00:00
this . _addThreepid . checkEmailLinkClicked ( ) . done ( ( ) => {
this . _addThreepid = null ;
2016-01-19 16:36:54 +00:00
this . setState ( {
phase : "UserSettings.LOADING" ,
} ) ;
this . _refreshFromServer ( ) ;
this . setState ( { email _add _pending : false } ) ;
} , ( err ) => {
2016-03-24 15:03:44 +00:00
this . setState ( { email _add _pending : false } ) ;
2017-05-23 14:16:31 +00:00
if ( err . errcode == 'M_THREEPID_AUTH_FAILED' ) {
2017-05-02 20:17:12 +00:00
const QuestionDialog = sdk . getComponent ( "dialogs.QuestionDialog" ) ;
2017-05-27 17:35:18 +00:00
let message = _t ( "Unable to verify email address." ) + " " +
2017-05-27 17:20:35 +00:00
_t ( "Please check your email and click on the link it contains. Once this is done, click continue." ) ;
2016-01-19 16:36:54 +00:00
Modal . createDialog ( QuestionDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Verification Pending" ) ,
2016-01-19 16:36:54 +00:00
description : message ,
2017-05-23 14:16:31 +00:00
button : _t ( 'Continue' ) ,
2016-01-19 16:36:54 +00:00
onFinished : this . onEmailDialogFinished ,
} ) ;
} else {
2017-05-02 20:17:12 +00:00
const ErrorDialog = sdk . getComponent ( "dialogs.ErrorDialog" ) ;
2017-03-12 22:59:41 +00:00
console . error ( "Unable to verify email address: " + err ) ;
2016-01-19 16:36:54 +00:00
Modal . createDialog ( ErrorDialog , {
2017-05-27 17:20:35 +00:00
title : _t ( "Unable to verify email address." ) ,
2017-05-23 14:16:31 +00:00
description : ( ( err && err . message ) ? err . message : _t ( "Operation failed" ) ) ,
2016-01-19 16:36:54 +00:00
} ) ;
}
} ) ;
} ,
2016-08-02 17:40:12 +00:00
_onDeactivateAccountClicked : function ( ) {
const DeactivateAccountDialog = sdk . getComponent ( "dialogs.DeactivateAccountDialog" ) ;
Modal . createDialog ( DeactivateAccountDialog , { } ) ;
} ,
2017-01-24 14:47:11 +00:00
_onBugReportClicked : function ( ) {
const BugReportDialog = sdk . getComponent ( "dialogs.BugReportDialog" ) ;
2017-01-25 16:33:00 +00:00
if ( ! BugReportDialog ) {
return ;
}
2017-01-24 14:47:11 +00:00
Modal . createDialog ( BugReportDialog , { } ) ;
} ,
2017-02-17 15:16:28 +00:00
_onClearCacheClicked : function ( ) {
2017-04-10 16:39:27 +00:00
if ( ! PlatformPeg . get ( ) ) return ;
2017-04-11 17:16:29 +00:00
MatrixClientPeg . get ( ) . stopClient ( ) ;
2017-02-17 15:37:49 +00:00
MatrixClientPeg . get ( ) . store . deleteAllData ( ) . done ( ( ) => {
2017-04-10 16:39:27 +00:00
PlatformPeg . get ( ) . reload ( ) ;
2017-02-17 15:16:28 +00:00
} ) ;
} ,
2016-12-14 16:00:50 +00:00
_onInviteStateChange : function ( event , member , oldMembership ) {
if ( member . userId === this . _me && oldMembership === "invite" ) {
this . forceUpdate ( ) ;
}
} ,
_onRejectAllInvitesClicked : function ( rooms , ev ) {
this . setState ( {
2017-05-02 20:17:12 +00:00
rejectingInvites : true ,
2016-12-14 16:00:50 +00:00
} ) ;
// reject the invites
2017-05-02 20:17:12 +00:00
const promises = rooms . map ( ( room ) => {
2016-12-14 16:00:50 +00:00
return MatrixClientPeg . get ( ) . leave ( room . roomId ) ;
} ) ;
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
// after trying to reject all the invites.
q . allSettled ( promises ) . then ( ( ) => {
this . setState ( {
2017-05-02 20:17:12 +00:00
rejectingInvites : false ,
2016-12-14 16:00:50 +00:00
} ) ;
2016-12-15 14:17:29 +00:00
} ) . done ( ) ;
2016-12-14 15:01:50 +00:00
} ,
2017-01-20 15:12:50 +00:00
_onExportE2eKeysClicked : function ( ) {
Modal . createDialogAsync (
( cb ) => {
2017-01-24 15:57:42 +00:00
require . ensure ( [ '../../async-components/views/dialogs/ExportE2eKeysDialog' ] , ( ) => {
cb ( require ( '../../async-components/views/dialogs/ExportE2eKeysDialog' ) ) ;
} , "e2e-export" ) ;
} , {
matrixClient : MatrixClientPeg . get ( ) ,
2017-05-02 20:17:12 +00:00
} ,
2017-01-24 15:57:42 +00:00
) ;
} ,
_onImportE2eKeysClicked : function ( ) {
Modal . createDialogAsync (
( cb ) => {
require . ensure ( [ '../../async-components/views/dialogs/ImportE2eKeysDialog' ] , ( ) => {
cb ( require ( '../../async-components/views/dialogs/ImportE2eKeysDialog' ) ) ;
} , "e2e-export" ) ;
2017-01-20 15:12:50 +00:00
} , {
matrixClient : MatrixClientPeg . get ( ) ,
2017-05-02 20:17:12 +00:00
} ,
2017-01-20 15:12:50 +00:00
) ;
} ,
2017-01-31 13:17:01 +00:00
_renderReferral : function ( ) {
2017-02-16 18:00:52 +00:00
const teamToken = this . props . teamToken ;
2017-01-31 13:17:01 +00:00
if ( ! teamToken ) {
return null ;
}
if ( typeof teamToken !== 'string' ) {
console . warn ( 'Team token not a string' ) ;
return null ;
}
2017-01-31 15:17:43 +00:00
const href = ( this . props . referralBaseUrl || window . location . origin ) +
2017-01-31 13:17:01 +00:00
` /#/register?referrer= ${ this . _me } &team_token= ${ teamToken } ` ;
return (
< div >
< h3 > Referral < / h 3 >
< div className = "mx_UserSettings_section" >
2017-05-27 17:20:35 +00:00
{ _t ( "Refer a friend to Riot:" ) } < a href = { href } > { href } < / a >
2017-01-31 13:17:01 +00:00
< / d i v >
< / d i v >
) ;
} ,
2017-05-27 19:21:23 +00:00
onLanguageChange : function ( newLang ) {
if ( this . state . language !== newLang ) {
UserSettingsStore . setLocalSetting ( 'language' , newLang ) ;
2017-05-27 19:11:00 +00:00
this . setState ( {
2017-05-27 19:21:23 +00:00
language : newLang ,
2017-05-27 19:11:00 +00:00
} ) ;
PlatformPeg . get ( ) . reload ( ) ;
}
2017-05-23 14:16:31 +00:00
} ,
_renderLanguageSetting : function ( ) {
2017-05-24 13:28:30 +00:00
const LanguageDropdown = sdk . getComponent ( 'views.elements.LanguageDropdown' ) ;
2017-05-24 13:36:14 +00:00
return < div >
2017-05-25 15:52:15 +00:00
< label htmlFor = "languageSelector" > { _t ( 'Interface Language' ) } < / l a b e l >
2017-05-24 13:36:14 +00:00
< LanguageDropdown ref = "language" onOptionChange = { this . onLanguageChange }
className = "mx_UserSettings_language"
2017-05-25 18:53:27 +00:00
value = { this . state . language }
2017-05-24 13:36:14 +00:00
/ >
< / d i v > ;
2017-05-23 14:16:31 +00:00
} ,
2016-07-18 00:35:42 +00:00
_renderUserInterfaceSettings : function ( ) {
return (
< div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "User Interface" ) } < / h 3 >
2016-07-18 00:35:42 +00:00
< div className = "mx_UserSettings_section" >
2017-01-18 14:06:47 +00:00
{ this . _renderUrlPreviewSelector ( ) }
{ SETTINGS _LABELS . map ( this . _renderSyncedSetting ) }
{ THEMES . map ( this . _renderThemeSelector ) }
2017-05-23 14:16:31 +00:00
{ this . _renderLanguageSetting ( ) }
2016-07-18 00:35:42 +00:00
< / d i v >
< / d i v >
) ;
} ,
2017-01-18 14:06:47 +00:00
_renderUrlPreviewSelector : function ( ) {
return < div className = "mx_UserSettings_toggle" >
< input id = "urlPreviewsDisabled"
type = "checkbox"
defaultChecked = { UserSettingsStore . getUrlPreviewsDisabled ( ) }
2017-05-02 20:17:12 +00:00
onChange = { ( e ) => UserSettingsStore . setUrlPreviewsDisabled ( e . target . checked ) }
2017-01-18 14:06:47 +00:00
/ >
< label htmlFor = "urlPreviewsDisabled" >
2017-05-23 14:16:31 +00:00
{ _t ( "Disable inline URL previews by default" ) }
2017-01-18 14:06:47 +00:00
< / l a b e l >
2017-01-20 14:22:27 +00:00
< / d i v > ;
2017-01-18 14:06:47 +00:00
} ,
_renderSyncedSetting : function ( setting ) {
return < div className = "mx_UserSettings_toggle" key = { setting . id } >
< input id = { setting . id }
type = "checkbox"
defaultChecked = { this . _syncedSettings [ setting . id ] }
2017-05-29 14:08:11 +00:00
onChange = {
( e ) => {
UserSettingsStore . setSyncedSetting ( setting . id , e . target . checked ) ;
if ( setting . fn ) setting . fn ( e . target . checked ) ;
}
}
2017-01-18 14:06:47 +00:00
/ >
< label htmlFor = { setting . id } >
2017-05-23 14:16:31 +00:00
{ _t ( setting . label ) }
2017-01-18 14:06:47 +00:00
< / l a b e l >
2017-01-20 14:22:27 +00:00
< / d i v > ;
2017-01-18 14:06:47 +00:00
} ,
_renderThemeSelector : function ( setting ) {
return < div className = "mx_UserSettings_toggle" key = { setting . id + "_" + setting . value } >
< input id = { setting . id + "_" + setting . value }
type = "radio"
name = { setting . id }
value = { setting . value }
defaultChecked = { this . _syncedSettings [ setting . id ] === setting . value }
2017-05-02 20:17:12 +00:00
onChange = { ( e ) => {
2017-01-18 14:06:47 +00:00
if ( e . target . checked ) {
2017-01-20 14:22:27 +00:00
UserSettingsStore . setSyncedSetting ( setting . id , setting . value ) ;
2017-01-18 14:06:47 +00:00
}
dis . dispatch ( {
action : 'set_theme' ,
value : setting . value ,
} ) ;
}
}
/ >
< label htmlFor = { setting . id + "_" + setting . value } >
{ setting . label }
< / l a b e l >
2017-01-20 14:22:27 +00:00
< / d i v > ;
2017-01-18 14:06:47 +00:00
} ,
2016-08-01 12:42:29 +00:00
_renderCryptoInfo : function ( ) {
2016-12-05 18:33:38 +00:00
const client = MatrixClientPeg . get ( ) ;
2016-12-05 20:03:43 +00:00
const deviceId = client . deviceId ;
2017-05-22 11:01:09 +00:00
let identityKey = client . getDeviceEd25519Key ( ) ;
if ( ! identityKey ) {
2017-05-23 14:16:31 +00:00
identityKey = _t ( "<not supported>" ) ;
2017-05-22 11:01:09 +00:00
} else {
identityKey = FormattingUtils . formatCryptoKey ( identityKey ) ;
}
2016-09-15 00:55:51 +00:00
2017-02-09 02:00:58 +00:00
let importExportButtons = null ;
2017-01-20 15:12:50 +00:00
if ( client . isCryptoEnabled ) {
2017-02-09 02:00:58 +00:00
importExportButtons = (
< div className = "mx_UserSettings_importExportButtons" >
< AccessibleButton className = "mx_UserSettings_button"
onClick = { this . _onExportE2eKeysClicked } >
2017-05-23 14:16:31 +00:00
{ _t ( "Export E2E room keys" ) }
2017-02-09 02:00:58 +00:00
< / A c c e s s i b l e B u t t o n >
< AccessibleButton className = "mx_UserSettings_button"
onClick = { this . _onImportE2eKeysClicked } >
2017-05-23 14:16:31 +00:00
{ _t ( "Import E2E room keys" ) }
2017-02-09 02:00:58 +00:00
< / A c c e s s i b l e B u t t o n >
< / d i v >
2017-01-24 15:57:42 +00:00
) ;
2017-01-20 15:12:50 +00:00
}
2016-06-08 12:09:07 +00:00
return (
< div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Cryptography" ) } < / h 3 >
2016-09-15 00:55:51 +00:00
< div className = "mx_UserSettings_section mx_UserSettings_cryptoSection" >
2016-06-08 12:09:07 +00:00
< ul >
2017-01-20 14:22:27 +00:00
< li > < label > Device ID : < / l a b e l > < s p a n > < c o d e > { d e v i c e I d } < / c o d e > < / s p a n > < / l i >
< li > < label > Device key : < / l a b e l > < s p a n > < c o d e > < b > { i d e n t i t y K e y } < / b > < / c o d e > < / s p a n > < / l i >
2016-06-08 12:09:07 +00:00
< / u l >
2017-02-09 02:00:58 +00:00
{ importExportButtons }
2016-06-08 12:09:07 +00:00
< / d i v >
2017-01-21 21:27:55 +00:00
< div className = "mx_UserSettings_section" >
{ CRYPTO _SETTINGS _LABELS . map ( this . _renderLocalSetting ) }
< / d i v >
2016-06-08 12:09:07 +00:00
< / d i v >
) ;
} ,
2017-01-21 17:39:31 +00:00
_renderLocalSetting : function ( setting ) {
return < div className = "mx_UserSettings_toggle" key = { setting . id } >
< input id = { setting . id }
type = "checkbox"
defaultChecked = { this . _localSettings [ setting . id ] }
onChange = {
2017-05-02 20:17:12 +00:00
( e ) => {
UserSettingsStore . setLocalSetting ( setting . id , e . target . checked ) ;
2017-05-29 14:08:11 +00:00
if ( setting . fn ) setting . fn ( e . target . checked ) ;
2017-01-21 17:39:31 +00:00
}
2017-05-29 14:08:11 +00:00
}
2017-01-21 17:39:31 +00:00
/ >
< label htmlFor = { setting . id } >
2017-05-23 14:16:31 +00:00
{ _t ( setting . label ) }
2017-01-21 17:39:31 +00:00
< / l a b e l >
< / d i v > ;
} ,
2016-08-01 12:42:29 +00:00
_renderDevicesPanel : function ( ) {
2017-05-02 20:17:12 +00:00
const DevicesPanel = sdk . getComponent ( 'settings.DevicesPanel' ) ;
2016-08-01 12:42:29 +00:00
return (
< div >
< h3 > Devices < / h 3 >
2016-09-15 00:55:51 +00:00
< DevicesPanel className = "mx_UserSettings_section" / >
2016-08-01 12:42:29 +00:00
< / d i v >
) ;
} ,
2017-01-24 14:47:11 +00:00
_renderBugReport : function ( ) {
2017-01-25 14:43:47 +00:00
if ( ! SdkConfig . get ( ) . bug _report _endpoint _url ) {
2017-05-02 20:17:12 +00:00
return < div / > ;
2017-01-25 14:43:47 +00:00
}
2017-01-24 14:47:11 +00:00
return (
< div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Bug Report" ) } < / h 3 >
2017-01-24 14:47:11 +00:00
< div className = "mx_UserSettings_section" >
2017-05-23 14:16:31 +00:00
< p > { _t ( "Found a bug?" ) } < / p >
2017-01-24 14:47:11 +00:00
< button className = "mx_UserSettings_button danger"
2017-05-25 16:35:18 +00:00
onClick = { this . _onBugReportClicked } > { _t ( 'Report it' ) }
2017-01-24 14:47:11 +00:00
< / b u t t o n >
< / d i v >
< / d i v >
) ;
} ,
2017-05-29 13:36:50 +00:00
_renderAnalyticsControl : function ( ) {
return < div >
< h3 > { _t ( 'Analytics' ) } < / h 3 >
< div className = "mx_UserSettings_section" >
{ _t ( 'Riot collects anonymous analytics to allow us to improve the application.' ) }
2017-05-29 14:11:37 +00:00
{ ANALYTICS _SETTINGS _LABELS . map ( this . _renderLocalSetting ) }
2017-05-29 13:36:50 +00:00
< / d i v >
< / d i v > ;
} ,
2017-01-20 14:22:27 +00:00
_renderLabs : function ( ) {
2016-08-05 16:18:45 +00:00
// default to enabled if undefined
2016-08-05 15:36:35 +00:00
if ( this . props . enableLabs === false ) return null ;
2017-06-01 14:53:08 +00:00
UserSettingsStore . doTranslations ( ) ;
2016-08-05 15:13:06 +00:00
2017-05-02 20:17:12 +00:00
const features = UserSettingsStore . LABS _FEATURES . map ( ( feature ) => (
2016-08-01 12:42:29 +00:00
< div key = { feature . id } className = "mx_UserSettings_toggle" >
< input
type = "checkbox"
id = { feature . id }
name = { feature . id }
2016-09-16 23:54:56 +00:00
defaultChecked = { UserSettingsStore . isFeatureEnabled ( feature . id ) }
2017-05-02 20:17:12 +00:00
onChange = { ( e ) => {
2016-09-17 13:29:40 +00:00
if ( MatrixClientPeg . get ( ) . isGuest ( ) ) {
e . target . checked = false ;
2017-05-02 20:17:12 +00:00
const NeedToRegisterDialog = sdk . getComponent ( "dialogs.NeedToRegisterDialog" ) ;
2016-09-17 13:29:40 +00:00
Modal . createDialog ( NeedToRegisterDialog , {
2017-05-23 14:16:31 +00:00
title : _t ( "Please Register" ) ,
2017-05-27 17:20:35 +00:00
description : _t ( "Guests can't use labs features. Please register." ) ,
2016-09-17 13:29:40 +00:00
} ) ;
return ;
}
2016-08-01 12:42:29 +00:00
UserSettingsStore . setFeatureEnabled ( feature . id , e . target . checked ) ;
this . forceUpdate ( ) ;
} } / >
< label htmlFor = { feature . id } > { feature . name } < / l a b e l >
< / d i v >
) ) ;
return (
< div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Labs" ) } < / h 3 >
2016-08-01 12:42:29 +00:00
< div className = "mx_UserSettings_section" >
2017-05-23 14:16:31 +00:00
< p > { _t ( "These are experimental features that may break in unexpected ways" ) } . { _t ( "Use with caution" ) } . < / p >
2016-08-01 12:42:29 +00:00
{ features }
< / d i v >
< / d i v >
2017-01-20 14:22:27 +00:00
) ;
2016-08-01 12:42:29 +00:00
} ,
2016-08-02 17:40:12 +00:00
_renderDeactivateAccount : function ( ) {
2016-08-03 10:34:31 +00:00
// We can't deactivate a guest account.
if ( MatrixClientPeg . get ( ) . isGuest ( ) ) return null ;
2016-08-02 17:40:12 +00:00
return < div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Deactivate Account" ) } < / h 3 >
2016-08-02 17:40:12 +00:00
< div className = "mx_UserSettings_section" >
2017-01-13 16:25:26 +00:00
< AccessibleButton className = "mx_UserSettings_button danger"
2017-05-23 14:16:31 +00:00
onClick = { this . _onDeactivateAccountClicked } > { _t ( "Deactivate my account" ) }
2017-01-13 16:25:26 +00:00
< / A c c e s s i b l e B u t t o n >
2016-08-02 17:40:12 +00:00
< / d i v >
< / d i v > ;
} ,
2017-02-17 15:16:28 +00:00
_renderClearCache : function ( ) {
return < div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Clear Cache" ) } < / h 3 >
2017-02-17 15:16:28 +00:00
< div className = "mx_UserSettings_section" >
< AccessibleButton className = "mx_UserSettings_button danger"
onClick = { this . _onClearCacheClicked } >
2017-05-23 14:16:31 +00:00
{ _t ( "Clear Cache and Reload" ) }
2017-02-17 15:16:28 +00:00
< / A c c e s s i b l e B u t t o n >
< / d i v >
< / d i v > ;
} ,
2016-12-14 15:01:50 +00:00
_renderBulkOptions : function ( ) {
2017-05-02 20:17:12 +00:00
const invitedRooms = MatrixClientPeg . get ( ) . getRooms ( ) . filter ( ( r ) => {
2016-12-14 15:01:50 +00:00
return r . hasMembershipState ( this . _me , "invite" ) ;
} ) ;
if ( invitedRooms . length === 0 ) {
return null ;
}
2016-12-14 16:00:50 +00:00
2017-05-02 20:17:12 +00:00
const Spinner = sdk . getComponent ( "elements.Spinner" ) ;
2016-12-14 16:00:50 +00:00
let reject = < Spinner / > ;
if ( ! this . state . rejectingInvites ) {
2016-12-14 16:04:20 +00:00
// bind() the invited rooms so any new invites that may come in as this button is clicked
// don't inadvertently get rejected as well.
2016-12-14 16:00:50 +00:00
reject = (
2017-01-13 16:25:26 +00:00
< AccessibleButton className = "mx_UserSettings_button danger"
2016-12-14 16:00:50 +00:00
onClick = { this . _onRejectAllInvitesClicked . bind ( this , invitedRooms ) } >
2017-05-30 14:09:57 +00:00
{ _t ( "Reject all %(invitedRooms)s invites" , { invitedRooms : invitedRooms . length } ) }
2017-01-13 16:25:26 +00:00
< / A c c e s s i b l e B u t t o n >
2016-12-14 16:00:50 +00:00
) ;
}
2016-12-14 15:01:50 +00:00
return < div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Bulk Options" ) } < / h 3 >
2016-12-14 15:01:50 +00:00
< div className = "mx_UserSettings_section" >
2016-12-14 16:00:50 +00:00
{ reject }
2016-12-14 15:01:50 +00:00
< / d i v >
< / d i v > ;
} ,
2017-05-24 14:40:50 +00:00
_renderElectronSettings : function ( ) {
const settings = this . state . electron _settings ;
if ( ! settings ) return ;
const { ipcRenderer } = require ( 'electron' ) ;
return < div >
2017-05-30 22:46:51 +00:00
< h3 > { _t ( 'Desktop specific' ) } < / h 3 >
2017-05-24 14:40:50 +00:00
< div className = "mx_UserSettings_section" >
< div className = "mx_UserSettings_toggle" >
< input type = "checkbox"
name = "auto-launch"
defaultChecked = { settings [ 'auto-launch' ] }
onChange = { ( e ) => {
ipcRenderer . send ( 'settings_set' , 'auto-launch' , e . target . checked ) ;
} }
/ >
2017-05-29 20:58:47 +00:00
< label htmlFor = "auto-launch" > { _t ( 'Start automatically after system login' ) } < / l a b e l >
2017-05-24 14:40:50 +00:00
< / d i v >
< / d i v >
< / d i v > ;
} ,
2017-04-28 17:21:22 +00:00
_mapWebRtcDevicesToSpans : function ( devices ) {
2017-06-01 22:25:44 +00:00
return devices . map ( ( device ) => < span key = { device . deviceId } > { devices [ device . deviceId ] } < / s p a n > ) ;
2017-04-28 17:21:22 +00:00
} ,
_setAudioInput : function ( deviceId ) {
this . setState ( { activeAudioInput : deviceId } ) ;
CallMediaHandler . setAudioInput ( deviceId ) ;
} ,
_setVideoInput : function ( deviceId ) {
this . setState ( { activeVideoInput : deviceId } ) ;
CallMediaHandler . setVideoInput ( deviceId ) ;
} ,
2017-05-25 00:25:17 +00:00
_requestMediaPermissions : function ( event ) {
2017-05-25 00:01:40 +00:00
const getUserMedia = (
window . navigator . getUserMedia || window . navigator . webkitGetUserMedia || window . navigator . mozGetUserMedia
) ;
if ( getUserMedia ) {
return getUserMedia . apply ( window . navigator , [
{ video : true , audio : true } ,
this . _refreshMediaDevices ,
2017-05-25 00:25:17 +00:00
function ( ) {
const ErrorDialog = sdk . getComponent ( 'dialogs.ErrorDialog' ) ;
Modal . createDialog ( ErrorDialog , {
2017-06-01 21:58:17 +00:00
title : _t ( 'No media permissions' ) ,
description : _t ( 'You may need to manually permit Riot to access your microphone/webcam' ) ,
2017-05-25 00:25:17 +00:00
} ) ;
} ,
2017-05-25 00:01:40 +00:00
] ) ;
}
} ,
2017-04-28 17:21:22 +00:00
_renderWebRtcSettings : function ( ) {
2017-05-25 00:01:40 +00:00
if ( this . state . mediaDevices === false ) {
return < div >
2017-06-01 21:58:17 +00:00
< h3 > { _t ( 'VoIP' ) } < / h 3 >
2017-05-25 00:01:40 +00:00
< div className = "mx_UserSettings_section" >
2017-05-25 00:25:17 +00:00
< p className = "mx_UserSettings_link" onClick = { this . _requestMediaPermissions } >
2017-06-01 21:58:17 +00:00
{ _t ( 'Missing Media Permissions, click here to request.' ) }
2017-05-25 00:25:17 +00:00
< / p >
2017-05-25 00:01:40 +00:00
< / d i v >
< / d i v > ;
} else if ( ! this . state . mediaDevices ) return ;
2017-04-28 17:21:22 +00:00
const Dropdown = sdk . getComponent ( 'elements.Dropdown' ) ;
2017-06-01 21:58:17 +00:00
let microphoneDropdown = < p > { _t ( 'No Microphones detected' ) } < / p > ;
let webcamDropdown = < p > { _t ( 'No Webcams detected' ) } < / p > ;
2017-04-28 17:21:22 +00:00
2017-06-01 22:25:44 +00:00
const defaultOption = {
deviceId : undefined ,
label : _t ( 'Default Device' ) ,
} ;
2017-04-28 17:21:22 +00:00
const audioInputs = this . state . mediaDevices . audioinput ;
2017-06-01 22:25:44 +00:00
if ( audioInputs . length > 0 ) {
2017-06-01 22:33:36 +00:00
if ( ! audioInputs . some ( ( input ) => input . deviceId === 'default' ) ) {
audioInputs . unshift ( defaultOption ) ;
}
2017-04-28 17:21:22 +00:00
microphoneDropdown = < div >
< h4 > Microphone < / h 4 >
< Dropdown
className = "mx_UserSettings_webRtcDevices_dropdown"
value = { this . state . activeAudioInput }
onOptionChange = { this . _setAudioInput } >
{ this . _mapWebRtcDevicesToSpans ( audioInputs ) }
< / D r o p d o w n >
< / d i v > ;
}
const videoInputs = this . state . mediaDevices . videoinput ;
2017-06-01 22:25:44 +00:00
if ( videoInputs . length > 0 ) {
2017-06-01 22:33:36 +00:00
if ( ! videoInputs . some ( ( input ) => input . deviceId === 'default' ) ) {
videoInputs . unshift ( defaultOption ) ;
}
2017-04-28 17:21:22 +00:00
webcamDropdown = < div >
< h4 > Cameras < / h 4 >
< Dropdown
className = "mx_UserSettings_webRtcDevices_dropdown"
value = { this . state . activeVideoInput }
onOptionChange = { this . _setVideoInput } >
{ this . _mapWebRtcDevicesToSpans ( videoInputs ) }
< / D r o p d o w n >
< / d i v > ;
}
return < div >
2017-06-01 21:58:17 +00:00
< h3 > { _t ( 'VoIP' ) } < / h 3 >
2017-04-28 17:21:22 +00:00
< div className = "mx_UserSettings_section" >
{ microphoneDropdown }
{ webcamDropdown }
< / d i v >
< / d i v > ;
} ,
2017-04-18 18:55:08 +00:00
_showSpoiler : function ( event ) {
const target = event . target ;
2017-05-02 20:17:12 +00:00
target . innerHTML = target . getAttribute ( 'data-spoiler' ) ;
2017-04-18 18:55:08 +00:00
const range = document . createRange ( ) ;
range . selectNodeContents ( target ) ;
const selection = window . getSelection ( ) ;
selection . removeAllRanges ( ) ;
selection . addRange ( range ) ;
} ,
2016-12-21 18:56:50 +00:00
nameForMedium : function ( medium ) {
2017-05-23 14:16:31 +00:00
if ( medium === 'msisdn' ) return _t ( 'Phone' ) ;
2017-05-26 16:56:51 +00:00
if ( medium === 'email' ) return _t ( 'Email' ) ;
2016-12-21 18:56:50 +00:00
return medium [ 0 ] . toUpperCase ( ) + medium . slice ( 1 ) ;
} ,
2017-03-16 15:16:24 +00:00
presentableTextForThreepid : function ( threepid ) {
2017-05-02 20:17:12 +00:00
if ( threepid . medium === 'msisdn' ) {
2017-03-16 15:16:24 +00:00
return '+' + threepid . address ;
} else {
return threepid . address ;
}
} ,
2015-11-30 15:52:41 +00:00
render : function ( ) {
2017-05-02 20:17:12 +00:00
const Loader = sdk . getComponent ( "elements.Spinner" ) ;
2015-12-18 00:37:56 +00:00
switch ( this . state . phase ) {
2015-12-23 11:47:56 +00:00
case "UserSettings.LOADING" :
2015-12-23 16:02:18 +00:00
return (
< Loader / >
) ;
2015-12-23 11:47:56 +00:00
case "UserSettings.DISPLAY" :
break ; // quit the switch to return the common state
default :
throw new Error ( "Unknown state.phase => " + this . state . phase ) ;
}
// can only get here if phase is UserSettings.DISPLAY
2017-05-02 20:17:12 +00:00
const SimpleRoomHeader = sdk . getComponent ( 'rooms.SimpleRoomHeader' ) ;
const ChangeDisplayName = sdk . getComponent ( "views.settings.ChangeDisplayName" ) ;
const ChangePassword = sdk . getComponent ( "views.settings.ChangePassword" ) ;
const ChangeAvatar = sdk . getComponent ( 'settings.ChangeAvatar' ) ;
const Notifications = sdk . getComponent ( "settings.Notifications" ) ;
const EditableText = sdk . getComponent ( 'elements.EditableText' ) ;
const avatarUrl = (
2015-12-23 16:52:59 +00:00
this . state . avatarUrl ? MatrixClientPeg . get ( ) . mxcUrlToHttp ( this . state . avatarUrl ) : null
) ;
2015-12-23 15:38:28 +00:00
2017-05-02 20:17:12 +00:00
const threepidsSection = this . state . threepids . map ( ( val , pidIndex ) => {
2016-12-22 15:03:24 +00:00
const id = "3pid-" + val . address ;
2016-01-19 16:36:54 +00:00
return (
< div className = "mx_UserSettings_profileTableRow" key = { pidIndex } >
< div className = "mx_UserSettings_profileLabelCell" >
2016-12-21 18:56:50 +00:00
< label htmlFor = { id } > { this . nameForMedium ( val . medium ) } < / l a b e l >
2016-01-19 16:36:54 +00:00
< / d i v >
< div className = "mx_UserSettings_profileInputCell" >
2017-03-16 15:16:24 +00:00
< input type = "text" key = { val . address } id = { id }
value = { this . presentableTextForThreepid ( val ) } disabled
/ >
2016-01-19 16:36:54 +00:00
< / d i v >
2017-01-20 21:00:22 +00:00
< div className = "mx_UserSettings_threepidButton mx_filterFlipColor" >
2017-05-23 14:16:31 +00:00
< img src = "img/cancel-small.svg" width = "14" height = "14" alt = { _t ( "Remove" ) } onClick = { this . onRemoveThreepidClicked . bind ( this , val ) } / >
2016-12-21 18:49:38 +00:00
< / d i v >
2016-01-19 16:36:54 +00:00
< / d i v >
) ;
} ) ;
2017-03-16 14:56:26 +00:00
let addEmailSection ;
2016-01-19 16:36:54 +00:00
if ( this . state . email _add _pending ) {
2017-03-16 14:56:26 +00:00
addEmailSection = < Loader key = "_email_add_spinner" / > ;
2016-03-16 10:33:20 +00:00
} else if ( ! MatrixClientPeg . get ( ) . isGuest ( ) ) {
2017-03-16 14:56:26 +00:00
addEmailSection = (
< div className = "mx_UserSettings_profileTableRow" key = "_newEmail" >
2016-01-19 16:36:54 +00:00
< div className = "mx_UserSettings_profileLabelCell" >
2017-05-26 17:03:24 +00:00
< label > { _t ( 'Email' ) } < / l a b e l >
2016-01-19 16:36:54 +00:00
< / d i v >
2016-01-24 18:15:08 +00:00
< div className = "mx_UserSettings_profileInputCell" >
< EditableText
2017-03-16 14:56:26 +00:00
ref = "add_email_input"
2016-01-24 18:15:08 +00:00
className = "mx_UserSettings_editable"
placeholderClassName = "mx_UserSettings_threepidPlaceholder"
2017-05-23 14:16:31 +00:00
placeholder = { _t ( "Add email address" ) }
2016-01-24 18:15:08 +00:00
blurToCancel = { false }
2017-03-16 14:56:26 +00:00
onValueChanged = { this . _onAddEmailEditFinished } / >
< / d i v >
< div className = "mx_UserSettings_threepidButton mx_filterFlipColor" >
< img src = "img/plus.svg" width = "14" height = "14" alt = "Add" onClick = { this . _addEmail } / >
< / d i v >
< / d i v >
) ;
}
2017-03-22 15:18:27 +00:00
const AddPhoneNumber = sdk . getComponent ( 'views.settings.AddPhoneNumber' ) ;
const addMsisdnSection = (
< AddPhoneNumber key = "_addMsisdn" onThreepidAdded = { this . _refreshFromServer } / >
) ;
2017-03-16 14:56:26 +00:00
threepidsSection . push ( addEmailSection ) ;
threepidsSection . push ( addMsisdnSection ) ;
2016-01-19 16:36:54 +00:00
2017-05-02 20:17:12 +00:00
let accountJsx ;
2016-01-07 17:23:32 +00:00
if ( MatrixClientPeg . get ( ) . isGuest ( ) ) {
accountJsx = (
< div className = "mx_UserSettings_button" onClick = { this . onUpgradeClicked } >
2017-05-23 14:16:31 +00:00
{ _t ( "Create an account" ) }
2016-01-07 17:23:32 +00:00
< / d i v >
) ;
2017-05-02 20:17:12 +00:00
} else {
2016-01-07 17:23:32 +00:00
accountJsx = (
< ChangePassword
className = "mx_UserSettings_accountTable"
rowClassName = "mx_UserSettings_profileTableRow"
rowLabelClassName = "mx_UserSettings_profileLabelCell"
rowInputClassName = "mx_UserSettings_profileInputCell"
2016-01-20 17:09:46 +00:00
buttonClassName = "mx_UserSettings_button mx_UserSettings_changePasswordButton"
2016-01-07 17:23:32 +00:00
onError = { this . onPasswordChangeError }
onFinished = { this . onPasswordChanged } / >
) ;
}
2017-05-02 20:17:12 +00:00
let notificationArea ;
2016-04-12 15:17:04 +00:00
if ( ! MatrixClientPeg . get ( ) . isGuest ( ) && this . state . threepids !== undefined ) {
2017-05-02 20:17:12 +00:00
notificationArea = ( < div >
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Notifications" ) } < / h 3 >
2016-02-10 11:48:35 +00:00
< div className = "mx_UserSettings_section" >
2016-06-08 13:54:34 +00:00
< Notifications threepids = { this . state . threepids } brand = { this . props . brand } / >
2016-02-10 11:48:35 +00:00
< / d i v >
< / d i v > ) ;
}
2016-01-07 17:23:32 +00:00
2017-04-21 02:04:34 +00:00
const olmVersion = MatrixClientPeg . get ( ) . olmVersion ;
2016-09-15 10:31:54 +00:00
// If the olmVersion is not defined then either crypto is disabled, or
// we are using a version old version of olm. We assume the former.
2017-04-21 02:04:34 +00:00
let olmVersionString = "<not-enabled>" ;
2016-09-15 10:31:54 +00:00
if ( olmVersion !== undefined ) {
2017-05-02 20:12:58 +00:00
olmVersionString = ` ${ olmVersion [ 0 ] } . ${ olmVersion [ 1 ] } . ${ olmVersion [ 2 ] } ` ;
2016-09-15 10:31:54 +00:00
}
2015-12-23 11:47:56 +00:00
return (
< div className = "mx_UserSettings" >
2016-09-13 11:18:22 +00:00
< SimpleRoomHeader
2017-05-23 14:16:31 +00:00
title = { _t ( "Settings" ) }
2016-09-13 11:18:22 +00:00
collapsedRhs = { this . props . collapsedRhs }
onCancelClick = { this . props . onClose }
/ >
2015-12-23 11:47:56 +00:00
2016-04-20 11:25:19 +00:00
< GeminiScrollbar className = "mx_UserSettings_body"
autoshow = { true } >
2016-01-15 13:11:14 +00:00
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Profile" ) } < / h 3 >
2015-12-23 11:47:56 +00:00
< div className = "mx_UserSettings_section" >
< div className = "mx_UserSettings_profileTable" >
< div className = "mx_UserSettings_profileTableRow" >
< div className = "mx_UserSettings_profileLabelCell" >
2017-05-23 14:16:31 +00:00
< label htmlFor = "displayName" > { _t ( 'Display name' ) } < / l a b e l >
2015-12-23 11:47:56 +00:00
< / d i v >
< div className = "mx_UserSettings_profileInputCell" >
2015-12-23 14:14:25 +00:00
< ChangeDisplayName / >
2015-12-23 11:47:56 +00:00
< / d i v >
< / d i v >
2016-01-19 16:36:54 +00:00
{ threepidsSection }
2015-12-23 15:38:28 +00:00
< / d i v >
2015-11-30 15:52:41 +00:00
2015-12-23 11:47:56 +00:00
< div className = "mx_UserSettings_avatarPicker" >
2016-01-15 16:33:34 +00:00
< div onClick = { this . onAvatarPickerClick } >
< ChangeAvatar ref = "changeAvatar" initialAvatarUrl = { avatarUrl }
showUploadSection = { false } className = "mx_UserSettings_avatarPicker_img" / >
< / d i v >
2015-12-23 16:52:59 +00:00
< div className = "mx_UserSettings_avatarPicker_edit" >
2016-01-15 12:35:30 +00:00
< label htmlFor = "avatarInput" ref = "file_label" >
2017-01-20 21:00:22 +00:00
< img src = "img/camera.svg" className = "mx_filterFlipColor"
2017-05-23 14:16:31 +00:00
alt = { _t ( "Upload avatar" ) } title = { _t ( "Upload avatar" ) }
2016-01-15 12:35:30 +00:00
width = "17" height = "15" / >
2015-12-23 16:52:59 +00:00
< / l a b e l >
< input id = "avatarInput" type = "file" onChange = { this . onAvatarSelected } / >
2015-11-30 15:52:41 +00:00
< / d i v >
2015-12-23 11:47:56 +00:00
< / d i v >
< / d i v >
2015-12-18 00:37:56 +00:00
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Account" ) } < / h 3 >
2015-12-23 15:38:28 +00:00
2017-05-23 14:16:31 +00:00
< div className = "mx_UserSettings_section cadcampoHide" >
2016-04-15 17:30:13 +00:00
2017-01-13 16:25:26 +00:00
< AccessibleButton className = "mx_UserSettings_logout mx_UserSettings_button" onClick = { this . onLogoutClicked } >
2017-05-23 14:16:31 +00:00
{ _t ( "Sign out" ) }
2017-01-13 16:25:26 +00:00
< / A c c e s s i b l e B u t t o n >
2016-01-15 12:35:30 +00:00
{ accountJsx }
2015-12-23 11:47:56 +00:00
< / d i v >
2015-12-18 00:37:56 +00:00
2017-01-31 13:17:01 +00:00
{ this . _renderReferral ( ) }
2017-05-02 20:17:12 +00:00
{ notificationArea }
2015-12-18 00:37:56 +00:00
2016-07-18 00:35:42 +00:00
{ this . _renderUserInterfaceSettings ( ) }
2016-06-13 16:34:12 +00:00
{ this . _renderLabs ( ) }
2017-04-28 17:21:22 +00:00
{ this . _renderWebRtcSettings ( ) }
2016-08-01 12:42:29 +00:00
{ this . _renderDevicesPanel ( ) }
{ this . _renderCryptoInfo ( ) }
2016-12-14 15:01:50 +00:00
{ this . _renderBulkOptions ( ) }
2017-01-24 14:47:11 +00:00
{ this . _renderBugReport ( ) }
2016-06-13 16:34:12 +00:00
2017-05-24 14:40:50 +00:00
{ PlatformPeg . get ( ) . isElectron ( ) && this . _renderElectronSettings ( ) }
2017-05-29 13:36:50 +00:00
{ this . _renderAnalyticsControl ( ) }
2017-05-23 14:16:31 +00:00
< h3 > { _t ( "Advanced" ) } < / h 3 >
2015-12-23 11:47:56 +00:00
< div className = "mx_UserSettings_section" >
< div className = "mx_UserSettings_advanced" >
2017-05-25 16:35:18 +00:00
{ _t ( "Logged in as:" ) } { this . _me }
2015-11-30 15:52:41 +00:00
< / d i v >
2017-04-18 18:55:08 +00:00
< div className = "mx_UserSettings_advanced" >
2017-05-25 16:35:18 +00:00
{ _t ( 'Access Token:' ) } < span className = "mx_UserSettings_advanced_spoiler" onClick = { this . _showSpoiler } data - spoiler = { MatrixClientPeg . get ( ) . getAccessToken ( ) } > & lt ; { _t ( "click to reveal" ) } & gt ; < / s p a n >
2017-04-18 18:55:08 +00:00
< / d i v >
2016-05-18 10:42:51 +00:00
< div className = "mx_UserSettings_advanced" >
2017-05-23 14:16:31 +00:00
{ _t ( "Homeserver is" ) } { MatrixClientPeg . get ( ) . getHomeserverUrl ( ) }
2016-05-18 10:42:51 +00:00
< / d i v >
< div className = "mx_UserSettings_advanced" >
2017-05-23 14:16:31 +00:00
{ _t ( "Identity Server is" ) } { MatrixClientPeg . get ( ) . getIdentityServerUrl ( ) }
2016-05-18 10:42:51 +00:00
< / d i v >
2015-12-23 16:02:18 +00:00
< div className = "mx_UserSettings_advanced" >
2017-05-25 16:35:18 +00:00
{ _t ( 'matrix-react-sdk version:' ) } { ( REACT _SDK _VERSION !== '<local>' )
2017-05-02 20:12:58 +00:00
? gHVersionLabel ( 'matrix-org/matrix-react-sdk' , REACT _SDK _VERSION )
2017-04-21 02:04:34 +00:00
: REACT _SDK _VERSION
} < br / >
2017-05-25 16:35:18 +00:00
{ _t ( 'riot-web version:' ) } { ( this . state . vectorVersion !== undefined )
2017-05-02 20:12:58 +00:00
? gHVersionLabel ( 'vector-im/riot-web' , this . state . vectorVersion )
2017-04-21 02:04:34 +00:00
: 'unknown'
} < br / >
2017-05-25 16:35:18 +00:00
{ _t ( "olm version:" ) } { olmVersionString } < br / >
2015-12-23 11:47:56 +00:00
< / d i v >
< / d i v >
2016-01-15 13:11:14 +00:00
2017-02-17 15:16:28 +00:00
{ this . _renderClearCache ( ) }
2016-08-02 17:40:12 +00:00
{ this . _renderDeactivateAccount ( ) }
2016-01-15 13:11:14 +00:00
< / G e m i n i S c r o l l b a r >
2015-12-23 11:47:56 +00:00
< / d i v >
) ;
2017-05-02 20:17:12 +00:00
} ,
2015-11-30 15:52:41 +00:00
} ) ;