// Copyright 2021 99cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Input, Tag, Menu, Divider, Button, Checkbox, Row, Col } from 'antd'; import { CloseOutlined, SearchOutlined } from '@ant-design/icons'; import classnames from 'classnames'; import { isEmpty, isBoolean } from 'lodash'; import styles from './index.less'; const option = PropTypes.shape({ label: PropTypes.string.isRequired, key: PropTypes.oneOfType([ PropTypes.string.isRequired, PropTypes.bool.isRequired, ]), component: PropTypes.node, }); const filterParam = PropTypes.shape({ label: PropTypes.string.isRequired, name: PropTypes.string.isRequired, isSingle: PropTypes.bool, isServer: PropTypes.bool, allowed: PropTypes.func, options: PropTypes.arrayOf(option), isTime: PropTypes.bool, }); export const getTags = (initValue, filterParams) => { if (!initValue || isEmpty(initValue)) { return {}; } if (isEmpty(filterParams)) { return {}; } const tags = []; const checkValues = []; Object.keys(initValue).forEach((key) => { const item = filterParams.find((it) => it.name === key); if (item) { const { options = [] } = item; const value = initValue[key]; if (options.length) { const optionItem = options.find((op) => op.key === value); if (optionItem && optionItem.isQuick) { checkValues.push(`${item.name}--${value}`); } } tags.push({ value, filter: item, }); } }); return { tags, checkValues, }; }; class MagicInput extends PureComponent { static propTypes = { filterParams: PropTypes.arrayOf(filterParam), // eslint-disable-next-line react/no-unused-prop-types initValue: PropTypes.object, placeholder: PropTypes.string, onInputChange: PropTypes.func, onInputFocus: PropTypes.func, }; static defaultProps = { filterParams: [], initValue: {}, placeholder: t('Click here for filters.'), }; constructor(props) { super(props); this.inputRef = React.createRef(); this.state = { tags: [], currentFilter: null, isFocus: false, optionClear: false, checkValues: [], }; } componentDidMount() { this.initTags(this.props); } getFilterParams = () => { // eslint-disable-next-line no-shadow const { filterParams } = this.props; const { tags } = this.state; const filters = []; filterParams.forEach((item) => { const alreadyTag = tags.find((it) => it.filter.name === item.name); if (!alreadyTag) { filters.push(item); } }); return filters; }; onTagsChange = () => { const { onInputChange } = this.props; const { tags } = this.state; onInputChange && onInputChange(tags); }; onFocusChange = (value) => { const { onInputFocus } = this.props; onInputFocus && onInputFocus(value); }; getDefaultFilter = () => { const { filterParams } = this.props; return filterParams.find((it) => !it.options); }; handleEnter = (e) => { e && e.preventDefault(); e && e.stopPropagation(); const { value } = e.currentTarget; if (!value) { return; } this.updateInput(value); }; handleBlur = () => { const { currentFilter } = this.state; if (currentFilter) { this.setState({ isFocus: true, }); this.onFocusChange(true); } else { this.onFocusChange(false); } }; handleKeyUp = (e) => { if (e.keyCode === 8 || e.keyCode === 46) { const { currentFilter, tags } = this.state; const { value } = this.inputRef.current.state; if (currentFilter && isEmpty(value)) { this.setState({ currentFilter: null, }); } else if (tags.length > 0 && isEmpty(value)) { this.handleTagClose(tags[tags.length - 1].filter.name); } } }; handleFocus = () => { this.setState({ isFocus: true, }); this.onFocusChange(true); }; handleInputChange = (e) => { this.setState({ inputValue: e.target.value, }); }; handleTagClose = (name) => { const { tags, checkValues } = this.state; const newTags = tags.filter((it) => it.filter.name !== name); const leftCheckValues = checkValues.filter( (it) => it.split('--')[0] !== name ); this.setState( { tags: newTags, optionClear: false, checkValues: leftCheckValues, }, () => { this.onTagsChange(); } ); }; handleOptionClick = ({ key }) => { let value; if (key === 'true') { value = true; } else { value = key === 'false' ? false : key; } this.updateInput(value); }; handleSelectFilter = ({ key }) => { // eslint-disable-next-line no-shadow const { filterParams } = this.props; const filter = filterParams.find((it) => it.name === key); this.setState( { currentFilter: filter, isFocus: true, }, () => { this.inputRef.current.focus(); this.onFocusChange(true); } ); }; initTags(props) { const { initValue, filterParams } = props; const { tags = [], checkValues } = getTags(initValue, filterParams); if (!tags.length) { return; } this.setState( { tags, checkValues, }, () => { this.onTagsChange(); } ); } renderKey() { const { currentFilter } = this.state; if (!currentFilter) { return null; } return ( {`${currentFilter.label}`} ); } renderTags() { const { tags } = this.state; const tagItems = tags.map((it) => { const { filter, value } = it; const { options } = filter; let label = value; if (options) { const current = options.find((item) => { const key = isBoolean(item.key) ? item.key.toString() : item.key; return key === (isBoolean(value) ? value.toString() : value); }); label = current ? current.label : value; } return ( this.handleTagClose(filter.name)} > {filter.label} {label} ); }); return tagItems; } renderOptions() { const { currentFilter, tags } = this.state; const { options, correlateOption } = currentFilter; if (!options) { return null; } const correlateTag = tags.filter( (it) => it.filter.name === correlateOption ); let subOptions = []; if (correlateOption && correlateTag[0]) { subOptions = options.filter( (it) => it.correlateValue.indexOf(correlateTag[0].value) > -1 ); } const menuItems = (subOptions[0] ? subOptions : options).map((it) => ( {it.label} )); return ( {menuItems} ); } renderMenu() { const { currentFilter, isFocus, optionClear, inputValue } = this.state; if (inputValue) { return null; } if (!isFocus) { return null; } if (currentFilter) { return this.renderOptions(); } let filters = this.getFilterParams(); if (optionClear) { filters = []; } const menuItems = filters.map((it) => ( {it.label} )); return ( {this.renderOptionsClose(filters)} {menuItems} ); } // eslint-disable-next-line react/sort-comp clearOptions = () => { this.setState({ optionClear: true, }); }; renderOptionsClose = (filters) => { const { filterParams } = this.props; const { optionClear } = this.state; if (optionClear || !filters[0] || filterParams.length === filters.length) { return null; } return (