From 3a91d4e5e419a43ff19a0736ce697f8d611d36e3 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 2 Mar 2017 17:48:56 -0500 Subject: PLT-3077 Add group messaging (#5489) * Implement server changes for group messaging * Majority of client-side implementation * Some server updates * Added new React multiselect component * Fix style issues * Add custom renderer for options * Fix model test * Update ENTER functionality for multiselect control * Remove buttons from multiselect UI control * Updating group messaging UI (#5524) * Move filter controls up a component level * Scroll with arrow keys * Updating mobile layout for multiselect (#5534) * Fix race condition when backspacing quickly * Hidden or new GMs show up for regular messages * Add overriding of number remaining text * Add UI filtering for team if config setting set * Add icon to channel switcher and class prop to status icon * Minor updates per feedback * Improving group messaging UI (#5563) * UX changes per feedback * Update email for group messages * UI fixes for group messaging (#5587) * Fix missing localization string * Add maximum users message when adding members to GM * Fix input clearing on Android * Updating group messaging UI (#5603) * Updating UI for group messaging (#5604) --- webapp/components/multiselect/multiselect.jsx | 257 ++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 webapp/components/multiselect/multiselect.jsx (limited to 'webapp/components/multiselect/multiselect.jsx') diff --git a/webapp/components/multiselect/multiselect.jsx b/webapp/components/multiselect/multiselect.jsx new file mode 100644 index 000000000..a3e32dccf --- /dev/null +++ b/webapp/components/multiselect/multiselect.jsx @@ -0,0 +1,257 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import MultiSelectList from './multiselect_list.jsx'; + +import {localizeMessage} from 'utils/utils.jsx'; +import Constants from 'utils/constants.jsx'; +const KeyCodes = Constants.KeyCodes; + +import React from 'react'; +import ReactSelect from 'react-select'; +import {FormattedMessage} from 'react-intl'; + +export default class MultiSelect extends React.Component { + constructor(props) { + super(props); + + this.onChange = this.onChange.bind(this); + this.onSelect = this.onSelect.bind(this); + this.onAdd = this.onAdd.bind(this); + this.onInput = this.onInput.bind(this); + this.handleEnterPress = this.handleEnterPress.bind(this); + this.nextPage = this.nextPage.bind(this); + this.prevPage = this.prevPage.bind(this); + + this.selected = null; + + this.state = { + page: 0 + }; + } + + componentDidMount() { + document.addEventListener('keydown', this.handleEnterPress); + this.refs.select.focus(); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.handleEnterPress); + } + + nextPage() { + if (this.props.handlePageChange) { + this.props.handlePageChange(this.state.page + 1, this.state.page); + } + this.refs.list.setSelected(0); + this.setState({page: this.state.page + 1}); + } + + prevPage() { + if (this.state.page === 0) { + return; + } + + if (this.props.handlePageChange) { + this.props.handlePageChange(this.state.page - 1, this.state.page); + } + this.refs.list.setSelected(0); + this.setState({page: this.state.page - 1}); + } + + onSelect(selected) { + this.selected = selected; + } + + onAdd(value) { + if (this.props.maxValues && this.props.values.length >= this.props.maxValues) { + return; + } + + for (let i = 0; i < this.props.values.length; i++) { + if (this.props.values[i].value === value.value) { + return; + } + } + + this.props.handleAdd(value); + this.selected = null; + this.refs.select.handleInputChange({target: {value: ''}}); + this.onInput(''); + this.refs.select.focus(); + } + + onInput(input) { + if (input === '') { + this.refs.list.setSelected(-1); + } else { + this.refs.list.setSelected(0); + } + this.selected = null; + + this.props.handleInput(input); + } + + handleEnterPress(e) { + switch (e.keyCode) { + case KeyCodes.ENTER: + if (this.selected == null) { + this.props.handleSubmit(); + return; + } + this.onAdd(this.selected); + break; + } + } + + onChange(values) { + if (values.length < this.props.values.length) { + this.props.handleDelete(values); + } + } + + render() { + const options = this.props.options; + + let numRemainingText; + if (this.props.numRemainingText) { + numRemainingText = this.props.numRemainingText; + } else if (this.props.maxValues != null) { + numRemainingText = ( + + ); + } + + let optionsToDisplay = []; + let nextButton; + let previousButton; + let noteTextContainer; + + if (this.props.noteText) { + noteTextContainer = ( +
+
+
{this.props.noteText}
+
+ ); + } + + if (options && options.length > this.props.perPage) { + const pageStart = this.state.page * this.props.perPage; + const pageEnd = pageStart + this.props.perPage; + optionsToDisplay = options.slice(pageStart, pageEnd); + + if (options.length > pageEnd) { + nextButton = ( + + ); + } + + if (this.state.page > 0) { + previousButton = ( + + ); + } + } else { + optionsToDisplay = options; + } + + return ( +
+
+
+ null} + arrowRenderer={() => null} + noResultsText={null} + placeholder={localizeMessage('multiselect.placeholder', 'Search and add members')} + /> + +
+
+
+ +
+ {numRemainingText} + {noteTextContainer} +
+
+ +
+ {previousButton} + {nextButton} +
+
+ ); + } +} + +MultiSelect.propTypes = { + options: React.PropTypes.arrayOf(React.PropTypes.object), + optionRenderer: React.PropTypes.func, + values: React.PropTypes.arrayOf(React.PropTypes.object), + valueRenderer: React.PropTypes.func, + handleInput: React.PropTypes.func, + handleDelete: React.PropTypes.func, + perPage: React.PropTypes.number, + handlePageChange: React.PropTypes.func, + handleAdd: React.PropTypes.func, + handleSubmit: React.PropTypes.func, + noteText: React.PropTypes.node, + maxValues: React.PropTypes.number, + numRemainingText: React.PropTypes.node +}; -- cgit v1.2.3-1-g7c22