New: Readarr 0.1

This commit is contained in:
ta264
2020-05-06 21:14:11 +01:00
parent 476f2d6047
commit 08496c82af
911 changed files with 14837 additions and 24442 deletions
@@ -15,8 +15,9 @@ const newRemotePathMapping = {
const selectDownloadClientHosts = createSelector(
(state) => state.settings.downloadClients.items,
(downloadClients) => {
const hosts = downloadClients.reduce((acc, downloadClient) => {
(state) => state.settings.rootFolders.items,
(downloadClients, rootFolders) => {
const dlhosts = downloadClients.reduce((acc, downloadClient) => {
const name = downloadClient.name;
const host = downloadClient.fields.find((field) => {
return field.name === 'host';
@@ -30,6 +31,17 @@ const selectDownloadClientHosts = createSelector(
return acc;
}, {});
const hosts = rootFolders.reduce((acc, folder) => {
const name = folder.name;
if (folder.isCalibreLibrary) {
const group = acc[folder.host] = acc[folder.host] || [];
group.push(name);
}
return acc;
}, dlhosts);
return Object.keys(hosts).map((host) => {
return {
key: host,
@@ -24,17 +24,17 @@ function ImportListMonitoringOptionsPopoverContent() {
<DescriptionList>
<DescriptionListItem
title="None"
data="Do not monitor artists or albums"
data="Do not monitor authors or books"
/>
<DescriptionListItem
title="Specific Album"
data="Monitor artists but only monitor albums explicitly included in the list"
title="Specific Book"
data="Monitor authors but only monitor books explicitly included in the list"
/>
<DescriptionListItem
title="All Artist Albums"
data="Monitor artists and all albums for each artist included on the import list"
title="All Author Books"
data="Monitor authors and all books for each author included on the import list"
/>
</DescriptionList>
);
@@ -44,8 +44,8 @@ function EditImportListModalContent(props) {
const monitorOptions = [
{ key: 'none', value: 'None' },
{ key: 'specificAlbum', value: 'Specific Album' },
{ key: 'entireArtist', value: 'All Artist Albums' }
{ key: 'specificAlbum', value: 'Specific Book' },
{ key: 'entireArtist', value: 'All Author Books' }
];
const {
@@ -115,7 +115,7 @@ function EditImportListModalContent(props) {
<FormInputGroup
type={inputTypes.CHECK}
name="enableAutomaticAdd"
helpText={'Add artist/albums to Readarr when syncs are performed via the UI or by Readarr'}
helpText={'Add author/books to Readarr when syncs are performed via the UI or by Readarr'}
{...enableAutomaticAdd}
onChange={onInputChange}
/>
@@ -142,7 +142,7 @@ function EditImportListModalContent(props) {
type={inputTypes.SELECT}
name="shouldMonitor"
values={monitorOptions}
helpText={'Monitor artists and albums added from this list'}
helpText={'Monitor authors and books added from this list'}
{...shouldMonitor}
onChange={onInputChange}
/>
@@ -191,7 +191,7 @@ function EditImportListModalContent(props) {
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText="Add artists from this list with these tags"
helpText="Add authors from this list with these tags"
{...tags}
onChange={onInputChange}
/>
@@ -11,6 +11,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
import RootFoldersConnector from './RootFolder/RootFoldersConnector';
import RemotePathMappingsConnector from 'Settings/DownloadClients/RemotePathMappings/RemotePathMappingsConnector';
import NamingConnector from './Naming/NamingConnector';
const rescanAfterRefreshOptions = [
@@ -64,6 +65,7 @@ class MediaManagement extends Component {
<PageContentBodyConnector>
<RootFoldersConnector />
<RemotePathMappingsConnector />
<NamingConnector />
{
@@ -40,18 +40,6 @@ class Naming extends Component {
});
}
onMultiDiscNamingModalOpenClick = () => {
this.setState({
isNamingModalOpen: true,
namingModalOptions: {
name: 'multiDiscTrackFormat',
album: true,
track: true,
additional: true
}
});
}
onArtistFolderNamingModalOpenClick = () => {
this.setState({
isNamingModalOpen: true,
@@ -61,16 +49,6 @@ class Naming extends Component {
});
}
onAlbumFolderNamingModalOpenClick = () => {
this.setState({
isNamingModalOpen: true,
namingModalOptions: {
name: 'albumFolderFormat',
album: true
}
});
}
onNamingModalClose = () => {
this.setState({ isNamingModalOpen: false });
}
@@ -99,12 +77,8 @@ class Naming extends Component {
const standardTrackFormatHelpTexts = [];
const standardTrackFormatErrors = [];
const multiDiscTrackFormatHelpTexts = [];
const multiDiscTrackFormatErrors = [];
const artistFolderFormatHelpTexts = [];
const artistFolderFormatErrors = [];
const albumFolderFormatHelpTexts = [];
const albumFolderFormatErrors = [];
if (examplesPopulated) {
if (examples.singleTrackExample) {
@@ -113,23 +87,11 @@ class Naming extends Component {
standardTrackFormatErrors.push({ message: 'Single Track: Invalid Format' });
}
if (examples.multiDiscTrackExample) {
multiDiscTrackFormatHelpTexts.push(`Multi Disc Track: ${examples.multiDiscTrackExample}`);
} else {
multiDiscTrackFormatErrors.push({ message: 'Single Track: Invalid Format' });
}
if (examples.artistFolderExample) {
artistFolderFormatHelpTexts.push(`Example: ${examples.artistFolderExample}`);
} else {
artistFolderFormatErrors.push({ message: 'Invalid Format' });
}
if (examples.albumFolderExample) {
albumFolderFormatHelpTexts.push(`Example: ${examples.albumFolderExample}`);
} else {
albumFolderFormatErrors.push({ message: 'Invalid Format' });
}
}
return (
@@ -188,22 +150,6 @@ class Naming extends Component {
errors={[...standardTrackFormatErrors, ...settings.standardTrackFormat.errors]}
/>
</FormGroup>
<FormGroup size={sizes.LARGE}>
<FormLabel>Multi Disc Track Format</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
type={inputTypes.TEXT}
name="multiDiscTrackFormat"
buttons={<FormInputButton onPress={this.onMultiDiscNamingModalOpenClick}>?</FormInputButton>}
onChange={onInputChange}
{...settings.multiDiscTrackFormat}
helpTexts={multiDiscTrackFormatHelpTexts}
errors={[...multiDiscTrackFormatErrors, ...settings.multiDiscTrackFormat.errors]}
/>
</FormGroup>
</div>
}
@@ -225,21 +171,6 @@ class Naming extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>Album Folder Format</FormLabel>
<FormInputGroup
inputClassName={styles.namingInput}
type={inputTypes.TEXT}
name="albumFolderFormat"
buttons={<FormInputButton onPress={this.onAlbumFolderNamingModalOpenClick}>?</FormInputButton>}
onChange={onInputChange}
{...settings.albumFolderFormat}
helpTexts={albumFolderFormatHelpTexts}
errors={[...albumFolderFormatErrors, ...settings.albumFolderFormat.errors]}
/>
</FormGroup>
{
namingModalOptions &&
<NamingModal
@@ -42,7 +42,16 @@ function EditRootFolderModalContent(props) {
defaultQualityProfileId,
defaultMetadataProfileId,
defaultMonitorOption,
defaultTags
defaultTags,
isCalibreLibrary,
host,
port,
urlBase,
username,
password,
outputFormat,
outputProfile,
useSsl
} = item;
return (
@@ -83,13 +92,129 @@ function EditRootFolderModalContent(props) {
type={id ? inputTypes.TEXT : inputTypes.PATH}
readOnly={!!id}
name="path"
helpText="Root Folder containing your music library"
helpText="Root Folder containing your book library"
helpTextWarning="This must be different to the directory where your download client puts files"
{...path}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Calibre Library</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="isCalibreLibrary"
helpText="Use calibre content server to manipulate library"
{...isCalibreLibrary}
onChange={onInputChange}
/>
</FormGroup>
{
isCalibreLibrary !== undefined && isCalibreLibrary.value &&
<div>
<FormGroup>
<FormLabel>Calibre Host</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="host"
helpText="Calibre content server host"
{...host}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Calibre Port</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="port"
helpText="Calibre content server port"
{...port}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Calibre Url Base</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="urlBase"
helpText="Adds a prefix to the calibre url, e.g. http://[host]:[port]/[urlBase]"
{...urlBase}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Calibre Username</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
helpText="Calibre content server username"
{...username}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Calibre Password</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
helpText="Calibre content server password"
{...password}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Convert to format</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="outputFormat"
helpText="Optionally ask calibre to convert to other formats on import. Comma separated list."
{...outputFormat}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Calibre Output Profile</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="outputProfile"
helpText="Output profile for conversion"
{...outputProfile}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Use SSL</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="useSsl"
helpText="Use SSL to connect to calibre content server"
{...useSsl}
onChange={onInputChange}
/>
</FormGroup>
</div>
}
<FormGroup>
<FormLabel>
Monitor
@@ -112,7 +237,7 @@ function EditRootFolderModalContent(props) {
name="defaultMonitorOption"
onChange={onInputChange}
{...defaultMonitorOption}
helpText="Default Monitoring Options for albums by artists detected in this folder"
helpText="Default Monitoring Options for books by authors detected in this folder"
/>
</FormGroup>
@@ -123,7 +248,7 @@ function EditRootFolderModalContent(props) {
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="defaultQualityProfileId"
helpText="Default Quality Profile for artists detected in this folder"
helpText="Default Quality Profile for authors detected in this folder"
{...defaultQualityProfileId}
onChange={onInputChange}
/>
@@ -148,7 +273,7 @@ function EditRootFolderModalContent(props) {
<FormInputGroup
type={inputTypes.METADATA_PROFILE_SELECT}
name="defaultMetadataProfileId"
helpText="Default Metadata Profile for artists detected in this folder"
helpText="Default Metadata Profile for authors detected in this folder"
{...defaultMetadataProfileId}
includeNone={true}
onChange={onInputChange}
@@ -161,7 +286,7 @@ function EditRootFolderModalContent(props) {
<FormInputGroup
type={inputTypes.TAG}
name="defaultTags"
helpText="Default Readarr Tags for artists detected in this folder"
helpText="Default Readarr Tags for authors detected in this folder"
{...defaultTags}
onChange={onInputChange}
/>
@@ -12,9 +12,6 @@ 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 PrimaryTypeItems from './PrimaryTypeItems';
import SecondaryTypeItems from './SecondaryTypeItems';
import ReleaseStatusItems from './ReleaseStatusItems';
import styles from './EditMetadataProfileModalContent.css';
function EditMetadataProfileModalContent(props) {
@@ -23,8 +20,6 @@ function EditMetadataProfileModalContent(props) {
error,
isSaving,
saveError,
primaryAlbumTypes,
secondaryAlbumTypes,
item,
isInUse,
onInputChange,
@@ -37,9 +32,13 @@ function EditMetadataProfileModalContent(props) {
const {
id,
name,
primaryAlbumTypes: itemPrimaryAlbumTypes,
secondaryAlbumTypes: itemSecondaryAlbumTypes,
releaseStatuses: itemReleaseStatuses
minRating,
minRatingCount,
skipMissingDate,
skipMissingIsbn,
skipPartsAndSets,
skipSeriesSecondary,
allowedLanguages
} = item;
return (
@@ -73,29 +72,86 @@ function EditMetadataProfileModalContent(props) {
/>
</FormGroup>
<PrimaryTypeItems
metadataProfileItems={itemPrimaryAlbumTypes.value}
errors={itemPrimaryAlbumTypes.errors}
warnings={itemPrimaryAlbumTypes.warnings}
formLabel="Primary Album Types"
{...otherProps}
/>
<FormGroup>
<FormLabel>Minimum Rating</FormLabel>
<SecondaryTypeItems
metadataProfileItems={itemSecondaryAlbumTypes.value}
errors={itemSecondaryAlbumTypes.errors}
warnings={itemSecondaryAlbumTypes.warnings}
formLabel="Secondary Album Types"
{...otherProps}
/>
<FormInputGroup
type={inputTypes.NUMBER}
name="minRating"
{...minRating}
isFloat={true}
min={0}
max={5}
onChange={onInputChange}
/>
</FormGroup>
<ReleaseStatusItems
metadataProfileItems={itemReleaseStatuses.value}
errors={itemReleaseStatuses.errors}
warnings={itemReleaseStatuses.warnings}
formLabel="Release Statuses"
{...otherProps}
/>
<FormGroup>
<FormLabel>Minimum Number of Ratings</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="minRatingCount"
{...minRatingCount}
min={0}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Skip books with missing release date</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipMissingDate"
{...skipMissingDate}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Skip books with no ISBN or ASIN</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipMissingIsbn"
{...skipMissingIsbn}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Skip part books and sets</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipPartsAndSets"
{...skipPartsAndSets}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Skip secondary series books</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipSeriesSecondary"
{...skipSeriesSecondary}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Allowed Languages</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="allowedLanguages"
{...allowedLanguages}
onChange={onInputChange}
/>
</FormGroup>
</Form>
}
@@ -140,9 +196,6 @@ EditMetadataProfileModalContent.propTypes = {
error: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
primaryAlbumTypes: PropTypes.arrayOf(PropTypes.object).isRequired,
secondaryAlbumTypes: PropTypes.arrayOf(PropTypes.object).isRequired,
releaseStatuses: PropTypes.arrayOf(PropTypes.object).isRequired,
item: PropTypes.object.isRequired,
isInUse: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,
@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@@ -8,87 +7,12 @@ import createProviderSettingsSelector from 'Store/Selectors/createProviderSettin
import { fetchMetadataProfileSchema, setMetadataProfileValue, saveMetadataProfile } from 'Store/Actions/settingsActions';
import EditMetadataProfileModalContent from './EditMetadataProfileModalContent';
function createPrimaryAlbumTypesSelector() {
return createSelector(
createProviderSettingsSelector('metadataProfiles'),
(metadataProfile) => {
const primaryAlbumTypes = metadataProfile.item.primaryAlbumTypes;
if (!primaryAlbumTypes || !primaryAlbumTypes.value) {
return [];
}
return _.reduceRight(primaryAlbumTypes.value, (result, { allowed, albumType }) => {
if (allowed) {
result.push({
key: albumType.id,
value: albumType.name
});
}
return result;
}, []);
}
);
}
function createSecondaryAlbumTypesSelector() {
return createSelector(
createProviderSettingsSelector('metadataProfiles'),
(metadataProfile) => {
const secondaryAlbumTypes = metadataProfile.item.secondaryAlbumTypes;
if (!secondaryAlbumTypes || !secondaryAlbumTypes.value) {
return [];
}
return _.reduceRight(secondaryAlbumTypes.value, (result, { allowed, albumType }) => {
if (allowed) {
result.push({
key: albumType.id,
value: albumType.name
});
}
return result;
}, []);
}
);
}
function createReleaseStatusesSelector() {
return createSelector(
createProviderSettingsSelector('metadataProfiles'),
(metadataProfile) => {
const releaseStatuses = metadataProfile.item.releaseStatuses;
if (!releaseStatuses || !releaseStatuses.value) {
return [];
}
return _.reduceRight(releaseStatuses.value, (result, { allowed, releaseStatus }) => {
if (allowed) {
result.push({
key: releaseStatus.id,
value: releaseStatus.name
});
}
return result;
}, []);
}
);
}
function createMapStateToProps() {
return createSelector(
createProviderSettingsSelector('metadataProfiles'),
createPrimaryAlbumTypesSelector(),
createSecondaryAlbumTypesSelector(),
createReleaseStatusesSelector(),
createProfileInUseSelector('metadataProfileId'),
(metadataProfile, primaryAlbumTypes, secondaryAlbumTypes, releaseStatuses, isInUse) => {
(metadataProfile, isInUse) => {
return {
primaryAlbumTypes,
secondaryAlbumTypes,
releaseStatuses,
...metadataProfile,
isInUse
};
@@ -139,59 +63,16 @@ class EditMetadataProfileModalContentConnector extends Component {
this.props.saveMetadataProfile({ id: this.props.id });
}
onMetadataPrimaryTypeItemAllowedChange = (id, allowed) => {
const metadataProfile = _.cloneDeep(this.props.item);
const item = _.find(metadataProfile.primaryAlbumTypes.value, (i) => i.albumType.id === id);
item.allowed = allowed;
this.props.setMetadataProfileValue({
name: 'primaryAlbumTypes',
value: metadataProfile.primaryAlbumTypes.value
});
}
onMetadataSecondaryTypeItemAllowedChange = (id, allowed) => {
const metadataProfile = _.cloneDeep(this.props.item);
const item = _.find(metadataProfile.secondaryAlbumTypes.value, (i) => i.albumType.id === id);
item.allowed = allowed;
this.props.setMetadataProfileValue({
name: 'secondaryAlbumTypes',
value: metadataProfile.secondaryAlbumTypes.value
});
}
onMetadataReleaseStatusItemAllowedChange = (id, allowed) => {
const metadataProfile = _.cloneDeep(this.props.item);
const item = _.find(metadataProfile.releaseStatuses.value, (i) => i.releaseStatus.id === id);
item.allowed = allowed;
this.props.setMetadataProfileValue({
name: 'releaseStatuses',
value: metadataProfile.releaseStatuses.value
});
}
//
// Render
render() {
if (_.isEmpty(this.props.item.primaryAlbumTypes) && !this.props.isFetching) {
return null;
}
return (
<EditMetadataProfileModalContent
{...this.state}
{...this.props}
onSavePress={this.onSavePress}
onInputChange={this.onInputChange}
onMetadataPrimaryTypeItemAllowedChange={this.onMetadataPrimaryTypeItemAllowedChange}
onMetadataSecondaryTypeItemAllowedChange={this.onMetadataSecondaryTypeItemAllowedChange}
onMetadataReleaseStatusItemAllowedChange={this.onMetadataReleaseStatusItemAllowedChange}
/>
);
}
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons, kinds } from 'Helpers/Props';
import Card from 'Components/Card';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import EditMetadataProfileModalConnector from './EditMetadataProfileModalConnector';
@@ -64,8 +63,6 @@ class MetadataProfile extends Component {
const {
id,
name,
primaryAlbumTypes,
secondaryAlbumTypes,
isDeleting
} = this.props;
@@ -88,46 +85,6 @@ class MetadataProfile extends Component {
/>
</div>
<div className={styles.albumTypes}>
{
primaryAlbumTypes.map((item) => {
if (!item.allowed) {
return null;
}
return (
<Label
key={item.albumType.id}
kind={kinds.default}
title={null}
>
{item.albumType.name}
</Label>
);
})
}
</div>
<div className={styles.albumTypes}>
{
secondaryAlbumTypes.map((item) => {
if (!item.allowed) {
return null;
}
return (
<Label
key={item.albumType.id}
kind={kinds.INFO}
title={null}
>
{item.albumType.name}
</Label>
);
})
}
</div>
<EditMetadataProfileModalConnector
id={id}
isOpen={this.state.isEditMetadataProfileModalOpen}
@@ -153,8 +110,6 @@ class MetadataProfile extends Component {
MetadataProfile.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
primaryAlbumTypes: PropTypes.arrayOf(PropTypes.object).isRequired,
secondaryAlbumTypes: PropTypes.arrayOf(PropTypes.object).isRequired,
isDeleting: PropTypes.bool.isRequired,
onConfirmDeleteMetadataProfile: PropTypes.func.isRequired,
onCloneMetadataProfilePress: PropTypes.func.isRequired
@@ -1,60 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import CheckInput from 'Components/Form/CheckInput';
import styles from './TypeItem.css';
class PrimaryTypeItem extends Component {
//
// Listeners
onAllowedChange = ({ value }) => {
const {
albumTypeId,
onMetadataPrimaryTypeItemAllowedChange
} = this.props;
onMetadataPrimaryTypeItemAllowedChange(albumTypeId, value);
}
//
// Render
render() {
const {
name,
allowed
} = this.props;
return (
<div
className={classNames(
styles.metadataProfileItem
)}
>
<label
className={styles.albumTypeName}
>
<CheckInput
containerClassName={styles.checkContainer}
name={name}
value={allowed}
onChange={this.onAllowedChange}
/>
{name}
</label>
</div>
);
}
}
PrimaryTypeItem.propTypes = {
albumTypeId: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
allowed: PropTypes.bool.isRequired,
sortIndex: PropTypes.number.isRequired,
onMetadataPrimaryTypeItemAllowedChange: PropTypes.func
};
export default PrimaryTypeItem;
@@ -1,87 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputHelpText from 'Components/Form/FormInputHelpText';
import PrimaryTypeItem from './PrimaryTypeItem';
import styles from './TypeItems.css';
class PrimaryTypeItems extends Component {
//
// Render
render() {
const {
metadataProfileItems,
errors,
warnings,
...otherProps
} = this.props;
return (
<FormGroup>
<FormLabel>Primary Types</FormLabel>
<div>
{
errors.map((error, index) => {
return (
<FormInputHelpText
key={index}
text={error.message}
isError={true}
isCheckInput={false}
/>
);
})
}
{
warnings.map((warning, index) => {
return (
<FormInputHelpText
key={index}
text={warning.message}
isWarning={true}
isCheckInput={false}
/>
);
})
}
<div className={styles.albumTypes}>
{
metadataProfileItems.map(({ allowed, albumType }, index) => {
return (
<PrimaryTypeItem
key={albumType.id}
albumTypeId={albumType.id}
name={albumType.name}
allowed={allowed}
sortIndex={index}
{...otherProps}
/>
);
}).reverse()
}
</div>
</div>
</FormGroup>
);
}
}
PrimaryTypeItems.propTypes = {
metadataProfileItems: PropTypes.arrayOf(PropTypes.object).isRequired,
errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object),
formLabel: PropTypes.string
};
PrimaryTypeItems.defaultProps = {
errors: [],
warnings: []
};
export default PrimaryTypeItems;
@@ -1,60 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import CheckInput from 'Components/Form/CheckInput';
import styles from './TypeItem.css';
class ReleaseStatusItem extends Component {
//
// Listeners
onAllowedChange = ({ value }) => {
const {
albumTypeId,
onMetadataReleaseStatusItemAllowedChange
} = this.props;
onMetadataReleaseStatusItemAllowedChange(albumTypeId, value);
}
//
// Render
render() {
const {
name,
allowed
} = this.props;
return (
<div
className={classNames(
styles.metadataProfileItem
)}
>
<label
className={styles.albumTypeName}
>
<CheckInput
containerClassName={styles.checkContainer}
name={name}
value={allowed}
onChange={this.onAllowedChange}
/>
{name}
</label>
</div>
);
}
}
ReleaseStatusItem.propTypes = {
albumTypeId: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
allowed: PropTypes.bool.isRequired,
sortIndex: PropTypes.number.isRequired,
onMetadataReleaseStatusItemAllowedChange: PropTypes.func
};
export default ReleaseStatusItem;
@@ -1,87 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputHelpText from 'Components/Form/FormInputHelpText';
import ReleaseStatusItem from './ReleaseStatusItem';
import styles from './TypeItems.css';
class ReleaseStatusItems extends Component {
//
// Render
render() {
const {
metadataProfileItems,
errors,
warnings,
...otherProps
} = this.props;
return (
<FormGroup>
<FormLabel>Release Statuses</FormLabel>
<div>
{
errors.map((error, index) => {
return (
<FormInputHelpText
key={index}
text={error.message}
isError={true}
isCheckInput={false}
/>
);
})
}
{
warnings.map((warning, index) => {
return (
<FormInputHelpText
key={index}
text={warning.message}
isWarning={true}
isCheckInput={false}
/>
);
})
}
<div className={styles.albumTypes}>
{
metadataProfileItems.map(({ allowed, releaseStatus }, index) => {
return (
<ReleaseStatusItem
key={releaseStatus.id}
albumTypeId={releaseStatus.id}
name={releaseStatus.name}
allowed={allowed}
sortIndex={index}
{...otherProps}
/>
);
})
}
</div>
</div>
</FormGroup>
);
}
}
ReleaseStatusItems.propTypes = {
metadataProfileItems: PropTypes.arrayOf(PropTypes.object).isRequired,
errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object),
formLabel: PropTypes.string
};
ReleaseStatusItems.defaultProps = {
errors: [],
warnings: []
};
export default ReleaseStatusItems;
@@ -1,60 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import CheckInput from 'Components/Form/CheckInput';
import styles from './TypeItem.css';
class SecondaryTypeItem extends Component {
//
// Listeners
onAllowedChange = ({ value }) => {
const {
albumTypeId,
onMetadataSecondaryTypeItemAllowedChange
} = this.props;
onMetadataSecondaryTypeItemAllowedChange(albumTypeId, value);
}
//
// Render
render() {
const {
name,
allowed
} = this.props;
return (
<div
className={classNames(
styles.metadataProfileItem
)}
>
<label
className={styles.albumTypeName}
>
<CheckInput
containerClassName={styles.checkContainer}
name={name}
value={allowed}
onChange={this.onAllowedChange}
/>
{name}
</label>
</div>
);
}
}
SecondaryTypeItem.propTypes = {
albumTypeId: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
allowed: PropTypes.bool.isRequired,
sortIndex: PropTypes.number.isRequired,
onMetadataSecondaryTypeItemAllowedChange: PropTypes.func
};
export default SecondaryTypeItem;
@@ -1,87 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputHelpText from 'Components/Form/FormInputHelpText';
import SecondaryTypeItem from './SecondaryTypeItem';
import styles from './TypeItems.css';
class SecondaryTypeItems extends Component {
//
// Render
render() {
const {
metadataProfileItems,
errors,
warnings,
...otherProps
} = this.props;
return (
<FormGroup>
<FormLabel>Secondary Types</FormLabel>
<div>
{
errors.map((error, index) => {
return (
<FormInputHelpText
key={index}
text={error.message}
isError={true}
isCheckInput={false}
/>
);
})
}
{
warnings.map((warning, index) => {
return (
<FormInputHelpText
key={index}
text={warning.message}
isWarning={true}
isCheckInput={false}
/>
);
})
}
<div className={styles.albumTypes}>
{
metadataProfileItems.map(({ allowed, albumType }, index) => {
return (
<SecondaryTypeItem
key={albumType.id}
albumTypeId={albumType.id}
name={albumType.name}
allowed={allowed}
sortIndex={index}
{...otherProps}
/>
);
})
}
</div>
</div>
</FormGroup>
);
}
}
SecondaryTypeItems.propTypes = {
metadataProfileItems: PropTypes.arrayOf(PropTypes.object).isRequired,
errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object),
formLabel: PropTypes.string
};
SecondaryTypeItems.defaultProps = {
errors: [],
warnings: []
};
export default SecondaryTypeItems;
@@ -1,25 +0,0 @@
.metadataProfileItem {
display: flex;
align-items: stretch;
width: 100%;
}
.checkContainer {
position: relative;
margin-right: 4px;
margin-bottom: 7px;
margin-left: 8px;
}
.albumTypeName {
display: flex;
flex-grow: 1;
margin-bottom: 0;
margin-left: 2px;
font-weight: normal;
line-height: 36px;
}
.isDragging {
opacity: 0.25;
}
@@ -1,6 +0,0 @@
.albumTypes {
margin-top: 10px;
/* TODO: This should consider the number of types in the list */
min-height: 200px;
user-select: none;
}
@@ -46,28 +46,12 @@ class QualityDefinition extends Component {
constructor(props, context) {
super(props, context);
this._forceUpdateTimeout = null;
this.state = {
sliderMinSize: getSliderValue(props.minSize, slider.min),
sliderMaxSize: getSliderValue(props.maxSize, slider.max)
};
}
componentDidMount() {
// A hack to deal with a bug in the slider component until a fix for it
// lands and an updated version is available.
// See: https://github.com/mpowaga/react-slider/issues/115
this._forceUpdateTimeout = setTimeout(() => this.forceUpdate(), 1);
}
componentWillUnmount() {
if (this._forceUpdateTimeout) {
clearTimeout(this._forceUpdateTimeout);
}
}
//
// Listeners
@@ -167,11 +151,11 @@ class QualityDefinition extends Component {
step={slider.step}
minDistance={10}
value={[sliderMinSize, sliderMaxSize]}
withBars={true}
withTracks={true}
snapDragDisabled={true}
className={styles.slider}
barClassName={styles.bar}
handleClassName={styles.handle}
trackClassName={styles.bar}
thumbClassName={styles.handle}
onChange={this.onSliderChange}
onAfterChange={this.onAfterSliderChange}
/>
@@ -11,7 +11,7 @@ function findMatchingItems(ids, items) {
function createMatchingArtistSelector() {
return createSelector(
(state, { artistIds }) => artistIds,
(state, { authorIds }) => authorIds,
createAllArtistSelector(),
findMatchingItems
);
+7 -7
View File
@@ -56,7 +56,7 @@ class Tag extends Component {
importListIds,
notificationIds,
restrictionIds,
artistIds
authorIds
} = this.props;
const {
@@ -69,7 +69,7 @@ class Tag extends Component {
importListIds.length ||
notificationIds.length ||
restrictionIds.length ||
artistIds.length
authorIds.length
);
return (
@@ -86,9 +86,9 @@ class Tag extends Component {
isTagUsed &&
<div>
{
!!artistIds.length &&
!!authorIds.length &&
<div>
{artistIds.length} artists
{authorIds.length} artists
</div>
}
@@ -132,7 +132,7 @@ class Tag extends Component {
<TagDetailsModal
label={label}
isTagUsed={isTagUsed}
artistIds={artistIds}
authorIds={authorIds}
delayProfileIds={delayProfileIds}
importListIds={importListIds}
notificationIds={notificationIds}
@@ -163,7 +163,7 @@ Tag.propTypes = {
importListIds: PropTypes.arrayOf(PropTypes.number).isRequired,
notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired,
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onConfirmDeleteTag: PropTypes.func.isRequired
};
@@ -172,7 +172,7 @@ Tag.defaultProps = {
importListIds: [],
notificationIds: [],
restrictionIds: [],
artistIds: []
authorIds: []
};
export default Tag;