// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {PureComponent} from 'react'; import {intlShape} from 'react-intl'; import {Text, View, Platform} from 'react-native'; import {goToScreen} from '@actions/navigation'; import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; import Markdown from '@components/markdown'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {ViewTypes} from '@constants'; import {ActionResult} from '@mm-redux/types/actions'; import {Channel} from '@mm-redux/types/channels'; import {DialogOption} from '@mm-redux/types/integrations'; import {Theme} from '@mm-redux/types/theme'; import {UserProfile} from '@mm-redux/types/users'; import {displayUsername} from '@mm-redux/utils/user_utils'; import {getMarkdownBlockStyles, getMarkdownTextStyles} from '@utils/markdown'; import {preventDoubleTap} from '@utils/tap'; import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; type Selection = DialogOption | Channel | UserProfile | DialogOption[] | Channel[] | UserProfile[]; type Props = { actions: { setAutocompleteSelector: (dataSource: any, onSelect: any, options: any, getDynamicOptions: any) => Promise; }; getDynamicOptions?: (term: string) => Promise; label?: string; placeholder?: string; dataSource?: string; options?: DialogOption[]; selected?: DialogOption | DialogOption[]; optional?: boolean; showRequiredAsterisk?: boolean; teammateNameDisplay?: string; theme: Theme; onSelected?: ((item: DialogOption) => void) | ((item: DialogOption[]) => void); helpText?: string; errorText?: string; roundedBorders?: boolean; disabled?: boolean; isMultiselect?: boolean; } type State = { selectedText: string; selected?: DialogOption | DialogOption[]; } export default class AutocompleteSelector extends PureComponent { static contextTypes = { intl: intlShape, }; static defaultProps = { optional: false, showRequiredAsterisk: false, roundedBorders: true, }; constructor(props: Props) { super(props); this.state = { selectedText: '', }; } static getDerivedStateFromProps(props: Props, state: State) { if (!props.selected || props.selected === state.selected) { return null; } if (!props.isMultiselect) { return { selectedText: (props.selected as DialogOption).text, selected: props.selected, }; } const options = props.selected as DialogOption[]; let selectedText = ''; const selected: DialogOption[] = []; options.forEach((option) => { if (selectedText !== '') { selectedText += ', '; } selectedText += option.text; selected.push(option); }); return { selectedText, selected, }; } handleSelect = (selected: Selection) => { if (!selected) { return; } const { dataSource, teammateNameDisplay, } = this.props; if (!this.props.isMultiselect) { let selectedText: string; let selectedValue: string; switch (dataSource) { case ViewTypes.DATA_SOURCE_USERS: { const typedSelected = selected as UserProfile; selectedText = displayUsername(typedSelected, teammateNameDisplay || ''); selectedValue = typedSelected.id; break; } case ViewTypes.DATA_SOURCE_CHANNELS: { const typedSelected = selected as Channel; selectedText = typedSelected.display_name; selectedValue = typedSelected.id; break; } default: { const typedSelected = selected as DialogOption; selectedText = typedSelected.text; selectedValue = typedSelected.value; } } this.setState({selectedText}); if (this.props.onSelected) { (this.props.onSelected as (opt: DialogOption) => void)({text: selectedText, value: selectedValue}); } return; } let selectedText = ''; const selectedOptions: DialogOption[] = []; switch (dataSource) { case ViewTypes.DATA_SOURCE_USERS: { const typedSelected = selected as UserProfile[]; typedSelected.forEach((option) => { if (selectedText !== '') { selectedText += ', '; } const text = displayUsername(option, teammateNameDisplay || ''); selectedText += text; selectedOptions.push({text, value: option.id}); }); break; } case ViewTypes.DATA_SOURCE_CHANNELS: { const typedSelected = selected as Channel[]; typedSelected.forEach((option) => { if (selectedText !== '') { selectedText += ', '; } const text = option.display_name; selectedText += text; selectedOptions.push({text, value: option.id}); }); break; } default: { const typedSelected = selected as DialogOption[]; typedSelected.forEach((option) => { if (selectedText !== '') { selectedText += ', '; } selectedText += option.text; selectedOptions.push(option); }); break; } } this.setState({selectedText}); if (this.props.onSelected) { (this.props.onSelected as (opt: DialogOption[]) => void)(selectedOptions); } }; goToSelectorScreen = preventDoubleTap(async () => { const closeButton = await CompassIcon.getImageSource(Platform.select({ios: 'arrow-back-ios', default: 'arrow-left'}), 24, this.props.theme.sidebarHeaderTextColor); const {formatMessage} = this.context.intl; const {actions, dataSource, options, placeholder, getDynamicOptions, theme} = this.props; const screen = 'SelectorScreen'; const title = placeholder || formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'}); const buttonName = formatMessage({id: 'mobile.forms.select.done', defaultMessage: 'Done'}); actions.setAutocompleteSelector(dataSource, this.handleSelect, options, getDynamicOptions); let screenOptions = {}; if (this.props.isMultiselect) { screenOptions = { topBar: { leftButtons: [{ id: 'close-dialog', icon: closeButton, }], rightButtons: [{ id: 'submit-form', showAsAction: 'always', text: buttonName, }], leftButtonColor: theme.sidebarHeaderTextColor, rightButtonColor: theme.sidebarHeaderTextColor, }, }; } goToScreen(screen, title, {isMultiselect: this.props.isMultiselect, selected: this.state.selected}, screenOptions); }); render() { const {intl} = this.context; const { placeholder, theme, label, helpText, errorText, optional, showRequiredAsterisk, roundedBorders, disabled, } = this.props; const {selectedText} = this.state; const style = getStyleSheet(theme); const textStyles = getMarkdownTextStyles(theme); const blockStyles = getMarkdownBlockStyles(theme); const chevron = Platform.select({ios: 'chevron-right', default: 'chevron-down'}); let text = placeholder || intl.formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'}); let selectedStyle = style.dropdownPlaceholder; if (selectedText) { text = selectedText; selectedStyle = style.dropdownSelected; } let inputStyle = style.input; if (roundedBorders) { inputStyle = style.roundedInput; } let optionalContent; let asterisk; if (optional) { optionalContent = ( ); } else if (showRequiredAsterisk) { asterisk = {' *'}; } let labelContent; if (label) { labelContent = ( {label} {asterisk} {optionalContent} ); } let helpTextContent; if (helpText) { helpTextContent = ( ); } let errorTextContent; if (errorText) { errorTextContent = ( ); } const noediting = disabled ? style.disabled : null; return ( {labelContent} {text} {helpTextContent} {errorTextContent} ); } } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const input = { borderWidth: 1, borderColor: changeOpacity(theme.centerChannelColor, 0.1), backgroundColor: changeOpacity(theme.centerChannelBg, 0.9), paddingLeft: 10, paddingRight: 30, paddingVertical: 7, height: 40, }; return { container: { width: '100%', marginBottom: 2, marginRight: 8, marginTop: 10, }, roundedInput: { ...input, borderRadius: 5, }, input, dropdownPlaceholder: { top: 3, marginLeft: 5, color: changeOpacity(theme.centerChannelColor, 0.5), }, dropdownSelected: { top: 3, marginLeft: 5, color: theme.centerChannelColor, }, icon: { position: 'absolute', top: 6, right: 12, fontSize: 28, }, labelContainer: { flexDirection: 'row', marginTop: 15, marginBottom: 10, }, label: { fontSize: 14, color: theme.centerChannelColor, marginLeft: 15, }, optional: { color: changeOpacity(theme.centerChannelColor, 0.5), fontSize: 14, marginLeft: 5, }, helpTextContainer: { marginHorizontal: 15, marginTop: 10, }, helpText: { fontSize: 12, color: changeOpacity(theme.centerChannelColor, 0.5), }, errorTextContainer: { marginHorizontal: 15, marginVertical: 10, }, errorText: { fontSize: 12, color: theme.errorTextColor, }, asterisk: { color: theme.errorTextColor, fontSize: 14, }, disabled: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), }, }; });