mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-25 22:36:59 -04:00
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:
+10
-10
@@ -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,
|
||||
|
||||
+6
-6
@@ -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
|
||||
|
||||
+1
-3
@@ -67,9 +67,7 @@ class EditDownloadClientModalContent extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !error &&
|
||||
<Form
|
||||
{...otherProps}
|
||||
>
|
||||
<Form {...otherProps}>
|
||||
{
|
||||
!!message &&
|
||||
<Alert
|
||||
|
||||
+5
-4
@@ -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,
|
||||
|
||||
+28
-9
@@ -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 {
|
||||
|
||||
+6
-6
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-6
@@ -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,
|
||||
|
||||
+5
-5
@@ -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;
|
||||
+6
-6
@@ -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);
|
||||
+47
-12
@@ -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;
|
||||
+22
-20
@@ -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
-1
@@ -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;
|
||||
+3
-3
@@ -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;
|
||||
+23
-23
@@ -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
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user