From 0ea0233c50dbccc498cb53481b9fdf18d027e5b2 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 14 Sep 2015 13:56:58 -0400 Subject: New add channel modal using react-bootstrap. --- web/react/.eslintrc | 3 +- web/react/components/change_url_modal.jsx | 177 + web/react/components/file_upload.jsx | 2 +- web/react/components/new_channel.jsx | 211 - web/react/components/new_channel_flow.jsx | 206 + web/react/components/new_channel_modal.jsx | 198 + web/react/components/sidebar.jsx | 24 +- web/react/package.json | 1 - web/react/pages/channel.jsx | 6 - web/react/utils/utils.jsx | 11 + web/sass-files/sass/partials/_forms.scss | 33 + web/sass-files/sass/styles.scss | 1 + web/static/css/bootstrap-3.3.1.css | 6332 ------- web/static/css/bootstrap-3.3.1.min.css | 5 - web/static/css/bootstrap-3.3.5.css | 6800 ++++++++ web/static/css/bootstrap-3.3.5.min.css | 5 + web/static/js/bootstrap-3.3.1.js | 2320 --- web/static/js/bootstrap-3.3.1.min.js | 7 - web/static/js/bootstrap-3.3.5.js | 2363 +++ web/static/js/bootstrap-3.3.5.min.js | 7 + web/static/js/jasny-bootstrap.js | 1024 ++ web/static/js/jasny-bootstrap.min.js | 6 + web/static/js/react-bootstrap-0.25.1.js | 15678 +++++++++++++++++ web/static/js/react-bootstrap-0.25.1.min.js | 14 + web/static/js/react-with-addons-0.13.1.js | 21574 ----------------------- web/static/js/react-with-addons-0.13.1.min.js | 16 - web/static/js/react-with-addons-0.13.3.js | 21642 ++++++++++++++++++++++++ web/static/js/react-with-addons-0.13.3.min.js | 18 + web/templates/head.html | 7 +- 29 files changed, 48206 insertions(+), 30485 deletions(-) create mode 100644 web/react/components/change_url_modal.jsx delete mode 100644 web/react/components/new_channel.jsx create mode 100644 web/react/components/new_channel_flow.jsx create mode 100644 web/react/components/new_channel_modal.jsx create mode 100644 web/sass-files/sass/partials/_forms.scss delete mode 100644 web/static/css/bootstrap-3.3.1.css delete mode 100644 web/static/css/bootstrap-3.3.1.min.css create mode 100644 web/static/css/bootstrap-3.3.5.css create mode 100644 web/static/css/bootstrap-3.3.5.min.css delete mode 100644 web/static/js/bootstrap-3.3.1.js delete mode 100644 web/static/js/bootstrap-3.3.1.min.js create mode 100644 web/static/js/bootstrap-3.3.5.js create mode 100644 web/static/js/bootstrap-3.3.5.min.js create mode 100644 web/static/js/jasny-bootstrap.js create mode 100644 web/static/js/jasny-bootstrap.min.js create mode 100644 web/static/js/react-bootstrap-0.25.1.js create mode 100644 web/static/js/react-bootstrap-0.25.1.min.js delete mode 100644 web/static/js/react-with-addons-0.13.1.js delete mode 100644 web/static/js/react-with-addons-0.13.1.min.js create mode 100644 web/static/js/react-with-addons-0.13.3.js create mode 100644 web/static/js/react-with-addons-0.13.3.min.js (limited to 'web') diff --git a/web/react/.eslintrc b/web/react/.eslintrc index 53cc75913..c0d0bb200 100644 --- a/web/react/.eslintrc +++ b/web/react/.eslintrc @@ -18,7 +18,8 @@ "es6": true }, "globals": { - "React": false + "React": false, + "ReactBootstrap": false }, "rules": { "comma-dangle": [2, "never"], diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx new file mode 100644 index 000000000..28fa70c1f --- /dev/null +++ b/web/react/components/change_url_modal.jsx @@ -0,0 +1,177 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Modal = ReactBootstrap.Modal; +var Utils = require('../utils/utils.jsx'); + +export default class ChangeUrlModal extends React.Component { + constructor(props) { + super(props); + + this.onURLChanged = this.onURLChanged.bind(this); + this.doSubmit = this.doSubmit.bind(this); + this.doCancel = this.doCancel.bind(this); + + this.state = { + currentURL: props.currentURL, + urlError: '', + userEdit: false + }; + } + componentWillReceiveProps(nextProps) { + // This check prevents the url being deleted when we re-render + // because of user status check + if (!this.state.userEdit) { + this.setState({ + currentURL: nextProps.currentURL + }); + } + } + componentDidUpdate(prevProps) { + if (this.props.show === true && prevProps.show === false) { + React.findDOMNode(this.refs.urlinput).select(); + } + } + onURLChanged(e) { + const url = e.target.value.trim(); + this.setState({currentURL: url.replace(/[^A-Za-z0-9-_]/g, '').toLowerCase(), userEdit: true}); + } + getURLError(url) { + let error = []; //eslint-disable-line prefer-const + if (url.length < 2) { + error.push({'Must be longer than two characters'}
); + } + if (url.charAt(0) === '-' || url.charAt(0) === '_') { + error.push({'Must start with a letter or number'}
); + } + if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) { + error.push({'Must end with a letter or number'}
); + } + if (url.indexOf('__') > -1) { + error.push({'Can not contain two underscores in a row.'}
); + } + + // In case of error we don't detect + if (error.length === 0) { + error.push({'Invalid URL'}
); + } + return error; + } + doSubmit(e) { + e.preventDefault(); + + const url = React.findDOMNode(this.refs.urlinput).value; + const cleanedURL = Utils.cleanUpUrlable(url); + if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) { + this.setState({urlError: this.getURLError(url)}); + return; + } + this.setState({urlError: '', userEdit: false}); + this.props.onModalSubmit(url); + } + doCancel() { + this.setState({urlError: '', userEdit: false}); + this.props.onModalDismissed(); + } + render() { + let urlClass = 'input-group input-group--limit'; + let urlError = null; + let serverError = null; + + if (this.state.urlError) { + urlClass += ' has-error'; + urlError = (

{this.state.urlError}

); + } + + if (this.props.serverError) { + serverError =

{this.props.serverError}

; + } + + const fullTeamUrl = Utils.getTeamURLFromAddressBar(); + const teamURL = Utils.getShortenedTeamURL(); + + return ( + + + {this.props.title} + +
+ +
{this.props.description}
+
+ +
+
+ + {teamURL} + + +
+ {urlError} + {serverError} +
+
+
+ + + + +
+
+ ); + } +} + +ChangeUrlModal.defaultProps = { + show: false, + title: 'Change URL', + desciption: '', + urlLabel: 'URL', + submitButtonText: 'Submit', + currentURL: '', + serverError: '' +}; + +ChangeUrlModal.propTypes = { + show: React.PropTypes.bool.isRequired, + title: React.PropTypes.string, + description: React.PropTypes.string, + urlLabel: React.PropTypes.string, + submitButtonText: React.PropTypes.string, + currentURL: React.PropTypes.string, + serverError: React.PropTypes.string, + onModalSubmit: React.PropTypes.func.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 534f0136e..d7b6f08b0 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -53,7 +53,7 @@ export default class FileUpload extends React.Component { } // generate a unique id that can be used by other components to refer back to this upload - var clientId = utils.generateId(); + let clientId = utils.generateId(); // prepare data to be uploaded var formData = new FormData(); diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx deleted file mode 100644 index 1a11fc652..000000000 --- a/web/react/components/new_channel.jsx +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var asyncClient = require('../utils/async_client.jsx'); -var UserStore = require('../stores/user_store.jsx'); - -export default class NewChannelModal extends React.Component { - constructor() { - super(); - - this.handleSubmit = this.handleSubmit.bind(this); - this.displayNameKeyUp = this.displayNameKeyUp.bind(this); - this.handleClose = this.handleClose.bind(this); - - this.state = {channelType: ''}; - } - handleSubmit(e) { - e.preventDefault(); - - var channel = {}; - var state = {serverError: ''}; - - channel.display_name = React.findDOMNode(this.refs.display_name).value.trim(); - if (!channel.display_name) { - state.displayNameError = 'This field is required'; - state.inValid = true; - } else if (channel.display_name.length > 22) { - state.displayNameError = 'This field must be less than 22 characters'; - state.inValid = true; - } else { - state.displayNameError = ''; - } - - channel.name = React.findDOMNode(this.refs.channel_name).value.trim(); - if (!channel.name) { - state.nameError = 'This field is required'; - state.inValid = true; - } else if (channel.name.length > 22) { - state.nameError = 'This field must be less than 22 characters'; - state.inValid = true; - } else { - var cleanedName = utils.cleanUpUrlable(channel.name); - if (cleanedName !== channel.name) { - state.nameError = "Must be lowercase alphanumeric characters, allowing '-' but not starting or ending with '-'"; - state.inValid = true; - } else { - state.nameError = ''; - } - } - - this.setState(state); - - if (state.inValid) { - return; - } - - var cu = UserStore.getCurrentUser(); - channel.team_id = cu.team_id; - - channel.description = React.findDOMNode(this.refs.channel_desc).value.trim(); - channel.type = this.state.channelType; - - client.createChannel(channel, - function success(data) { - $(React.findDOMNode(this.refs.modal)).modal('hide'); - - asyncClient.getChannel(data.id); - utils.switchChannel(data); - - React.findDOMNode(this.refs.display_name).value = ''; - React.findDOMNode(this.refs.channel_name).value = ''; - React.findDOMNode(this.refs.channel_desc).value = ''; - }.bind(this), - function error(err) { - state.serverError = err.message; - state.inValid = true; - this.setState(state); - }.bind(this) - ); - } - displayNameKeyUp() { - var displayName = React.findDOMNode(this.refs.display_name).value.trim(); - var channelName = utils.cleanUpUrlable(displayName); - React.findDOMNode(this.refs.channel_name).value = channelName; - } - componentDidMount() { - var self = this; - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) { - var button = e.relatedTarget; - self.setState({channelType: $(button).attr('data-channeltype')}); - }); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); - } - componentWillUnmount() { - $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); - } - handleClose() { - $(React.findDOMNode(this)).find('.form-control').each(function clearForms() { - this.value = ''; - }); - - this.setState({channelType: '', displayNameError: '', nameError: '', serverError: '', inValid: false}); - } - render() { - var displayNameError = null; - var nameError = null; - var serverError = null; - var displayNameClass = 'form-group'; - var nameClass = 'form-group'; - - if (this.state.displayNameError) { - displayNameError = ; - displayNameClass += ' has-error'; - } - if (this.state.nameError) { - nameError = ; - nameClass += ' has-error'; - } - if (this.state.serverError) { - serverError =
; - } - - var channelTerm = 'Channel'; - if (this.state.channelType === 'P') { - channelTerm = 'Group'; - } - - return ( -