From 304d1e34624000dd3249da1ba90878dc26f50a5e Mon Sep 17 00:00:00 2001 From: Qstick <qstick@gmail.com> Date: Mon, 6 Jan 2020 23:31:33 -0500 Subject: [PATCH] TagSelect field type (cherry picked from commit 09347f79c5c486ccb88d732c1bac1cacc668536c) Closes #558 --- .../src/Components/Form/FormInputGroup.js | 4 + .../Components/Form/ProviderFieldFormGroup.js | 2 + .../Form/TagSelectInputConnector.js | 102 ++++++++++++++++++ frontend/src/Helpers/Props/inputTypes.js | 2 + .../Annotations/FieldDefinitionAttribute.cs | 3 +- .../ClientSchema/SchemaBuilder.cs | 2 +- 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 frontend/src/Components/Form/TagSelectInputConnector.js diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index e38d7970e..93d625277 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -26,6 +26,7 @@ import PathInputConnector from './PathInputConnector'; import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector'; import RootFolderSelectInputConnector from './RootFolderSelectInputConnector'; import TagInputConnector from './TagInputConnector'; +import TagSelectInputConnector from './TagSelectInputConnector'; import TextArea from './TextArea'; import TextInput from './TextInput'; import TextTagInputConnector from './TextTagInputConnector'; @@ -103,6 +104,9 @@ function getComponent(type) { case inputTypes.TEXT_TAG: return TextTagInputConnector; + case inputTypes.TAG_SELECT: + return TagSelectInputConnector; + case inputTypes.UMASK: return UMaskInput; diff --git a/frontend/src/Components/Form/ProviderFieldFormGroup.js b/frontend/src/Components/Form/ProviderFieldFormGroup.js index 043cc432b..9f2967fb5 100644 --- a/frontend/src/Components/Form/ProviderFieldFormGroup.js +++ b/frontend/src/Components/Form/ProviderFieldFormGroup.js @@ -31,6 +31,8 @@ function getType({ type, selectOptionsProviderAction }) { return inputTypes.SELECT; case 'tag': return inputTypes.TEXT_TAG; + case 'tagSelect': + return inputTypes.TAG_SELECT; case 'textbox': return inputTypes.TEXT; case 'oAuth': diff --git a/frontend/src/Components/Form/TagSelectInputConnector.js b/frontend/src/Components/Form/TagSelectInputConnector.js new file mode 100644 index 000000000..23afe6da1 --- /dev/null +++ b/frontend/src/Components/Form/TagSelectInputConnector.js @@ -0,0 +1,102 @@ +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import TagInput from './TagInput'; + +function createMapStateToProps() { + return createSelector( + (state, { value }) => value, + (state, { values }) => values, + (tags, tagList) => { + const sortedTags = _.sortBy(tagList, 'value'); + + return { + tags: tags.reduce((acc, tag) => { + const matchingTag = _.find(tagList, { key: tag }); + + if (matchingTag) { + acc.push({ + id: tag, + name: matchingTag.value + }); + } + + return acc; + }, []), + + tagList: sortedTags.map(({ key: id, value: name }) => { + return { + id, + name + }; + }), + + allTags: sortedTags + }; + } + ); +} + +class TagSelectInputConnector extends Component { + + // + // Listeners + + onTagAdd = (tag) => { + const { + name, + value, + allTags + } = this.props; + + const existingTag =_.some(allTags, { key: tag.id }); + + const newValue = value.slice(); + + if (existingTag) { + newValue.push(tag.id); + } + + this.props.onChange({ name, value: newValue }); + }; + + onTagDelete = ({ index }) => { + const { + name, + value + } = this.props; + + const newValue = value.slice(); + newValue.splice(index, 1); + + this.props.onChange({ + name, + value: newValue + }); + }; + + // + // Render + + render() { + return ( + <TagInput + onTagAdd={this.onTagAdd} + onTagDelete={this.onTagDelete} + {...this.props} + /> + ); + } +} + +TagSelectInputConnector.propTypes = { + name: PropTypes.string.isRequired, + value: PropTypes.arrayOf(PropTypes.number).isRequired, + values: PropTypes.arrayOf(PropTypes.object).isRequired, + allTags: PropTypes.arrayOf(PropTypes.object).isRequired, + onChange: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps)(TagSelectInputConnector); diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js index c4cfb7ecf..73ba65ce2 100644 --- a/frontend/src/Helpers/Props/inputTypes.js +++ b/frontend/src/Helpers/Props/inputTypes.js @@ -22,6 +22,7 @@ export const TAG = 'tag'; export const TEXT = 'text'; export const TEXT_AREA = 'textArea'; export const TEXT_TAG = 'textTag'; +export const TAG_SELECT = 'tagSelect'; export const UMASK = 'umask'; export const all = [ @@ -49,5 +50,6 @@ export const all = [ TEXT, TEXT_AREA, TEXT_TAG, + TAG_SELECT, UMASK ]; diff --git a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs index 67f78ad87..e15791153 100644 --- a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs +++ b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs @@ -65,7 +65,8 @@ namespace NzbDrone.Core.Annotations Captcha, OAuth, Device, - Bookshelf + Bookshelf, + TagSelect } public enum HiddenType diff --git a/src/Readarr.Http/ClientSchema/SchemaBuilder.cs b/src/Readarr.Http/ClientSchema/SchemaBuilder.cs index 6a25e8cd8..bfcd9bbb3 100644 --- a/src/Readarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Readarr.Http/ClientSchema/SchemaBuilder.cs @@ -107,7 +107,7 @@ namespace Readarr.Http.ClientSchema Placeholder = fieldAttribute.Placeholder }; - if (fieldAttribute.Type == FieldType.Select) + if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect) { if (fieldAttribute.SelectOptionsProviderAction.IsNotNullOrWhiteSpace()) {