New: Release Profiles, Frontend updates (#580)

* New: Release Profiles - UI Updates

* New: Release Profiles - API Changes

* New: Release Profiles - Test Updates

* New: Release Profiles - Backend Updates

* New: Interactive Artist Search

* New: Change Montiored on Album Details Page

* New: Show Duration on Album Details Page

* Fixed: Manual Import not working if no albums are Missing

* Fixed: Sort search input by sortTitle

* Fixed: Queue columnLabel throwing JS error
This commit is contained in:
Qstick
2019-02-23 17:39:11 -05:00
committed by GitHub
parent f126eafd26
commit 3f064c94b9
409 changed files with 6882 additions and 3176 deletions
@@ -19,9 +19,9 @@ class AddDownloadClientModalContent extends Component {
render() {
const {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
usenetDownloadClients,
torrentDownloadClients,
onDownloadClientSelect,
@@ -31,22 +31,22 @@ class AddDownloadClientModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Add DownloadClient
Add Download Client
</ModalHeader>
<ModalBody>
{
isFetching &&
isSchemaFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
!isSchemaFetching && !!schemaError &&
<div>Unable to add a new downloadClient, please try again.</div>
}
{
isPopulated && !error &&
isSchemaPopulated && !schemaError &&
<div>
<Alert kind={kinds.INFO}>
@@ -103,9 +103,9 @@ class AddDownloadClientModalContent extends Component {
}
AddDownloadClientModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
isPopulated: PropTypes.bool.isRequired,
isSchemaFetching: PropTypes.bool.isRequired,
isSchemaPopulated: PropTypes.bool.isRequired,
schemaError: PropTypes.object,
usenetDownloadClients: PropTypes.arrayOf(PropTypes.object).isRequired,
torrentDownloadClients: PropTypes.arrayOf(PropTypes.object).isRequired,
onDownloadClientSelect: PropTypes.func.isRequired,
@@ -11,9 +11,9 @@ function createMapStateToProps() {
(state) => state.settings.downloadClients,
(downloadClients) => {
const {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
schema
} = downloadClients;
@@ -21,9 +21,9 @@ function createMapStateToProps() {
const torrentDownloadClients = _.filter(schema, { protocol: 'torrent' });
return {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
usenetDownloadClients,
torrentDownloadClients
};
@@ -68,12 +68,18 @@ class DownloadClient extends Component {
</div>
<div className={styles.enabled}>
<Label
kind={enable ? kinds.SUCCESS : kinds.DANGER}
outline={!enable}
>
Enabled
</Label>
{
enable ?
<Label kind={kinds.SUCCESS}>
Enabled
</Label> :
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
</Label>
}
</div>
<EditDownloadClientModalConnector
@@ -67,9 +67,7 @@ class EditDownloadClientModalContent extends Component {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
{
!!message &&
<Alert
@@ -23,6 +23,7 @@ function EditRemotePathMappingModalContent(props) {
isSaving,
saveError,
item,
downloadClientHosts,
onInputChange,
onSavePress,
onModalClose,
@@ -55,17 +56,16 @@ function EditRemotePathMappingModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Host</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
type={inputTypes.AUTO_COMPLETE}
name="host"
helpText="The same host you specified for the remote Download Client"
{...host}
values={downloadClientHosts}
onChange={onInputChange}
/>
</FormGroup>
@@ -140,6 +140,7 @@ EditRemotePathMappingModalContent.propTypes = {
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.shape(remotePathMappingShape).isRequired,
downloadClientHosts: PropTypes.arrayOf(PropTypes.string).isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
@@ -13,11 +13,29 @@ const newRemotePathMapping = {
localPath: ''
};
const selectDownloadClientHosts = createSelector(
(state) => state.settings.downloadClients.items,
(downloadClients) => {
return downloadClients.reduce((acc, downloadClient) => {
const host = downloadClient.fields.find((field) => {
return field.name === 'host';
});
if (host && !acc.includes(host.value)) {
acc.push(host.value);
}
return acc;
}, []);
}
);
function createRemotePathMappingSelector() {
return createSelector(
(state, { id }) => id,
(state) => state.settings.remotePathMappings,
(id, remotePathMappings) => {
selectDownloadClientHosts,
(id, remotePathMappings, downloadClientHosts) => {
const {
isFetching,
error,
@@ -37,7 +55,8 @@ function createRemotePathMappingSelector() {
isSaving,
saveError,
item: settings.settings,
...settings
...settings,
downloadClientHosts
};
}
);
@@ -55,8 +74,8 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
setRemotePathMappingValue,
saveRemotePathMapping
dispatchSetRemotePathMappingValue: setRemotePathMappingValue,
dispatchSaveRemotePathMapping: saveRemotePathMapping
};
class EditRemotePathMappingModalContentConnector extends Component {
@@ -67,7 +86,7 @@ class EditRemotePathMappingModalContentConnector extends Component {
componentDidMount() {
if (!this.props.id) {
Object.keys(newRemotePathMapping).forEach((name) => {
this.props.setRemotePathMappingValue({
this.props.dispatchSetRemotePathMappingValue({
name,
value: newRemotePathMapping[name]
});
@@ -85,11 +104,11 @@ class EditRemotePathMappingModalContentConnector extends Component {
// Listeners
onInputChange = ({ name, value }) => {
this.props.setRemotePathMappingValue({ name, value });
this.props.dispatchSetRemotePathMappingValue({ name, value });
}
onSavePress = () => {
this.props.saveRemotePathMapping({ id: this.props.id });
this.props.dispatchSaveRemotePathMapping({ id: this.props.id });
}
//
@@ -111,8 +130,8 @@ EditRemotePathMappingModalContentConnector.propTypes = {
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
setRemotePathMappingValue: PropTypes.func.isRequired,
saveRemotePathMapping: PropTypes.func.isRequired,
dispatchSetRemotePathMappingValue: PropTypes.func.isRequired,
dispatchSaveRemotePathMapping: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
@@ -8,11 +8,15 @@
}
.host {
flex: 0 0 300px;
@add-mixin truncate;
flex: 0 1 300px;
}
.path {
flex: 0 0 400px;
@add-mixin truncate;
flex: 0 1 400px;
}
.actions {
@@ -5,11 +5,15 @@
}
.host {
flex: 0 0 300px;
@add-mixin truncate;
flex: 0 1 300px;
}
.path {
flex: 0 0 400px;
@add-mixin truncate;
flex: 0 1 400px;
}
.addRemotePathMapping {
@@ -17,8 +17,8 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
fetchRemotePathMappings,
deleteRemotePathMapping
dispatchFetchRemotePathMappings: fetchRemotePathMappings,
dispatchDeleteRemotePathMapping: deleteRemotePathMapping
};
class RemotePathMappingsConnector extends Component {
@@ -27,14 +27,14 @@ class RemotePathMappingsConnector extends Component {
// Lifecycle
componentDidMount() {
this.props.fetchRemotePathMappings();
this.props.dispatchFetchRemotePathMappings();
}
//
// Listeners
onConfirmDeleteRemotePathMapping = (id) => {
this.props.deleteRemotePathMapping({ id });
this.props.dispatchDeleteRemotePathMapping({ id });
}
//
@@ -52,8 +52,8 @@ class RemotePathMappingsConnector extends Component {
}
RemotePathMappingsConnector.propTypes = {
fetchRemotePathMappings: PropTypes.func.isRequired,
deleteRemotePathMapping: PropTypes.func.isRequired
dispatchFetchRemotePathMappings: PropTypes.func.isRequired,
dispatchDeleteRemotePathMapping: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(RemotePathMappingsConnector);
+44 -41
View File
@@ -87,56 +87,59 @@ function HostSettings(props) {
</FormGroup>
{
enableSsl.value &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Port</FormLabel>
enableSsl.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Port</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="sslPort"
min={1}
max={65535}
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslPort}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.NUMBER}
name="sslPort"
min={1}
max={65535}
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslPort}
/>
</FormGroup> :
null
}
{
isWindows && enableSsl.value &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Hash</FormLabel>
isWindows && enableSsl.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>SSL Cert Hash</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertHash"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslCertHash}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.TEXT}
name="sslCertHash"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange}
{...sslCertHash}
/>
</FormGroup> :
null
}
{
mode !== 'service' &&
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Open browser on start</FormLabel>
isWindows && mode !== 'service' ?
<FormGroup size={sizes.MEDIUM}>
<FormLabel>Open browser on start</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="launchBrowser"
helpText=" Open a web browser and navigate to Lidarr homepage on app start."
onChange={onInputChange}
{...launchBrowser}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.CHECK}
name="launchBrowser"
helpText=" Open a web browser and navigate to Lidarr homepage on app start."
onChange={onInputChange}
{...launchBrowser}
/>
</FormGroup> :
null
}
</FieldSet>
@@ -6,6 +6,11 @@ import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
const branchValues = [
'develop',
'nightly'
];
function UpdateSettings(props) {
const {
advancedSettings,
@@ -39,12 +44,13 @@ function UpdateSettings(props) {
<FormLabel>Branch</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
type={inputTypes.AUTO_COMPLETE}
name="branch"
helpText="Branch to use to update Lidarr"
helpLink="https://github.com/Lidarr/Lidarr/wiki/Release-Branches"
onChange={onInputChange}
{...branch}
values={branchValues}
onChange={onInputChange}
/>
</FormGroup>
@@ -19,9 +19,9 @@ class AddImportListModalContent extends Component {
render() {
const {
isFetching,
isPopulated,
error,
isSchemaFetching,
isSchemaPopulated,
schemaError,
allLists,
onImportListSelect,
onModalClose
@@ -35,17 +35,17 @@ class AddImportListModalContent extends Component {
<ModalBody>
{
isFetching &&
isSchemaFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
!isSchemaFetching && !!schemaError &&
<div>Unable to add a new list, please try again.</div>
}
{
isPopulated && !error &&
isSchemaPopulated && !schemaError &&
<div>
<Alert kind={kinds.INFO}>
@@ -85,9 +85,9 @@ class AddImportListModalContent extends Component {
}
AddImportListModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
isSchemaFetching: PropTypes.bool.isRequired,
isSchemaPopulated: PropTypes.bool.isRequired,
schemaError: PropTypes.object,
allLists: PropTypes.arrayOf(PropTypes.object).isRequired,
onImportListSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
@@ -10,18 +10,18 @@ function createMapStateToProps() {
(state) => state.settings.importLists,
(importLists) => {
const {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
schema
} = importLists;
const allLists = schema;
return {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
allLists
};
}
@@ -67,9 +67,7 @@ function EditImportListModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Name</FormLabel>
@@ -7,18 +7,6 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import EditImportListModalConnector from './EditImportListModalConnector';
import styles from './ImportList.css';
function getLabelKind(supports, enabled) {
if (!supports) {
return kinds.DEFAULT;
}
if (!enabled) {
return kinds.DANGER;
}
return kinds.SUCCESS;
}
class ImportList extends Component {
//
@@ -80,12 +68,13 @@ class ImportList extends Component {
</div>
<div className={styles.enabled}>
<Label
kind={getLabelKind(true, enableAutomaticAdd)}
outline={true && !enableAutomaticAdd}
>
Automatic Add
</Label>
{
enableAutomaticAdd &&
<Label kind={kinds.SUCCESS}>
Automatic Add
</Label>
}
</div>
<EditImportListModalConnector
@@ -8,7 +8,6 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import IndexersConnector from './Indexers/IndexersConnector';
import IndexerOptionsConnector from './Options/IndexerOptionsConnector';
import RestrictionsConnector from './Restrictions/RestrictionsConnector';
class IndexerSettings extends Component {
@@ -84,8 +83,6 @@ class IndexerSettings extends Component {
onChildMounted={this.onChildMounted}
onChildStateChange={this.onChildStateChange}
/>
<RestrictionsConnector />
</PageContentBodyConnector>
</PageContent>
);
@@ -19,9 +19,9 @@ class AddIndexerModalContent extends Component {
render() {
const {
isFetching,
isPopulated,
error,
isSchemaFetching,
isSchemaPopulated,
schemaError,
usenetIndexers,
torrentIndexers,
onIndexerSelect,
@@ -36,17 +36,17 @@ class AddIndexerModalContent extends Component {
<ModalBody>
{
isFetching &&
isSchemaFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
!isSchemaFetching && !!schemaError &&
<div>Unable to add a new indexer, please try again.</div>
}
{
isPopulated && !error &&
isSchemaPopulated && !schemaError &&
<div>
<Alert kind={kinds.INFO}>
@@ -103,9 +103,9 @@ class AddIndexerModalContent extends Component {
}
AddIndexerModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
isSchemaFetching: PropTypes.bool.isRequired,
isSchemaPopulated: PropTypes.bool.isRequired,
schemaError: PropTypes.object,
usenetIndexers: PropTypes.arrayOf(PropTypes.object).isRequired,
torrentIndexers: PropTypes.arrayOf(PropTypes.object).isRequired,
onIndexerSelect: PropTypes.func.isRequired,
@@ -11,9 +11,9 @@ function createMapStateToProps() {
(state) => state.settings.indexers,
(indexers) => {
const {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
schema
} = indexers;
@@ -21,9 +21,9 @@ function createMapStateToProps() {
const torrentIndexers = _.filter(schema, { protocol: 'torrent' });
return {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
usenetIndexers,
torrentIndexers
};
@@ -64,9 +64,7 @@ function EditIndexerModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Name</FormLabel>
@@ -7,18 +7,6 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import EditIndexerModalConnector from './EditIndexerModalConnector';
import styles from './Indexer.css';
function getLabelKind(supports, enabled) {
if (!supports) {
return kinds.DEFAULT;
}
if (!enabled) {
return kinds.DANGER;
}
return kinds.SUCCESS;
}
class Indexer extends Component {
//
@@ -84,26 +72,37 @@ class Indexer extends Component {
</div>
<div className={styles.enabled}>
<Label
kind={getLabelKind(supportsRss, enableRss)}
outline={supportsRss && !enableRss}
>
RSS
</Label>
<Label
kind={getLabelKind(supportsSearch, enableAutomaticSearch)}
outline={supportsSearch && !enableAutomaticSearch}
>
Automatic Search
</Label>
{
supportsRss && enableRss &&
<Label kind={kinds.SUCCESS}>
RSS
</Label>
}
<Label
kind={getLabelKind(supportsSearch, enableInteractiveSearch)}
outline={supportsSearch && !enableInteractiveSearch}
>
Interactive Search
</Label>
{
supportsSearch && enableAutomaticSearch &&
<Label kind={kinds.SUCCESS}>
Automatic Search
</Label>
}
{
supportsSearch && enableInteractiveSearch &&
<Label kind={kinds.SUCCESS}>
Interactive Search
</Label>
}
{
!enableRss && !enableAutomaticSearch && !enableInteractiveSearch &&
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
</Label>
}
</div>
<EditIndexerModalConnector
@@ -1,148 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import split from 'Utilities/String/split';
import { kinds } from 'Helpers/Props';
import Card from 'Components/Card';
import Label from 'Components/Label';
import TagList from 'Components/TagList';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import EditRestrictionModalConnector from './EditRestrictionModalConnector';
import styles from './Restriction.css';
class Restriction extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditRestrictionModalOpen: false,
isDeleteRestrictionModalOpen: false
};
}
//
// Listeners
onEditRestrictionPress = () => {
this.setState({ isEditRestrictionModalOpen: true });
}
onEditRestrictionModalClose = () => {
this.setState({ isEditRestrictionModalOpen: false });
}
onDeleteRestrictionPress = () => {
this.setState({
isEditRestrictionModalOpen: false,
isDeleteRestrictionModalOpen: true
});
}
onDeleteRestrictionModalClose= () => {
this.setState({ isDeleteRestrictionModalOpen: false });
}
onConfirmDeleteRestriction = () => {
this.props.onConfirmDeleteRestriction(this.props.id);
}
//
// Render
render() {
const {
id,
required,
ignored,
tags,
tagList
} = this.props;
return (
<Card
className={styles.restriction}
overlayContent={true}
onPress={this.onEditRestrictionPress}
>
<div>
{
split(required).map((item) => {
if (!item) {
return null;
}
return (
<Label
key={item}
kind={kinds.SUCCESS}
>
{item}
</Label>
);
})
}
</div>
<div>
{
split(ignored).map((item) => {
if (!item) {
return null;
}
return (
<Label
key={item}
kind={kinds.DANGER}
>
{item}
</Label>
);
})
}
</div>
<TagList
tags={tags}
tagList={tagList}
/>
<EditRestrictionModalConnector
id={id}
isOpen={this.state.isEditRestrictionModalOpen}
onModalClose={this.onEditRestrictionModalClose}
onDeleteRestrictionPress={this.onDeleteRestrictionPress}
/>
<ConfirmModal
isOpen={this.state.isDeleteRestrictionModalOpen}
kind={kinds.DANGER}
title="Delete Restriction"
message={'Are you sure you want to delete this restriction?'}
confirmLabel="Delete"
onConfirm={this.onConfirmDeleteRestriction}
onCancel={this.onDeleteRestrictionModalClose}
/>
</Card>
);
}
}
Restriction.propTypes = {
id: PropTypes.number.isRequired,
required: PropTypes.string.isRequired,
ignored: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteRestriction: PropTypes.func.isRequired
};
Restriction.defaultProps = {
required: '',
ignored: ''
};
export default Restriction;
@@ -1,61 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchRestrictions, deleteRestriction } from 'Store/Actions/settingsActions';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import Restrictions from './Restrictions';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.restrictions,
createTagsSelector(),
(restrictions, tagList) => {
return {
...restrictions,
tagList
};
}
);
}
const mapDispatchToProps = {
fetchRestrictions,
deleteRestriction
};
class RestrictionsConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchRestrictions();
}
//
// Listeners
onConfirmDeleteRestriction = (id) => {
this.props.deleteRestriction({ id });
}
//
// Render
render() {
return (
<Restrictions
{...this.props}
onConfirmDeleteRestriction={this.onConfirmDeleteRestriction}
/>
);
}
}
RestrictionsConnector.propTypes = {
fetchRestrictions: PropTypes.func.isRequired,
deleteRestriction: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(RestrictionsConnector);
@@ -10,6 +10,7 @@ import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
import NamingConnector from './Naming/NamingConnector';
const rescanAfterRefreshOptions = [
@@ -56,14 +57,20 @@ class MediaManagement extends Component {
/>
<PageContentBodyConnector>
<NamingConnector />
{
isFetching &&
<LoadingIndicator />
<FieldSet legend="Naming Settings">
<LoadingIndicator />
</FieldSet>
}
{
!isFetching && error &&
<div>Unable to load Media Management settings</div>
<FieldSet legend="Naming Settings">
<div>Unable to load Media Management settings</div>
</FieldSet>
}
{
@@ -72,8 +79,6 @@ class MediaManagement extends Component {
id="mediaManagementSettings"
{...otherProps}
>
<NamingConnector />
{
advancedSettings &&
<FieldSet legend="Folders">
@@ -369,6 +374,10 @@ class MediaManagement extends Component {
}
</Form>
}
<FieldSet legend="Root Folders">
<RootFoldersConnector />
</FieldSet>
</PageContentBodyConnector>
</PageContent>
);
@@ -13,6 +13,95 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import NamingOption from './NamingOption';
import styles from './NamingModal.css';
const separatorOptions = [
{ key: ' ', value: 'Space ( )' },
{ key: '.', value: 'Period (.)' },
{ key: '_', value: 'Underscore (_)' },
{ key: '-', value: 'Dash (-)' }
];
const caseOptions = [
{ key: 'title', value: 'Default Case' },
{ key: 'lower', value: 'Lower Case' },
{ key: 'upper', value: 'Upper Case' }
];
const fileNameTokens = [
{
token: '{Artist Name} - {Album Title} - {track:00} - {Track Title} {Quality Full}',
example: 'Artist Name - Album Title - 01 - Track Title MP3-320 Proper'
},
{
token: '{Artist.Name}.{Album.Title}.{track:00}.{TrackClean.Title}.{Quality.Full}',
example: 'Artist.Name.Album.Title.01.Track.Title.MP3-320'
}
];
const artistTokens = [
{ token: '{Artist Name}', example: 'Artist Name' },
{ token: '{Artist NameThe}', example: 'Artist Name, The' },
{ token: '{Artist CleanName}', example: 'Artist Name' }
];
const albumTokens = [
{ token: '{Album Title}', example: 'Album Title' },
{ token: '{Album TitleThe}', example: 'Album Title, The' },
{ token: '{Album CleanTitle}', example: 'Album Title' },
{ token: '{Album Type}', example: 'Album Type' },
{ token: '{Album Disambiguation}', example: 'Disambiguation' }
];
const mediumTokens = [
{ token: '{medium:0}', example: '1' },
{ token: '{medium:00}', example: '01' }
];
const mediumFormatTokens = [
{ token: '{Medium Format}', example: 'CD' }
];
const trackTokens = [
{ token: '{track:0}', example: '1' },
{ token: '{track:00}', example: '01' }
];
const releaseDateTokens = [
{ token: '{Release Year}', example: '2016' }
];
const trackTitleTokens = [
{ token: '{Track Title}', example: 'Track Title' },
{ token: '{Track CleanTitle}', example: 'Track Title' }
];
const qualityTokens = [
{ token: '{Quality Full}', example: 'FLAC Proper' },
{ token: '{Quality Title}', example: 'FLAC' }
];
const mediaInfoTokens = [
{ token: '{MediaInfo AudioCodec}', example: 'FLAC' },
{ token: '{MediaInfo AudioChannels}', example: '2.0' },
{ token: '{MediaInfo AudioBitsPerSample}', example: '24bit' },
{ token: '{MediaInfo AudioSampleRate}', example: '44.1kHz' }
];
const otherTokens = [
{ token: '{Release Group}', example: 'Rls Grp' },
{ token: '{Preferred Words}', example: 'iNTERNAL' }
];
const originalTokens = [
{ token: '{Original Title}', example: 'Artist.Name.S01E01.HDTV.x264-EVOLVE' },
{ token: '{Original Filename}', example: 'artist.name.s01e01.hdtv.x264-EVOLVE' }
];
class NamingModal extends Component {
//
@@ -95,94 +184,6 @@ class NamingModal extends Component {
case: tokenCase
} = this.state;
const separatorOptions = [
{ key: ' ', value: 'Space ( )' },
{ key: '.', value: 'Period (.)' },
{ key: '_', value: 'Underscore (_)' },
{ key: '-', value: 'Dash (-)' }
];
const caseOptions = [
{ key: 'title', value: 'Default Case' },
{ key: 'lower', value: 'Lower Case' },
{ key: 'upper', value: 'Upper Case' }
];
const fileNameTokens = [
{
token: '{Artist Name} - {Album Title} - {track:00} - {Track Title} {Quality Full}',
example: 'Artist Name - Album Title - 01 - Track Title MP3-320 Proper'
},
{
token: '{Artist.Name}.{Album.Title}.{track:00}.{TrackClean.Title}.{Quality.Full}',
example: 'Artist.Name.Album.Title.01.Track.Title.MP3-320'
}
];
const artistTokens = [
{ token: '{Artist Name}', example: 'Artist Name' },
{ token: '{Artist NameThe}', example: 'Artist Name, The' },
{ token: '{Artist CleanName}', example: 'Artist Name' }
];
const albumTokens = [
{ token: '{Album Title}', example: 'Album Title' },
{ token: '{Album TitleThe}', example: 'Album Title, The' },
{ token: '{Album CleanTitle}', example: 'Album Title' },
{ token: '{Album Type}', example: 'Album Type' },
{ token: '{Album Disambiguation}', example: 'Disambiguation' }
];
const mediumTokens = [
{ token: '{medium:0}', example: '1' },
{ token: '{medium:00}', example: '01' }
];
const mediumFormatTokens = [
{ token: '{Medium Format}', example: 'CD' }
];
const trackTokens = [
{ token: '{track:0}', example: '1' },
{ token: '{track:00}', example: '01' }
];
const releaseDateTokens = [
{ token: '{Release Year}', example: '2016' }
];
const trackTitleTokens = [
{ token: '{Track Title}', example: 'Track Title' },
{ token: '{Track CleanTitle}', example: 'Track Title' }
];
const qualityTokens = [
{ token: '{Quality Full}', example: 'FLAC Proper' },
{ token: '{Quality Title}', example: 'FLAC' }
];
const mediaInfoTokens = [
{ token: '{MediaInfo AudioCodec}', example: 'FLAC' },
{ token: '{MediaInfo AudioChannels}', example: '2.0' },
{ token: '{MediaInfo AudioBitsPerSample}', example: '24bit' },
{ token: '{MediaInfo AudioSampleRate}', example: '44.1kHz' }
];
const releaseGroupTokens = [
{ token: '{Release Group}', example: 'Rls Grp' }
];
const originalTokens = [
{ token: '{Original Title}', example: 'Artist.Name.S01E01.HDTV.x264-EVOLVE' },
{ token: '{Original Filename}', example: 'artist.name.s01e01.hdtv.x264-EVOLVE' }
];
return (
<Modal
isOpen={isOpen}
@@ -451,10 +452,10 @@ class NamingModal extends Component {
</div>
</FieldSet>
<FieldSet legend="Release Group">
<FieldSet legend="Other">
<div className={styles.groups}>
{
releaseGroupTokens.map(({ token, example }) => {
otherTokens.map(({ token, example }) => {
return (
<NamingOption
key={token}
@@ -17,7 +17,7 @@
}
.small {
width: 420px;
width: 460px;
}
.large {
@@ -32,6 +32,9 @@
}
.example {
display: flex;
align-items: center;
align-self: stretch;
flex: 0 0 50%;
padding: 6px 16px;
background-color: #ddd;
@@ -38,9 +38,7 @@ function EditMetadataModalContent(props) {
</ModalHeader>
<ModalBody>
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Enable</FormLabel>
@@ -10,8 +10,6 @@
font-size: 24px;
}
.label {
composes: label from 'Components/Label.css';
width: 100%;
.section {
margin-top: 10px;
}
@@ -6,14 +6,6 @@ import Label from 'Components/Label';
import EditMetadataModalConnector from './EditMetadataModalConnector';
import styles from './Metadata.css';
function getKind(enable) {
if (enable) {
return kinds.SUCCESS;
}
return kinds.DANGER;
}
class Metadata extends Component {
//
@@ -49,6 +41,17 @@ class Metadata extends Component {
fields
} = this.props;
const metadataFields = [];
const imageFields = [];
fields.forEach((field) => {
if (field.section === 'metadata') {
metadataFields.push(field);
} else {
imageFields.push(field);
}
});
return (
<Card
className={styles.metadata}
@@ -59,31 +62,73 @@ class Metadata extends Component {
{name}
</div>
<div>
<Label
kind={getKind(enable)}
outline={!enable}
>
Enable
</Label>
</div>
<div>
{
fields.map((field) => {
return (
<Label
key={field.label}
kind={enable ? getKind(field.value) : kinds.DEFAULT}
outline={enable && !field.value}
>
{field.label}
</Label>
);
})
enable ?
<Label kind={kinds.SUCCESS}>
Enabled
</Label> :
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
</Label>
}
</div>
{
enable && !!metadataFields.length &&
<div>
<div className={styles.section}>
Metadata
</div>
{
metadataFields.map((field) => {
if (!field.value) {
return null;
}
return (
<Label
key={field.label}
kind={kinds.SUCCESS}
>
{field.label}
</Label>
);
})
}
</div>
}
{
enable && !!imageFields.length &&
<div>
<div className={styles.section}>
Images
</div>
{
imageFields.map((field) => {
if (!field.value) {
return null;
}
return (
<Label
key={field.label}
kind={kinds.SUCCESS}
>
{field.label}
</Label>
);
})
}
</div>
}
<EditMetadataModalConnector
id={id}
isOpen={this.state.isEditMetadataModalOpen}
@@ -16,9 +16,9 @@ class AddNotificationModalContent extends Component {
render() {
const {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
schema,
onNotificationSelect,
onModalClose
@@ -32,17 +32,17 @@ class AddNotificationModalContent extends Component {
<ModalBody>
{
isFetching &&
isSchemaFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
!isSchemaFetching && !!schemaError &&
<div>Unable to add a new notification, please try again.</div>
}
{
isPopulated && !error &&
isSchemaPopulated && !schemaError &&
<div>
<div className={styles.notifications}>
{
@@ -74,9 +74,9 @@ class AddNotificationModalContent extends Component {
}
AddNotificationModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
isPopulated: PropTypes.bool.isRequired,
isSchemaFetching: PropTypes.bool.isRequired,
isSchemaPopulated: PropTypes.bool.isRequired,
schemaError: PropTypes.object,
schema: PropTypes.arrayOf(PropTypes.object).isRequired,
onNotificationSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
@@ -10,16 +10,16 @@ function createMapStateToProps() {
(state) => state.settings.notifications,
(notifications) => {
const {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
schema
} = notifications;
return {
isFetching,
error,
isPopulated,
isSchemaFetching,
isSchemaPopulated,
schemaError,
schema
};
}
@@ -72,9 +72,7 @@ function EditNotificationModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
{
!!message &&
<Alert
@@ -7,18 +7,6 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import EditNotificationModalConnector from './EditNotificationModalConnector';
import styles from './Notification.css';
function getLabelKind(supports, enabled) {
if (!supports) {
return kinds.DEFAULT;
}
if (!enabled) {
return kinds.DANGER;
}
return kinds.SUCCESS;
}
class Notification extends Component {
//
@@ -88,40 +76,50 @@ class Notification extends Component {
{name}
</div>
<Label
kind={getLabelKind(supportsOnGrab, onGrab)}
outline={supportsOnGrab && !onGrab}
>
On Grab
</Label>
{
supportsOnGrab && onGrab &&
<Label kind={kinds.SUCCESS}>
On Grab
</Label>
}
<Label
kind={getLabelKind(supportsOnAlbumDownload, onAlbumDownload)}
outline={supportsOnAlbumDownload && !onAlbumDownload}
>
On Album Download
</Label>
{
supportsOnAlbumDownload && onAlbumDownload &&
<Label kind={kinds.SUCCESS}>
On Album Download
</Label>
}
<Label
kind={getLabelKind(supportsOnDownload, onDownload)}
outline={supportsOnDownload && !onDownload}
>
On Track Download
</Label>
{
supportsOnDownload && onDownload &&
<Label kind={kinds.SUCCESS}>
On Download
</Label>
}
<Label
kind={getLabelKind(supportsOnUpgrade, onDownload && onUpgrade)}
outline={supportsOnUpgrade && !(onDownload && onUpgrade)}
>
On Upgrade
</Label>
{
supportsOnUpgrade && onDownload && onUpgrade &&
<Label kind={kinds.SUCCESS}>
On Upgrade
</Label>
}
<Label
kind={getLabelKind(supportsOnRename, onRename)}
outline={supportsOnRename && !onRename}
>
On Rename
</Label>
{
supportsOnRename && onRename &&
<Label kind={kinds.SUCCESS}>
On Rename
</Label>
}
{
!onGrab && !onAlbumDownload && !onDownload && !onRename &&
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
</Label>
}
<EditNotificationModalConnector
id={id}
@@ -61,9 +61,7 @@ function EditDelayProfileModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Protocol</FormLabel>
@@ -16,6 +16,13 @@ const newDelayProfile = {
tags: []
};
const protocolOptions = [
{ key: 'preferUsenet', value: 'Prefer Usenet' },
{ key: 'preferTorrent', value: 'Prefer Torrent' },
{ key: 'onlyUsenet', value: 'Only Usenet' },
{ key: 'onlyTorrent', value: 'Only Torrent' }
];
function createDelayProfileSelector() {
return createSelector(
(state, { id }) => id,
@@ -50,13 +57,6 @@ function createMapStateToProps() {
return createSelector(
createDelayProfileSelector(),
(delayProfile) => {
const protocolOptions = [
{ key: 'preferUsenet', value: 'Prefer Usenet' },
{ key: 'preferTorrent', value: 'Prefer Torrent' },
{ key: 'onlyUsenet', value: 'Only Usenet' },
{ key: 'onlyTorrent', value: 'Only Torrent' }
];
const enableUsenet = delayProfile.item.enableUsenet.value;
const enableTorrent = delayProfile.item.enableTorrent.value;
const preferredProtocol = delayProfile.item.preferredProtocol.value;
@@ -35,6 +35,7 @@ function EditLanguageProfileModalContent(props) {
const {
id,
name,
upgradeAllowed,
cutoff,
languages: itemLanguages
} = item;
@@ -58,9 +59,7 @@ function EditLanguageProfileModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Name</FormLabel>
@@ -73,19 +72,36 @@ function EditLanguageProfileModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Cutoff</FormLabel>
<FormLabel>
Upgrades Allowed
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="cutoff"
{...cutoff}
value={cutoff ? cutoff.value.id : 0}
values={languages}
helpText="Once this language is reached Lidarr will no longer download albums"
onChange={onCutoffChange}
type={inputTypes.CHECK}
name="upgradeAllowed"
{...upgradeAllowed}
helpText="If disabled languages will not be upgraded"
onChange={onInputChange}
/>
</FormGroup>
{
upgradeAllowed.value &&
<FormGroup>
<FormLabel>Upgrade Until</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="cutoff"
{...cutoff}
value={cutoff ? cutoff.value.id : 0}
values={languages}
helpText="Once this language is reached Sonarr will no longer download episodes"
onChange={onCutoffChange}
/>
</FormGroup>
}
<LanguageProfileItems
languageProfileItems={itemLanguages.value}
errors={itemLanguages.errors}
@@ -64,6 +64,7 @@ class LanguageProfile extends Component {
const {
id,
name,
upgradeAllowed,
cutoff,
languages,
isDeleting
@@ -95,13 +96,13 @@ class LanguageProfile extends Component {
return null;
}
const isCutoff = item.language.id === cutoff.id;
const isCutoff = upgradeAllowed && item.language.id === cutoff.id;
return (
<Label
key={item.language.id}
kind={isCutoff ? kinds.INFO : kinds.default}
title={isCutoff ? 'Cutoff' : null}
title={isCutoff ? 'Upgrade until this language is met or exceeded' : null}
>
{item.language.name}
</Label>
@@ -135,6 +136,7 @@ class LanguageProfile extends Component {
LanguageProfile.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
upgradeAllowed: PropTypes.bool.isRequired,
cutoff: PropTypes.object.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
isDeleting: PropTypes.bool.isRequired,
@@ -61,9 +61,7 @@ function EditMetadataProfileModalContent(props) {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Name</FormLabel>
@@ -8,6 +8,7 @@ import QualityProfilesConnector from './Quality/QualityProfilesConnector';
import LanguageProfilesConnector from './Language/LanguageProfilesConnector';
import MetadataProfilesConnector from './Metadata/MetadataProfilesConnector';
import DelayProfilesConnector from './Delay/DelayProfilesConnector';
import ReleaseProfilesConnector from './Release/ReleaseProfilesConnector';
class Profiles extends Component {
@@ -26,6 +27,7 @@ class Profiles extends Component {
<LanguageProfilesConnector />
<MetadataProfilesConnector />
<DelayProfilesConnector />
<ReleaseProfilesConnector />
</PageContentBodyConnector>
</PageContent>
);
@@ -105,6 +105,7 @@ class EditQualityProfileModalContent extends Component {
const {
id,
name,
upgradeAllowed,
cutoff,
items
} = item;
@@ -139,9 +140,7 @@ class EditQualityProfileModalContent extends Component {
{
!isFetching && !error &&
<Form
{...otherProps}
>
<Form {...otherProps}>
<div className={styles.formGroupsContainer}>
<div className={styles.formGroupWrapper}>
<FormGroup size={sizes.EXTRA_SMALL}>
@@ -159,18 +158,35 @@ class EditQualityProfileModalContent extends Component {
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Cutoff
Upgrades Allowed
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="cutoff"
{...cutoff}
values={qualities}
helpText="Once this quality is reached Lidarr will no longer download albums"
onChange={onCutoffChange}
type={inputTypes.CHECK}
name="upgradeAllowed"
{...upgradeAllowed}
helpText="If disabled qualities will not be upgraded"
onChange={onInputChange}
/>
</FormGroup>
{
upgradeAllowed.value &&
<FormGroup size={sizes.EXTRA_SMALL}>
<FormLabel size={sizes.SMALL}>
Upgrade Until
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="cutoff"
{...cutoff}
values={qualities}
helpText="Once this quality is reached Sonarr will no longer download episodes"
onChange={onCutoffChange}
/>
</FormGroup>
}
</div>
<div className={styles.formGroupWrapper}>
@@ -65,6 +65,7 @@ class QualityProfile extends Component {
const {
id,
name,
upgradeAllowed,
cutoff,
items,
isDeleting
@@ -97,20 +98,20 @@ class QualityProfile extends Component {
}
if (item.quality) {
const isCutoff = item.quality.id === cutoff;
const isCutoff = upgradeAllowed && item.quality.id === cutoff;
return (
<Label
key={item.quality.id}
kind={isCutoff ? kinds.INFO : kinds.default}
title={isCutoff ? 'Cutoff' : null}
title={isCutoff ? 'Upgrade until this quality is met or exceeded' : null}
>
{item.quality.name}
</Label>
);
}
const isCutoff = item.id === cutoff;
const isCutoff = upgradeAllowed && item.id === cutoff;
return (
<Tooltip
@@ -174,6 +175,7 @@ class QualityProfile extends Component {
QualityProfile.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
upgradeAllowed: PropTypes.bool.isRequired,
cutoff: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
isDeleting: PropTypes.bool.isRequired,
@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
import React from 'react';
import { sizes } from 'Helpers/Props';
import Modal from 'Components/Modal/Modal';
import EditRestrictionModalContentConnector from './EditRestrictionModalContentConnector';
import EditReleaseProfileModalContentConnector from './EditReleaseProfileModalContentConnector';
function EditRestrictionModal({ isOpen, onModalClose, ...otherProps }) {
function EditReleaseProfileModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditRestrictionModalContentConnector
<EditReleaseProfileModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@@ -19,9 +19,9 @@ function EditRestrictionModal({ isOpen, onModalClose, ...otherProps }) {
);
}
EditRestrictionModal.propTypes = {
EditReleaseProfileModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditRestrictionModal;
export default EditReleaseProfileModal;
@@ -2,19 +2,19 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditRestrictionModal from './EditRestrictionModal';
import EditReleaseProfileModal from './EditReleaseProfileModal';
const mapDispatchToProps = {
clearPendingChanges
};
class EditRestrictionModalConnector extends Component {
class EditReleaseProfileModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.clearPendingChanges({ section: 'settings.restrictions' });
this.props.clearPendingChanges({ section: 'settings.releaseProfiles' });
this.props.onModalClose();
}
@@ -23,7 +23,7 @@ class EditRestrictionModalConnector extends Component {
render() {
return (
<EditRestrictionModal
<EditReleaseProfileModal
{...this.props}
onModalClose={this.onModalClose}
/>
@@ -31,9 +31,9 @@ class EditRestrictionModalConnector extends Component {
}
}
EditRestrictionModalConnector.propTypes = {
EditReleaseProfileModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(null, mapDispatchToProps)(EditRestrictionModalConnector);
export default connect(null, mapDispatchToProps)(EditReleaseProfileModalConnector);
@@ -11,9 +11,12 @@ import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
import styles from './EditRestrictionModalContent.css';
import styles from './EditReleaseProfileModalContent.css';
function EditRestrictionModalContent(props) {
// Tab, enter, and comma
const tagInputDelimiters = [9, 13, 188];
function EditReleaseProfileModalContent(props) {
const {
isSaving,
saveError,
@@ -21,7 +24,7 @@ function EditRestrictionModalContent(props) {
onInputChange,
onModalClose,
onSavePress,
onDeleteRestrictionPress,
onDeleteReleaseProfilePress,
...otherProps
} = props;
@@ -29,19 +32,19 @@ function EditRestrictionModalContent(props) {
id,
required,
ignored,
preferred,
includePreferredWhenRenaming,
tags
} = item;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit Restriction' : 'Add Restriction'}
{id ? 'Edit Release Profile' : 'Add Release Profile'}
</ModalHeader>
<ModalBody>
<Form
{...otherProps}
>
<Form {...otherProps}>
<FormGroup>
<FormLabel>Must Contain</FormLabel>
@@ -51,6 +54,7 @@ function EditRestrictionModalContent(props) {
helpText="The release must contain at least one of these terms (case insensitive)"
kind={kinds.SUCCESS}
placeholder="Add new restriction"
delimiters={tagInputDelimiters}
{...required}
onChange={onInputChange}
/>
@@ -65,18 +69,49 @@ function EditRestrictionModalContent(props) {
helpText="The release will be rejected if it contains one or more of terms (case insensitive)"
kind={kinds.DANGER}
placeholder="Add new restriction"
delimiters={tagInputDelimiters}
{...ignored}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Preferred</FormLabel>
<FormInputGroup
type={inputTypes.KEY_VALUE_LIST}
name="preferred"
helpTexts={[
'The release will be preferred based on the each term\'s score (case insensitive)',
'A positive score will be more preferred',
'A negative score will be less preferred'
]}
{...preferred}
keyPlaceholder="Term"
valuePlaceholder="Score"
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Include Preferred when Renaming</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includePreferredWhenRenaming"
helpText="Include in {Preferred Words} renaming format"
{...includePreferredWhenRenaming}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText="Restrictions will apply to artist at least one matching tag. Leave blank to apply to all artist"
helpText="Release profiles will apply to artists with at least one matching tag. Leave blank to apply to all artists"
{...tags}
onChange={onInputChange}
/>
@@ -89,7 +124,7 @@ function EditRestrictionModalContent(props) {
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteRestrictionPress}
onPress={onDeleteReleaseProfilePress}
>
Delete
</Button>
@@ -113,14 +148,14 @@ function EditRestrictionModalContent(props) {
);
}
EditRestrictionModalContent.propTypes = {
EditReleaseProfileModalContent.propTypes = {
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onDeleteRestrictionPress: PropTypes.func
onDeleteReleaseProfilePress: PropTypes.func
};
export default EditRestrictionModalContent;
export default EditReleaseProfileModalContent;
@@ -4,20 +4,22 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import selectSettings from 'Store/Selectors/selectSettings';
import { setRestrictionValue, saveRestriction } from 'Store/Actions/settingsActions';
import EditRestrictionModalContent from './EditRestrictionModalContent';
import { setReleaseProfileValue, saveReleaseProfile } from 'Store/Actions/settingsActions';
import EditReleaseProfileModalContent from './EditReleaseProfileModalContent';
const newRestriction = {
const newReleaseProfile = {
required: '',
ignored: '',
preferred: [],
includePreferredWhenRenaming: false,
tags: []
};
function createMapStateToProps() {
return createSelector(
(state, { id }) => id,
(state) => state.settings.restrictions,
(id, restrictions) => {
(state) => state.settings.releaseProfiles,
(id, releaseProfiles) => {
const {
isFetching,
error,
@@ -25,9 +27,9 @@ function createMapStateToProps() {
saveError,
pendingChanges,
items
} = restrictions;
} = releaseProfiles;
const profile = id ? _.find(items, { id }) : newRestriction;
const profile = id ? _.find(items, { id }) : newReleaseProfile;
const settings = selectSettings(profile, pendingChanges, saveError);
return {
@@ -44,21 +46,21 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
setRestrictionValue,
saveRestriction
setReleaseProfileValue,
saveReleaseProfile
};
class EditRestrictionModalContentConnector extends Component {
class EditReleaseProfileModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.id) {
Object.keys(newRestriction).forEach((name) => {
this.props.setRestrictionValue({
Object.keys(newReleaseProfile).forEach((name) => {
this.props.setReleaseProfileValue({
name,
value: newRestriction[name]
value: newReleaseProfile[name]
});
});
}
@@ -74,11 +76,11 @@ class EditRestrictionModalContentConnector extends Component {
// Listeners
onInputChange = ({ name, value }) => {
this.props.setRestrictionValue({ name, value });
this.props.setReleaseProfileValue({ name, value });
}
onSavePress = () => {
this.props.saveRestriction({ id: this.props.id });
this.props.saveReleaseProfile({ id: this.props.id });
}
//
@@ -86,7 +88,7 @@ class EditRestrictionModalContentConnector extends Component {
render() {
return (
<EditRestrictionModalContent
<EditReleaseProfileModalContent
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
@@ -97,15 +99,15 @@ class EditRestrictionModalContentConnector extends Component {
}
}
EditRestrictionModalContentConnector.propTypes = {
EditReleaseProfileModalContentConnector.propTypes = {
id: PropTypes.number,
isFetching: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
setRestrictionValue: PropTypes.func.isRequired,
saveRestriction: PropTypes.func.isRequired,
setReleaseProfileValue: PropTypes.func.isRequired,
saveReleaseProfile: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(EditRestrictionModalContentConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(EditReleaseProfileModalContentConnector);
@@ -1,4 +1,4 @@
.restriction {
.releaseProfile {
composes: card from 'Components/Card.css';
width: 290px;
@@ -0,0 +1,168 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import split from 'Utilities/String/split';
import { kinds } from 'Helpers/Props';
import Card from 'Components/Card';
import Label from 'Components/Label';
import TagList from 'Components/TagList';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import EditReleaseProfileModalConnector from './EditReleaseProfileModalConnector';
import styles from './ReleaseProfile.css';
class ReleaseProfile extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditReleaseProfileModalOpen: false,
isDeleteReleaseProfileModalOpen: false
};
}
//
// Listeners
onEditReleaseProfilePress = () => {
this.setState({ isEditReleaseProfileModalOpen: true });
}
onEditReleaseProfileModalClose = () => {
this.setState({ isEditReleaseProfileModalOpen: false });
}
onDeleteReleaseProfilePress = () => {
this.setState({
isEditReleaseProfileModalOpen: false,
isDeleteReleaseProfileModalOpen: true
});
}
onDeleteReleaseProfileModalClose= () => {
this.setState({ isDeleteReleaseProfileModalOpen: false });
}
onConfirmDeleteReleaseProfile = () => {
this.props.onConfirmDeleteReleaseProfile(this.props.id);
}
//
// Render
render() {
const {
id,
required,
ignored,
preferred,
tags,
tagList
} = this.props;
return (
<Card
className={styles.releaseProfile}
overlayContent={true}
onPress={this.onEditReleaseProfilePress}
>
<div>
{
split(required).map((item) => {
if (!item) {
return null;
}
return (
<Label
key={item}
kind={kinds.SUCCESS}
>
{item}
</Label>
);
})
}
</div>
<div>
{
split(ignored).map((item) => {
if (!item) {
return null;
}
return (
<Label
key={item}
kind={kinds.DANGER}
>
{item}
</Label>
);
})
}
</div>
<div>
{
preferred.map((item) => {
const isPreferred = item.value >= 0;
return (
<Label
key={item.key}
kind={isPreferred ? kinds.DEFAULT : kinds.WARNING}
>
{item.key} {isPreferred && '+'}{item.value}
</Label>
);
})
}
</div>
<TagList
tags={tags}
tagList={tagList}
/>
<EditReleaseProfileModalConnector
id={id}
isOpen={this.state.isEditReleaseProfileModalOpen}
onModalClose={this.onEditReleaseProfileModalClose}
onDeleteReleaseProfilePress={this.onDeleteReleaseProfilePress}
/>
<ConfirmModal
isOpen={this.state.isDeleteReleaseProfileModalOpen}
kind={kinds.DANGER}
title="Delete ReleaseProfile"
message={'Are you sure you want to delete this releaseProfile?'}
confirmLabel="Delete"
onConfirm={this.onConfirmDeleteReleaseProfile}
onCancel={this.onDeleteReleaseProfileModalClose}
/>
</Card>
);
}
}
ReleaseProfile.propTypes = {
id: PropTypes.number.isRequired,
required: PropTypes.string.isRequired,
ignored: PropTypes.string.isRequired,
preferred: PropTypes.arrayOf(PropTypes.object).isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteReleaseProfile: PropTypes.func.isRequired
};
ReleaseProfile.defaultProps = {
required: '',
ignored: '',
preferred: []
};
export default ReleaseProfile;
@@ -1,10 +1,10 @@
.restrictions {
.releaseProfiles {
display: flex;
flex-wrap: wrap;
}
.addRestriction {
composes: restriction from './Restriction.css';
.addReleaseProfile {
composes: releaseProfile from './ReleaseProfile.css';
background-color: $cardAlternateBackgroundColor;
color: $gray;
@@ -5,11 +5,11 @@ import FieldSet from 'Components/FieldSet';
import Card from 'Components/Card';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import Restriction from './Restriction';
import EditRestrictionModalConnector from './EditRestrictionModalConnector';
import styles from './Restrictions.css';
import ReleaseProfile from './ReleaseProfile';
import EditReleaseProfileModalConnector from './EditReleaseProfileModalConnector';
import styles from './ReleaseProfiles.css';
class Restrictions extends Component {
class ReleaseProfiles extends Component {
//
// Lifecycle
@@ -18,19 +18,19 @@ class Restrictions extends Component {
super(props, context);
this.state = {
isAddRestrictionModalOpen: false
isAddReleaseProfileModalOpen: false
};
}
//
// Listeners
onAddRestrictionPress = () => {
this.setState({ isAddRestrictionModalOpen: true });
onAddReleaseProfilePress = () => {
this.setState({ isAddReleaseProfileModalOpen: true });
}
onAddRestrictionModalClose = () => {
this.setState({ isAddRestrictionModalOpen: false });
onAddReleaseProfileModalClose = () => {
this.setState({ isAddReleaseProfileModalOpen: false });
}
//
@@ -40,20 +40,20 @@ class Restrictions extends Component {
const {
items,
tagList,
onConfirmDeleteRestriction,
onConfirmDeleteReleaseProfile,
...otherProps
} = this.props;
return (
<FieldSet legend="Restrictions">
<FieldSet legend="Release Profiles">
<PageSectionContent
errorMessage="Unable to load Restrictions"
errorMessage="Unable to load ReleaseProfiles"
{...otherProps}
>
<div className={styles.restrictions}>
<div className={styles.releaseProfiles}>
<Card
className={styles.addRestriction}
onPress={this.onAddRestrictionPress}
className={styles.addReleaseProfile}
onPress={this.onAddReleaseProfilePress}
>
<div className={styles.center}>
<Icon
@@ -66,20 +66,20 @@ class Restrictions extends Component {
{
items.map((item) => {
return (
<Restriction
<ReleaseProfile
key={item.id}
tagList={tagList}
{...item}
onConfirmDeleteRestriction={onConfirmDeleteRestriction}
onConfirmDeleteReleaseProfile={onConfirmDeleteReleaseProfile}
/>
);
})
}
</div>
<EditRestrictionModalConnector
isOpen={this.state.isAddRestrictionModalOpen}
onModalClose={this.onAddRestrictionModalClose}
<EditReleaseProfileModalConnector
isOpen={this.state.isAddReleaseProfileModalOpen}
onModalClose={this.onAddReleaseProfileModalClose}
/>
</PageSectionContent>
</FieldSet>
@@ -87,12 +87,12 @@ class Restrictions extends Component {
}
}
Restrictions.propTypes = {
ReleaseProfiles.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteRestriction: PropTypes.func.isRequired
onConfirmDeleteReleaseProfile: PropTypes.func.isRequired
};
export default Restrictions;
export default ReleaseProfiles;
@@ -0,0 +1,61 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchReleaseProfiles, deleteReleaseProfile } from 'Store/Actions/settingsActions';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import ReleaseProfiles from './ReleaseProfiles';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.releaseProfiles,
createTagsSelector(),
(releaseProfiles, tagList) => {
return {
...releaseProfiles,
tagList
};
}
);
}
const mapDispatchToProps = {
fetchReleaseProfiles,
deleteReleaseProfile
};
class ReleaseProfilesConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchReleaseProfiles();
}
//
// Listeners
onConfirmDeleteReleaseProfile = (id) => {
this.props.deleteReleaseProfile({ id });
}
//
// Render
render() {
return (
<ReleaseProfiles
{...this.props}
onConfirmDeleteReleaseProfile={this.onConfirmDeleteReleaseProfile}
/>
);
}
}
ReleaseProfilesConnector.propTypes = {
fetchReleaseProfiles: PropTypes.func.isRequired,
deleteReleaseProfile: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ReleaseProfilesConnector);
@@ -2,28 +2,38 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactSlider from 'react-slider';
import formatBytes from 'Utilities/Number/formatBytes';
import roundNumber from 'Utilities/Number/roundNumber';
import { kinds } from 'Helpers/Props';
import Label from 'Components/Label';
import NumberInput from 'Components/Form/NumberInput';
import TextInput from 'Components/Form/TextInput';
import styles from './QualityDefinition.css';
const MIN = 0;
const MAX = 1500;
const slider = {
min: 0,
max: 1500,
step: 1
min: MIN,
max: roundNumber(Math.pow(MAX, 1 / 1.1)),
step: 0.1
};
function getValue(value) {
if (value < slider.min) {
return slider.min;
function getValue(inputValue) {
if (inputValue < MIN) {
return MIN;
}
if (value > slider.max) {
return slider.max;
if (inputValue > MAX) {
return MAX;
}
return value;
return roundNumber(inputValue);
}
function getSliderValue(value, defaultValue) {
const sliderValue = value ? Math.pow(value, 1 / 1.1) : defaultValue;
return roundNumber(sliderValue);
}
class QualityDefinition extends Component {
@@ -35,6 +45,11 @@ class QualityDefinition extends Component {
super(props, context);
this._forceUpdateTimeout = null;
this.state = {
sliderMinSize: getSliderValue(props.minSize, slider.min),
sliderMaxSize: getSliderValue(props.maxSize, slider.max)
};
}
componentDidMount() {
@@ -54,15 +69,37 @@ class QualityDefinition extends Component {
//
// Listeners
onSizeChange = ([minSize, maxSize]) => {
maxSize = maxSize === slider.max ? null : maxSize;
onSliderChange = ([sliderMinSize, sliderMaxSize]) => {
this.setState({
sliderMinSize,
sliderMaxSize
});
this.props.onSizeChange({ minSize, maxSize });
this.props.onSizeChange({
minSize: roundNumber(Math.pow(sliderMinSize, 1.1)),
maxSize: sliderMaxSize === slider.max ? null : roundNumber(Math.pow(sliderMaxSize, 1.1))
});
}
onAfterSliderChange = () => {
const {
minSize,
maxSize
} = this.props;
this.setState({
sliderMiSize: getSliderValue(minSize, slider.min),
sliderMaxSize: getSliderValue(maxSize, slider.max)
});
}
onMinSizeChange = ({ value }) => {
const minSize = getValue(value);
this.setState({
sliderMinSize: getSliderValue(minSize, slider.min)
});
this.props.onSizeChange({
minSize,
maxSize: this.props.maxSize
@@ -70,7 +107,11 @@ class QualityDefinition extends Component {
}
onMaxSizeChange = ({ value }) => {
const maxSize = value === slider.max ? null : getValue(value);
const maxSize = value === MAX ? null : getValue(value);
this.setState({
sliderMaxSize: getSliderValue(maxSize, slider.max)
});
this.props.onSizeChange({
minSize: this.props.minSize,
@@ -92,6 +133,11 @@ class QualityDefinition extends Component {
onTitleChange
} = this.props;
const {
sliderMinSize,
sliderMaxSize
} = this.state;
const minBytes = minSize * 128;
const maxBytes = maxSize && maxSize * 128;
@@ -123,13 +169,14 @@ class QualityDefinition extends Component {
max={slider.max}
step={slider.step}
minDistance={10}
value={[minSize || slider.min, maxSize || slider.max]}
value={[sliderMinSize, sliderMaxSize]}
withBars={true}
snapDragDisabled={true}
className={styles.slider}
barClassName={styles.bar}
handleClassName={styles.handle}
onChange={this.onSizeChange}
onChange={this.onSliderChange}
onAfterChange={this.onAfterSliderChange}
/>
<div className={styles.sizes}>
@@ -154,9 +201,10 @@ class QualityDefinition extends Component {
<NumberInput
className={styles.sizeInput}
name={`${id}.min`}
min={slider.min}
max={maxSize ? maxSize - 10 : slider.max - 10}
value={minSize || slider.min}
value={minSize || MIN}
min={MIN}
max={maxSize ? maxSize - 10 : MAX - 10}
step={0.1}
isFloat={true}
onChange={this.onMinSizeChange}
/>
@@ -169,7 +217,9 @@ class QualityDefinition extends Component {
className={styles.sizeInput}
name={`${id}.max`}
min={minSize + 10}
value={maxSize || slider.max}
value={maxSize || MAX}
max={MAX}
step={0.1}
isFloat={true}
onChange={this.onMaxSizeChange}
/>
@@ -5,12 +5,6 @@ import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import QualityDefinition from './QualityDefinition';
function mapStateToProps(state) {
return {
advancedSettings: state.settings.advancedSettings
};
}
const mapDispatchToProps = {
setQualityDefinitionValue,
clearPendingChanges
@@ -67,4 +61,4 @@ QualityDefinitionConnector.propTypes = {
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(QualityDefinitionConnector);
export default connect(null, mapDispatchToProps)(QualityDefinitionConnector);
@@ -12,8 +12,8 @@ class QualityDefinitions extends Component {
render() {
const {
advancedSettings,
items,
advancedSettings,
...otherProps
} = this.props;
@@ -27,8 +27,12 @@ class QualityDefinitions extends Component {
<div className={styles.quality}>Quality</div>
<div className={styles.title}>Title</div>
<div className={styles.sizeLimit}>Size Limit</div>
{advancedSettings &&
<div className={styles.kilobitsPerSecond}>Kilobits Per Second</div>
{
advancedSettings ?
<div className={styles.kilobitsPerSecond}>
Kilobits Per Second
</div> :
null
}
</div>
@@ -39,6 +43,7 @@ class QualityDefinitions extends Component {
<QualityDefinitionConnector
key={item.id}
{...item}
advancedSettings={advancedSettings}
/>
);
})
@@ -57,11 +62,11 @@ class QualityDefinitions extends Component {
}
QualityDefinitions.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
defaultProfile: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired
items: PropTypes.arrayOf(PropTypes.object).isRequired,
advancedSettings: PropTypes.bool.isRequired
};
export default QualityDefinitions;
@@ -18,10 +18,10 @@ function createMapStateToProps() {
});
return {
advancedSettings,
...qualityDefinitions,
items,
hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges)
hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges),
advancedSettings
};
}
);
+3 -3
View File
@@ -21,7 +21,7 @@ function Settings() {
</Link>
<div className={styles.summary}>
Naming and file management settings
Naming, file management settings and root folders
</div>
<Link
@@ -32,7 +32,7 @@ function Settings() {
</Link>
<div className={styles.summary}>
Quality, Language, Metadata, and Delay profiles
Quality, Language, Metadata, Delay, and Release profiles
</div>
<Link
@@ -54,7 +54,7 @@ function Settings() {
</Link>
<div className={styles.summary}>
Indexers and release restrictions
Indexers and indexer options
</div>
<Link
@@ -0,0 +1,47 @@
import PropTypes from 'prop-types';
import React from 'react';
import titleCase from 'Utilities/String/titleCase';
function TagDetailsDelayProfile(props) {
const {
preferredProtocol,
enableUsenet,
enableTorrent,
usenetDelay,
torrentDelay
} = props;
return (
<div>
<div>
Protocol: {titleCase(preferredProtocol)}
</div>
<div>
{
enableUsenet ?
`Usenet Delay: ${usenetDelay}` :
'Usenet disabled'
}
</div>
<div>
{
enableTorrent ?
`Torrent Delay: ${torrentDelay}` :
'Torrents disabled'
}
</div>
</div>
);
}
TagDetailsDelayProfile.propTypes = {
preferredProtocol: PropTypes.string.isRequired,
enableUsenet: PropTypes.bool.isRequired,
enableTorrent: PropTypes.bool.isRequired,
usenetDelay: PropTypes.number.isRequired,
torrentDelay: PropTypes.number.isRequired
};
export default TagDetailsDelayProfile;
@@ -9,6 +9,7 @@ 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 TagDetailsDelayProfile from './TagDetailsDelayProfile';
import styles from './TagDetailsModalContent.css';
function TagDetailsModalContent(props) {
@@ -19,7 +20,7 @@ function TagDetailsModalContent(props) {
delayProfiles,
importLists,
notifications,
restrictions,
releaseProfiles,
onModalClose,
onDeleteTagPress
} = props;
@@ -53,13 +54,27 @@ function TagDetailsModalContent(props) {
{
!!delayProfiles.length &&
<FieldSet legend="Delay Profiles">
<FieldSet legend="Delay Profile">
{
delayProfiles.map((item) => {
const {
id,
preferredProtocol,
enableUsenet,
enableTorrent,
usenetDelay,
torrentDelay
} = item;
return (
<div key={item.id}>
{item.name}
</div>
<TagDetailsDelayProfile
key={id}
preferredProtocol={preferredProtocol}
enableUsenet={enableUsenet}
enableTorrent={enableTorrent}
usenetDelay={usenetDelay}
torrentDelay={torrentDelay}
/>
);
})
}
@@ -97,10 +112,10 @@ function TagDetailsModalContent(props) {
}
{
!!restrictions.length &&
<FieldSet legend="Restrictions">
!!releaseProfiles.length &&
<FieldSet legend="Release Profiles">
{
restrictions.map((item) => {
releaseProfiles.map((item) => {
return (
<div
key={item.id}
@@ -172,7 +187,7 @@ TagDetailsModalContent.propTypes = {
delayProfiles: PropTypes.arrayOf(PropTypes.object).isRequired,
importLists: PropTypes.arrayOf(PropTypes.object).isRequired,
notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
restrictions: PropTypes.arrayOf(PropTypes.object).isRequired,
releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteTagPress: PropTypes.func.isRequired
};
@@ -41,10 +41,10 @@ function createMatchingNotificationsSelector() {
);
}
function createMatchingRestrictionsSelector() {
function createMatchingReleaseProfilesSelector() {
return createSelector(
(state, { restrictionIds }) => restrictionIds,
(state) => state.settings.restrictions.items,
(state) => state.settings.releaseProfiles.items,
findMatchingItems
);
}
@@ -55,14 +55,14 @@ function createMapStateToProps() {
createMatchingDelayProfilesSelector(),
createMatchingImportListsSelector(),
createMatchingNotificationsSelector(),
createMatchingRestrictionsSelector(),
(artist, delayProfiles, importLists, notifications, restrictions) => {
createMatchingReleaseProfilesSelector(),
(artist, delayProfiles, importLists, notifications, releaseProfiles) => {
return {
artist,
delayProfiles,
importLists,
notifications,
restrictions
releaseProfiles
};
}
);
+5 -5
View File
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchTagDetails } from 'Store/Actions/tagActions';
import { fetchDelayProfiles, fetchNotifications, fetchRestrictions, fetchImportLists } from 'Store/Actions/settingsActions';
import { fetchDelayProfiles, fetchNotifications, fetchReleaseProfiles, fetchImportLists } from 'Store/Actions/settingsActions';
import Tags from './Tags';
function createMapStateToProps() {
@@ -29,7 +29,7 @@ const mapDispatchToProps = {
dispatchFetchDelayProfiles: fetchDelayProfiles,
dispatchFetchImportLists: fetchImportLists,
dispatchFetchNotifications: fetchNotifications,
dispatchFetchRestrictions: fetchRestrictions
dispatchFetchReleaseProfiles: fetchReleaseProfiles
};
class MetadatasConnector extends Component {
@@ -43,14 +43,14 @@ class MetadatasConnector extends Component {
dispatchFetchDelayProfiles,
dispatchFetchImportLists,
dispatchFetchNotifications,
dispatchFetchRestrictions
dispatchFetchReleaseProfiles
} = this.props;
dispatchFetchTagDetails();
dispatchFetchDelayProfiles();
dispatchFetchImportLists();
dispatchFetchNotifications();
dispatchFetchRestrictions();
dispatchFetchReleaseProfiles();
}
//
@@ -70,7 +70,7 @@ MetadatasConnector.propTypes = {
dispatchFetchDelayProfiles: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired,
dispatchFetchNotifications: PropTypes.func.isRequired,
dispatchFetchRestrictions: PropTypes.func.isRequired
dispatchFetchReleaseProfiles: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector);
+1 -1
View File
@@ -164,7 +164,7 @@ class UISettings extends Component {
legend="Style"
>
<FormGroup>
<FormLabel>Enable Color-Impaired mode</FormLabel>
<FormLabel>Enable Color-Impaired Mode</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableColorImpairedMode"