mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-13 18:27:08 -07:00
New: UI Updates, Tag manager, More custom filters (#437)
* New: UI Updates, Tag manager, More custom filters * fixup! Fix ScanFixture Unit Tests * Fixed: Sentry Errors from UI don't have release, branch, environment * Changed: Bump Mobile Detect for New Device Detection * Fixed: Build on changes to package.json * fixup! Add MetadataProfile filter option * fixup! Tag Note, Blacklist, Manual Import * fixup: Remove connectSection * fixup: root folder comment
This commit is contained in:
parent
afa78b1d20
commit
6581b3a2c5
198 changed files with 3057 additions and 888 deletions
|
@ -59,9 +59,7 @@ class DownloadClients extends Component {
|
|||
} = this.state;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Download Clients"
|
||||
>
|
||||
<FieldSet legend="Download Clients">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load download clients"
|
||||
{...otherProps}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditDownloadClientModalContentConnector from './EditDownloadClientModalContentConnector';
|
||||
|
||||
function EditDownloadClientModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { setDownloadClientValue, setDownloadClientFieldValue, saveDownloadClient, testDownloadClient } from 'Store/Actions/settingsActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('downloadClients'),
|
||||
(advancedSettings, downloadClient) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -85,10 +85,4 @@ EditDownloadClientModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'downloadClients' }
|
||||
)(EditDownloadClientModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditDownloadClientModalContentConnector);
|
||||
|
|
|
@ -33,9 +33,7 @@ function DownloadClientOptions(props) {
|
|||
{
|
||||
hasSettings && !isFetching && !error &&
|
||||
<div>
|
||||
<FieldSet
|
||||
legend="Completed Download Handling"
|
||||
>
|
||||
<FieldSet legend="Completed Download Handling">
|
||||
<Form>
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>Enable</FormLabel>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import { fetchDownloadClientOptions, setDownloadClientOptionsValue, saveDownloadClientOptions } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import DownloadClientOptions from './DownloadClientOptions';
|
||||
|
||||
const SECTION = 'downloadClientOptions';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createSettingsSectionSelector(),
|
||||
createSettingsSectionSelector(SECTION),
|
||||
(advancedSettings, sectionSettings) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -62,7 +64,7 @@ class DownloadClientOptionsConnector extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
||||
this.props.dispatchClearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -86,7 +88,6 @@ class DownloadClientOptionsConnector extends Component {
|
|||
}
|
||||
|
||||
DownloadClientOptionsConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
hasPendingChanges: PropTypes.bool.isRequired,
|
||||
dispatchFetchDownloadClientOptions: PropTypes.func.isRequired,
|
||||
|
@ -97,10 +98,4 @@ DownloadClientOptionsConnector.propTypes = {
|
|||
onChildStateChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'settings.downloadClientOptions' }
|
||||
)(DownloadClientOptionsConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientOptionsConnector);
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditRemotePathMappingModalContentConnector from './EditRemotePathMappingModalContentConnector';
|
||||
|
||||
function EditRemotePathMappingModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -44,9 +44,7 @@ class RemotePathMappings extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Remote Path Mappings"
|
||||
>
|
||||
<FieldSet legend="Remote Path Mappings">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Remote Path Mappings"
|
||||
{...otherProps}
|
||||
|
|
|
@ -49,7 +49,8 @@ function BackupSettings(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="backupInterval"
|
||||
helpText="Interval in days"
|
||||
unit="days"
|
||||
helpText="Interval to backup the Lidarr DB and settings"
|
||||
onChange={onInputChange}
|
||||
{...backupInterval}
|
||||
/>
|
||||
|
@ -64,7 +65,8 @@ function BackupSettings(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="backupRetention"
|
||||
helpText="Retention in days. Automatic backups older the retention will be cleaned up automatically"
|
||||
unit="days"
|
||||
helpText="Automatic backups older than the retention will be cleaned up automatically"
|
||||
onChange={onInputChange}
|
||||
{...backupRetention}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
|
@ -9,14 +10,15 @@ import { setGeneralSettingsValue, saveGeneralSettings, fetchGeneralSettings } fr
|
|||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { restart } from 'Store/Actions/systemActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import GeneralSettings from './GeneralSettings';
|
||||
|
||||
const SECTION = 'general';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createSettingsSectionSelector(),
|
||||
createSettingsSectionSelector(SECTION),
|
||||
createCommandsSelector(),
|
||||
createSystemStatusSelector(),
|
||||
(advancedSettings, sectionSettings, commands, systemStatus) => {
|
||||
|
@ -59,7 +61,7 @@ class GeneralSettingsConnector extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: this.props.section });
|
||||
this.props.clearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -98,7 +100,6 @@ class GeneralSettingsConnector extends Component {
|
|||
}
|
||||
|
||||
GeneralSettingsConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
isResettingApiKey: PropTypes.bool.isRequired,
|
||||
setGeneralSettingsValue: PropTypes.func.isRequired,
|
||||
saveGeneralSettings: PropTypes.func.isRequired,
|
||||
|
@ -108,10 +109,4 @@ GeneralSettingsConnector.propTypes = {
|
|||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'settings.general' }
|
||||
)(GeneralSettingsConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(GeneralSettingsConnector);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { setImportListValue, setImportListFieldValue, saveImportList, testImportList } from 'Store/Actions/settingsActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import EditImportListModalContent from './EditImportListModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
@ -11,7 +11,7 @@ function createMapStateToProps() {
|
|||
(state) => state.settings.advancedSettings,
|
||||
(state) => state.settings.languageProfiles,
|
||||
(state) => state.settings.metadataProfiles,
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('importLists'),
|
||||
(advancedSettings, languageProfiles, metadataProfiles, importList) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -89,10 +89,4 @@ EditImportListModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'importLists' }
|
||||
)(EditImportListModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditImportListModalContentConnector);
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditIndexerModalContentConnector from './EditIndexerModalContentConnector';
|
||||
|
||||
function EditIndexerModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -96,7 +96,7 @@ function EditIndexerModalContent(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableAutomaticSearch"
|
||||
helpText={supportsSearch.value && 'Will be used when automatic searches are performed via the UI or by Lidarr'}
|
||||
helpText={supportsSearch.value ? 'Will be used when automatic searches are performed via the UI or by Lidarr' : undefined}
|
||||
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
|
||||
isDisabled={!supportsSearch.value}
|
||||
{...enableAutomaticSearch}
|
||||
|
@ -110,7 +110,7 @@ function EditIndexerModalContent(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableInteractiveSearch"
|
||||
helpText={supportsSearch.value && 'Will be used when interactive search is used'}
|
||||
helpText={supportsSearch.value ? 'Will be used when interactive search is used' : undefined}
|
||||
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
|
||||
isDisabled={!supportsSearch.value}
|
||||
{...enableInteractiveSearch}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { setIndexerValue, setIndexerFieldValue, saveIndexer, testIndexer } from 'Store/Actions/settingsActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import EditIndexerModalContent from './EditIndexerModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('indexers'),
|
||||
(advancedSettings, indexer) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -85,10 +85,4 @@ EditIndexerModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'indexers' }
|
||||
)(EditIndexerModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditIndexerModalContentConnector);
|
||||
|
|
|
@ -59,9 +59,7 @@ class Indexers extends Component {
|
|||
} = this.state;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Indexers"
|
||||
>
|
||||
<FieldSet legend="Indexers">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Indexers"
|
||||
{...otherProps}
|
||||
|
|
|
@ -19,9 +19,7 @@ function IndexerOptions(props) {
|
|||
} = props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Options"
|
||||
>
|
||||
<FieldSet legend="Options">
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
|
@ -42,6 +40,7 @@ function IndexerOptions(props) {
|
|||
type={inputTypes.NUMBER}
|
||||
name="minimumAge"
|
||||
min={0}
|
||||
unit="minutes"
|
||||
helpText="Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
|
||||
onChange={onInputChange}
|
||||
{...settings.minimumAge}
|
||||
|
@ -55,6 +54,7 @@ function IndexerOptions(props) {
|
|||
type={inputTypes.NUMBER}
|
||||
name="maximumSize"
|
||||
min={0}
|
||||
unit="MB"
|
||||
helpText="Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited."
|
||||
onChange={onInputChange}
|
||||
{...settings.maximumSize}
|
||||
|
@ -68,6 +68,7 @@ function IndexerOptions(props) {
|
|||
type={inputTypes.NUMBER}
|
||||
name="retention"
|
||||
min={0}
|
||||
unit="days"
|
||||
helpText="Usenet only: Set to zero to set for unlimited retention"
|
||||
onChange={onInputChange}
|
||||
{...settings.retention}
|
||||
|
@ -84,6 +85,7 @@ function IndexerOptions(props) {
|
|||
type={inputTypes.NUMBER}
|
||||
name="rssSyncInterval"
|
||||
min={0}
|
||||
unit="minutes"
|
||||
helpText="Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
|
||||
helpTextWarning="This will apply to all indexers, please follow the rules set forth by them"
|
||||
helpLink="https://github.com/Lidarr/Lidarr/wiki/RSS-Sync"
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import { fetchIndexerOptions, setIndexerOptionsValue, saveIndexerOptions } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import IndexerOptions from './IndexerOptions';
|
||||
|
||||
const SECTION = 'indexerOptions';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createSettingsSectionSelector(),
|
||||
createSettingsSectionSelector(SECTION),
|
||||
(advancedSettings, sectionSettings) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -62,7 +64,7 @@ class IndexerOptionsConnector extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
||||
this.props.dispatchClearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -86,7 +88,6 @@ class IndexerOptionsConnector extends Component {
|
|||
}
|
||||
|
||||
IndexerOptionsConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
hasPendingChanges: PropTypes.bool.isRequired,
|
||||
dispatchFetchIndexerOptions: PropTypes.func.isRequired,
|
||||
|
@ -97,10 +98,4 @@ IndexerOptionsConnector.propTypes = {
|
|||
onChildStateChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'settings.indexerOptions' }
|
||||
)(IndexerOptionsConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(IndexerOptionsConnector);
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditRestrictionModalContentConnector from './EditRestrictionModalContentConnector';
|
||||
|
||||
function EditRestrictionModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -45,9 +45,7 @@ class Restrictions extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Restrictions"
|
||||
>
|
||||
<FieldSet legend="Restrictions">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Restrictions"
|
||||
{...otherProps}
|
||||
|
|
|
@ -64,9 +64,7 @@ class MediaManagement extends Component {
|
|||
|
||||
{
|
||||
advancedSettings &&
|
||||
<FieldSet
|
||||
legend="Folders"
|
||||
>
|
||||
<FieldSet legend="Folders">
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import { fetchMediaManagementSettings, setMediaManagementSettingsValue, saveMediaManagementSettings, saveNamingSettings } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import MediaManagement from './MediaManagement';
|
||||
|
||||
const SECTION = 'mediaManagement';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
(state) => state.settings.naming,
|
||||
createSettingsSectionSelector(),
|
||||
createSettingsSectionSelector(SECTION),
|
||||
createSystemStatusSelector(),
|
||||
(advancedSettings, namingSettings, sectionSettings, systemStatus) => {
|
||||
return {
|
||||
|
@ -44,7 +46,7 @@ class MediaManagementConnector extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: this.props.section });
|
||||
this.props.clearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -74,7 +76,6 @@ class MediaManagementConnector extends Component {
|
|||
}
|
||||
|
||||
MediaManagementConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
fetchMediaManagementSettings: PropTypes.func.isRequired,
|
||||
setMediaManagementSettingsValue: PropTypes.func.isRequired,
|
||||
saveMediaManagementSettings: PropTypes.func.isRequired,
|
||||
|
@ -82,10 +83,4 @@ MediaManagementConnector.propTypes = {
|
|||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'settings.mediaManagement' }
|
||||
)(MediaManagementConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MediaManagementConnector);
|
||||
|
|
|
@ -113,9 +113,7 @@ class Naming extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Track Naming"
|
||||
>
|
||||
<FieldSet legend="Track Naming">
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import { fetchNamingSettings, setNamingSettingsValue, fetchNamingExamples } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import Naming from './Naming';
|
||||
|
||||
const SECTION = 'naming';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
(state) => state.settings.namingExamples,
|
||||
createSettingsSectionSelector(),
|
||||
createSettingsSectionSelector(SECTION),
|
||||
(advancedSettings, examples, sectionSettings) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -48,7 +50,7 @@ class NamingConnector extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: this.props.section });
|
||||
this.props.clearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -86,17 +88,10 @@ class NamingConnector extends Component {
|
|||
}
|
||||
|
||||
NamingConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
fetchNamingSettings: PropTypes.func.isRequired,
|
||||
setNamingSettingsValue: PropTypes.func.isRequired,
|
||||
fetchNamingExamples: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'settings.naming' }
|
||||
)(NamingConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(NamingConnector);
|
||||
|
|
|
@ -163,8 +163,8 @@ class NamingModal extends Component {
|
|||
];
|
||||
|
||||
const qualityTokens = [
|
||||
{ token: '{Quality Full}', example: 'HDTV 720p Proper' },
|
||||
{ token: '{Quality Title}', example: 'HDTV 720p' }
|
||||
{ token: '{Quality Full}', example: 'FLAC Proper' },
|
||||
{ token: '{Quality Title}', example: 'FLAC' }
|
||||
];
|
||||
|
||||
const mediaInfoTokens = [
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditMetadataModalContentConnector from './EditMetadataModalContentConnector';
|
||||
|
||||
function EditMetadataModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -13,9 +13,7 @@ function Metadatas(props) {
|
|||
} = props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Metadata Consumers"
|
||||
>
|
||||
<FieldSet legend="Metadata Consumers">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Metadata"
|
||||
{...otherProps}
|
||||
|
|
|
@ -36,9 +36,7 @@ function MetadataProvider(props) {
|
|||
<Form>
|
||||
{
|
||||
advancedSettings &&
|
||||
<FieldSet
|
||||
legend="Metadata Provider Source"
|
||||
>
|
||||
<FieldSet legend="Metadata Provider Source">
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import { setMetadataProviderValue, saveMetadataProvider, fetchMetadataProvider } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import MetadataProvider from './MetadataProvider';
|
||||
|
||||
const SECTION = 'metadataProvider';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createSettingsSectionSelector(),
|
||||
createSettingsSectionSelector(SECTION),
|
||||
(advancedSettings, sectionSettings) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -43,7 +45,7 @@ class MetadataProviderConnector extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: this.props.section });
|
||||
this.props.clearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -74,7 +76,6 @@ class MetadataProviderConnector extends Component {
|
|||
}
|
||||
|
||||
MetadataProviderConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
hasPendingChanges: PropTypes.bool.isRequired,
|
||||
setMetadataProviderValue: PropTypes.func.isRequired,
|
||||
saveMetadataProvider: PropTypes.func.isRequired,
|
||||
|
@ -83,10 +84,4 @@ MetadataProviderConnector.propTypes = {
|
|||
onHasPendingChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
{ withRef: true },
|
||||
{ section: 'settings.metadataProvider' }
|
||||
)(MetadataProviderConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MetadataProviderConnector);
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditNotificationModalContentConnector from './EditNotificationModalContentConnector';
|
||||
|
||||
function EditNotificationModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { setNotificationValue, setNotificationFieldValue, saveNotification, testNotification } from 'Store/Actions/settingsActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import EditNotificationModalContent from './EditNotificationModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('notifications'),
|
||||
(advancedSettings, notification) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -85,10 +85,4 @@ EditNotificationModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'notifications' }
|
||||
)(EditNotificationModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditNotificationModalContentConnector);
|
||||
|
|
|
@ -59,9 +59,7 @@ class Notifications extends Component {
|
|||
} = this.state;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Connections"
|
||||
>
|
||||
<FieldSet legend="Connections">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Notifications"
|
||||
{...otherProps}
|
||||
|
|
|
@ -66,9 +66,7 @@ class DelayProfiles extends Component {
|
|||
|
||||
return (
|
||||
<Measure onMeasure={this.onMeasure}>
|
||||
<FieldSet
|
||||
legend="Delay Profiles"
|
||||
>
|
||||
<FieldSet legend="Delay Profiles">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Delay Profiles"
|
||||
{...otherProps}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditDelayProfileModalContentConnector from './EditDelayProfileModalContentConnector';
|
||||
|
||||
function EditDelayProfileModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -85,6 +85,7 @@ function EditDelayProfileModalContent(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="usenetDelay"
|
||||
unit="minutes"
|
||||
{...usenetDelay}
|
||||
helpText="Delay in minutes to wait before grabbing a release from Usenet"
|
||||
onChange={onInputChange}
|
||||
|
@ -100,6 +101,7 @@ function EditDelayProfileModalContent(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="torrentDelay"
|
||||
unit="minutes"
|
||||
{...torrentDelay}
|
||||
helpText="Delay in minutes to wait before grabbing a torrent"
|
||||
onChange={onInputChange}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditLanguageProfileModalContentConnector from './EditLanguageProfileModalContentConnector';
|
||||
|
||||
function EditLanguageProfileModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createProfileInUseSelector from 'Store/Selectors/createProfileInUseSelector';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { fetchLanguageProfileSchema, setLanguageProfileValue, saveLanguageProfile } from 'Store/Actions/settingsActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import EditLanguageProfileModalContent from './EditLanguageProfileModalContent';
|
||||
|
||||
function createLanguagesSelector() {
|
||||
return createSelector(
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('languageProfiles'),
|
||||
(languageProfile) => {
|
||||
const languages = languageProfile.item.languages;
|
||||
if (!languages || !languages.value) {
|
||||
|
@ -33,7 +33,7 @@ function createLanguagesSelector() {
|
|||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('languageProfiles'),
|
||||
createLanguagesSelector(),
|
||||
createProfileInUseSelector('languageProfileId'),
|
||||
(languageProfile, languages, isInUse) => {
|
||||
|
@ -186,10 +186,4 @@ EditLanguageProfileModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'languageProfiles' }
|
||||
)(EditLanguageProfileModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditLanguageProfileModalContentConnector);
|
||||
|
|
|
@ -51,9 +51,7 @@ class LanguageProfiles extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Language Profiles"
|
||||
>
|
||||
<FieldSet legend="Language Profiles">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Language Profiles"
|
||||
{...otherProps}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditMetadataProfileModalContentConnector from './EditMetadataProfileModalContentConnector';
|
||||
|
||||
function EditMetadataProfileModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createProfileInUseSelector from 'Store/Selectors/createProfileInUseSelector';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { fetchMetadataProfileSchema, setMetadataProfileValue, saveMetadataProfile } from 'Store/Actions/settingsActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import EditMetadataProfileModalContent from './EditMetadataProfileModalContent';
|
||||
|
||||
function createPrimaryAlbumTypesSelector() {
|
||||
return createSelector(
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('metadataProfiles'),
|
||||
(metadataProfile) => {
|
||||
const primaryAlbumTypes = metadataProfile.item.primaryAlbumTypes;
|
||||
if (!primaryAlbumTypes || !primaryAlbumTypes.value) {
|
||||
|
@ -79,7 +79,7 @@ function createReleaseStatusesSelector() {
|
|||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('metadataProfiles'),
|
||||
createPrimaryAlbumTypesSelector(),
|
||||
createSecondaryAlbumTypesSelector(),
|
||||
createReleaseStatusesSelector(),
|
||||
|
@ -210,10 +210,4 @@ EditMetadataProfileModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'metadataProfiles' }
|
||||
)(EditMetadataProfileModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditMetadataProfileModalContentConnector);
|
||||
|
|
|
@ -51,9 +51,7 @@ class MetadataProfiles extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Metadata Profiles"
|
||||
>
|
||||
<FieldSet legend="Metadata Profiles">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Metadata Profiles"
|
||||
{...otherProps}
|
||||
|
|
|
@ -145,7 +145,7 @@ class EditQualityProfileModalContent extends Component {
|
|||
<div className={styles.formGroupsContainer}>
|
||||
<div className={styles.formGroupWrapper}>
|
||||
<FormGroup size={sizes.EXTRA_SMALL}>
|
||||
<FormLabel size={sizes.small}>
|
||||
<FormLabel size={sizes.SMALL}>
|
||||
Name
|
||||
</FormLabel>
|
||||
|
||||
|
@ -158,7 +158,7 @@ class EditQualityProfileModalContent extends Component {
|
|||
</FormGroup>
|
||||
|
||||
<FormGroup size={sizes.EXTRA_SMALL}>
|
||||
<FormLabel size={sizes.small}>
|
||||
<FormLabel size={sizes.SMALL}>
|
||||
Cutoff
|
||||
</FormLabel>
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createProfileInUseSelector from 'Store/Selectors/createProfileInUseSelector';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { fetchQualityProfileSchema, setQualityProfileValue, saveQualityProfile } from 'Store/Actions/settingsActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import EditQualityProfileModalContent from './EditQualityProfileModalContent';
|
||||
|
||||
function getQualityItemGroupId(qualityProfile) {
|
||||
|
@ -33,7 +33,7 @@ function parseIndex(index) {
|
|||
|
||||
function createQualitiesSelector() {
|
||||
return createSelector(
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('qualityProfiles'),
|
||||
(qualityProfile) => {
|
||||
const items = qualityProfile.item.items;
|
||||
if (!items || !items.value) {
|
||||
|
@ -63,7 +63,7 @@ function createQualitiesSelector() {
|
|||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createProviderSettingsSelector(),
|
||||
createProviderSettingsSelector('qualityProfiles'),
|
||||
createQualitiesSelector(),
|
||||
createProfileInUseSelector('qualityProfileId'),
|
||||
(qualityProfile, qualities, isInUse) => {
|
||||
|
@ -439,10 +439,4 @@ EditQualityProfileModalContentConnector.propTypes = {
|
|||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'qualityProfiles' }
|
||||
)(EditQualityProfileModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditQualityProfileModalContentConnector);
|
||||
|
|
|
@ -51,9 +51,7 @@ class QualityProfiles extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Quality Profiles"
|
||||
>
|
||||
<FieldSet legend="Quality Profiles">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Quality Profiles"
|
||||
{...otherProps}c={true}
|
||||
|
|
|
@ -17,9 +17,7 @@ class QualityDefinitions extends Component {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Quality Definitions"
|
||||
>
|
||||
<FieldSet legend="Quality Definitions">
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Quality Definitions"
|
||||
{...otherProps}
|
||||
|
|
|
@ -101,6 +101,17 @@ function Settings() {
|
|||
Create metadata files when tracks are imported or artist are refreshed
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/tags"
|
||||
>
|
||||
Tags
|
||||
</Link>
|
||||
|
||||
<div className={styles.summary}>
|
||||
Manage artist, profile, restriction, and notification tags
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="/settings/general"
|
||||
|
|
33
frontend/src/Settings/Tags/Details/TagDetailsModal.js
Normal file
33
frontend/src/Settings/Tags/Details/TagDetailsModal.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import TagDetailsModalContentConnector from './TagDetailsModalContentConnector';
|
||||
|
||||
function TagDetailsModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.SMALL}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<TagDetailsModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
TagDetailsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default TagDetailsModal;
|
|
@ -0,0 +1,26 @@
|
|||
.items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.item {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
.restriction {
|
||||
margin-bottom: 5px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
composes: button from 'Components/Link/Button.css';
|
||||
|
||||
margin-right: auto;
|
||||
}
|
163
frontend/src/Settings/Tags/Details/TagDetailsModalContent.js
Normal file
163
frontend/src/Settings/Tags/Details/TagDetailsModalContent.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import split from 'Utilities/String/split';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Label from 'Components/Label';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import styles from './TagDetailsModalContent.css';
|
||||
|
||||
function TagDetailsModalContent(props) {
|
||||
const {
|
||||
label,
|
||||
isTagUsed,
|
||||
artist,
|
||||
delayProfiles,
|
||||
notifications,
|
||||
restrictions,
|
||||
onModalClose,
|
||||
onDeleteTagPress
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Tag Details - {label}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
!isTagUsed &&
|
||||
<div>Tag is not used and can be deleted</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!artist.length &&
|
||||
<FieldSet legend="Artists">
|
||||
{
|
||||
artist.map((item) => {
|
||||
return (
|
||||
<div key={item.id}>
|
||||
{item.artistName}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
!!delayProfiles.length &&
|
||||
<FieldSet legend="Delay Profiles">
|
||||
{
|
||||
delayProfiles.map((item) => {
|
||||
return (
|
||||
<div key={item.id}>
|
||||
{item.name}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
!!notifications.length &&
|
||||
<FieldSet legend="Connections">
|
||||
{
|
||||
notifications.map((item) => {
|
||||
return (
|
||||
<div key={item.id}>
|
||||
{item.name}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
!!restrictions.length &&
|
||||
<FieldSet legend="Restrictions">
|
||||
{
|
||||
restrictions.map((item) => {
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={styles.restriction}
|
||||
>
|
||||
<div>
|
||||
{
|
||||
split(item.required).map((r) => {
|
||||
return (
|
||||
<Label
|
||||
key={r}
|
||||
kind={kinds.SUCCESS}
|
||||
>
|
||||
{r}
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
split(item.ignored).map((i) => {
|
||||
return (
|
||||
<Label
|
||||
key={i}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
{i}
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FieldSet>
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
kind={kinds.DANGER}
|
||||
isDisabled={isTagUsed}
|
||||
onPress={onDeleteTagPress}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
}
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
TagDetailsModalContent.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
isTagUsed: PropTypes.bool.isRequired,
|
||||
artist: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
delayProfiles: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
restrictions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onDeleteTagPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default TagDetailsModalContent;
|
|
@ -0,0 +1,61 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
||||
import TagDetailsModalContent from './TagDetailsModalContent';
|
||||
|
||||
function findMatchingItems(ids, items) {
|
||||
return items.filter((s) => {
|
||||
return ids.includes(s.id);
|
||||
});
|
||||
}
|
||||
|
||||
function createMatchingArtistSelector() {
|
||||
return createSelector(
|
||||
(state, { artistIds }) => artistIds,
|
||||
createAllArtistSelector(),
|
||||
findMatchingItems
|
||||
);
|
||||
}
|
||||
|
||||
function createMatchingDelayProfilesSelector() {
|
||||
return createSelector(
|
||||
(state, { delayProfileIds }) => delayProfileIds,
|
||||
(state) => state.settings.delayProfiles.items,
|
||||
findMatchingItems
|
||||
);
|
||||
}
|
||||
|
||||
function createMatchingNotificationsSelector() {
|
||||
return createSelector(
|
||||
(state, { notificationIds }) => notificationIds,
|
||||
(state) => state.settings.notifications.items,
|
||||
findMatchingItems
|
||||
);
|
||||
}
|
||||
|
||||
function createMatchingRestrictionsSelector() {
|
||||
return createSelector(
|
||||
(state, { restrictionIds }) => restrictionIds,
|
||||
(state) => state.settings.restrictions.items,
|
||||
findMatchingItems
|
||||
);
|
||||
}
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createMatchingArtistSelector(),
|
||||
createMatchingDelayProfilesSelector(),
|
||||
createMatchingNotificationsSelector(),
|
||||
createMatchingRestrictionsSelector(),
|
||||
(artist, delayProfiles, notifications, restrictions) => {
|
||||
return {
|
||||
artist,
|
||||
delayProfiles,
|
||||
notifications,
|
||||
restrictions
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(TagDetailsModalContent);
|
11
frontend/src/Settings/Tags/Tag.css
Normal file
11
frontend/src/Settings/Tags/Tag.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.tag {
|
||||
composes: card from 'Components/Card.css';
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 20px;
|
||||
font-weight: 300;
|
||||
font-size: 24px;
|
||||
}
|
166
frontend/src/Settings/Tags/Tag.js
Normal file
166
frontend/src/Settings/Tags/Tag.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import Card from 'Components/Card';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TagDetailsModal from './Details/TagDetailsModal';
|
||||
import styles from './Tag.css';
|
||||
|
||||
class Tag extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isDetailsModalOpen: false,
|
||||
isDeleteTagModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onShowDetailsPress = () => {
|
||||
this.setState({ isDetailsModalOpen: true });
|
||||
}
|
||||
|
||||
onDetailsModalClose = () => {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
|
||||
onDeleteTagPress = () => {
|
||||
this.setState({
|
||||
isDetailsModalOpen: false,
|
||||
isDeleteTagModalOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteTagModalClose= () => {
|
||||
this.setState({ isDeleteTagModalOpen: false });
|
||||
}
|
||||
|
||||
onConfirmDeleteTag = () => {
|
||||
this.props.onConfirmDeleteTag({ id: this.props.id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
delayProfileIds,
|
||||
notificationIds,
|
||||
restrictionIds,
|
||||
artistIds
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isDetailsModalOpen,
|
||||
isDeleteTagModalOpen
|
||||
} = this.state;
|
||||
|
||||
const isTagUsed = !!(
|
||||
delayProfileIds.length ||
|
||||
notificationIds.length ||
|
||||
restrictionIds.length ||
|
||||
artistIds.length
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={styles.tag}
|
||||
overlayContent={true}
|
||||
onPress={this.onShowDetailsPress}
|
||||
>
|
||||
<div className={styles.label}>
|
||||
{label}
|
||||
</div>
|
||||
|
||||
{
|
||||
isTagUsed &&
|
||||
<div>
|
||||
{
|
||||
!!artistIds.length &&
|
||||
<div>
|
||||
{artistIds.length} artists
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!delayProfileIds.length &&
|
||||
<div>
|
||||
{delayProfileIds.length} delay profile{delayProfileIds.length > 1 && 's'}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!notificationIds.length &&
|
||||
<div>
|
||||
{notificationIds.length} connection{notificationIds.length > 1 && 's'}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!restrictionIds.length &&
|
||||
<div>
|
||||
{restrictionIds.length} restriction{restrictionIds.length > 1 && 's'}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isTagUsed &&
|
||||
<div>
|
||||
No links
|
||||
</div>
|
||||
}
|
||||
|
||||
<TagDetailsModal
|
||||
label={label}
|
||||
isTagUsed={isTagUsed}
|
||||
artistIds={artistIds}
|
||||
delayProfileIds={delayProfileIds}
|
||||
notificationIds={notificationIds}
|
||||
restrictionIds={restrictionIds}
|
||||
isOpen={isDetailsModalOpen}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
onDeleteTagPress={this.onDeleteTagPress}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isDeleteTagModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title="Delete Tag"
|
||||
message={`Are you sure you want to delete the tag '${label}'?`}
|
||||
confirmLabel="Delete"
|
||||
onConfirm={this.onConfirmDeleteTag}
|
||||
onCancel={this.onDeleteTagModalClose}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tag.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
delayProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
onConfirmDeleteTag: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Tag.defaultProps = {
|
||||
delayProfileIds: [],
|
||||
notificationIds: [],
|
||||
restrictionIds: [],
|
||||
artistIds: []
|
||||
};
|
||||
|
||||
export default Tag;
|
22
frontend/src/Settings/Tags/TagConnector.js
Normal file
22
frontend/src/Settings/Tags/TagConnector.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createTagDetailsSelector from 'Store/Selectors/createTagDetailsSelector';
|
||||
import { deleteTag } from 'Store/Actions/tagActions';
|
||||
import Tag from './Tag';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createTagDetailsSelector(),
|
||||
(tagDetails) => {
|
||||
return {
|
||||
...tagDetails
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = {
|
||||
onConfirmDeleteTag: deleteTag
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapStateToProps)(Tag);
|
21
frontend/src/Settings/Tags/TagSettings.js
Normal file
21
frontend/src/Settings/Tags/TagSettings.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import TagsConnector from './TagsConnector';
|
||||
|
||||
function TagSettings() {
|
||||
return (
|
||||
<PageContent title="Tags">
|
||||
<SettingsToolbarConnector
|
||||
showSave={false}
|
||||
/>
|
||||
|
||||
<PageContentBodyConnector>
|
||||
<TagsConnector />
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default TagSettings;
|
4
frontend/src/Settings/Tags/Tags.css
Normal file
4
frontend/src/Settings/Tags/Tags.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
50
frontend/src/Settings/Tags/Tags.js
Normal file
50
frontend/src/Settings/Tags/Tags.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import TagConnector from './TagConnector';
|
||||
import Link from 'Components/Link/Link';
|
||||
import styles from './Tags.css';
|
||||
|
||||
function Tags(props) {
|
||||
const {
|
||||
items,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
if (!items.length) {
|
||||
return (
|
||||
<div>No tags have been added yet. Add tags to link artists with delay profiles, restrictions, or notifications. Click <Link to='https://github.com/lidarr/Lidarr/wiki/Tags'>here</Link> to find out more about tags in Lidarr.</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldSet
|
||||
legend="Tags"
|
||||
>
|
||||
<PageSectionContent
|
||||
errorMessage="Unable to load Tags"
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.tags}>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<TagConnector
|
||||
key={item.id}
|
||||
{...item}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</PageSectionContent>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
|
||||
Tags.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
export default Tags;
|
72
frontend/src/Settings/Tags/TagsConnector.js
Normal file
72
frontend/src/Settings/Tags/TagsConnector.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchTagDetails } from 'Store/Actions/tagActions';
|
||||
import { fetchDelayProfiles, fetchNotifications, fetchRestrictions } from 'Store/Actions/settingsActions';
|
||||
import Tags from './Tags';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.tags,
|
||||
(tags) => {
|
||||
const isFetching = tags.isFetching || tags.details.isFetching;
|
||||
const error = tags.error || tags.details.error;
|
||||
const isPopulated = tags.isPopulated && tags.details.isPopulated;
|
||||
|
||||
return {
|
||||
...tags,
|
||||
isFetching,
|
||||
error,
|
||||
isPopulated
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchTagDetails: fetchTagDetails,
|
||||
dispatchFetchDelayProfiles: fetchDelayProfiles,
|
||||
dispatchFetchNotifications: fetchNotifications,
|
||||
dispatchFetchRestrictions: fetchRestrictions
|
||||
};
|
||||
|
||||
class MetadatasConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
dispatchFetchTagDetails,
|
||||
dispatchFetchDelayProfiles,
|
||||
dispatchFetchNotifications,
|
||||
dispatchFetchRestrictions
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchTagDetails();
|
||||
dispatchFetchDelayProfiles();
|
||||
dispatchFetchNotifications();
|
||||
dispatchFetchRestrictions();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Tags
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MetadatasConnector.propTypes = {
|
||||
dispatchFetchTagDetails: PropTypes.func.isRequired,
|
||||
dispatchFetchDelayProfiles: PropTypes.func.isRequired,
|
||||
dispatchFetchNotifications: PropTypes.func.isRequired,
|
||||
dispatchFetchRestrictions: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector);
|
|
@ -82,9 +82,7 @@ class UISettings extends Component {
|
|||
id="uiSettings"
|
||||
{...otherProps}
|
||||
>
|
||||
<FieldSet
|
||||
legend="Calendar"
|
||||
>
|
||||
<FieldSet legend="Calendar">
|
||||
<FormGroup>
|
||||
<FormLabel>First Day of Week</FormLabel>
|
||||
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import { setUISettingsValue, saveUISettings, fetchUISettings } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import UISettings from './UISettings';
|
||||
|
||||
const SECTION = 'ui';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createSettingsSectionSelector(),
|
||||
createSettingsSectionSelector(SECTION),
|
||||
(advancedSettings, sectionSettings) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
|
@ -37,7 +39,7 @@ class UISettingsConnector extends Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: this.props.section });
|
||||
this.props.clearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -66,17 +68,10 @@ class UISettingsConnector extends Component {
|
|||
}
|
||||
|
||||
UISettingsConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
setUISettingsValue: PropTypes.func.isRequired,
|
||||
saveUISettings: PropTypes.func.isRequired,
|
||||
fetchUISettings: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'settings.ui' }
|
||||
)(UISettingsConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(UISettingsConnector);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue