diff --git a/frontend/src/Components/Form/EnhancedSelectInput.js b/frontend/src/Components/Form/EnhancedSelectInput.js index 80f24ae8d..08690161d 100644 --- a/frontend/src/Components/Form/EnhancedSelectInput.js +++ b/frontend/src/Components/Form/EnhancedSelectInput.js @@ -58,11 +58,30 @@ function getSelectedIndex(props) { values } = props; + if (Array.isArray(value)) { + return values.findIndex((v) => { + return value.size && v.key === value[0]; + }); + } + return values.findIndex((v) => { return v.key === value; }); } +function isSelectedItem(index, props) { + const { + value, + values + } = props; + + if (Array.isArray(value)) { + return value.includes(values[index].key); + } + + return values[index].key === value; +} + function getKey(selectedIndex, values) { return values[selectedIndex].key; } @@ -92,7 +111,7 @@ class EnhancedSelectInput extends Component { this._scheduleUpdate(); } - if (prevProps.value !== this.props.value) { + if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) { this.setState({ selectedIndex: getSelectedIndex(this.props) }); @@ -134,7 +153,7 @@ class EnhancedSelectInput extends Component { const button = document.getElementById(this._buttonId); const options = document.getElementById(this._optionsId); - if (!button || this.state.isMobile) { + if (!button || !event.target.isConnected || this.state.isMobile) { return; } @@ -177,7 +196,7 @@ class EnhancedSelectInput extends Component { } if ( - selectedIndex == null || + selectedIndex == null || selectedIndex === -1 || getSelectedOption(selectedIndex, values).isDisabled ) { if (keyCode === keyCodes.UP_ARROW) { @@ -235,12 +254,27 @@ class EnhancedSelectInput extends Component { } onSelect = (value) => { - this.setState({ isOpen: false }); + if (Array.isArray(this.props.value)) { + let newValue = null; + const index = this.props.value.indexOf(value); + if (index === -1) { + newValue = this.props.values.map((v) => v.key).filter((v) => (v === value) || this.props.value.includes(v)); + } else { + newValue = [...this.props.value]; + newValue.splice(index, 1); + } + this.props.onChange({ + name: this.props.name, + value: newValue + }); + } else { + this.setState({ isOpen: false }); - this.props.onChange({ - name: this.props.name, - value - }); + this.props.onChange({ + name: this.props.name, + value + }); + } } onMeasure = ({ width }) => { @@ -258,10 +292,12 @@ class EnhancedSelectInput extends Component { const { className, disabledClassName, + value, values, isDisabled, hasError, hasWarning, + valueOptions, selectedValueOptions, selectedValueComponent: SelectedValueComponent, optionComponent: OptionComponent @@ -274,6 +310,7 @@ class EnhancedSelectInput extends Component { isMobile } = this.state; + const isMultiSelect = Array.isArray(value); const selectedOption = getSelectedOption(selectedIndex, values); return ( @@ -302,9 +339,12 @@ class EnhancedSelectInput extends Component { onPress={this.onPress} > {selectedOption ? selectedOption.value : null} @@ -358,11 +398,18 @@ class EnhancedSelectInput extends Component { > { values.map((v, index) => { + const hasParent = v.parentKey !== undefined; + const depth = hasParent ? 1 : 0; + const parentSelected = hasParent && value.includes(v.parentKey); return ( { values.map((v, index) => { + const hasParent = v.parentKey !== undefined; + const depth = hasParent ? 1 : 0; + const parentSelected = hasParent && value.includes(v.parentKey); return ( { + // CheckInput requires a handler. Swallow the change event because onPress will already handle it via event propagation. + } + // // Render render() { const { className, + id, isSelected, isDisabled, isHidden, + isMultiSelect, isMobile, children } = this.props; @@ -37,8 +44,8 @@ class EnhancedSelectInputOption extends Component { + + { + isMultiSelect && + + } + {children} { @@ -67,6 +87,7 @@ EnhancedSelectInputOption.propTypes = { isSelected: PropTypes.bool.isRequired, isDisabled: PropTypes.bool.isRequired, isHidden: PropTypes.bool.isRequired, + isMultiSelect: PropTypes.bool.isRequired, isMobile: PropTypes.bool.isRequired, children: PropTypes.node.isRequired, onSelect: PropTypes.func.isRequired @@ -75,7 +96,8 @@ EnhancedSelectInputOption.propTypes = { EnhancedSelectInputOption.defaultProps = { className: styles.option, isDisabled: false, - isHidden: false + isHidden: false, + isMultiSelect: false }; export default EnhancedSelectInputOption; diff --git a/frontend/src/Components/Form/HintedSelectInputOption.js b/frontend/src/Components/Form/HintedSelectInputOption.js index 73ac50a60..6ca92aeaf 100644 --- a/frontend/src/Components/Form/HintedSelectInputOption.js +++ b/frontend/src/Components/Form/HintedSelectInputOption.js @@ -6,14 +6,23 @@ import styles from './HintedSelectInputOption.css'; function HintedSelectInputOption(props) { const { + id, value, hint, + isSelected, + isDisabled, + isMultiSelect, isMobile, ...otherProps } = props; return ( @@ -36,9 +45,19 @@ function HintedSelectInputOption(props) { } HintedSelectInputOption.propTypes = { + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, value: PropTypes.string.isRequired, hint: PropTypes.node, + isSelected: PropTypes.bool.isRequired, + isDisabled: PropTypes.bool.isRequired, + isMultiSelect: PropTypes.bool.isRequired, isMobile: PropTypes.bool.isRequired }; +HintedSelectInputOption.defaultProps = { + isDisabled: false, + isHidden: false, + isMultiSelect: false +}; + export default HintedSelectInputOption; diff --git a/frontend/src/Components/Form/HintedSelectInputSelectedValue.js b/frontend/src/Components/Form/HintedSelectInputSelectedValue.js index d43c3e4da..07f6c9e25 100644 --- a/frontend/src/Components/Form/HintedSelectInputSelectedValue.js +++ b/frontend/src/Components/Form/HintedSelectInputSelectedValue.js @@ -1,23 +1,43 @@ +import _ from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; +import Label from 'Components/Label'; import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue'; import styles from './HintedSelectInputSelectedValue.css'; function HintedSelectInputSelectedValue(props) { const { value, + values, hint, + isMultiSelect, includeHint, ...otherProps } = props; + const valuesMap = isMultiSelect && _.keyBy(values, 'key'); + return (
- {value} + { + isMultiSelect && + value.map((key, index) => { + const v = valuesMap[key]; + return ( + + ); + }) + } + + { + !isMultiSelect && value + }
{ @@ -31,12 +51,15 @@ function HintedSelectInputSelectedValue(props) { } HintedSelectInputSelectedValue.propTypes = { - value: PropTypes.string, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired, + values: PropTypes.arrayOf(PropTypes.object).isRequired, hint: PropTypes.string, + isMultiSelect: PropTypes.bool.isRequired, includeHint: PropTypes.bool.isRequired }; HintedSelectInputSelectedValue.defaultProps = { + isMultiSelect: false, includeHint: true }; diff --git a/frontend/src/Components/Form/ProviderFieldFormGroup.js b/frontend/src/Components/Form/ProviderFieldFormGroup.js index 85fa6e26d..da13f8a20 100644 --- a/frontend/src/Components/Form/ProviderFieldFormGroup.js +++ b/frontend/src/Components/Form/ProviderFieldFormGroup.js @@ -6,7 +6,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup'; import FormLabel from 'Components/Form/FormLabel'; import { inputTypes } from 'Helpers/Props'; -function getType(type) { +function getType(type, value) { switch (type) { case 'captcha': return inputTypes.CAPTCHA; @@ -45,7 +45,8 @@ function getSelectValues(selectOptions) { return _.reduce(selectOptions, (result, option) => { result.push({ key: option.value, - value: option.name + value: option.name, + hint: option.hint }); return result; @@ -86,7 +87,7 @@ function ProviderFieldFormGroup(props) { {label} GetSelectOptions(Type selectOptions) { - var options = from Enum e in Enum.GetValues(selectOptions) - select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() }; + var options = selectOptions.GetFields().Where(v => v.IsStatic).Select(v => + { + var name = v.Name.Replace('_', ' '); + var value = Convert.ToInt32(v.GetRawConstantValue()); + var attrib = v.GetCustomAttribute(); + if (attrib != null) + { + return new SelectOption + { + Value = value, + Name = attrib.Label ?? name, + Order = attrib.Order, + Hint = attrib.Hint ?? $"({value})" + }; + } + else + { + return new SelectOption + { + Value = value, + Name = name, + Order = value, + Hint = $"({value})" + }; + } + }); - return options.OrderBy(o => o.Value).ToList(); + return options.OrderBy(o => o.Order).ToList(); } private static Func GetValueConverter(Type propertyType) diff --git a/src/Lidarr.Http/ClientSchema/SelectOption.cs b/src/Lidarr.Http/ClientSchema/SelectOption.cs index 58029e8fa..1317ae334 100644 --- a/src/Lidarr.Http/ClientSchema/SelectOption.cs +++ b/src/Lidarr.Http/ClientSchema/SelectOption.cs @@ -4,5 +4,7 @@ { public int Value { get; set; } public string Name { get; set; } + public int Order { get; set; } + public string Hint { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs index 0160f4731..4fb6a16d0 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs @@ -42,7 +42,7 @@ namespace NzbDrone.Core.Indexers.FileList [FieldDefinition(3, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")] public string BaseUrl { get; set; } - [FieldDefinition(4, Label = "Categories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "Categories for use in search and feeds, leave blank to disable standard/daily shows")] + [FieldDefinition(4, Label = "Categories", Type = FieldType.Select, SelectOptions = typeof(FileListCategories), HelpText = "Categories for use in search and feeds")] public IEnumerable Categories { get; set; } [FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] diff --git a/src/Sonarr.Http/ClientSchema/SelectOption.cs b/src/Sonarr.Http/ClientSchema/SelectOption.cs deleted file mode 100644 index 130453095..000000000 --- a/src/Sonarr.Http/ClientSchema/SelectOption.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Sonarr.Http.ClientSchema -{ - public class SelectOption - { - public int Value { get; set; } - public string Name { get; set; } - public int Order { get; set; } - public string Hint { get; set; } - } -}