diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index f0a31ce906..27959ec7ea 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +var Modal = ReactBootstrap.Modal; var UserStore = require('../stores/user_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); var AsyncClient = require('../utils/async_client.jsx'); @@ -30,16 +31,23 @@ export default class AccessHistoryModal extends React.Component { } onShow() { AsyncClient.getAudits(); + + $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300); + if ($(window).width() > 768) { + $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar(); + } } onHide() { - $('#user_settings').modal('show'); this.setState({moreInfo: []}); + this.props.onModalDismissed(); } componentDidMount() { UserStore.addAuditsChangeListener(this.onAuditChange); - $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - - $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); + } + componentDidUpdate(prevProps) { + if (this.props.show && !prevProps.show) { + this.onShow(); + } } componentWillUnmount() { UserStore.removeAuditsChangeListener(this.onAuditChange); @@ -380,43 +388,23 @@ export default class AccessHistoryModal extends React.Component { } return ( -
- -
+ + + {'Access History'} + + + {content} + + ); } } + +AccessHistoryModal.propTypes = { + show: React.PropTypes.bool.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 2c944913f8..6a24870f67 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -4,6 +4,7 @@ const UserStore = require('../stores/user_store.jsx'); const Client = require('../utils/client.jsx'); const AsyncClient = require('../utils/async_client.jsx'); +const Modal = ReactBootstrap.Modal; const LoadingScreen = require('./loading_screen.jsx'); const Utils = require('../utils/utils.jsx'); @@ -49,16 +50,23 @@ export default class ActivityLogModal extends React.Component { } onShow() { AsyncClient.getSessions(); + + $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300); + if ($(window).width() > 768) { + $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar(); + } } onHide() { - $('#user_settings').modal('show'); this.setState({moreInfo: []}); + this.props.onModalDismissed(); } componentDidMount() { UserStore.addSessionsChangeListener(this.onListenerChange); - $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow); - - $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide); + } + componentDidUpdate(prevProps) { + if (this.props.show && !prevProps.show) { + this.onShow(); + } } componentWillUnmount() { UserStore.removeSessionsChangeListener(this.onListenerChange); @@ -151,44 +159,24 @@ export default class ActivityLogModal extends React.Component { } return ( -
- -
+ + + {'Active Sessions'} + +

{'Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the \'Logout\' button below to end a session.'}

+ + {content} + +
); } } + +ActivityLogModal.propTypes = { + show: React.PropTypes.bool.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx index 12002f33fc..cdef1c1ea3 100644 --- a/web/react/components/confirm_modal.jsx +++ b/web/react/components/confirm_modal.jsx @@ -1,70 +1,63 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +const Modal = ReactBootstrap.Modal; + export default class ConfirmModal extends React.Component { constructor(props) { super(props); this.handleConfirm = this.handleConfirm.bind(this); + } - this.state = {}; - } handleConfirm() { - $('#' + this.props.parent_id).attr('data-confirm', 'true'); - $('#' + this.props.parent_id).modal('hide'); - $('#' + this.props.id).modal('hide'); + this.props.onConfirm(); } + render() { return ( - + + {this.props.title} + + + {this.props.message} + + + + + + ); } } ConfirmModal.defaultProps = { - parent_id: '', - id: '', title: '', message: '', confirm_button: '' }; ConfirmModal.propTypes = { - parent_id: React.PropTypes.string, - id: React.PropTypes.string, + show: React.PropTypes.bool.isRequired, title: React.PropTypes.string, message: React.PropTypes.string, - confirm_button: React.PropTypes.string + confirm_button: React.PropTypes.string, + onConfirm: React.PropTypes.func.isRequired, + onCancel: React.PropTypes.func.isRequired }; diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index bea7007250..c09477a696 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -2,55 +2,50 @@ // See License.txt for license information. var utils = require('../utils/utils.jsx'); +var ActionTypes = require('../utils/constants.jsx').ActionTypes; +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Client = require('../utils/client.jsx'); +var ModalStore = require('../stores/modal_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); var ConfirmModal = require('./confirm_modal.jsx'); +const Modal = ReactBootstrap.Modal; + export default class InviteMemberModal extends React.Component { constructor(props) { super(props); + this.handleToggle = this.handleToggle.bind(this); this.handleSubmit = this.handleSubmit.bind(this); + this.handleHide = this.handleHide.bind(this); this.addInviteFields = this.addInviteFields.bind(this); this.clearFields = this.clearFields.bind(this); this.removeInviteFields = this.removeInviteFields.bind(this); this.state = { + show: false, inviteIds: [0], idCount: 0, emailErrors: {}, firstNameErrors: {}, lastNameErrors: {}, - emailEnabled: global.window.mm_config.SendEmailNotifications === 'true' + emailEnabled: global.window.mm_config.SendEmailNotifications === 'true', + showConfirmModal: false }; } componentDidMount() { - var self = this; - $('#invite_member').on('hide.bs.modal', function hide(e) { - if ($('#invite_member').attr('data-confirm') === 'true') { - $('#invite_member').attr('data-confirm', 'false'); - return; - } + ModalStore.addModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle); + } - var notEmpty = false; - for (var i = 0; i < self.state.inviteIds.length; i++) { - var index = self.state.inviteIds[i]; - if (ReactDOM.findDOMNode(self.refs['email' + index]).value.trim() !== '') { - notEmpty = true; - break; - } - } + componentWillUnmount() { + ModalStore.removeModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle); + } - if (notEmpty) { - $('#confirm_invite_modal').modal('show'); - e.preventDefault(); - } - }); - - $('#invite_member').on('hidden.bs.modal', function show() { - self.clearFields(); + handleToggle(value) { + this.setState({ + show: value }); } @@ -94,25 +89,57 @@ export default class InviteMemberModal extends React.Component { var data = {}; data.invites = invites; - Client.inviteMembers(data, - function success() { - $(ReactDOM.findDOMNode(this.refs.modal)).attr('data-confirm', 'true'); - $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); - }.bind(this), - function fail(err) { + Client.inviteMembers( + data, + () => { + this.handleHide(false); + }, + (err) => { if (err.message === 'This person is already on your team') { emailErrors[err.detailed_error] = err.message; this.setState({emailErrors: emailErrors}); } else { this.setState({serverError: err.message}); } - }.bind(this) + } ); } - componentDidUpdate() { - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200); - $(ReactDOM.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll'); + handleHide(requireConfirm) { + if (requireConfirm) { + var notEmpty = false; + for (var i = 0; i < this.state.inviteIds.length; i++) { + var index = this.state.inviteIds[i]; + if (ReactDOM.findDOMNode(this.refs['email' + index]).value.trim() !== '') { + notEmpty = true; + break; + } + } + + if (notEmpty) { + this.setState({ + showConfirmModal: true + }); + + return; + } + } + + this.clearFields(); + + this.setState({ + show: false, + showConfirmModal: false + }); + } + + componentDidUpdate(prevProps, prevState) { + if (!prevState.show && this.state.show) { + $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300); + if ($(window).width() > 768) { + $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar(); + } + } } addInviteFields() { @@ -292,7 +319,7 @@ export default class InviteMemberModal extends React.Component { ); } else { var teamInviteLink = null; - if (currentUser && this.props.teamType === 'O') { + if (currentUser && TeamStore.getCurrent().type === 'O') { var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id; var link = ( @@ -302,11 +329,7 @@ export default class InviteMemberModal extends React.Component { data-target='#get_link' data-title='Team Invite' data-value={linkUrl} - onClick={ - function click() { - $('#invite_member').modal('hide'); - } - } + onClick={() => this.handleHide(this, false)} >Team Invite Link ); @@ -327,64 +350,54 @@ export default class InviteMemberModal extends React.Component { return (
- + {sendButton} + + this.setState({showConfirmModal: false})} />
); } - return
; + + return null; + } + + static show() { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, + value: true + }); } } InviteMemberModal.propTypes = { - teamType: React.PropTypes.string }; diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index 029b9c1378..0b755f3778 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -7,6 +7,8 @@ var UserStore = require('../stores/user_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); var AboutBuildModal = require('./about_build_modal.jsx'); +var InviteMemberModal = require('./invite_member_modal.jsx'); +var UserSettingsModal = require('./user_settings/user_settings_modal.jsx'); var Constants = require('../utils/constants.jsx'); @@ -33,7 +35,10 @@ export default class NavbarDropdown extends React.Component { this.onListenerChange = this.onListenerChange.bind(this); this.aboutModalDismissed = this.aboutModalDismissed.bind(this); - this.state = getStateFromStores(); + const state = getStateFromStores(); + state.showUserSettingsModal = false; + state.showAboutModal = false; + this.state = state; } handleLogoutClick(e) { e.preventDefault(); @@ -88,8 +93,7 @@ export default class NavbarDropdown extends React.Component {
  • {'Invite New Member'} @@ -210,8 +214,7 @@ export default class NavbarDropdown extends React.Component {
  • this.setState({showUserSettingsModal: true})} > {'Account Settings'} @@ -256,6 +259,10 @@ export default class NavbarDropdown extends React.Component { {'About Mattermost'}
  • + this.setState({showUserSettingsModal: false})} + /> - Invite New Member + + Invite New Member + ); @@ -115,9 +123,11 @@ export default class SidebarRightMenu extends React.Component {
  • Account Settings
  • + onClick={() => this.setState({showUserSettingsModal: true})} + > + Account Settings + + {teamSettingsLink} {inviteLink} {teamLink} @@ -141,6 +151,10 @@ export default class SidebarRightMenu extends React.Component { >Report a Problem
    + this.setState({showUserSettingsModal: false})} + /> ); } diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx index 17fe31c65e..4d47db2a89 100644 --- a/web/react/components/team_settings_modal.jsx +++ b/web/react/components/team_settings_modal.jsx @@ -17,11 +17,13 @@ export default class TeamSettingsModal extends React.Component { }; } componentDidMount() { - $('body').on('click', '.modal-back', function handleBackClick() { + const modal = $(ReactDOM.findDOMNode(this.refs.modal)); + + modal.on('click', '.modal-back', function handleBackClick() { $(this).closest('.modal-dialog').removeClass('display--content'); $(this).closest('.modal-dialog').find('.settings-table .nav li.active').removeClass('active'); }); - $('body').on('click', '.modal-header .close', () => { + modal.on('click', '.modal-header .close', () => { setTimeout(() => { $('.modal-dialog.display--content').removeClass('display--content'); }, 500); diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 1a9ac0ad39..24da106d0b 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 546e26ca30..e089ce973e 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 (
    ); @@ -52,10 +60,14 @@ export default class UserSettings extends React.Component { return (
    ); @@ -63,10 +75,13 @@ export default class UserSettings extends React.Component { return (
    ); @@ -74,9 +89,14 @@ export default class UserSettings extends React.Component { return (
    ); @@ -84,8 +104,11 @@ export default class UserSettings extends React.Component { return (
    ); @@ -93,10 +116,13 @@ export default class UserSettings extends React.Component { return (
    ); @@ -104,10 +130,13 @@ export default class UserSettings extends React.Component { return (
    ); @@ -115,10 +144,13 @@ export default class UserSettings extends React.Component { return (
    ); @@ -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 910444735f..2616981bac 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} > @@ -146,7 +134,10 @@ export default class AdvancedSettingsDisplay extends React.Component { className='modal-title' ref='title' > - + {'Advanced Settings'} @@ -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 7b4b54e272..425645c1f5 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(); + + Utils.applyTheme(state.theme); + } + resetFields() { const state = this.getStateFromStores(); state.serverError = null; - state.theme.codeTheme = this.originalCodeTheme; + this.setState(state); Utils.applyTheme(state.theme); - this.setState(state); - - $('.ps-container.modal-body').scrollTop(0); - $('.ps-container.modal-body').perfectScrollbar('update'); - $('#user_settings').modal('hide'); + 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 { {'Cancel'} @@ -218,8 +234,8 @@ export default class UserSettingsAppearance extends React.Component { @@ -227,7 +243,11 @@ export default class UserSettingsAppearance extends React.Component { className='modal-title' ref='title' > - {'Appearance Settings'} + + {'Appearance 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 c2d7a9710d..e6adba1d4b 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} > @@ -70,7 +71,11 @@ export default class DeveloperTab extends React.Component { className='modal-title' ref='title' > - {'Developer Settings'} + + {'Developer 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 d086c78a9b..3c12ead23c 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} > @@ -234,7 +225,10 @@ export default class UserSettingsDisplay extends React.Component { className='modal-title' ref='title' > - + {'Display Settings'}
    @@ -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 3adac197ae..9f0c161940 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} > @@ -586,7 +572,10 @@ export default class UserSettingsGeneralTab extends React.Component { className='modal-title' ref='title' > - + {'General Settings'} @@ -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 4a9915a1ff..744a6beeab 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} > @@ -111,7 +100,10 @@ export default class UserSettingsIntegrationsTab extends React.Component { className='modal-title' ref='title' > - + {'Integration Settings'} @@ -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 18dd490e78..4dcf32cb9b 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; } - componentDidMount() { - $('body').on('click', '.modal-back', function changeDisplay() { - $(this).closest('.modal-dialog').removeClass('display--content'); - }); - $('body').on('click', '.modal-header .close', () => { - setTimeout(() => { - $('.modal-dialog.display--content').removeClass('display--content'); - }, 500); + + 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(); + } + + // called after the dialog is fully hidden and faded out + handleHidden() { + this.setState({ + active_tab: 'general', + active_section: '' }); } - updateTab(tab) { - this.setState({active_tab: tab}); + + // 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: '' + }); } - updateSection(section) { - this.setState({active_section: section}); + + handleConfirm() { + this.setState({ + showConfirmModal: false, + enforceFocus: true + }); + + this.requireConfirm = false; + + if (this.afterConfirm) { + this.afterConfirm(); + this.afterConfirm = null; + } } + + 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 ( -
    - +

    - Security Settings + + {'Security Settings'}

    @@ -253,25 +259,29 @@ export default class SecurityTab extends React.Component { + +
    ); } @@ -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 }; diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 067dcde507..2f872effdb 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -9,7 +9,6 @@ var ErrorStore = require('../stores/error_store.jsx'); var MentionList = require('../components/mention_list.jsx'); var GetLinkModal = require('../components/get_link_modal.jsx'); -var MemberInviteModal = require('../components/invite_member_modal.jsx'); var EditChannelModal = require('../components/edit_channel_modal.jsx'); var DeleteChannelModal = require('../components/delete_channel_modal.jsx'); var RenameChannelModal = require('../components/rename_channel_modal.jsx'); @@ -18,17 +17,15 @@ var DeletePostModal = require('../components/delete_post_modal.jsx'); var MoreChannelsModal = require('../components/more_channels.jsx'); var PostDeletedModal = require('../components/post_deleted_modal.jsx'); var ChannelNotificationsModal = require('../components/channel_notifications.jsx'); -var UserSettingsModal = require('../components/user_settings/user_settings_modal.jsx'); var TeamSettingsModal = require('../components/team_settings_modal.jsx'); var ChannelMembersModal = require('../components/channel_members.jsx'); var ChannelInviteModal = require('../components/channel_invite_modal.jsx'); var TeamMembersModal = require('../components/team_members.jsx'); var ChannelInfoModal = require('../components/channel_info_modal.jsx'); -var AccessHistoryModal = require('../components/access_history_modal.jsx'); -var ActivityLogModal = require('../components/activity_log_modal.jsx'); var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx'); var RegisterAppModal = require('../components/register_app_modal.jsx'); var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx'); +var InviteMemberModal = require('../components/invite_member_modal.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var Constants = require('../utils/constants.jsx'); @@ -83,8 +80,8 @@ function setupChannelPage(props) { ); ReactDOM.render( - , - document.getElementById('user_settings_modal') + , + document.getElementById('invite_member_modal') ); ReactDOM.render( @@ -102,11 +99,6 @@ function setupChannelPage(props) { document.getElementById('team_members_modal') ); - ReactDOM.render( - , - document.getElementById('invite_member_modal') - ); - ReactDOM.render( , document.getElementById('edit_channel_modal') @@ -162,16 +154,6 @@ function setupChannelPage(props) { document.getElementById('post_deleted_modal') ); - ReactDOM.render( - , - document.getElementById('access_history_modal') - ); - - ReactDOM.render( - , - document.getElementById('activity_log_modal') - ); - ReactDOM.render( , document.getElementById('removed_from_channel_modal') diff --git a/web/react/stores/modal_store.jsx b/web/react/stores/modal_store.jsx new file mode 100644 index 0000000000..dc65d48da3 --- /dev/null +++ b/web/react/stores/modal_store.jsx @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const EventEmitter = require('events').EventEmitter; + +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; + +class ModalStoreClass extends EventEmitter { + constructor() { + super(); + + this.addModalListener = this.addModalListener.bind(this); + this.removeModalListener = this.removeModalListener.bind(this); + + this.handleEventPayload = this.handleEventPayload.bind(this); + this.dispatchToken = AppDispatcher.register(this.handleEventPayload); + } + + addModalListener(action, callback) { + this.on(action, callback); + } + + removeModalListener(action, callback) { + this.removeListener(action, callback); + } + + handleEventPayload(payload) { + const action = payload.action; + + switch (action.type) { + case ActionTypes.TOGGLE_IMPORT_THEME_MODAL: + case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL: + this.emit(action.type, action.value); + break; + } + } +} + +const ModalStore = new ModalStoreClass(); +export default ModalStore; diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index aedb3dc09d..4fa7224b70 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -13,7 +13,6 @@ var CHANGE_EVENT_SESSIONS = 'change_sessions'; var CHANGE_EVENT_AUDITS = 'change_audits'; var CHANGE_EVENT_TEAMS = 'change_teams'; var CHANGE_EVENT_STATUSES = 'change_statuses'; -var TOGGLE_IMPORT_MODAL_EVENT = 'toggle_import_modal'; class UserStoreClass extends EventEmitter { constructor() { @@ -34,9 +33,6 @@ class UserStoreClass extends EventEmitter { this.emitStatusesChange = this.emitStatusesChange.bind(this); this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this); this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this); - this.emitToggleImportModal = this.emitToggleImportModal.bind(this); - this.addImportModalListener = this.addImportModalListener.bind(this); - this.removeImportModalListener = this.removeImportModalListener.bind(this); this.getCurrentId = this.getCurrentId.bind(this); this.getCurrentUser = this.getCurrentUser.bind(this); this.setCurrentUser = this.setCurrentUser.bind(this); @@ -124,18 +120,6 @@ class UserStoreClass extends EventEmitter { this.removeListener(CHANGE_EVENT_STATUSES, callback); } - emitToggleImportModal(value) { - this.emit(TOGGLE_IMPORT_MODAL_EVENT, value); - } - - addImportModalListener(callback) { - this.on(TOGGLE_IMPORT_MODAL_EVENT, callback); - } - - removeImportModalListener(callback) { - this.removeListener(TOGGLE_IMPORT_MODAL_EVENT, callback); - } - getCurrentUser() { if (this.getProfiles()[global.window.mm_user.id] == null) { this.saveProfile(global.window.mm_user); @@ -353,10 +337,6 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => { UserStore.pSetStatuses(action.statuses); UserStore.emitStatusesChange(); break; - case ActionTypes.TOGGLE_IMPORT_THEME_MODAL: - UserStore.emitToggleImportModal(action.value); - break; - default: } }); diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_mssages.jsx index b3f8684567..161c797618 100644 --- a/web/react/utils/channel_intro_mssages.jsx +++ b/web/react/utils/channel_intro_mssages.jsx @@ -3,6 +3,7 @@ // See License.txt for license information. const Utils = require('./utils.jsx'); +const InviteMemberModal = require('../components/invite_member_modal.jsx'); const UserProfile = require('../components/user_profile.jsx'); const ChannelStore = require('../stores/channel_store.jsx'); const Constants = require('../utils/constants.jsx'); @@ -109,8 +110,7 @@ export function createDefaultIntroMessage(channel) { {'Invite others to this team'} @@ -213,6 +213,7 @@ export function createStandardIntroMessage(channel) { > {'Invite others to this ' + uiType} + ); } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 39be577df3..b8d346ba79 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -39,7 +39,8 @@ module.exports = { RECIEVED_LOGS: null, RECIEVED_ALL_TEAMS: null, - TOGGLE_IMPORT_THEME_MODAL: null + TOGGLE_IMPORT_THEME_MODAL: null, + TOGGLE_INVITE_MEMBER_MODAL: null }), PayloadSources: keyMirror({ diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index 96a6cf2ab1..3be83ed2fe 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -13,6 +13,10 @@ .settings-modal { width:800px; max-width: 100%; + .modal-content { + width:800px; + max-width: 100%; + } .modal-back { width: 40px; height: 56px;