Added MultiSelect input control for provider settings

This commit is contained in:
Taloth Saldono 2020-09-22 22:40:33 +02:00
parent 20a6284062
commit 7ee7e1be5d
7 changed files with 173 additions and 23 deletions

View File

@ -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,6 +254,20 @@ class EnhancedSelectInput extends Component {
}
onSelect = (value) => {
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({
@ -242,6 +275,7 @@ class EnhancedSelectInput extends Component {
value
});
}
}
onMeasure = ({ width }) => {
this.setState({ width });
@ -258,6 +292,7 @@ class EnhancedSelectInput extends Component {
const {
className,
disabledClassName,
value,
values,
isDisabled,
hasError,
@ -275,6 +310,7 @@ class EnhancedSelectInput extends Component {
isMobile
} = this.state;
const isMultiSelect = Array.isArray(value);
const selectedOption = getSelectedOption(selectedIndex, values);
return (
@ -303,9 +339,12 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
<SelectedValueComponent
value={value}
values={values}
{...selectedValueOptions}
{...selectedOption}
isDisabled={isDisabled}
isMultiSelect={isMultiSelect}
>
{selectedOption ? selectedOption.value : null}
</SelectedValueComponent>
@ -359,11 +398,17 @@ 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 (
<OptionComponent
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
depth={depth}
isSelected={isSelectedItem(index, this.props)}
isDisabled={parentSelected}
isMultiSelect={isMultiSelect}
{...valueOptions}
{...v}
isMobile={false}
@ -401,11 +446,17 @@ class EnhancedSelectInput extends Component {
<Scroller className={styles.optionsModalScroller}>
{
values.map((v, index) => {
const hasParent = v.parentKey !== undefined;
const depth = hasParent ? 1 : 0;
const parentSelected = hasParent && value.includes(v.parentKey);
return (
<OptionComponent
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
depth={depth}
isSelected={isSelectedItem(index, this.props)}
isMultiSelect={isMultiSelect}
isDisabled={parentSelected}
{...valueOptions}
{...v}
isMobile={true}
@ -429,9 +480,9 @@ EnhancedSelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool,
isDisabled: PropTypes.bool.isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
valueOptions: PropTypes.object.isRequired,

View File

@ -11,6 +11,16 @@
}
}
.optionCheck {
composes: container from '~./CheckInput.css';
flex: 0 0 0;
}
.optionCheckInput {
composes: input from '~./CheckInput.css';
margin-top: 0px;
}
.isSelected {
background-color: #e2e2e2;

View File

@ -4,6 +4,7 @@ import classNames from 'classnames';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import CheckInput from './CheckInput';
import styles from './EnhancedSelectInputOption.css';
class EnhancedSelectInputOption extends Component {
@ -20,15 +21,21 @@ class EnhancedSelectInputOption extends Component {
onSelect(id);
}
onCheckPress = () => {
// 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 {
<Link
className={classNames(
className,
isSelected && styles.isSelected,
isDisabled && styles.isDisabled,
isSelected && !isMultiSelect && styles.isSelected,
isDisabled && !isMultiSelect && styles.isDisabled,
isHidden && styles.isHidden,
isMobile && styles.isMobile
)}
@ -46,6 +53,19 @@ class EnhancedSelectInputOption extends Component {
isDisabled={isDisabled}
onPress={this.onPress}
>
{
isMultiSelect &&
<CheckInput
className={styles.optionCheckInput}
containerClassName={styles.optionCheck}
name={`select-${id}`}
value={isSelected}
isDisabled={isDisabled}
onChange={this.onCheckPress}
/>
}
{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;

View File

@ -6,14 +6,23 @@ import styles from './HintedSelectInputOption.css';
function HintedSelectInputOption(props) {
const {
id,
value,
hint,
isSelected,
isDisabled,
isMultiSelect,
isMobile,
...otherProps
} = props;
return (
<EnhancedSelectInputOption
id={id}
isSelected={isSelected}
isDisabled={isDisabled}
isHidden={isDisabled}
isMultiSelect={isMultiSelect}
isMobile={isMobile}
{...otherProps}
>
@ -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;

View File

@ -1,23 +1,43 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
import Label from 'Components/Label';
import styles from './HintedSelectInputSelectedValue.css';
function HintedSelectInputSelectedValue(props) {
const {
value,
values,
hint,
isMultiSelect,
includeHint,
...otherProps
} = props;
const valuesMap = isMultiSelect && _.keyBy(values, 'key');
return (
<EnhancedSelectInputSelectedValue
className={styles.selectedValue}
{...otherProps}
>
<div className={styles.valueText}>
{value}
{
isMultiSelect &&
value.map((key, index) => {
const v = valuesMap[key];
return (
<Label key={key}>
{v ? v.value : key}
</Label>
);
})
}
{
!isMultiSelect && value
}
</div>
{
@ -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
};

View File

@ -6,7 +6,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
function getType(type) {
function getType(type, value) {
switch (type) {
case 'captcha':
return inputTypes.CAPTCHA;
@ -43,7 +43,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;
@ -84,7 +85,7 @@ function ProviderFieldFormGroup(props) {
<FormLabel>{label}</FormLabel>
<FormInputGroup
type={getType(type)}
type={getType(type, value)}
name={name}
label={label}
helpText={helpText}

View File

@ -144,10 +144,34 @@ namespace Sonarr.Http.ClientSchema
private static List<SelectOption> 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<FieldOptionAttribute>();
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<object, object> GetValueConverter(Type propertyType)