diff options
Diffstat (limited to 'web/react/components/user_settings')
15 files changed, 424 insertions, 253 deletions
diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx index 3dbed72c3..35f836adb 100644 --- a/web/react/components/user_settings/custom_theme_chooser.jsx +++ b/web/react/components/user_settings/custom_theme_chooser.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var Constants = require('../../utils/constants.jsx'); +import Constants from '../../utils/constants.jsx'; export default class CustomThemeChooser extends React.Component { constructor(props) { @@ -14,7 +14,10 @@ export default class CustomThemeChooser extends React.Component { this.state = {}; } componentDidMount() { - $('.color-picker').colorpicker().on('changeColor', this.onPickerChange); + $('.color-picker').colorpicker({ + format: 'hex' + }); + $('.color-picker').on('changeColor', this.onPickerChange); } onPickerChange(e) { const theme = this.props.theme; diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 4d594bb1b..3df9dfedf 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -1,14 +1,14 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -const ModalStore = require('../../stores/modal_store.jsx'); -const UserStore = require('../../stores/user_store.jsx'); -const Utils = require('../../utils/utils.jsx'); -const Client = require('../../utils/client.jsx'); +import ModalStore from '../../stores/modal_store.jsx'; +import UserStore from '../../stores/user_store.jsx'; +import * as Utils from '../../utils/utils.jsx'; +import * as Client from '../../utils/client.jsx'; const Modal = ReactBootstrap.Modal; -const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx'); -const Constants = require('../../utils/constants.jsx'); +import AppDispatcher from '../../dispatcher/app_dispatcher.jsx'; +import Constants from '../../utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; export default class ImportThemeModal extends React.Component { diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx index 128c011ea..9ebb55646 100644 --- a/web/react/components/user_settings/manage_incoming_hooks.jsx +++ b/web/react/components/user_settings/manage_incoming_hooks.jsx @@ -1,11 +1,11 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var Client = require('../../utils/client.jsx'); -var Utils = require('../../utils/utils.jsx'); -var Constants = require('../../utils/constants.jsx'); -var ChannelStore = require('../../stores/channel_store.jsx'); -var LoadingScreen = require('../loading_screen.jsx'); +import * as Client from '../../utils/client.jsx'; +import * as Utils from '../../utils/utils.jsx'; +import Constants from '../../utils/constants.jsx'; +import ChannelStore from '../../stores/channel_store.jsx'; +import LoadingScreen from '../loading_screen.jsx'; export default class ManageIncomingHooks extends React.Component { constructor() { diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx index 7b7cf7401..9c0fe3709 100644 --- a/web/react/components/user_settings/manage_outgoing_hooks.jsx +++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx @@ -1,12 +1,12 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -const LoadingScreen = require('../loading_screen.jsx'); +import LoadingScreen from '../loading_screen.jsx'; -const ChannelStore = require('../../stores/channel_store.jsx'); +import ChannelStore from '../../stores/channel_store.jsx'; -const Client = require('../../utils/client.jsx'); -const Constants = require('../../utils/constants.jsx'); +import * as Client from '../../utils/client.jsx'; +import Constants from '../../utils/constants.jsx'; export default class ManageOutgoingHooks extends React.Component { constructor() { @@ -188,7 +188,7 @@ export default class ManageOutgoingHooks extends React.Component { key={hook.id} className='webhook__item' > - <div className='padding-top x2'> + <div className='padding-top x2 webhook__url'> <strong>{'URLs: '}</strong><span className='word-break--all'>{hook.callback_urls.join(', ')}</span> </div> {channelDiv} diff --git a/web/react/components/user_settings/premade_theme_chooser.jsx b/web/react/components/user_settings/premade_theme_chooser.jsx index 22cfcebcd..9889bff5c 100644 --- a/web/react/components/user_settings/premade_theme_chooser.jsx +++ b/web/react/components/user_settings/premade_theme_chooser.jsx @@ -1,8 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var Utils = require('../../utils/utils.jsx'); -var Constants = require('../../utils/constants.jsx'); +import * as Utils from '../../utils/utils.jsx'; +import Constants from '../../utils/constants.jsx'; export default class PremadeThemeChooser extends React.Component { constructor(props) { diff --git a/web/react/components/user_settings/user_settings.jsx b/web/react/components/user_settings/user_settings.jsx index e089ce973..54d98bbde 100644 --- a/web/react/components/user_settings/user_settings.jsx +++ b/web/react/components/user_settings/user_settings.jsx @@ -1,16 +1,16 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../../stores/user_store.jsx'); -var utils = require('../../utils/utils.jsx'); -var NotificationsTab = require('./user_settings_notifications.jsx'); -var SecurityTab = require('./user_settings_security.jsx'); -var GeneralTab = require('./user_settings_general.jsx'); -var AppearanceTab = require('./user_settings_appearance.jsx'); -var DeveloperTab = require('./user_settings_developer.jsx'); -var IntegrationsTab = require('./user_settings_integrations.jsx'); -var DisplayTab = require('./user_settings_display.jsx'); -var AdvancedTab = require('./user_settings_advanced.jsx'); +import UserStore from '../../stores/user_store.jsx'; +import * as utils from '../../utils/utils.jsx'; +import NotificationsTab from './user_settings_notifications.jsx'; +import SecurityTab from './user_settings_security.jsx'; +import GeneralTab from './user_settings_general.jsx'; +import AppearanceTab from './user_settings_appearance.jsx'; +import DeveloperTab from './user_settings_developer.jsx'; +import IntegrationsTab from './user_settings_integrations.jsx'; +import DisplayTab from './user_settings_display.jsx'; +import AdvancedTab from './user_settings_advanced.jsx'; export default class UserSettings extends React.Component { constructor(props) { @@ -36,7 +36,7 @@ export default class UserSettings extends React.Component { onListenerChange() { var user = UserStore.getCurrentUser(); - if (!utils.areStatesEqual(this.state.user, user)) { + if (!utils.areObjectsEqual(this.state.user, user)) { this.setState({user}); } } diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx index 2616981ba..c15936ccd 100644 --- a/web/react/components/user_settings/user_settings_advanced.jsx +++ b/web/react/components/user_settings/user_settings_advanced.jsx @@ -1,11 +1,12 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -const Client = require('../../utils/client.jsx'); -const SettingItemMin = require('../setting_item_min.jsx'); -const SettingItemMax = require('../setting_item_max.jsx'); -const Constants = require('../../utils/constants.jsx'); -const PreferenceStore = require('../../stores/preference_store.jsx'); +import * as Client from '../../utils/client.jsx'; +import SettingItemMin from '../setting_item_min.jsx'; +import SettingItemMax from '../setting_item_max.jsx'; +import Constants from '../../utils/constants.jsx'; +import PreferenceStore from '../../stores/preference_store.jsx'; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; export default class AdvancedSettingsDisplay extends React.Component { constructor(props) { @@ -13,21 +14,33 @@ export default class AdvancedSettingsDisplay extends React.Component { this.updateSection = this.updateSection.bind(this); this.updateSetting = this.updateSetting.bind(this); - this.setupInitialState = this.setupInitialState.bind(this); + this.toggleFeature = this.toggleFeature.bind(this); + this.saveEnabledFeatures = this.saveEnabledFeatures.bind(this); - this.state = this.setupInitialState(); - } + const preReleaseFeaturesKeys = Object.keys(PreReleaseFeatures); + const advancedSettings = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS); + const settings = { + send_on_ctrl_enter: PreferenceStore.getPreference( + Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, + 'send_on_ctrl_enter', + {value: 'false'} + ).value + }; - setupInitialState() { - const sendOnCtrlEnter = PreferenceStore.getPreference( - Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, - 'send_on_ctrl_enter', - {value: 'false'} - ).value; + let enabledFeatures = 0; + advancedSettings.forEach((setting) => { + preReleaseFeaturesKeys.forEach((key) => { + const feature = PreReleaseFeatures[key]; + if (setting.name === Constants.FeatureTogglePrefix + feature.label) { + settings[setting.name] = setting.value; + if (setting.value === 'true') { + enabledFeatures++; + } + } + }); + }); - return { - settings: {send_on_ctrl_enter: sendOnCtrlEnter} - }; + this.state = {preReleaseFeatures: PreReleaseFeatures, settings, preReleaseFeaturesKeys, enabledFeatures}; } updateSetting(setting, value) { @@ -36,14 +49,45 @@ export default class AdvancedSettingsDisplay extends React.Component { this.setState(settings); } - handleSubmit(setting) { - const preference = PreferenceStore.setPreference( - Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, - setting, - this.state.settings[setting] - ); + toggleFeature(feature, checked) { + const settings = this.state.settings; + settings[Constants.FeatureTogglePrefix + feature] = String(checked); + + let enabledFeatures = 0; + Object.keys(this.state.settings).forEach((setting) => { + if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0 && this.state.settings[setting] === 'true') { + enabledFeatures++; + } + }); + + this.setState({settings, enabledFeatures}); + } + + saveEnabledFeatures() { + const features = []; + Object.keys(this.state.settings).forEach((setting) => { + if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0) { + features.push(setting); + } + }); + + this.handleSubmit(features); + } - Client.savePreferences([preference], + handleSubmit(settings) { + const preferences = []; + + (Array.isArray(settings) ? settings : [settings]).forEach((setting) => { + preferences.push( + PreferenceStore.setPreference( + Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, + setting, + String(this.state.settings[setting]) + ) + ); + }); + + Client.savePreferences(preferences, () => { PreferenceStore.emitChange(); this.updateSection(''); @@ -118,6 +162,66 @@ export default class AdvancedSettingsDisplay extends React.Component { ); } + let previewFeaturesSection; + let previewFeaturesSectionDivider; + if (this.state.preReleaseFeaturesKeys.length > 0) { + previewFeaturesSectionDivider = ( + <div className='divider-light'/> + ); + + if (this.props.activeSection === 'advancedPreviewFeatures') { + const inputs = []; + + this.state.preReleaseFeaturesKeys.forEach((key) => { + const feature = this.state.preReleaseFeatures[key]; + inputs.push( + <div key={'advancedPreviewFeatures_' + feature.label}> + <div className='checkbox'> + <label> + <input + type='checkbox' + checked={this.state.settings[Constants.FeatureTogglePrefix + feature.label] === 'true'} + onChange={(e) => { + this.toggleFeature(feature.label, e.target.checked); + }} + /> + {feature.description} + </label> + </div> + </div> + ); + }); + + inputs.push( + <div key='advancedPreviewFeatures_helptext'> + <br/> + {'Check any pre-released features you\'d like to preview. You may also need to refresh the page before the setting will take effect.'} + </div> + ); + + previewFeaturesSection = ( + <SettingItemMax + title='Preview pre-release features' + inputs={inputs} + submit={this.saveEnabledFeatures} + server_error={serverError} + updateSection={(e) => { + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + previewFeaturesSection = ( + <SettingItemMin + title='Preview pre-release features' + describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? ' Feature ' : ' Features ') + 'enabled'} + updateSection={() => this.props.updateSection('advancedPreviewFeatures')} + /> + ); + } + } + return ( <div> <div className='modal-header'> @@ -145,6 +249,8 @@ export default class AdvancedSettingsDisplay extends React.Component { <h3 className='tab-header'>{'Advanced Settings'}</h3> <div className='divider-dark first'/> {ctrlSendSection} + {previewFeaturesSectionDivider} + {previewFeaturesSection} <div className='divider-dark'/> </div> </div> diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx index d73b5f476..ad41ab771 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -1,14 +1,16 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../../stores/user_store.jsx'); -var Client = require('../../utils/client.jsx'); -var Utils = require('../../utils/utils.jsx'); +import CustomThemeChooser from './custom_theme_chooser.jsx'; +import PremadeThemeChooser from './premade_theme_chooser.jsx'; -const CustomThemeChooser = require('./custom_theme_chooser.jsx'); -const PremadeThemeChooser = require('./premade_theme_chooser.jsx'); -const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx'); -const Constants = require('../../utils/constants.jsx'); +import UserStore from '../../stores/user_store.jsx'; + +import AppDispatcher from '../../dispatcher/app_dispatcher.jsx'; +import * as Client from '../../utils/client.jsx'; +import * as Utils from '../../utils/utils.jsx'; + +import Constants from '../../utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; export default class UserSettingsAppearance extends React.Component { @@ -66,7 +68,7 @@ export default class UserSettingsAppearance extends React.Component { onChange() { const newState = this.getStateFromStores(); - if (!Utils.areStatesEqual(this.state, newState)) { + if (!Utils.areObjectsEqual(this.state, newState)) { this.setState(newState); } diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx index e6adba1d4..01e13be57 100644 --- a/web/react/components/user_settings/user_settings_developer.jsx +++ b/web/react/components/user_settings/user_settings_developer.jsx @@ -1,18 +1,21 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var SettingItemMin = require('../setting_item_min.jsx'); -var SettingItemMax = require('../setting_item_max.jsx'); +import SettingItemMin from '../setting_item_min.jsx'; +import SettingItemMax from '../setting_item_max.jsx'; +import * as EventHelpers from '../../dispatcher/event_helpers.jsx'; export default class DeveloperTab extends React.Component { constructor(props) { super(props); + this.register = this.register.bind(this); + this.state = {}; } register() { - $('#user_settings1').modal('hide'); - $('#register_app').modal('show'); + this.props.closeModal(); + EventHelpers.showRegisterAppModal(); } render() { var appSection; @@ -21,7 +24,10 @@ export default class DeveloperTab extends React.Component { var inputs = []; inputs.push( - <div className='form-group'> + <div + key='registerbtn' + className='form-group' + > <div className='col-sm-7'> <a className='btn btn-sm btn-primary' diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx index 43c8d33d1..c464258de 100644 --- a/web/react/components/user_settings/user_settings_display.jsx +++ b/web/react/components/user_settings/user_settings_display.jsx @@ -6,14 +6,17 @@ import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import Constants from '../../utils/constants.jsx'; import PreferenceStore from '../../stores/preference_store.jsx'; +import * as Utils from '../../utils/utils.jsx'; function getDisplayStateFromStores() { const militaryTime = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', {value: 'false'}); const nameFormat = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'username'}); + const selectedFont = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', {value: Constants.DEFAULT_FONT}); return { militaryTime: militaryTime.value, - nameFormat: nameFormat.value + nameFormat: nameFormat.value, + selectedFont: selectedFont.value }; } @@ -24,15 +27,20 @@ export default class UserSettingsDisplay extends React.Component { this.handleSubmit = this.handleSubmit.bind(this); this.handleClockRadio = this.handleClockRadio.bind(this); this.handleNameRadio = this.handleNameRadio.bind(this); + this.handleFont = this.handleFont.bind(this); this.updateSection = this.updateSection.bind(this); this.state = getDisplayStateFromStores(); + this.selectedFont = this.state.selectedFont; } handleSubmit() { const timePreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', this.state.militaryTime); const namePreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', this.state.nameFormat); + const fontPreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', this.state.selectedFont); - savePreferences([timePreference, namePreference], + this.selectedFont = this.state.selectedFont; + + savePreferences([timePreference, namePreference, fontPreference], () => { PreferenceStore.emitChange(); this.updateSection(''); @@ -48,6 +56,10 @@ export default class UserSettingsDisplay extends React.Component { handleNameRadio(nameFormat) { this.setState({nameFormat}); } + handleFont(selectedFont) { + Utils.applyFont(selectedFont); + this.setState({selectedFont}); + } updateSection(section) { this.setState(getDisplayStateFromStores()); this.props.updateSection(section); @@ -56,6 +68,8 @@ export default class UserSettingsDisplay extends React.Component { const serverError = this.state.serverError || null; let clockSection; let nameFormatSection; + let fontSection; + if (this.props.activeSection === 'clock') { const clockFormat = [false, false]; if (this.state.militaryTime === 'true') { @@ -209,6 +223,66 @@ export default class UserSettingsDisplay extends React.Component { ); } + if (this.props.activeSection === 'font') { + const options = []; + Object.keys(Constants.FONTS).forEach((fontName, idx) => { + const className = Constants.FONTS[fontName]; + options.push( + <option + key={'font_' + idx} + value={fontName} + className={className} + > + {fontName} + </option> + ); + }); + + const inputs = [ + <div key='userDisplayNameOptions'> + <div + className='dropdown' + > + <select + className='form-control' + type='text' + value={this.state.selectedFont} + onChange={(e) => this.handleFont(e.target.value)} + > + {options} + </select> + </div> + <div><br/>{'Select the font displayed in the Mattermost user interface.'}</div> + </div> + ]; + + fontSection = ( + <SettingItemMax + title='Display Font' + inputs={inputs} + submit={this.handleSubmit} + server_error={serverError} + updateSection={(e) => { + if (this.selectedFont !== this.state.selectedFont) { + this.handleFont(this.selectedFont); + } + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + fontSection = ( + <SettingItemMin + title='Display Font' + describe={this.state.selectedFont} + updateSection={() => { + this.props.updateSection('font'); + }} + /> + ); + } + return ( <div> <div className='modal-header'> @@ -235,6 +309,8 @@ export default class UserSettingsDisplay extends React.Component { <div className='user-settings'> <h3 className='tab-header'>{'Display Settings'}</h3> <div className='divider-dark first'/> + {fontSection} + <div className='divider-dark'/> {clockSection} <div className='divider-dark'/> {nameFormatSection} diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index 1bfae6930..962efd7a2 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -1,15 +1,16 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../../stores/user_store.jsx'); -var ErrorStore = require('../../stores/error_store.jsx'); -var SettingItemMin = require('../setting_item_min.jsx'); -var SettingItemMax = require('../setting_item_max.jsx'); -var SettingPicture = require('../setting_picture.jsx'); -var client = require('../../utils/client.jsx'); -var AsyncClient = require('../../utils/async_client.jsx'); -var utils = require('../../utils/utils.jsx'); -var assign = require('object-assign'); +import SettingItemMin from '../setting_item_min.jsx'; +import SettingItemMax from '../setting_item_max.jsx'; +import SettingPicture from '../setting_picture.jsx'; + +import UserStore from '../../stores/user_store.jsx'; +import ErrorStore from '../../stores/error_store.jsx'; + +import * as Client from '../../utils/client.jsx'; +import * as AsyncClient from '../../utils/async_client.jsx'; +import * as Utils from '../../utils/utils.jsx'; export default class UserSettingsGeneralTab extends React.Component { constructor(props) { @@ -32,17 +33,15 @@ export default class UserSettingsGeneralTab extends React.Component { this.updatePicture = this.updatePicture.bind(this); this.updateSection = this.updateSection.bind(this); - this.setupInitialState = this.setupInitialState.bind(this); - this.state = this.setupInitialState(props); } submitUsername(e) { e.preventDefault(); - var user = this.props.user; - var username = this.state.username.trim().toLowerCase(); + const user = Object.assign({}, this.props.user); + const username = this.state.username.trim().toLowerCase(); - var usernameError = utils.isValidUsername(username); + const usernameError = Utils.isValidUsername(username); if (usernameError === 'Cannot use a reserved word as a username.') { this.setState({clientError: 'This username is reserved, please choose a new one.'}); return; @@ -52,7 +51,7 @@ export default class UserSettingsGeneralTab extends React.Component { } if (user.username === username) { - this.setState({clientError: 'You must submit a new username'}); + this.setState({clientError: 'You must submit a new username.', emailError: '', serverError: ''}); return; } @@ -63,11 +62,11 @@ export default class UserSettingsGeneralTab extends React.Component { submitNickname(e) { e.preventDefault(); - var user = UserStore.getCurrentUser(); - var nickname = this.state.nickname.trim(); + const user = Object.assign({}, this.props.user); + const nickname = this.state.nickname.trim(); if (user.nickname === nickname) { - this.setState({clientError: 'You must submit a new nickname'}); + this.setState({clientError: 'You must submit a new nickname.', emailError: '', serverError: ''}); return; } @@ -78,12 +77,12 @@ export default class UserSettingsGeneralTab extends React.Component { submitName(e) { e.preventDefault(); - var user = UserStore.getCurrentUser(); - var firstName = this.state.firstName.trim(); - var lastName = this.state.lastName.trim(); + const user = Object.assign({}, this.props.user); + const firstName = this.state.firstName.trim(); + const lastName = this.state.lastName.trim(); if (user.first_name === firstName && user.last_name === lastName) { - this.setState({clientError: 'You must submit a new first or last name'}); + this.setState({clientError: 'You must submit a new first or last name.', emailError: '', serverError: ''}); return; } @@ -95,21 +94,21 @@ export default class UserSettingsGeneralTab extends React.Component { submitEmail(e) { e.preventDefault(); - var user = UserStore.getCurrentUser(); - var email = this.state.email.trim().toLowerCase(); - var confirmEmail = this.state.confirmEmail.trim().toLowerCase(); + const user = Object.assign({}, this.props.user); + const email = this.state.email.trim().toLowerCase(); + const confirmEmail = this.state.confirmEmail.trim().toLowerCase(); if (user.email === email) { return; } - if (email === '' || !utils.isEmail(email)) { - this.setState({emailError: 'Please enter a valid email address'}); + if (email === '' || !Utils.isEmail(email)) { + this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''}); return; } if (email !== confirmEmail) { - this.setState({emailError: 'The new emails you entered do not match'}); + this.setState({emailError: 'The new emails you entered do not match.', clientError: '', serverError: ''}); return; } @@ -117,7 +116,7 @@ export default class UserSettingsGeneralTab extends React.Component { this.submitUser(user, true); } submitUser(user, emailUpdated) { - client.updateUser(user, + Client.updateUser(user, () => { this.updateSection(''); AsyncClient.getMe(); @@ -130,13 +129,13 @@ export default class UserSettingsGeneralTab extends React.Component { } }, (err) => { - var state = this.setupInitialState(this.props); + let serverError; if (err.message) { - state.serverError = err.message; + serverError = err.message; } else { - state.serverError = err; + serverError = err; } - this.setState(state); + this.setState({serverError, emailError: '', clientError: ''}); } ); } @@ -151,10 +150,10 @@ export default class UserSettingsGeneralTab extends React.Component { return; } - var picture = this.state.picture; + const picture = this.state.picture; if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') { - this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures'}); + this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'}); return; } @@ -162,17 +161,17 @@ export default class UserSettingsGeneralTab extends React.Component { formData.append('image', picture, picture.name); this.setState({loadingPicture: true}); - client.uploadProfileImage(formData, - function imageUploadSuccess() { + Client.uploadProfileImage(formData, + () => { this.submitActive = false; AsyncClient.getMe(); window.location.reload(); - }.bind(this), - function imageUploadFailure(err) { + }, + (err) => { var state = this.setupInitialState(this.props); state.serverError = err.message; this.setState(state); - }.bind(this) + } ); } updateUsername(e) { @@ -205,34 +204,34 @@ export default class UserSettingsGeneralTab extends React.Component { } updateSection(section) { const emailChangeInProgress = this.state.emailChangeInProgress; - this.setState(assign({}, this.setupInitialState(this.props), {emailChangeInProgress: emailChangeInProgress, clientError: '', serverError: '', emailError: ''})); + this.setState(Object.assign({}, this.setupInitialState(this.props), {emailChangeInProgress, clientError: '', serverError: '', emailError: ''})); this.submitActive = false; this.props.updateSection(section); } setupInitialState(props) { - var user = props.user; + const user = props.user; return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname, email: user.email, confirmEmail: '', picture: null, loadingPicture: false, emailChangeInProgress: false}; } render() { - var user = this.props.user; + const user = this.props.user; - var clientError = null; + let clientError = null; if (this.state.clientError) { clientError = this.state.clientError; } - var serverError = null; + let serverError = null; if (this.state.serverError) { serverError = this.state.serverError; } - var emailError = null; + let emailError = null; if (this.state.emailError) { emailError = this.state.emailError; } - var nameSection; - var inputs = []; + let nameSection; + const inputs = []; if (this.props.activeSection === 'name') { inputs.push( @@ -298,15 +297,15 @@ export default class UserSettingsGeneralTab extends React.Component { submit={this.submitName} server_error={serverError} client_error={clientError} - updateSection={function clearSection(e) { + updateSection={(e) => { this.updateSection(''); e.preventDefault(); - }.bind(this)} + }} extraInfo={extraInfo} /> ); } else { - var fullName = ''; + let fullName = ''; if (user.first_name && user.last_name) { fullName = user.first_name + ' ' + user.last_name; @@ -320,17 +319,17 @@ export default class UserSettingsGeneralTab extends React.Component { <SettingItemMin title='Full Name' describe={fullName} - updateSection={function updateNameSection() { + updateSection={() => { this.updateSection('name'); - }.bind(this)} + }} /> ); } - var nicknameSection; + let nicknameSection; if (this.props.activeSection === 'nickname') { let nicknameLabel = 'Nickname'; - if (utils.isMobile()) { + if (Utils.isMobile()) { nicknameLabel = ''; } @@ -364,10 +363,10 @@ export default class UserSettingsGeneralTab extends React.Component { submit={this.submitNickname} server_error={serverError} client_error={clientError} - updateSection={function clearSection(e) { + updateSection={(e) => { this.updateSection(''); e.preventDefault(); - }.bind(this)} + }} extraInfo={extraInfo} /> ); @@ -376,17 +375,17 @@ export default class UserSettingsGeneralTab extends React.Component { <SettingItemMin title='Nickname' describe={UserStore.getCurrentUser().nickname} - updateSection={function updateNicknameSection() { + updateSection={() => { this.updateSection('nickname'); - }.bind(this)} + }} /> ); } - var usernameSection; + let usernameSection; if (this.props.activeSection === 'username') { let usernameLabel = 'Username'; - if (utils.isMobile()) { + if (Utils.isMobile()) { usernameLabel = ''; } @@ -416,10 +415,10 @@ export default class UserSettingsGeneralTab extends React.Component { submit={this.submitUsername} server_error={serverError} client_error={clientError} - updateSection={function clearSection(e) { + updateSection={(e) => { this.updateSection(''); e.preventDefault(); - }.bind(this)} + }} extraInfo={extraInfo} /> ); @@ -428,22 +427,23 @@ export default class UserSettingsGeneralTab extends React.Component { <SettingItemMin title='Username' describe={UserStore.getCurrentUser().username} - updateSection={function updateUsernameSection() { + updateSection={() => { this.updateSection('username'); - }.bind(this)} + }} /> ); } - var emailSection; + + let emailSection; if (this.props.activeSection === 'email') { const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true'; const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true'; - let helpText = 'Email is used for notifications, and requires verification if changed.'; + let helpText = 'Email is used for sign-in, notifications, and password reset. Email requires verification if changed.'; if (!emailEnabled) { helpText = <div className='setting-list__hint text-danger'>{'Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'}</div>; } else if (!emailVerificationEnabled) { - helpText = 'Email is used for notifications.'; + helpText = 'Email is used for sign-in, notifications, and password reset.'; } else if (this.state.emailChangeInProgress) { const newEmail = UserStore.getCurrentUser().email; if (newEmail) { @@ -507,10 +507,10 @@ export default class UserSettingsGeneralTab extends React.Component { submit={submit} server_error={serverError} client_error={emailError} - updateSection={function clearSection(e) { + updateSection={(e) => { this.updateSection(''); e.preventDefault(); - }.bind(this)} + }} /> ); } else { @@ -534,26 +534,26 @@ export default class UserSettingsGeneralTab extends React.Component { <SettingItemMin title='Email' describe={describe} - updateSection={function updateEmailSection() { + updateSection={() => { this.updateSection('email'); - }.bind(this)} + }} /> ); } - var pictureSection; + let pictureSection; if (this.props.activeSection === 'picture') { pictureSection = ( <SettingPicture title='Profile Picture' submit={this.submitPicture} - src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + utils.getSessionIndex()} + src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()} server_error={serverError} client_error={clientError} - updateSection={function clearSection(e) { + updateSection={(e) => { this.updateSection(''); e.preventDefault(); - }.bind(this)} + }} picture={this.state.picture} pictureChange={this.updatePicture} submitActive={this.submitActive} @@ -561,17 +561,17 @@ export default class UserSettingsGeneralTab extends React.Component { /> ); } else { - var minMessage = 'Click \'Edit\' to upload an image.'; + let minMessage = 'Click \'Edit\' to upload an image.'; if (user.last_picture_update) { - minMessage = 'Image last updated ' + utils.displayDate(user.last_picture_update); + minMessage = 'Image last updated ' + Utils.displayDate(user.last_picture_update); } pictureSection = ( <SettingItemMin title='Profile Picture' describe={minMessage} - updateSection={function updatePictureSection() { + updateSection={() => { this.updateSection('picture'); - }.bind(this)} + }} /> ); } @@ -619,10 +619,10 @@ export default class UserSettingsGeneralTab extends React.Component { } UserSettingsGeneralTab.propTypes = { - user: React.PropTypes.object, - updateSection: React.PropTypes.func, - updateTab: React.PropTypes.func, - activeSection: React.PropTypes.string, + user: React.PropTypes.object.isRequired, + updateSection: React.PropTypes.func.isRequired, + updateTab: React.PropTypes.func.isRequired, + activeSection: React.PropTypes.string.isRequired, closeModal: React.PropTypes.func.isRequired, collapseModal: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx index 744a6beea..a86510eb3 100644 --- a/web/react/components/user_settings/user_settings_integrations.jsx +++ b/web/react/components/user_settings/user_settings_integrations.jsx @@ -1,10 +1,10 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var SettingItemMin = require('../setting_item_min.jsx'); -var SettingItemMax = require('../setting_item_max.jsx'); -var ManageIncomingHooks = require('./manage_incoming_hooks.jsx'); -var ManageOutgoingHooks = require('./manage_outgoing_hooks.jsx'); +import SettingItemMin from '../setting_item_min.jsx'; +import SettingItemMax from '../setting_item_max.jsx'; +import ManageIncomingHooks from './manage_incoming_hooks.jsx'; +import ManageOutgoingHooks from './manage_outgoing_hooks.jsx'; export default class UserSettingsIntegrationsTab extends React.Component { constructor(props) { diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx index 4dcf32cb9..36e1aa217 100644 --- a/web/react/components/user_settings/user_settings_modal.jsx +++ b/web/react/components/user_settings/user_settings_modal.jsx @@ -1,15 +1,16 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -const ConfirmModal = require('../confirm_modal.jsx'); +import ConfirmModal from '../confirm_modal.jsx'; const Modal = ReactBootstrap.Modal; -const SettingsSidebar = require('../settings_sidebar.jsx'); -const UserSettings = require('./user_settings.jsx'); +import SettingsSidebar from '../settings_sidebar.jsx'; +import UserSettings from './user_settings.jsx'; export default class UserSettingsModal extends React.Component { constructor(props) { super(props); + this.handleShow = this.handleShow.bind(this); this.handleHide = this.handleHide.bind(this); this.handleHidden = this.handleHidden.bind(this); this.handleCollapse = this.handleCollapse.bind(this); @@ -33,12 +34,24 @@ export default class UserSettingsModal extends React.Component { this.requireConfirm = false; } + componentDidMount() { + if (this.props.show) { + this.handleShow(); + } + } + componentDidUpdate(prevProps) { - if (!prevProps.show && this.props.show) { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300); - if ($(window).width() > 768) { - $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar(); - } + if (this.props.show && !prevProps.show) { + this.handleShow(); + } + } + + handleShow() { + if ($(window).width() > 768) { + $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar(); + $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); + } else { + $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 50); } } diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx index c6f47804f..f762405af 100644 --- a/web/react/components/user_settings/user_settings_notifications.jsx +++ b/web/react/components/user_settings/user_settings_notifications.jsx @@ -1,16 +1,18 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../../stores/user_store.jsx'); -var SettingItemMin = require('../setting_item_min.jsx'); -var SettingItemMax = require('../setting_item_max.jsx'); -var client = require('../../utils/client.jsx'); -var AsyncClient = require('../../utils/async_client.jsx'); -var utils = require('../../utils/utils.jsx'); +import SettingItemMin from '../setting_item_min.jsx'; +import SettingItemMax from '../setting_item_max.jsx'; + +import UserStore from '../../stores/user_store.jsx'; + +import * as Client from '../../utils/client.jsx'; +import * as AsyncClient from '../../utils/async_client.jsx'; +import * as Utils from '../../utils/utils.jsx'; function getNotificationsStateFromStores() { var user = UserStore.getCurrentUser(); - var soundNeeded = !utils.isBrowserFirefox(); + var soundNeeded = !Utils.isBrowserFirefox(); var sound = 'true'; if (user.notify_props && user.notify_props.desktop_sound) { @@ -76,7 +78,9 @@ export default class NotificationsTab extends React.Component { super(props); this.handleSubmit = this.handleSubmit.bind(this); + this.handleCancel = this.handleCancel.bind(this); this.updateSection = this.updateSection.bind(this); + this.updateState = this.updateState.bind(this); this.onListenerChange = this.onListenerChange.bind(this); this.handleNotifyRadio = this.handleNotifyRadio.bind(this); this.handleEmailRadio = this.handleEmailRadio.bind(this); @@ -116,7 +120,7 @@ export default class NotificationsTab extends React.Component { data.all = this.state.allKey.toString(); data.channel = this.state.channelKey.toString(); - client.updateUserNotifyProps(data, + Client.updateUserNotifyProps(data, function success() { this.props.updateSection(''); AsyncClient.getMe(); @@ -126,10 +130,21 @@ export default class NotificationsTab extends React.Component { }.bind(this) ); } + handleCancel(e) { + this.updateState(); + this.props.updateSection(''); + e.preventDefault(); + } updateSection(section) { - this.setState(getNotificationsStateFromStores()); + this.updateState(); this.props.updateSection(section); } + updateState() { + const newState = getNotificationsStateFromStores(); + if (!Utils.areObjectsEqual(newState, this.state)) { + this.setState(newState); + } + } componentDidMount() { UserStore.addChangeListener(this.onListenerChange); } @@ -137,10 +152,7 @@ export default class NotificationsTab extends React.Component { UserStore.removeChangeListener(this.onListenerChange); } onListenerChange() { - var newState = getNotificationsStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } + this.updateState(); } handleNotifyRadio(notifyLevel) { this.setState({notifyLevel: notifyLevel}); @@ -243,11 +255,6 @@ export default class NotificationsTab extends React.Component { </div> ); - handleUpdateDesktopSection = function updateDesktopSection(e) { - this.props.updateSection(''); - e.preventDefault(); - }.bind(this); - const extraInfo = <span>{'Desktop notifications are available on Firefox, Safari, and Chrome.'}</span>; desktopSection = ( @@ -257,7 +264,7 @@ export default class NotificationsTab extends React.Component { inputs={inputs} submit={this.handleSubmit} server_error={serverError} - updateSection={handleUpdateDesktopSection} + updateSection={this.handleCancel} /> ); } else { @@ -322,11 +329,6 @@ export default class NotificationsTab extends React.Component { </div> ); - handleUpdateSoundSection = function updateSoundSection(e) { - this.props.updateSection(''); - e.preventDefault(); - }.bind(this); - const extraInfo = <span>{'Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'}</span>; soundSection = ( @@ -336,7 +338,7 @@ export default class NotificationsTab extends React.Component { inputs={inputs} submit={this.handleSubmit} server_error={serverError} - updateSection={handleUpdateSoundSection} + updateSection={this.handleCancel} /> ); } else { @@ -403,18 +405,13 @@ export default class NotificationsTab extends React.Component { </div> ); - handleUpdateEmailSection = function updateEmailSection(e) { - this.props.updateSection(''); - e.preventDefault(); - }.bind(this); - emailSection = ( <SettingItemMax title='Email notifications' inputs={inputs} submit={this.handleSubmit} server_error={serverError} - updateSection={handleUpdateEmailSection} + updateSection={this.handleCancel} /> ); } else { @@ -510,7 +507,7 @@ export default class NotificationsTab extends React.Component { }.bind(this); inputs.push( <div key='userNotificationAllOption'> - <div className='checkbox'> + <div className='checkbox hidden'> <label> <input type='checkbox' @@ -564,17 +561,13 @@ export default class NotificationsTab extends React.Component { </div> ); - handleUpdateKeysSection = function updateKeysSection(e) { - this.props.updateSection(''); - e.preventDefault(); - }.bind(this); keysSection = ( <SettingItemMax title='Words that trigger mentions' inputs={inputs} submit={this.handleSubmit} server_error={serverError} - updateSection={handleUpdateKeysSection} + updateSection={this.handleCancel} /> ); } else { @@ -588,9 +581,11 @@ export default class NotificationsTab extends React.Component { if (this.state.mentionKey) { keys.push('@' + user.username); } - if (this.state.allKey) { - keys.push('@all'); - } + + // if (this.state.allKey) { + // keys.push('@all'); + // } + if (this.state.channelKey) { keys.push('@channel'); } @@ -649,7 +644,7 @@ export default class NotificationsTab extends React.Component { ref='wrapper' className='user-settings' > - <h3 className='tab-header'>Notifications</h3> + <h3 className='tab-header'>{'Notifications'}</h3> <div className='divider-dark first'/> {desktopSection} <div className='divider-light'/> diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx index 61d13ed8b..fa2fecf07 100644 --- a/web/react/components/user_settings/user_settings_security.jsx +++ b/web/react/components/user_settings/user_settings_security.jsx @@ -1,46 +1,26 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var SettingItemMin = require('../setting_item_min.jsx'); -var SettingItemMax = require('../setting_item_max.jsx'); -var AccessHistoryModal = require('../access_history_modal.jsx'); -var ActivityLogModal = require('../activity_log_modal.jsx'); -var Client = require('../../utils/client.jsx'); -var AsyncClient = require('../../utils/async_client.jsx'); -var Constants = require('../../utils/constants.jsx'); +import SettingItemMin from '../setting_item_min.jsx'; +import SettingItemMax from '../setting_item_max.jsx'; +import AccessHistoryModal from '../access_history_modal.jsx'; +import ActivityLogModal from '../activity_log_modal.jsx'; +import ToggleModalButton from '../toggle_modal_button.jsx'; +import * as Client from '../../utils/client.jsx'; +import * as AsyncClient from '../../utils/async_client.jsx'; +import Constants from '../../utils/constants.jsx'; export default class SecurityTab extends React.Component { constructor(props) { super(props); - this.showAccessHistoryModal = this.showAccessHistoryModal.bind(this); - this.showActivityLogModal = this.showActivityLogModal.bind(this); - this.hideModals = this.hideModals.bind(this); this.submitPassword = this.submitPassword.bind(this); this.updateCurrentPassword = this.updateCurrentPassword.bind(this); this.updateNewPassword = this.updateNewPassword.bind(this); this.updateConfirmPassword = this.updateConfirmPassword.bind(this); this.setupInitialState = this.setupInitialState.bind(this); - const state = this.setupInitialState(); - state.showAccessHistoryModal = false; - state.showActivityLogModal = false; - this.state = state; - } - showAccessHistoryModal() { - this.props.setEnforceFocus(false); - this.setState({showAccessHistoryModal: true}); - } - showActivityLogModal() { - this.props.setEnforceFocus(false); - this.setState({showActivityLogModal: true}); - } - hideModals() { - this.props.setEnforceFocus(true); - this.setState({ - showAccessHistoryModal: false, - showActivityLogModal: false - }); + this.state = this.setupInitialState(); } submitPassword(e) { e.preventDefault(); @@ -258,30 +238,20 @@ export default class SecurityTab extends React.Component { {passwordSection} <div className='divider-dark'/> <br></br> - <a + <ToggleModalButton className='security-links theme' - href='#' - onClick={this.showAccessHistoryModal} + dialogType={AccessHistoryModal} > <i className='fa fa-clock-o'></i>View Access History - </a> + </ToggleModalButton> <b> </b> - <a + <ToggleModalButton className='security-links theme' - href='#' - onClick={this.showActivityLogModal} + dialogType={ActivityLogModal} > - <i className='fa fa-globe'></i>View and Logout of Active Sessions - </a> + <i className='fa fa-clock-o'></i>{'View and Logout of Active Sessions'} + </ToggleModalButton> </div> - <AccessHistoryModal - show={this.state.showAccessHistoryModal} - onModalDismissed={this.hideModals} - /> - <ActivityLogModal - show={this.state.showActivityLogModal} - onModalDismissed={this.hideModals} - /> </div> ); } |