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())
                         {