Added MultiSelect input control for provider settings
This commit is contained in:
parent
20a6284062
commit
7ee7e1be5d
|
@ -58,11 +58,30 @@ function getSelectedIndex(props) {
|
||||||
values
|
values
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return values.findIndex((v) => {
|
||||||
|
return value.size && v.key === value[0];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return values.findIndex((v) => {
|
return values.findIndex((v) => {
|
||||||
return v.key === value;
|
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) {
|
function getKey(selectedIndex, values) {
|
||||||
return values[selectedIndex].key;
|
return values[selectedIndex].key;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +111,7 @@ class EnhancedSelectInput extends Component {
|
||||||
this._scheduleUpdate();
|
this._scheduleUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.value !== this.props.value) {
|
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedIndex: getSelectedIndex(this.props)
|
selectedIndex: getSelectedIndex(this.props)
|
||||||
});
|
});
|
||||||
|
@ -134,7 +153,7 @@ class EnhancedSelectInput extends Component {
|
||||||
const button = document.getElementById(this._buttonId);
|
const button = document.getElementById(this._buttonId);
|
||||||
const options = document.getElementById(this._optionsId);
|
const options = document.getElementById(this._optionsId);
|
||||||
|
|
||||||
if (!button || this.state.isMobile) {
|
if (!button || !event.target.isConnected || this.state.isMobile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +196,7 @@ class EnhancedSelectInput extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
selectedIndex == null ||
|
selectedIndex == null || selectedIndex === -1 ||
|
||||||
getSelectedOption(selectedIndex, values).isDisabled
|
getSelectedOption(selectedIndex, values).isDisabled
|
||||||
) {
|
) {
|
||||||
if (keyCode === keyCodes.UP_ARROW) {
|
if (keyCode === keyCodes.UP_ARROW) {
|
||||||
|
@ -235,6 +254,20 @@ class EnhancedSelectInput extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelect = (value) => {
|
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.setState({ isOpen: false });
|
||||||
|
|
||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
|
@ -242,6 +275,7 @@ class EnhancedSelectInput extends Component {
|
||||||
value
|
value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMeasure = ({ width }) => {
|
onMeasure = ({ width }) => {
|
||||||
this.setState({ width });
|
this.setState({ width });
|
||||||
|
@ -258,6 +292,7 @@ class EnhancedSelectInput extends Component {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
disabledClassName,
|
disabledClassName,
|
||||||
|
value,
|
||||||
values,
|
values,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
hasError,
|
hasError,
|
||||||
|
@ -275,6 +310,7 @@ class EnhancedSelectInput extends Component {
|
||||||
isMobile
|
isMobile
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const isMultiSelect = Array.isArray(value);
|
||||||
const selectedOption = getSelectedOption(selectedIndex, values);
|
const selectedOption = getSelectedOption(selectedIndex, values);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -303,9 +339,12 @@ class EnhancedSelectInput extends Component {
|
||||||
onPress={this.onPress}
|
onPress={this.onPress}
|
||||||
>
|
>
|
||||||
<SelectedValueComponent
|
<SelectedValueComponent
|
||||||
|
value={value}
|
||||||
|
values={values}
|
||||||
{...selectedValueOptions}
|
{...selectedValueOptions}
|
||||||
{...selectedOption}
|
{...selectedOption}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
isMultiSelect={isMultiSelect}
|
||||||
>
|
>
|
||||||
{selectedOption ? selectedOption.value : null}
|
{selectedOption ? selectedOption.value : null}
|
||||||
</SelectedValueComponent>
|
</SelectedValueComponent>
|
||||||
|
@ -359,11 +398,17 @@ class EnhancedSelectInput extends Component {
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
values.map((v, index) => {
|
values.map((v, index) => {
|
||||||
|
const hasParent = v.parentKey !== undefined;
|
||||||
|
const depth = hasParent ? 1 : 0;
|
||||||
|
const parentSelected = hasParent && value.includes(v.parentKey);
|
||||||
return (
|
return (
|
||||||
<OptionComponent
|
<OptionComponent
|
||||||
key={v.key}
|
key={v.key}
|
||||||
id={v.key}
|
id={v.key}
|
||||||
isSelected={index === selectedIndex}
|
depth={depth}
|
||||||
|
isSelected={isSelectedItem(index, this.props)}
|
||||||
|
isDisabled={parentSelected}
|
||||||
|
isMultiSelect={isMultiSelect}
|
||||||
{...valueOptions}
|
{...valueOptions}
|
||||||
{...v}
|
{...v}
|
||||||
isMobile={false}
|
isMobile={false}
|
||||||
|
@ -401,11 +446,17 @@ class EnhancedSelectInput extends Component {
|
||||||
<Scroller className={styles.optionsModalScroller}>
|
<Scroller className={styles.optionsModalScroller}>
|
||||||
{
|
{
|
||||||
values.map((v, index) => {
|
values.map((v, index) => {
|
||||||
|
const hasParent = v.parentKey !== undefined;
|
||||||
|
const depth = hasParent ? 1 : 0;
|
||||||
|
const parentSelected = hasParent && value.includes(v.parentKey);
|
||||||
return (
|
return (
|
||||||
<OptionComponent
|
<OptionComponent
|
||||||
key={v.key}
|
key={v.key}
|
||||||
id={v.key}
|
id={v.key}
|
||||||
isSelected={index === selectedIndex}
|
depth={depth}
|
||||||
|
isSelected={isSelectedItem(index, this.props)}
|
||||||
|
isMultiSelect={isMultiSelect}
|
||||||
|
isDisabled={parentSelected}
|
||||||
{...valueOptions}
|
{...valueOptions}
|
||||||
{...v}
|
{...v}
|
||||||
isMobile={true}
|
isMobile={true}
|
||||||
|
@ -429,9 +480,9 @@ EnhancedSelectInput.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabledClassName: PropTypes.string,
|
disabledClassName: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
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,
|
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool.isRequired,
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
hasWarning: PropTypes.bool,
|
hasWarning: PropTypes.bool,
|
||||||
valueOptions: PropTypes.object.isRequired,
|
valueOptions: PropTypes.object.isRequired,
|
||||||
|
|
|
@ -11,6 +11,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.optionCheck {
|
||||||
|
composes: container from '~./CheckInput.css';
|
||||||
|
flex: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionCheckInput {
|
||||||
|
composes: input from '~./CheckInput.css';
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.isSelected {
|
.isSelected {
|
||||||
background-color: #e2e2e2;
|
background-color: #e2e2e2;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import classNames from 'classnames';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import CheckInput from './CheckInput';
|
||||||
import styles from './EnhancedSelectInputOption.css';
|
import styles from './EnhancedSelectInputOption.css';
|
||||||
|
|
||||||
class EnhancedSelectInputOption extends Component {
|
class EnhancedSelectInputOption extends Component {
|
||||||
|
@ -20,15 +21,21 @@ class EnhancedSelectInputOption extends Component {
|
||||||
onSelect(id);
|
onSelect(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCheckPress = () => {
|
||||||
|
// CheckInput requires a handler. Swallow the change event because onPress will already handle it via event propagation.
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
|
id,
|
||||||
isSelected,
|
isSelected,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isHidden,
|
isHidden,
|
||||||
|
isMultiSelect,
|
||||||
isMobile,
|
isMobile,
|
||||||
children
|
children
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -37,8 +44,8 @@ class EnhancedSelectInputOption extends Component {
|
||||||
<Link
|
<Link
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
isSelected && styles.isSelected,
|
isSelected && !isMultiSelect && styles.isSelected,
|
||||||
isDisabled && styles.isDisabled,
|
isDisabled && !isMultiSelect && styles.isDisabled,
|
||||||
isHidden && styles.isHidden,
|
isHidden && styles.isHidden,
|
||||||
isMobile && styles.isMobile
|
isMobile && styles.isMobile
|
||||||
)}
|
)}
|
||||||
|
@ -46,6 +53,19 @@ class EnhancedSelectInputOption extends Component {
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
onPress={this.onPress}
|
onPress={this.onPress}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{
|
||||||
|
isMultiSelect &&
|
||||||
|
<CheckInput
|
||||||
|
className={styles.optionCheckInput}
|
||||||
|
containerClassName={styles.optionCheck}
|
||||||
|
name={`select-${id}`}
|
||||||
|
value={isSelected}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
onChange={this.onCheckPress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -67,6 +87,7 @@ EnhancedSelectInputOption.propTypes = {
|
||||||
isSelected: PropTypes.bool.isRequired,
|
isSelected: PropTypes.bool.isRequired,
|
||||||
isDisabled: PropTypes.bool.isRequired,
|
isDisabled: PropTypes.bool.isRequired,
|
||||||
isHidden: PropTypes.bool.isRequired,
|
isHidden: PropTypes.bool.isRequired,
|
||||||
|
isMultiSelect: PropTypes.bool.isRequired,
|
||||||
isMobile: PropTypes.bool.isRequired,
|
isMobile: PropTypes.bool.isRequired,
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired
|
onSelect: PropTypes.func.isRequired
|
||||||
|
@ -75,7 +96,8 @@ EnhancedSelectInputOption.propTypes = {
|
||||||
EnhancedSelectInputOption.defaultProps = {
|
EnhancedSelectInputOption.defaultProps = {
|
||||||
className: styles.option,
|
className: styles.option,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
isHidden: false
|
isHidden: false,
|
||||||
|
isMultiSelect: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EnhancedSelectInputOption;
|
export default EnhancedSelectInputOption;
|
||||||
|
|
|
@ -6,14 +6,23 @@ import styles from './HintedSelectInputOption.css';
|
||||||
|
|
||||||
function HintedSelectInputOption(props) {
|
function HintedSelectInputOption(props) {
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
value,
|
value,
|
||||||
hint,
|
hint,
|
||||||
|
isSelected,
|
||||||
|
isDisabled,
|
||||||
|
isMultiSelect,
|
||||||
isMobile,
|
isMobile,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedSelectInputOption
|
<EnhancedSelectInputOption
|
||||||
|
id={id}
|
||||||
|
isSelected={isSelected}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
isHidden={isDisabled}
|
||||||
|
isMultiSelect={isMultiSelect}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
|
@ -36,9 +45,19 @@ function HintedSelectInputOption(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
HintedSelectInputOption.propTypes = {
|
HintedSelectInputOption.propTypes = {
|
||||||
|
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
hint: PropTypes.node,
|
hint: PropTypes.node,
|
||||||
|
isSelected: PropTypes.bool.isRequired,
|
||||||
|
isDisabled: PropTypes.bool.isRequired,
|
||||||
|
isMultiSelect: PropTypes.bool.isRequired,
|
||||||
isMobile: PropTypes.bool.isRequired
|
isMobile: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
HintedSelectInputOption.defaultProps = {
|
||||||
|
isDisabled: false,
|
||||||
|
isHidden: false,
|
||||||
|
isMultiSelect: false
|
||||||
|
};
|
||||||
|
|
||||||
export default HintedSelectInputOption;
|
export default HintedSelectInputOption;
|
||||||
|
|
|
@ -1,23 +1,43 @@
|
||||||
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
|
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
|
||||||
|
import Label from 'Components/Label';
|
||||||
import styles from './HintedSelectInputSelectedValue.css';
|
import styles from './HintedSelectInputSelectedValue.css';
|
||||||
|
|
||||||
function HintedSelectInputSelectedValue(props) {
|
function HintedSelectInputSelectedValue(props) {
|
||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
|
values,
|
||||||
hint,
|
hint,
|
||||||
|
isMultiSelect,
|
||||||
includeHint,
|
includeHint,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const valuesMap = isMultiSelect && _.keyBy(values, 'key');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedSelectInputSelectedValue
|
<EnhancedSelectInputSelectedValue
|
||||||
className={styles.selectedValue}
|
className={styles.selectedValue}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
<div className={styles.valueText}>
|
<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>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -31,12 +51,15 @@ function HintedSelectInputSelectedValue(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
HintedSelectInputSelectedValue.propTypes = {
|
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,
|
hint: PropTypes.string,
|
||||||
|
isMultiSelect: PropTypes.bool.isRequired,
|
||||||
includeHint: PropTypes.bool.isRequired
|
includeHint: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
HintedSelectInputSelectedValue.defaultProps = {
|
HintedSelectInputSelectedValue.defaultProps = {
|
||||||
|
isMultiSelect: false,
|
||||||
includeHint: true
|
includeHint: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
|
||||||
function getType(type) {
|
function getType(type, value) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'captcha':
|
case 'captcha':
|
||||||
return inputTypes.CAPTCHA;
|
return inputTypes.CAPTCHA;
|
||||||
|
@ -43,7 +43,8 @@ function getSelectValues(selectOptions) {
|
||||||
return _.reduce(selectOptions, (result, option) => {
|
return _.reduce(selectOptions, (result, option) => {
|
||||||
result.push({
|
result.push({
|
||||||
key: option.value,
|
key: option.value,
|
||||||
value: option.name
|
value: option.name,
|
||||||
|
hint: option.hint
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -84,7 +85,7 @@ function ProviderFieldFormGroup(props) {
|
||||||
<FormLabel>{label}</FormLabel>
|
<FormLabel>{label}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={getType(type)}
|
type={getType(type, value)}
|
||||||
name={name}
|
name={name}
|
||||||
label={label}
|
label={label}
|
||||||
helpText={helpText}
|
helpText={helpText}
|
||||||
|
|
|
@ -144,10 +144,34 @@ namespace Sonarr.Http.ClientSchema
|
||||||
|
|
||||||
private static List<SelectOption> GetSelectOptions(Type selectOptions)
|
private static List<SelectOption> GetSelectOptions(Type selectOptions)
|
||||||
{
|
{
|
||||||
var options = from Enum e in Enum.GetValues(selectOptions)
|
var options = selectOptions.GetFields().Where(v => v.IsStatic).Select(v =>
|
||||||
select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() };
|
{
|
||||||
|
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)
|
private static Func<object, object> GetValueConverter(Type propertyType)
|
||||||
|
|
Loading…
Reference in New Issue