+
\\^$.|?*+()[{ have special meanings and need escaping with a \\
' }} />
+ {'More details'} {'Here'}
+
+
+ {'Regular expressions can be tested '}
+ Here
+
+
+ }
+
+
+
+ Name
+
+
+
+
+
+ {
+ fields && fields.map((field) => {
+ return (
+
+ );
+ })
+ }
+
+
+
+ Negate
+
+
+
+
+
+
+
+ Required
+
+
+
+
+
+
+
+ {
+ id ?
+ :
+ null
+ }
+
+
+
+
+ Save
+
+
+
+ );
+}
+
+EditSpecificationModalContent.propTypes = {
+ id: PropTypes.number,
+ onDeleteSpecificationPress: PropTypes.func,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default EditSpecificationModalContent;
diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContentConnector.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContentConnector.js
new file mode 100644
index 000000000..8f27b74e0
--- /dev/null
+++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/EditSpecificationModalContentConnector.js
@@ -0,0 +1,78 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { clearAutoTaggingSpecificationPending, saveAutoTaggingSpecification, setAutoTaggingSpecificationFieldValue, setAutoTaggingSpecificationValue } from 'Store/Actions/settingsActions';
+import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
+import EditSpecificationModalContent from './EditSpecificationModalContent';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.settings.advancedSettings,
+ createProviderSettingsSelector('autoTaggingSpecifications'),
+ (advancedSettings, specification) => {
+ return {
+ advancedSettings,
+ ...specification
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ setAutoTaggingSpecificationValue,
+ setAutoTaggingSpecificationFieldValue,
+ saveAutoTaggingSpecification,
+ clearAutoTaggingSpecificationPending
+};
+
+class EditSpecificationModalContentConnector extends Component {
+
+ //
+ // Listeners
+
+ onInputChange = ({ name, value }) => {
+ this.props.setAutoTaggingSpecificationValue({ name, value });
+ };
+
+ onFieldChange = ({ name, value }) => {
+ this.props.setAutoTaggingSpecificationFieldValue({ name, value });
+ };
+
+ onCancelPress = () => {
+ this.props.clearAutoTaggingSpecificationPending();
+ this.props.onModalClose();
+ };
+
+ onSavePress = () => {
+ this.props.saveAutoTaggingSpecification({ id: this.props.id });
+ this.props.onModalClose();
+ };
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EditSpecificationModalContentConnector.propTypes = {
+ id: PropTypes.number,
+ item: PropTypes.object.isRequired,
+ setAutoTaggingSpecificationValue: PropTypes.func.isRequired,
+ setAutoTaggingSpecificationFieldValue: PropTypes.func.isRequired,
+ clearAutoTaggingSpecificationPending: PropTypes.func.isRequired,
+ saveAutoTaggingSpecification: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(EditSpecificationModalContentConnector);
diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.css b/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.css
new file mode 100644
index 000000000..e329fc313
--- /dev/null
+++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.css
@@ -0,0 +1,38 @@
+.autoTagging {
+ composes: card from '~Components/Card.css';
+
+ width: 300px;
+}
+
+.nameContainer {
+ display: flex;
+ justify-content: space-between;
+}
+
+.name {
+ @add-mixin truncate;
+
+ margin-bottom: 20px;
+ font-weight: 300;
+ font-size: 24px;
+}
+
+.cloneButton {
+ composes: button from '~Components/Link/IconButton.css';
+
+ height: 36px;
+}
+
+.labels {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 5px;
+ pointer-events: all;
+}
+
+.tooltipLabel {
+ composes: label from '~Components/Label.css';
+
+ margin: 0;
+ border: none;
+}
diff --git a/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.js b/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.js
new file mode 100644
index 000000000..b53bc74b6
--- /dev/null
+++ b/frontend/src/Settings/Tags/AutoTagging/Specifications/Specification.js
@@ -0,0 +1,121 @@
+import PropTypes from 'prop-types';
+import React, { useCallback, useState } from 'react';
+import Card from 'Components/Card';
+import Label from 'Components/Label';
+import IconButton from 'Components/Link/IconButton';
+import ConfirmModal from 'Components/Modal/ConfirmModal';
+import { icons, kinds } from 'Helpers/Props';
+import EditSpecificationModal from './EditSpecificationModal';
+import styles from './Specification.css';
+
+export default function Specification(props) {
+ const {
+ id,
+ implementationName,
+ name,
+ required,
+ negate,
+ onConfirmDeleteSpecification,
+ onCloneSpecificationPress
+ } = props;
+
+ const [isEditModalOpen, setIsEditModalOpen] = useState(false);
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+
+ const onEditPress = useCallback(() => {
+ setIsEditModalOpen(true);
+ }, [setIsEditModalOpen]);
+
+ const onEditModalClose = useCallback(() => {
+ setIsEditModalOpen(false);
+ }, [setIsEditModalOpen]);
+
+ const onDeletePress = useCallback(() => {
+ setIsEditModalOpen(false);
+ setIsDeleteModalOpen(true);
+ }, [setIsEditModalOpen, setIsDeleteModalOpen]);
+
+ const onDeleteModalClose = useCallback(() => {
+ setIsDeleteModalOpen(false);
+ }, [setIsDeleteModalOpen]);
+
+ const onConfirmDelete = useCallback(() => {
+ onConfirmDeleteSpecification(id);
+ }, [id, onConfirmDeleteSpecification]);
+
+ const onClonePress = useCallback(() => {
+ onCloneSpecificationPress(id);
+ }, [id, onCloneSpecificationPress]);
+
+ return (
+
+
+
+
+
+
+ {
+ negate ?
+ :
+ null
+ }
+
+ {
+ required ?
+ :
+ null
+ }
+
+
+
+
+
+
+ );
+}
+
+Specification.propTypes = {
+ id: PropTypes.number.isRequired,
+ implementation: PropTypes.string.isRequired,
+ implementationName: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ negate: PropTypes.bool.isRequired,
+ required: PropTypes.bool.isRequired,
+ fields: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onConfirmDeleteSpecification: PropTypes.func.isRequired,
+ onCloneSpecificationPress: PropTypes.func.isRequired
+};
diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js
index 48a008960..6f4d8f02c 100644
--- a/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js
+++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContent.js
@@ -21,6 +21,7 @@ function TagDetailsModalContent(props) {
notifications,
releaseProfiles,
indexers,
+ autoTags,
onModalClose,
onDeleteTagPress
} = props;
@@ -177,6 +178,22 @@ function TagDetailsModalContent(props) {
:
null
}
+
+ {
+ autoTags.length ?
+
:
+ null
+ }
@@ -211,6 +228,7 @@ TagDetailsModalContent.propTypes = {
notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired,
indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
+ autoTags: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteTagPress: PropTypes.func.isRequired
};
diff --git a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js
index 71948e1e2..ce138b5f7 100644
--- a/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js
+++ b/frontend/src/Settings/Tags/Details/TagDetailsModalContentConnector.js
@@ -77,6 +77,14 @@ function createMatchingIndexersSelector() {
);
}
+function createMatchingAutoTagsSelector() {
+ return createSelector(
+ (state, { autoTagIds }) => autoTagIds,
+ (state) => state.settings.autoTaggings.items,
+ findMatchingItems
+ );
+}
+
function createMapStateToProps() {
return createSelector(
createMatchingSeriesSelector(),
@@ -85,14 +93,16 @@ function createMapStateToProps() {
createMatchingNotificationsSelector(),
createMatchingReleaseProfilesSelector(),
createMatchingIndexersSelector(),
- (series, delayProfiles, importLists, notifications, releaseProfiles, indexers) => {
+ createMatchingAutoTagsSelector(),
+ (series, delayProfiles, importLists, notifications, releaseProfiles, indexers, autoTags) => {
return {
series,
delayProfiles,
importLists,
notifications,
releaseProfiles,
- indexers
+ indexers,
+ autoTags
};
}
);
diff --git a/frontend/src/Settings/Tags/Tag.js b/frontend/src/Settings/Tags/Tag.js
index 59e6af42d..b16a58f75 100644
--- a/frontend/src/Settings/Tags/Tag.js
+++ b/frontend/src/Settings/Tags/Tag.js
@@ -4,6 +4,7 @@ import Card from 'Components/Card';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props';
import TagDetailsModal from './Details/TagDetailsModal';
+import TagInUse from './TagInUse';
import styles from './Tag.css';
class Tag extends Component {
@@ -57,6 +58,7 @@ class Tag extends Component {
notificationIds,
restrictionIds,
indexerIds,
+ autoTagIds,
seriesIds
} = this.props;
@@ -71,6 +73,7 @@ class Tag extends Component {
notificationIds.length ||
restrictionIds.length ||
indexerIds.length ||
+ autoTagIds.length ||
seriesIds.length
);
@@ -85,56 +88,46 @@ class Tag extends Component {
{
- isTagUsed &&
+ isTagUsed ?
- {
- seriesIds.length ?
-
- {seriesIds.length} series
-
:
- null
- }
+
- {
- delayProfileIds.length ?
-
- {delayProfileIds.length} delay profile{delayProfileIds.length > 1 && 's'}
-
:
- null
- }
+
- {
- importListIds.length ?
-
- {importListIds.length} import list{importListIds.length > 1 && 's'}
-
:
- null
- }
+
- {
- notificationIds.length ?
-
- {notificationIds.length} connection{notificationIds.length > 1 && 's'}
-
:
- null
- }
+
- {
- restrictionIds.length ?
-
- {restrictionIds.length} release profile{restrictionIds.length > 1 && 's'}
-
:
- null
- }
+
- {
- indexerIds.length ?
-
- {indexerIds.length} indexer{indexerIds.length > 1 && 's'}
-
:
- null
- }
-
+