diff options
author | Christopher Speller <crspeller@gmail.com> | 2015-11-04 12:59:41 -0500 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2015-11-04 12:59:41 -0500 |
commit | aee6d9b608aceb7b68ab1e1a583917c2c3ee4485 (patch) | |
tree | 8908ba8d14cafe8dd1532cc429920b8c243dd465 /web/react/components/user_settings | |
parent | 2840c317e5a88264aa044261558dda6d91ee1ff3 (diff) | |
parent | a7ceba2e57f5b693afa51b0aefd04081646c6948 (diff) | |
download | chat-aee6d9b608aceb7b68ab1e1a583917c2c3ee4485.tar.gz chat-aee6d9b608aceb7b68ab1e1a583917c2c3ee4485.tar.bz2 chat-aee6d9b608aceb7b68ab1e1a583917c2c3ee4485.zip |
Merge pull request #1287 from hmhealey/plt382
PLT-382 Refactored some modals to use React-Bootstrap and added a confirmation when discarding theme changes
Diffstat (limited to 'web/react/components/user_settings')
11 files changed, 355 insertions, 182 deletions
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 1a9ac0ad3..24da106d0 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -1,6 +1,7 @@ // 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'); @@ -24,10 +25,10 @@ export default class ImportThemeModal extends React.Component { }; } componentDidMount() { - UserStore.addImportModalListener(this.updateShow); + ModalStore.addModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow); } componentWillUnmount() { - UserStore.removeImportModalListener(this.updateShow); + ModalStore.removeModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow); } updateShow(show) { this.setState({show}); @@ -74,7 +75,6 @@ export default class ImportThemeModal extends React.Component { this.setState({show: false}); Utils.applyTheme(theme); - $('#user_settings').modal('show'); }, (err) => { var state = this.getStateFromStores(); diff --git a/web/react/components/user_settings/user_settings.jsx b/web/react/components/user_settings/user_settings.jsx index 546e26ca3..e089ce973 100644 --- a/web/react/components/user_settings/user_settings.jsx +++ b/web/react/components/user_settings/user_settings.jsx @@ -16,6 +16,7 @@ export default class UserSettings extends React.Component { constructor(props) { super(props); + this.getActiveTab = this.getActiveTab.bind(this); this.onListenerChange = this.onListenerChange.bind(this); this.state = {user: UserStore.getCurrentUser()}; @@ -29,10 +30,14 @@ export default class UserSettings extends React.Component { UserStore.removeChangeListener(this.onListenerChange); } + getActiveTab() { + return this.refs.activeTab; + } + onListenerChange() { var user = UserStore.getCurrentUser(); if (!utils.areStatesEqual(this.state.user, user)) { - this.setState({user: user}); + this.setState({user}); } } @@ -41,10 +46,13 @@ export default class UserSettings extends React.Component { return ( <div> <GeneralTab + ref='activeTab' user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} /> </div> ); @@ -52,10 +60,14 @@ export default class UserSettings extends React.Component { return ( <div> <SecurityTab + ref='activeTab' user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} + setEnforceFocus={this.props.setEnforceFocus} /> </div> ); @@ -63,10 +75,13 @@ export default class UserSettings extends React.Component { return ( <div> <NotificationsTab + ref='activeTab' user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} /> </div> ); @@ -74,9 +89,14 @@ export default class UserSettings extends React.Component { return ( <div> <AppearanceTab + ref='activeTab' activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} + setEnforceFocus={this.props.setEnforceFocus} + setRequireConfirm={this.props.setRequireConfirm} /> </div> ); @@ -84,8 +104,11 @@ export default class UserSettings extends React.Component { return ( <div> <DeveloperTab + ref='activeTab' activeSection={this.props.activeSection} updateSection={this.props.updateSection} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} /> </div> ); @@ -93,10 +116,13 @@ export default class UserSettings extends React.Component { return ( <div> <IntegrationsTab + ref='activeTab' user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} /> </div> ); @@ -104,10 +130,13 @@ export default class UserSettings extends React.Component { return ( <div> <DisplayTab + ref='activeTab' user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} /> </div> ); @@ -115,10 +144,13 @@ export default class UserSettings extends React.Component { return ( <div> <AdvancedTab + ref='activeTab' user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} + closeModal={this.props.closeModal} + collapseModal={this.props.collapseModal} /> </div> ); @@ -132,5 +164,9 @@ UserSettings.propTypes = { activeTab: React.PropTypes.string, activeSection: React.PropTypes.string, updateSection: React.PropTypes.func, - updateTab: React.PropTypes.func + updateTab: React.PropTypes.func, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired, + setEnforceFocus: React.PropTypes.func.isRequired, + setRequireConfirm: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx index 910444735..2616981ba 100644 --- a/web/react/components/user_settings/user_settings_advanced.jsx +++ b/web/react/components/user_settings/user_settings_advanced.jsx @@ -13,7 +13,6 @@ export default class AdvancedSettingsDisplay extends React.Component { this.updateSection = this.updateSection.bind(this); this.updateSetting = this.updateSetting.bind(this); - this.handleClose = this.handleClose.bind(this); this.setupInitialState = this.setupInitialState.bind(this); this.state = this.setupInitialState(); @@ -59,18 +58,6 @@ export default class AdvancedSettingsDisplay extends React.Component { this.props.updateSection(section); } - handleClose() { - this.updateSection(''); - } - - componentDidMount() { - $('#user_settings').on('hidden.bs.modal', this.handleClose); - } - - componentWillUnmount() { - $('#user_settings').off('hidden.bs.modal', this.handleClose); - } - render() { const serverError = this.state.serverError || null; let ctrlSendSection; @@ -139,6 +126,7 @@ export default class AdvancedSettingsDisplay extends React.Component { className='close' data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> </button> @@ -146,7 +134,10 @@ export default class AdvancedSettingsDisplay extends React.Component { className='modal-title' ref='title' > - <i className='modal-back'></i> + <i + className='modal-back' + onClick={this.props.collapseModal} + /> {'Advanced Settings'} </h4> </div> @@ -165,5 +156,7 @@ AdvancedSettingsDisplay.propTypes = { user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, - activeSection: React.PropTypes.string + activeSection: React.PropTypes.string, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx index 7b4b54e27..425645c1f 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -20,13 +20,13 @@ export default class UserSettingsAppearance extends React.Component { this.submitTheme = this.submitTheme.bind(this); this.updateTheme = this.updateTheme.bind(this); this.updateCodeTheme = this.updateCodeTheme.bind(this); - this.handleClose = this.handleClose.bind(this); + this.deactivate = this.deactivate.bind(this); + this.resetFields = this.resetFields.bind(this); this.handleImportModal = this.handleImportModal.bind(this); this.state = this.getStateFromStores(); - this.originalTheme = this.state.theme; - this.originalCodeTheme = this.state.theme.codeTheme; + this.originalTheme = Object.assign({}, this.state.theme); } componentDidMount() { UserStore.addChangeListener(this.onChange); @@ -34,7 +34,6 @@ export default class UserSettingsAppearance extends React.Component { if (this.props.activeSection === 'theme') { $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); } - $('#user_settings').on('hidden.bs.modal', this.handleClose); } componentDidUpdate() { if (this.props.activeSection === 'theme') { @@ -44,14 +43,13 @@ export default class UserSettingsAppearance extends React.Component { } componentWillUnmount() { UserStore.removeChangeListener(this.onChange); - $('#user_settings').off('hidden.bs.modal', this.handleClose); } getStateFromStores() { const user = UserStore.getCurrentUser(); let theme = null; if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) { - theme = user.theme_props; + theme = Object.assign({}, user.theme_props); } else { theme = $.extend(true, {}, Constants.THEMES.default); } @@ -73,6 +71,8 @@ export default class UserSettingsAppearance extends React.Component { if (!Utils.areStatesEqual(this.state, newState)) { this.setState(newState); } + + this.props.setEnforceFocus(true); } submitTheme(e) { e.preventDefault(); @@ -86,11 +86,11 @@ export default class UserSettingsAppearance extends React.Component { me: data }); - $('#user_settings').off('hidden.bs.modal', this.handleClose); - this.props.updateTab('general'); + this.props.setRequireConfirm(false); + this.originalTheme = Object.assign({}, this.state.theme); + $('.ps-container.modal-body').scrollTop(0); $('.ps-container.modal-body').perfectScrollbar('update'); - $('#user_settings').modal('hide'); }, (err) => { var state = this.getStateFromStores(); @@ -103,37 +103,53 @@ export default class UserSettingsAppearance extends React.Component { if (!theme.codeTheme) { theme.codeTheme = this.state.theme.codeTheme; } + + let themeChanged = this.state.theme.length === theme.length; + if (!themeChanged) { + for (const field in theme) { + if (theme.hasOwnProperty(field)) { + if (this.state.theme[field] !== theme[field]) { + themeChanged = true; + break; + } + } + } + } + + this.props.setRequireConfirm(themeChanged); + this.setState({theme}); Utils.applyTheme(theme); } updateCodeTheme(codeTheme) { var theme = this.state.theme; theme.codeTheme = codeTheme; - this.setState({theme}); - Utils.applyTheme(theme); + this.updateTheme(theme); } updateType(type) { this.setState({type}); } - handleClose() { + deactivate() { const state = this.getStateFromStores(); - state.serverError = null; - state.theme.codeTheme = this.originalCodeTheme; Utils.applyTheme(state.theme); - + } + resetFields() { + const state = this.getStateFromStores(); + state.serverError = null; this.setState(state); - $('.ps-container.modal-body').scrollTop(0); - $('.ps-container.modal-body').perfectScrollbar('update'); - $('#user_settings').modal('hide'); + Utils.applyTheme(state.theme); + + this.props.setRequireConfirm(false); } handleImportModal() { - $('#user_settings').modal('hide'); AppDispatcher.handleViewAction({ type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL, value: true }); + + this.props.setEnforceFocus(false); } render() { var serverError; @@ -204,7 +220,7 @@ export default class UserSettingsAppearance extends React.Component { <a className='btn btn-sm theme' href='#' - onClick={this.handleClose} + onClick={this.resetFields} > {'Cancel'} </a> @@ -218,8 +234,8 @@ export default class UserSettingsAppearance extends React.Component { <button type='button' className='close' - data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> </button> @@ -227,7 +243,11 @@ export default class UserSettingsAppearance extends React.Component { className='modal-title' ref='title' > - <i className='modal-back'></i>{'Appearance Settings'} + <i + className='modal-back' + onClick={this.props.collapseModal} + /> + {'Appearance Settings'} </h4> </div> <div className='user-settings'> @@ -253,5 +273,9 @@ UserSettingsAppearance.defaultProps = { }; UserSettingsAppearance.propTypes = { activeSection: React.PropTypes.string, - updateTab: React.PropTypes.func + updateTab: React.PropTypes.func, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired, + setRequireConfirm: React.PropTypes.func.isRequired, + setEnforceFocus: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx index c2d7a9710..e6adba1d4 100644 --- a/web/react/components/user_settings/user_settings_developer.jsx +++ b/web/react/components/user_settings/user_settings_developer.jsx @@ -63,6 +63,7 @@ export default class DeveloperTab extends React.Component { className='close' data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> </button> @@ -70,7 +71,11 @@ export default class DeveloperTab extends React.Component { className='modal-title' ref='title' > - <i className='modal-back'></i>{'Developer Settings'} + <i + className='modal-back' + onClick={this.props.collapseModal} + /> + {'Developer Settings'} </h4> </div> <div className='user-settings'> @@ -89,5 +94,7 @@ DeveloperTab.defaultProps = { }; DeveloperTab.propTypes = { activeSection: React.PropTypes.string, - updateSection: React.PropTypes.func + updateSection: React.PropTypes.func, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx index d086c78a9..3c12ead23 100644 --- a/web/react/components/user_settings/user_settings_display.jsx +++ b/web/react/components/user_settings/user_settings_display.jsx @@ -25,7 +25,6 @@ export default class UserSettingsDisplay extends React.Component { this.handleClockRadio = this.handleClockRadio.bind(this); this.handleNameRadio = this.handleNameRadio.bind(this); this.updateSection = this.updateSection.bind(this); - this.handleClose = this.handleClose.bind(this); this.state = getDisplayStateFromStores(); } @@ -53,15 +52,6 @@ export default class UserSettingsDisplay extends React.Component { this.setState(getDisplayStateFromStores()); this.props.updateSection(section); } - handleClose() { - this.updateSection(''); - } - componentDidMount() { - $('#user_settings').on('hidden.bs.modal', this.handleClose); - } - componentWillUnmount() { - $('#user_settings').off('hidden.bs.modal', this.handleClose); - } render() { const serverError = this.state.serverError || null; let clockSection; @@ -227,6 +217,7 @@ export default class UserSettingsDisplay extends React.Component { className='close' data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> </button> @@ -234,7 +225,10 @@ export default class UserSettingsDisplay extends React.Component { className='modal-title' ref='title' > - <i className='modal-back'></i> + <i + className='modal-back' + onClick={this.props.collapseModal} + /> {'Display Settings'} </h4> </div> @@ -255,5 +249,7 @@ UserSettingsDisplay.propTypes = { user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, - activeSection: React.PropTypes.string + activeSection: React.PropTypes.string, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index 3adac197a..9f0c16194 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -32,7 +32,6 @@ export default class UserSettingsGeneralTab extends React.Component { this.updatePicture = this.updatePicture.bind(this); this.updateSection = this.updateSection.bind(this); - this.handleClose = this.handleClose.bind(this); this.setupInitialState = this.setupInitialState.bind(this); this.state = this.setupInitialState(props); @@ -210,20 +209,6 @@ export default class UserSettingsGeneralTab extends React.Component { this.submitActive = false; this.props.updateSection(section); } - handleClose() { - $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearForms() { - this.value = ''; - }); - - this.setState(assign({}, this.setupInitialState(this.props), {clientError: null, serverError: null, emailError: null})); - this.props.updateSection(''); - } - componentDidMount() { - $('#user_settings').on('hidden.bs.modal', this.handleClose); - } - componentWillUnmount() { - $('#user_settings').off('hidden.bs.modal', this.handleClose); - } setupInitialState(props) { var user = props.user; @@ -579,6 +564,7 @@ export default class UserSettingsGeneralTab extends React.Component { className='close' data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> </button> @@ -586,7 +572,10 @@ export default class UserSettingsGeneralTab extends React.Component { className='modal-title' ref='title' > - <i className='modal-back'></i> + <i + className='modal-back' + onClick={this.props.collapseModal} + /> {'General Settings'} </h4> </div> @@ -613,5 +602,7 @@ UserSettingsGeneralTab.propTypes = { user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, - activeSection: React.PropTypes.string + activeSection: React.PropTypes.string, + 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 4a9915a1f..744a6beea 100644 --- a/web/react/components/user_settings/user_settings_integrations.jsx +++ b/web/react/components/user_settings/user_settings_integrations.jsx @@ -11,24 +11,12 @@ export default class UserSettingsIntegrationsTab extends React.Component { super(props); this.updateSection = this.updateSection.bind(this); - this.handleClose = this.handleClose.bind(this); this.state = {}; } updateSection(section) { this.props.updateSection(section); } - handleClose() { - this.updateSection(''); - $('.ps-container.modal-body').scrollTop(0); - $('.ps-container.modal-body').perfectScrollbar('update'); - } - componentDidMount() { - $('#user_settings').on('hidden.bs.modal', this.handleClose); - } - componentWillUnmount() { - $('#user_settings').off('hidden.bs.modal', this.handleClose); - } render() { let incomingHooksSection; let outgoingHooksSection; @@ -104,6 +92,7 @@ export default class UserSettingsIntegrationsTab extends React.Component { className='close' data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > <span aria-hidden='true'>{'×'}</span> </button> @@ -111,7 +100,10 @@ export default class UserSettingsIntegrationsTab extends React.Component { className='modal-title' ref='title' > - <i className='modal-back'></i> + <i + className='modal-back' + onClick={this.props.collapseModal} + /> {'Integration Settings'} </h4> </div> @@ -132,5 +124,7 @@ UserSettingsIntegrationsTab.propTypes = { user: React.PropTypes.object, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, - activeSection: React.PropTypes.string + activeSection: React.PropTypes.string, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx index 18dd490e7..4dcf32cb9 100644 --- a/web/react/components/user_settings/user_settings_modal.jsx +++ b/web/react/components/user_settings/user_settings_modal.jsx @@ -1,34 +1,161 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var SettingsSidebar = require('../settings_sidebar.jsx'); -var UserSettings = require('./user_settings.jsx'); +const ConfirmModal = require('../confirm_modal.jsx'); +const Modal = ReactBootstrap.Modal; +const SettingsSidebar = require('../settings_sidebar.jsx'); +const UserSettings = require('./user_settings.jsx'); export default class UserSettingsModal extends React.Component { constructor(props) { super(props); + this.handleHide = this.handleHide.bind(this); + this.handleHidden = this.handleHidden.bind(this); + this.handleCollapse = this.handleCollapse.bind(this); + this.handleConfirm = this.handleConfirm.bind(this); + this.handleCancelConfirmation = this.handleCancelConfirmation.bind(this); + + this.deactivateTab = this.deactivateTab.bind(this); + this.closeModal = this.closeModal.bind(this); + this.collapseModal = this.collapseModal.bind(this); + this.updateTab = this.updateTab.bind(this); this.updateSection = this.updateSection.bind(this); - this.state = {active_tab: 'general', active_section: ''}; + this.state = { + active_tab: 'general', + active_section: '', + showConfirmModal: false, + enforceFocus: true + }; + + this.requireConfirm = false; + } + + 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(); + } + } + } + + // Called when the close button is pressed on the main modal + handleHide() { + if (this.requireConfirm) { + this.afterConfirm = () => this.handleHide(); + this.showConfirmModal(); + + return false; + } + + this.deactivateTab(); + this.props.onModalDismissed(); } - componentDidMount() { - $('body').on('click', '.modal-back', function changeDisplay() { - $(this).closest('.modal-dialog').removeClass('display--content'); + + // called after the dialog is fully hidden and faded out + handleHidden() { + this.setState({ + active_tab: 'general', + active_section: '' }); - $('body').on('click', '.modal-header .close', () => { - setTimeout(() => { - $('.modal-dialog.display--content').removeClass('display--content'); - }, 500); + } + + // Called to hide the settings pane when on mobile + handleCollapse() { + $(ReactDOM.findDOMNode(this.refs.modalBody)).closest('.modal-dialog').removeClass('display--content'); + + this.deactivateTab(); + + this.setState({ + active_tab: '', + active_section: '' }); } - updateTab(tab) { - this.setState({active_tab: tab}); + + handleConfirm() { + this.setState({ + showConfirmModal: false, + enforceFocus: true + }); + + this.requireConfirm = false; + + if (this.afterConfirm) { + this.afterConfirm(); + this.afterConfirm = null; + } } - updateSection(section) { - this.setState({active_section: section}); + + handleCancelConfirmation() { + this.setState({ + showConfirmModal: false, + enforceFocus: true + }); + + this.afterConfirm = null; } + + showConfirmModal(afterConfirm) { + this.setState({ + showConfirmModal: true, + enforceFocus: false + }); + + if (afterConfirm) { + this.afterConfirm = afterConfirm; + } + } + + // Called to let settings tab perform cleanup before being closed + deactivateTab() { + const activeTab = this.refs.userSettings.getActiveTab(); + if (activeTab && activeTab.deactivate) { + activeTab.deactivate(); + } + } + + // Called by settings tabs when their close button is pressed + closeModal() { + if (this.requireConfirm) { + this.showConfirmModal(this.closeModal); + } else { + this.handleHide(); + } + } + + // Called by settings tabs when their back button is pressed + collapseModal() { + if (this.requireConfirm) { + this.showConfirmModal(this.collapseModal); + } else { + this.handleCollapse(); + } + } + + updateTab(tab, skipConfirm) { + if (!skipConfirm && this.requireConfirm) { + this.showConfirmModal(() => this.updateTab(tab, true)); + } else { + this.deactivateTab(); + + this.setState({ + active_tab: tab, + active_section: '' + }); + } + } + + updateSection(section, skipConfirm) { + if (!skipConfirm && this.requireConfirm) { + this.showConfirmModal(() => this.updateSection(section, true)); + } else { + this.setState({active_section: section}); + } + } + render() { var tabs = []; tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'}); @@ -46,33 +173,17 @@ export default class UserSettingsModal extends React.Component { tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'}); return ( - <div - className='modal fade' - ref='modal' - id='user_settings' - role='dialog' - tabIndex='-1' - aria-hidden='true' + <Modal + dialogClassName='settings-modal' + show={this.props.show} + onHide={this.handleHide} + onExited={this.handleHidden} + enforceFocus={this.state.enforceFocus} > - <div className='modal-dialog settings-modal'> - <div className='modal-content'> - <div className='modal-header'> - <button - type='button' - className='close' - data-dismiss='modal' - aria-label='Close' - > - <span aria-hidden='true'>{'×'}</span> - </button> - <h4 - className='modal-title' - ref='title' - > - {'Account Settings'} - </h4> - </div> - <div className='modal-body'> + <Modal.Header closeButton={true}> + <Modal.Title>{'Account Settings'}</Modal.Title> + </Modal.Header> + <Modal.Body ref='modalBody'> <div className='settings-table'> <div className='settings-links'> <SettingsSidebar @@ -83,17 +194,33 @@ export default class UserSettingsModal extends React.Component { </div> <div className='settings-content minimize-settings'> <UserSettings + ref='userSettings' activeTab={this.state.active_tab} activeSection={this.state.active_section} updateSection={this.updateSection} updateTab={this.updateTab} + closeModal={this.closeModal} + collapseModal={this.collapseModal} + setEnforceFocus={(enforceFocus) => this.setState({enforceFocus})} + setRequireConfirm={(requireConfirm) => this.requireConfirm = requireConfirm} /> </div> </div> - </div> - </div> - </div> - </div> + </Modal.Body> + <ConfirmModal + title='Discard Changes?' + message='You have unsaved changes, are you sure you want to discard them?' + confirm_button='Yes, Discard' + show={this.state.showConfirmModal} + onConfirm={this.handleConfirm} + onCancel={this.handleCancelConfirmation} + /> + </Modal> ); } } + +UserSettingsModal.propTypes = { + show: React.PropTypes.bool.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx index 2b904763c..c6f47804f 100644 --- a/web/react/components/user_settings/user_settings_notifications.jsx +++ b/web/react/components/user_settings/user_settings_notifications.jsx @@ -7,7 +7,6 @@ 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'); -var assign = require('object-assign'); function getNotificationsStateFromStores() { var user = UserStore.getCurrentUser(); @@ -77,7 +76,6 @@ export default class NotificationsTab extends React.Component { super(props); this.handleSubmit = this.handleSubmit.bind(this); - this.handleClose = this.handleClose.bind(this); this.updateSection = this.updateSection.bind(this); this.onListenerChange = this.onListenerChange.bind(this); this.handleNotifyRadio = this.handleNotifyRadio.bind(this); @@ -128,27 +126,15 @@ export default class NotificationsTab extends React.Component { }.bind(this) ); } - handleClose() { - $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearField() { - this.value = ''; - }); - - this.setState(assign({}, getNotificationsStateFromStores(), {serverError: null})); - - this.props.updateTab('general'); - } updateSection(section) { this.setState(getNotificationsStateFromStores()); this.props.updateSection(section); } componentDidMount() { UserStore.addChangeListener(this.onListenerChange); - $('#user_settings').on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { UserStore.removeChangeListener(this.onListenerChange); - $('#user_settings').off('hidden.bs.modal', this.handleClose); - this.props.updateSection(''); } onListenerChange() { var newState = getNotificationsStateFromStores(); @@ -644,15 +630,19 @@ export default class NotificationsTab extends React.Component { className='close' data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'×'}</span> </button> <h4 className='modal-title' ref='title' > - <i className='modal-back'></i> - Notifications + <i + className='modal-back' + onClick={this.props.collapseModal} + /> + {'Notification Settings'} </h4> </div> <div @@ -686,5 +676,7 @@ NotificationsTab.propTypes = { updateSection: React.PropTypes.func, updateTab: React.PropTypes.func, activeSection: React.PropTypes.string, - activeTab: React.PropTypes.string + activeTab: React.PropTypes.string, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired }; diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx index 983a10df0..61d13ed8b 100644 --- a/web/react/components/user_settings/user_settings_security.jsx +++ b/web/react/components/user_settings/user_settings_security.jsx @@ -3,6 +3,8 @@ 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'); @@ -11,14 +13,34 @@ 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.handleClose = this.handleClose.bind(this); this.setupInitialState = this.setupInitialState.bind(this); - this.state = this.setupInitialState(); + 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 + }); } submitPassword(e) { e.preventDefault(); @@ -75,30 +97,9 @@ export default class SecurityTab extends React.Component { updateConfirmPassword(e) { this.setState({confirmPassword: e.target.value}); } - handleHistoryOpen() { - $('#user_settings').modal('hide'); - } - handleDevicesOpen() { - $('#user_settings').modal('hide'); - } - handleClose() { - $(ReactDOM.findDOMNode(this)).find('.form-control').each(function resetValue() { - this.value = ''; - }); - this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); - - this.props.updateTab('general'); - } setupInitialState() { return {currentPassword: '', newPassword: '', confirmPassword: ''}; } - componentDidMount() { - $('#user_settings').on('hidden.bs.modal', this.handleClose); - } - componentWillUnmount() { - $('#user_settings').off('hidden.bs.modal', this.handleClose); - this.props.updateSection(''); - } render() { var serverError; if (this.state.serverError) { @@ -236,14 +237,19 @@ export default class SecurityTab extends React.Component { className='close' data-dismiss='modal' aria-label='Close' + onClick={this.props.closeModal} > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'×'}</span> </button> <h4 className='modal-title' ref='title' > - <i className='modal-back'></i>Security Settings + <i + className='modal-back' + onClick={this.props.collapseModal} + /> + {'Security Settings'} </h4> </div> <div className='user-settings'> @@ -253,25 +259,29 @@ export default class SecurityTab extends React.Component { <div className='divider-dark'/> <br></br> <a - data-toggle='modal' className='security-links theme' - data-target='#access-history' href='#' - onClick={this.handleHistoryOpen} + onClick={this.showAccessHistoryModal} > <i className='fa fa-clock-o'></i>View Access History </a> <b> </b> <a - data-toggle='modal' className='security-links theme' - data-target='#activity-log' href='#' - onClick={this.handleDevicesOpen} + onClick={this.showActivityLogModal} > <i className='fa fa-globe'></i>View and Logout of Active Sessions </a> </div> + <AccessHistoryModal + show={this.state.showAccessHistoryModal} + onModalDismissed={this.hideModals} + /> + <ActivityLogModal + show={this.state.showActivityLogModal} + onModalDismissed={this.hideModals} + /> </div> ); } @@ -285,5 +295,8 @@ SecurityTab.propTypes = { user: React.PropTypes.object, activeSection: React.PropTypes.string, updateSection: React.PropTypes.func, - updateTab: React.PropTypes.func + updateTab: React.PropTypes.func, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired, + setEnforceFocus: React.PropTypes.func.isRequired }; |