mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-10-30 07:22:35 +01:00
Added MultiSelect input control for provider settings
This commit is contained in:
parent
20a6284062
commit
7ee7e1be5d
@ -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,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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user