mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-23 17:14:46 -04:00
Compare commits
99 Commits
v5.3.3.853
...
v5.4.2.866
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cc04245ec | ||
|
|
2caf3c6725 | ||
|
|
41ff9352b9 | ||
|
|
d7b9b2ccb2 | ||
|
|
e90a50a3aa | ||
|
|
a0dd26c353 | ||
|
|
2286055d6a | ||
|
|
0a5a4e0a6f | ||
|
|
619c38c493 | ||
|
|
0b8694c627 | ||
|
|
e2793e56e9 | ||
|
|
68f61da321 | ||
|
|
8edb541e21 | ||
|
|
d441becc74 | ||
|
|
a97b2ee2ed | ||
|
|
e70c61e24e | ||
|
|
d1f96746e0 | ||
|
|
35893697bd | ||
|
|
540c150b93 | ||
|
|
48f819caee | ||
|
|
4ad7b60d9d | ||
|
|
7e4231fc0e | ||
|
|
94287d9427 | ||
|
|
8ec6b5dd4d | ||
|
|
4be43c9f2b | ||
|
|
c388cf968b | ||
|
|
b6b809f473 | ||
|
|
9dd31be7b3 | ||
|
|
25ab396a2c | ||
|
|
145cd74969 | ||
|
|
b9c76d9bed | ||
|
|
63f16924b1 | ||
|
|
a91a9f7fd9 | ||
|
|
4c8e9f204e | ||
|
|
d64ee6681f | ||
|
|
2ecc57cd31 | ||
|
|
9620207503 | ||
|
|
0b090e5f39 | ||
|
|
51cb0920ed | ||
|
|
a90d6682d3 | ||
|
|
db62eddf5a | ||
|
|
ac2b2e6215 | ||
|
|
9581dd9764 | ||
|
|
6c459c744a | ||
|
|
4676ecfce9 | ||
|
|
ef92af9dd8 | ||
|
|
b144482d68 | ||
|
|
173b1d6a4c | ||
|
|
5f624a147b | ||
|
|
af066da4ff | ||
|
|
937ebcdac3 | ||
|
|
67f5199667 | ||
|
|
38cd130da5 | ||
|
|
ed340be2b1 | ||
|
|
34cfb58b39 | ||
|
|
3d0f22ca7c | ||
|
|
2510f44c25 | ||
|
|
c0bf75cae3 | ||
|
|
a63ab1ddd6 | ||
|
|
41cb020ff0 | ||
|
|
d660309b5a | ||
|
|
222c19e4b3 | ||
|
|
b08981dee0 | ||
|
|
4a9c0b2240 | ||
|
|
8970b1276f | ||
|
|
e868dbf911 | ||
|
|
e38b31a220 | ||
|
|
9b1dac4b57 | ||
|
|
20ac0bb0e1 | ||
|
|
9ffa1cc2b9 | ||
|
|
422db874f0 | ||
|
|
adf647f3e1 | ||
|
|
dc81f51d40 | ||
|
|
c9da7ee0c9 | ||
|
|
7198aa24a6 | ||
|
|
35c6fef2d1 | ||
|
|
deac2bdf5c | ||
|
|
8837473ed8 | ||
|
|
4ac538682d | ||
|
|
0277b2b201 | ||
|
|
e73015010e | ||
|
|
f704ab1512 | ||
|
|
2f1e077e0d | ||
|
|
cd3397a7a1 | ||
|
|
b3517c14de | ||
|
|
2d05708fa9 | ||
|
|
2ca581f2b6 | ||
|
|
8289b8978f | ||
|
|
54c1f54b13 | ||
|
|
918fcfd86e | ||
|
|
f55206537c | ||
|
|
d2d9ac8b9d | ||
|
|
ca1a40723b | ||
|
|
bfff736cfc | ||
|
|
c2d28dd41b | ||
|
|
0e8a1ca522 | ||
|
|
1ba7bfe585 | ||
|
|
0be449033f | ||
|
|
3b1d4460ad |
@@ -9,14 +9,14 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.3.3'
|
||||
majorVersion: '5.4.2'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.417'
|
||||
nodeVersion: '16.X'
|
||||
nodeVersion: '20.X'
|
||||
innoVersion: '6.2.2'
|
||||
windowsImage: 'windows-2022'
|
||||
linuxImage: 'ubuntu-20.04'
|
||||
|
||||
@@ -10,6 +10,7 @@ import styles from './ImportMovieRow.css';
|
||||
function ImportMovieRow(props) {
|
||||
const {
|
||||
id,
|
||||
relativePath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
@@ -31,7 +32,7 @@ function ImportMovieRow(props) {
|
||||
/>
|
||||
|
||||
<VirtualTableRowCell className={styles.folder}>
|
||||
{id}
|
||||
{relativePath}
|
||||
</VirtualTableRowCell>
|
||||
|
||||
<VirtualTableRowCell className={styles.movie}>
|
||||
@@ -73,6 +74,7 @@ function ImportMovieRow(props) {
|
||||
|
||||
ImportMovieRow.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
relativePath: PropTypes.string.isRequired,
|
||||
monitor: PropTypes.string.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
minimumAvailability: PropTypes.string.isRequired,
|
||||
|
||||
@@ -30,7 +30,7 @@ class ImportMovieTable extends Component {
|
||||
unmappedFolders.forEach((unmappedFolder) => {
|
||||
const id = unmappedFolder.name;
|
||||
|
||||
onMovieLookup(id, unmappedFolder.path);
|
||||
onMovieLookup(id, unmappedFolder.path, unmappedFolder.relativePath);
|
||||
|
||||
onSetImportMovieValue({
|
||||
id,
|
||||
|
||||
@@ -25,10 +25,11 @@ function createMapStateToProps() {
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onMovieLookup(name, path) {
|
||||
onMovieLookup(name, path, relativePath) {
|
||||
dispatch(queueLookupMovie({
|
||||
name,
|
||||
path,
|
||||
relativePath,
|
||||
term: name
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -14,6 +14,50 @@ import styles from './CollectionFooter.css';
|
||||
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const monitoredOptions = [
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
key: 'monitored',
|
||||
get value() {
|
||||
return translate('Monitored');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'unmonitored',
|
||||
get value() {
|
||||
return translate('Unmonitored');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const searchOnAddOptions = [
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
key: 'yes',
|
||||
get value() {
|
||||
return translate('Yes');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'no',
|
||||
get value() {
|
||||
return translate('No');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
class CollectionFooter extends Component {
|
||||
|
||||
//
|
||||
@@ -23,12 +67,12 @@ class CollectionFooter extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
monitor: NO_CHANGE,
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
qualityProfileId: NO_CHANGE,
|
||||
minimumAvailability: NO_CHANGE,
|
||||
rootFolderPath: NO_CHANGE,
|
||||
destinationRootFolder: null
|
||||
searchOnAdd: NO_CHANGE
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,8 +88,9 @@ class CollectionFooter extends Component {
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
qualityProfileId: NO_CHANGE,
|
||||
minimumAvailability: NO_CHANGE,
|
||||
rootFolderPath: NO_CHANGE,
|
||||
minimumAvailability: NO_CHANGE
|
||||
searchOnAdd: NO_CHANGE
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,11 +108,12 @@ class CollectionFooter extends Component {
|
||||
|
||||
onUpdateSelectedPress = () => {
|
||||
const {
|
||||
monitor,
|
||||
monitored,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath
|
||||
rootFolderPath,
|
||||
searchOnAdd
|
||||
} = this.state;
|
||||
|
||||
const changes = {};
|
||||
@@ -92,6 +138,10 @@ class CollectionFooter extends Component {
|
||||
changes.rootFolderPath = rootFolderPath;
|
||||
}
|
||||
|
||||
if (searchOnAdd !== NO_CHANGE) {
|
||||
changes.searchOnAdd = searchOnAdd === 'yes';
|
||||
}
|
||||
|
||||
this.props.onUpdateSelectedPress(changes);
|
||||
};
|
||||
|
||||
@@ -109,15 +159,10 @@ class CollectionFooter extends Component {
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath
|
||||
rootFolderPath,
|
||||
searchOnAdd
|
||||
} = this.state;
|
||||
|
||||
const monitoredOptions = [
|
||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||
{ key: 'monitored', value: translate('Monitored') },
|
||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||
];
|
||||
|
||||
const selectedCount = selectedIds.length;
|
||||
|
||||
return (
|
||||
@@ -125,7 +170,7 @@ class CollectionFooter extends Component {
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('MonitorCollection')}
|
||||
isSaving={isSaving}
|
||||
isSaving={isSaving && monitored !== NO_CHANGE}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
@@ -140,7 +185,7 @@ class CollectionFooter extends Component {
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('MonitorMovies')}
|
||||
isSaving={isSaving}
|
||||
isSaving={isSaving && monitor !== NO_CHANGE}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
@@ -198,10 +243,25 @@ class CollectionFooter extends Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('SearchMoviesOnAdd')}
|
||||
isSaving={isSaving && searchOnAdd !== NO_CHANGE}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
name="searchOnAdd"
|
||||
value={searchOnAdd}
|
||||
values={searchOnAddOptions}
|
||||
isDisabled={!selectedCount}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.buttonContainer}>
|
||||
<div className={styles.buttonContainerContent}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('CollectionsSelectedInterp', [selectedCount])}
|
||||
label={translate('CountCollectionsSelected', { count: selectedCount })}
|
||||
isSaving={false}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { maxBy } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -50,7 +51,7 @@ class FilterBuilderModalContent extends Component {
|
||||
if (id) {
|
||||
dispatchSetFilter({ selectedFilterKey: id });
|
||||
} else {
|
||||
const last = customFilters[customFilters.length -1];
|
||||
const last = maxBy(customFilters, 'id');
|
||||
dispatchSetFilter({ selectedFilterKey: last.id });
|
||||
}
|
||||
|
||||
@@ -108,7 +109,7 @@ class FilterBuilderModalContent extends Component {
|
||||
this.setState({
|
||||
labelErrors: [
|
||||
{
|
||||
message: 'Label is required'
|
||||
message: translate('LabelIsRequired')
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -146,13 +147,13 @@ class FilterBuilderModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Custom Filter
|
||||
{translate('CustomFilter')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.labelContainer}>
|
||||
<div className={styles.label}>
|
||||
Label
|
||||
{translate('Label')}
|
||||
</div>
|
||||
|
||||
<div className={styles.labelInputContainer}>
|
||||
@@ -166,9 +167,7 @@ class FilterBuilderModalContent extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.label}>
|
||||
{translate('Filters')}
|
||||
</div>
|
||||
<div className={styles.label}>{translate('Filters')}</div>
|
||||
|
||||
<div className={styles.rows}>
|
||||
{
|
||||
|
||||
@@ -37,8 +37,8 @@ class CustomFilter extends Component {
|
||||
dispatchSetFilter
|
||||
} = this.props;
|
||||
|
||||
// Assume that delete and then unmounting means the delete was successful.
|
||||
// Moving this check to a ancestor would be more accurate, but would have
|
||||
// Assume that delete and then unmounting means the deletion was successful.
|
||||
// Moving this check to an ancestor would be more accurate, but would have
|
||||
// more boilerplate.
|
||||
if (this.state.isDeleting && id === selectedFilterKey) {
|
||||
dispatchSetFilter({ selectedFilterKey: 'all' });
|
||||
|
||||
@@ -25,7 +25,8 @@ function createMapStateToProps() {
|
||||
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
|
||||
return {
|
||||
key: downloadClient.id,
|
||||
value: downloadClient.name
|
||||
value: downloadClient.name,
|
||||
hint: `(${downloadClient.id})`
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -282,6 +282,7 @@ FormInputGroup.propTypes = {
|
||||
includeNoChange: PropTypes.bool,
|
||||
includeNoChangeDisabled: PropTypes.bool,
|
||||
selectedValueOptions: PropTypes.object,
|
||||
indexerFlags: PropTypes.number,
|
||||
pending: PropTypes.bool,
|
||||
errors: PropTypes.arrayOf(PropTypes.object),
|
||||
warnings: PropTypes.arrayOf(PropTypes.object),
|
||||
|
||||
@@ -4,22 +4,18 @@ import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
interface IndexerFlagsSelectInputProps {
|
||||
name: string;
|
||||
indexerFlags: number;
|
||||
onChange(payload: object): void;
|
||||
}
|
||||
|
||||
const selectIndexerFlagsValues = (selectedFlags: number) =>
|
||||
createSelector(
|
||||
(state: AppState) => state.settings.indexerFlags,
|
||||
(indexerFlags) => {
|
||||
const value = indexerFlags.items
|
||||
.filter(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
(item) => (selectedFlags & item.id) === item.id
|
||||
)
|
||||
.map(({ id }) => id);
|
||||
const value = indexerFlags.items.reduce((acc: number[], { id }) => {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if ((selectedFlags & id) === id) {
|
||||
acc.push(id);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const values = indexerFlags.items.map(({ id, name }) => ({
|
||||
key: id,
|
||||
@@ -33,6 +29,12 @@ const selectIndexerFlagsValues = (selectedFlags: number) =>
|
||||
}
|
||||
);
|
||||
|
||||
interface IndexerFlagsSelectInputProps {
|
||||
name: string;
|
||||
indexerFlags: number;
|
||||
onChange(payload: object): void;
|
||||
}
|
||||
|
||||
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
|
||||
const { indexerFlags, onChange } = props;
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ class TextTagInputConnector extends Component {
|
||||
render() {
|
||||
return (
|
||||
<TagInput
|
||||
delimiters={['Tab', 'Enter', ',']}
|
||||
tagList={[]}
|
||||
onTagAdd={this.onTagAdd}
|
||||
onTagDelete={this.onTagDelete}
|
||||
|
||||
@@ -19,7 +19,7 @@ function ImportListList({ lists, importListList }) {
|
||||
return (
|
||||
<Label
|
||||
key={list.id}
|
||||
kind={kinds.INFO}
|
||||
kind={kinds.SUCCESS}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
{list.name}
|
||||
|
||||
@@ -101,7 +101,7 @@ const links = [
|
||||
to: '/settings/downloadclients'
|
||||
},
|
||||
{
|
||||
title: () => translate('Lists'),
|
||||
title: () => translate('ImportLists'),
|
||||
to: '/settings/importlists'
|
||||
},
|
||||
{
|
||||
@@ -121,7 +121,7 @@ const links = [
|
||||
to: '/settings/general'
|
||||
},
|
||||
{
|
||||
title: () => translate('UI'),
|
||||
title: () => translate('Ui'),
|
||||
to: '/settings/ui'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -329,10 +329,7 @@ class DiscoverMovie extends Component {
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
(view === 'posters' || view === 'overview') &&
|
||||
<PageToolbarSeparator />
|
||||
}
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<DiscoverMovieViewMenu
|
||||
view={view}
|
||||
|
||||
@@ -97,6 +97,8 @@ class DiscoverMovieOverview extends Component {
|
||||
isExisting,
|
||||
isExcluded,
|
||||
isRecommendation,
|
||||
isPopular,
|
||||
isTrending,
|
||||
isSelected,
|
||||
overviewOptions,
|
||||
...otherProps
|
||||
@@ -214,6 +216,26 @@ class DiscoverMovieOverview extends Component {
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isPopular ?
|
||||
<Label
|
||||
kind={kinds.INFO}
|
||||
>
|
||||
{translate('Popular')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isTrending ?
|
||||
<Label
|
||||
kind={kinds.INFO}
|
||||
>
|
||||
{translate('Trending')}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
<ImportListListConnector
|
||||
lists={lists}
|
||||
/>
|
||||
@@ -283,6 +305,8 @@ DiscoverMovieOverview.propTypes = {
|
||||
isExisting: PropTypes.bool.isRequired,
|
||||
isExcluded: PropTypes.bool.isRequired,
|
||||
isRecommendation: PropTypes.bool.isRequired,
|
||||
isPopular: PropTypes.bool.isRequired,
|
||||
isTrending: PropTypes.bool.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
lists: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired
|
||||
|
||||
@@ -57,10 +57,12 @@
|
||||
flex: 0 0 115px;
|
||||
}
|
||||
|
||||
.isTrending,
|
||||
.isPopular,
|
||||
.isRecommendation {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 50px;
|
||||
flex: 0 0 30px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -7,7 +7,9 @@ interface CssExports {
|
||||
'digitalRelease': string;
|
||||
'genres': string;
|
||||
'inCinemas': string;
|
||||
'isPopular': string;
|
||||
'isRecommendation': string;
|
||||
'isTrending': string;
|
||||
'lists': string;
|
||||
'originalLanguage': string;
|
||||
'physicalRelease': string;
|
||||
|
||||
@@ -7,6 +7,7 @@ import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import DiscoverMovieTableOptionsConnector from './DiscoverMovieTableOptionsConnector';
|
||||
import styles from './DiscoverMovieHeader.css';
|
||||
|
||||
@@ -98,6 +99,43 @@ class DiscoverMovieHeader extends Component {
|
||||
<Icon
|
||||
name={icons.RECOMMENDED}
|
||||
size={12}
|
||||
title={translate('Recommendation')}
|
||||
/>
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'isTrending') {
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={true}
|
||||
{...otherProps}
|
||||
>
|
||||
<Icon
|
||||
name={icons.TRENDING}
|
||||
size={12}
|
||||
title={translate('Trending')}
|
||||
/>
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'isPopular') {
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={true}
|
||||
{...otherProps}
|
||||
>
|
||||
<Icon
|
||||
name={icons.POPULAR}
|
||||
size={12}
|
||||
title={translate('Popular')}
|
||||
/>
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
|
||||
@@ -76,10 +76,12 @@
|
||||
flex: 1 0 110px;
|
||||
}
|
||||
|
||||
.isTrending,
|
||||
.isPopular,
|
||||
.isRecommendation {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 50px;
|
||||
flex: 0 0 30px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@@ -95,6 +97,11 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
width: 20px !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.externalLinks {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ interface CssExports {
|
||||
'externalLinks': string;
|
||||
'genres': string;
|
||||
'inCinemas': string;
|
||||
'isPopular': string;
|
||||
'isRecommendation': string;
|
||||
'isTrending': string;
|
||||
'lists': string;
|
||||
'originalLanguage': string;
|
||||
'physicalRelease': string;
|
||||
@@ -21,6 +23,7 @@ interface CssExports {
|
||||
'runtime': string;
|
||||
'sortTitle': string;
|
||||
'status': string;
|
||||
'statusIcon': string;
|
||||
'studio': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
|
||||
@@ -82,6 +82,8 @@ class DiscoverMovieRow extends Component {
|
||||
isExisting,
|
||||
isExcluded,
|
||||
isRecommendation,
|
||||
isTrending,
|
||||
isPopular,
|
||||
isSelected,
|
||||
lists,
|
||||
onSelectedChange
|
||||
@@ -305,6 +307,7 @@ class DiscoverMovieRow extends Component {
|
||||
{
|
||||
isRecommendation ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.RECOMMENDED}
|
||||
size={12}
|
||||
title={translate('MovieIsRecommend')}
|
||||
@@ -315,6 +318,46 @@ class DiscoverMovieRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'isTrending') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
{
|
||||
isTrending ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.TRENDING}
|
||||
size={12}
|
||||
title={translate('MovieIsTrending')}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'isPopular') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
{
|
||||
isPopular ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.POPULAR}
|
||||
size={12}
|
||||
title={translate('MovieIsPopular')}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
@@ -404,6 +447,8 @@ DiscoverMovieRow.propTypes = {
|
||||
isExcluded: PropTypes.bool.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
isRecommendation: PropTypes.bool.isRequired,
|
||||
isPopular: PropTypes.bool.isRequired,
|
||||
isTrending: PropTypes.bool.isRequired,
|
||||
lists: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import {
|
||||
faArrowCircleLeft as fasArrowCircleLeft,
|
||||
faArrowCircleRight as fasArrowCircleRight,
|
||||
faArrowTrendUp as fasArrowTrendUp,
|
||||
faAsterisk as fasAsterisk,
|
||||
faBackward as fasBackward,
|
||||
faBan as fasBan,
|
||||
@@ -233,6 +234,7 @@ export const TAGS = fasTags;
|
||||
export const TBA = fasQuestionCircle;
|
||||
export const TEST = fasVial;
|
||||
export const TRANSLATE = fasLanguage;
|
||||
export const TRENDING = fasArrowTrendUp;
|
||||
export const UNGROUP = farObjectUngroup;
|
||||
export const UNKNOWN = fasQuestion;
|
||||
export const UNMONITORED = farBookmark;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent';
|
||||
|
||||
interface SelectIndexerFlagsModalProps {
|
||||
isOpen: boolean;
|
||||
indexerFlags: number;
|
||||
modalTitle: string;
|
||||
onIndexerFlagsSelect(indexerFlags: number): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
function SelectIndexerFlagsModal(props: SelectIndexerFlagsModalProps) {
|
||||
const {
|
||||
isOpen,
|
||||
indexerFlags,
|
||||
modalTitle,
|
||||
onIndexerFlagsSelect,
|
||||
onModalClose,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<SelectIndexerFlagsModalContent
|
||||
indexerFlags={indexerFlags}
|
||||
modalTitle={modalTitle}
|
||||
onIndexerFlagsSelect={onIndexerFlagsSelect}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectIndexerFlagsModal;
|
||||
@@ -0,0 +1,7 @@
|
||||
.modalBody {
|
||||
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
7
frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css.d.ts
vendored
Normal file
7
frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'modalBody': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
@@ -0,0 +1,75 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './SelectIndexerFlagsModalContent.css';
|
||||
|
||||
interface SelectIndexerFlagsModalContentProps {
|
||||
indexerFlags: number;
|
||||
modalTitle: string;
|
||||
onIndexerFlagsSelect(indexerFlags: number): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
function SelectIndexerFlagsModalContent(
|
||||
props: SelectIndexerFlagsModalContentProps
|
||||
) {
|
||||
const { modalTitle, onIndexerFlagsSelect, onModalClose } = props;
|
||||
const [indexerFlags, setIndexerFlags] = useState(props.indexerFlags);
|
||||
|
||||
const onIndexerFlagsChange = useCallback(
|
||||
({ value }: { value: number }) => {
|
||||
setIndexerFlags(value);
|
||||
},
|
||||
[setIndexerFlags]
|
||||
);
|
||||
|
||||
const onIndexerFlagsSelectWrapper = useCallback(() => {
|
||||
onIndexerFlagsSelect(indexerFlags);
|
||||
}, [indexerFlags, onIndexerFlagsSelect]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('SetIndexerFlagsModalTitle', { modalTitle })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody
|
||||
className={styles.modalBody}
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('IndexerFlags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.INDEXER_FLAGS_SELECT}
|
||||
name="indexerFlags"
|
||||
indexerFlags={indexerFlags}
|
||||
autoFocus={true}
|
||||
onChange={onIndexerFlagsChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button kind={kinds.SUCCESS} onPress={onIndexerFlagsSelectWrapper}>
|
||||
{translate('SetIndexerFlags')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectIndexerFlagsModalContent;
|
||||
@@ -26,6 +26,7 @@ import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||
import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
|
||||
import ImportMode from 'InteractiveImport/ImportMode';
|
||||
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||
import InteractiveImport, {
|
||||
InteractiveImportCommandOptions,
|
||||
} from 'InteractiveImport/InteractiveImport';
|
||||
@@ -59,7 +60,13 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import InteractiveImportRow from './InteractiveImportRow';
|
||||
import styles from './InteractiveImportModalContent.css';
|
||||
|
||||
type SelectType = 'select' | 'movie' | 'releaseGroup' | 'quality' | 'language';
|
||||
type SelectType =
|
||||
| 'select'
|
||||
| 'movie'
|
||||
| 'releaseGroup'
|
||||
| 'quality'
|
||||
| 'language'
|
||||
| 'indexerFlags';
|
||||
|
||||
type FilterExistingFiles = 'all' | 'new';
|
||||
|
||||
@@ -113,6 +120,15 @@ const COLUMNS = [
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'indexerFlags',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.FLAG,
|
||||
title: () => translate('IndexerFlags'),
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, {
|
||||
@@ -242,25 +258,6 @@ function InteractiveImportModalContent(
|
||||
const [interactiveImportErrorMessage, setInteractiveImportErrorMessage] =
|
||||
useState<string | null>(null);
|
||||
const [selectState, setSelectState] = useSelectState();
|
||||
const [bulkSelectOptions, setBulkSelectOptions] = useState([
|
||||
{
|
||||
key: 'select',
|
||||
value: translate('SelectDropdown'),
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: 'quality',
|
||||
value: translate('SelectQuality'),
|
||||
},
|
||||
{
|
||||
key: 'releaseGroup',
|
||||
value: translate('SelectReleaseGroup'),
|
||||
},
|
||||
{
|
||||
key: 'language',
|
||||
value: translate('SelectLanguage'),
|
||||
},
|
||||
]);
|
||||
const { allSelected, allUnselected, selectedState } = selectState;
|
||||
const previousIsDeleting = usePrevious(isDeleting);
|
||||
const dispatch = useDispatch();
|
||||
@@ -276,26 +273,60 @@ function InteractiveImportModalContent(
|
||||
}
|
||||
}
|
||||
|
||||
const showIndexerFlags = items.some((item) => item.indexerFlags);
|
||||
|
||||
if (!showIndexerFlags) {
|
||||
const indexerFlagsColumn = result.find((c) => c.name === 'indexerFlags');
|
||||
|
||||
if (indexerFlagsColumn) {
|
||||
indexerFlagsColumn.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [showMovie]);
|
||||
}, [showMovie, items]);
|
||||
|
||||
const selectedIds: number[] = useMemo(() => {
|
||||
return getSelectedIds(selectedState);
|
||||
}, [selectedState]);
|
||||
|
||||
const bulkSelectOptions = useMemo(() => {
|
||||
const options = [
|
||||
{
|
||||
key: 'select',
|
||||
value: translate('SelectDropdown'),
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: 'quality',
|
||||
value: translate('SelectQuality'),
|
||||
},
|
||||
{
|
||||
key: 'releaseGroup',
|
||||
value: translate('SelectReleaseGroup'),
|
||||
},
|
||||
{
|
||||
key: 'language',
|
||||
value: translate('SelectLanguage'),
|
||||
},
|
||||
{
|
||||
key: 'indexerFlags',
|
||||
value: translate('SelectIndexerFlags'),
|
||||
},
|
||||
];
|
||||
|
||||
if (allowMovieChange) {
|
||||
options.splice(1, 0, {
|
||||
key: 'movie',
|
||||
value: translate('SelectMovie'),
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [allowMovieChange]);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (allowMovieChange) {
|
||||
const newBulkSelectOptions = [...bulkSelectOptions];
|
||||
|
||||
newBulkSelectOptions.splice(1, 0, {
|
||||
key: 'movie',
|
||||
value: translate('SelectMovie'),
|
||||
});
|
||||
|
||||
setBulkSelectOptions(newBulkSelectOptions);
|
||||
}
|
||||
|
||||
if (initialSortKey) {
|
||||
const sortProps: { sortKey: string; sortDirection?: string } = {
|
||||
sortKey: initialSortKey,
|
||||
@@ -415,7 +446,14 @@ function InteractiveImportModalContent(
|
||||
const isSelected = selectedIds.indexOf(item.id) > -1;
|
||||
|
||||
if (isSelected) {
|
||||
const { movie, releaseGroup, quality, languages, movieFileId } = item;
|
||||
const {
|
||||
movie,
|
||||
releaseGroup,
|
||||
quality,
|
||||
languages,
|
||||
indexerFlags,
|
||||
movieFileId,
|
||||
} = item;
|
||||
|
||||
if (!movie) {
|
||||
setInteractiveImportErrorMessage(
|
||||
@@ -449,6 +487,7 @@ function InteractiveImportModalContent(
|
||||
releaseGroup,
|
||||
quality,
|
||||
languages,
|
||||
indexerFlags,
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -462,6 +501,7 @@ function InteractiveImportModalContent(
|
||||
releaseGroup,
|
||||
quality,
|
||||
languages,
|
||||
indexerFlags,
|
||||
downloadId,
|
||||
movieFileId,
|
||||
});
|
||||
@@ -619,6 +659,22 @@ function InteractiveImportModalContent(
|
||||
[selectedIds, dispatch]
|
||||
);
|
||||
|
||||
const onIndexerFlagsSelect = useCallback(
|
||||
(indexerFlags: number) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItems({
|
||||
ids: selectedIds,
|
||||
indexerFlags,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(reprocessInteractiveImportItems({ ids: selectedIds }));
|
||||
|
||||
setSelectModalOpen(null);
|
||||
},
|
||||
[selectedIds, dispatch]
|
||||
);
|
||||
|
||||
const errorMessage = getErrorMessage(
|
||||
error,
|
||||
translate('InteractiveImportLoadError')
|
||||
@@ -793,6 +849,14 @@ function InteractiveImportModalContent(
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
|
||||
<SelectIndexerFlagsModal
|
||||
isOpen={selectModalOpen === 'indexerFlags'}
|
||||
indexerFlags={0}
|
||||
modalTitle={modalTitle}
|
||||
onIndexerFlagsSelect={onIndexerFlagsSelect}
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmDeleteModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
|
||||
@@ -8,11 +8,13 @@ import Column from 'Components/Table/Column';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
||||
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import Language from 'Language/Language';
|
||||
import IndexerFlags from 'Movie/IndexerFlags';
|
||||
import Movie from 'Movie/Movie';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
@@ -30,7 +32,12 @@ import translate from 'Utilities/String/translate';
|
||||
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
|
||||
import styles from './InteractiveImportRow.css';
|
||||
|
||||
type SelectType = 'movie' | 'releaseGroup' | 'quality' | 'language';
|
||||
type SelectType =
|
||||
| 'movie'
|
||||
| 'releaseGroup'
|
||||
| 'quality'
|
||||
| 'language'
|
||||
| 'indexerFlags';
|
||||
|
||||
type SelectedChangeProps = SelectStateInputProps & {
|
||||
hasMovieFileId: boolean;
|
||||
@@ -47,6 +54,7 @@ interface InteractiveImportRowProps {
|
||||
size: number;
|
||||
customFormats?: object[];
|
||||
customFormatScore?: number;
|
||||
indexerFlags: number;
|
||||
rejections: Rejection[];
|
||||
columns: Column[];
|
||||
movieFileId?: number;
|
||||
@@ -69,6 +77,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
size,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
indexerFlags,
|
||||
rejections,
|
||||
isSelected,
|
||||
modalTitle,
|
||||
@@ -84,6 +93,10 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
() => columns.find((c) => c.name === 'movie')?.isVisible ?? false,
|
||||
[columns]
|
||||
);
|
||||
const isIndexerFlagsColumnVisible = useMemo(
|
||||
() => columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false,
|
||||
[columns]
|
||||
);
|
||||
|
||||
const [selectModalOpen, setSelectModalOpen] = useState<SelectType | null>(
|
||||
null
|
||||
@@ -223,12 +236,34 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
|
||||
);
|
||||
|
||||
const onSelectIndexerFlagsPress = useCallback(() => {
|
||||
setSelectModalOpen('indexerFlags');
|
||||
}, [setSelectModalOpen]);
|
||||
|
||||
const onIndexerFlagsSelect = useCallback(
|
||||
(indexerFlags: number) => {
|
||||
dispatch(
|
||||
updateInteractiveImportItem({
|
||||
id,
|
||||
indexerFlags,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(reprocessInteractiveImportItems({ ids: [id] }));
|
||||
|
||||
setSelectModalOpen(null);
|
||||
selectRowAfterChange();
|
||||
},
|
||||
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
|
||||
);
|
||||
|
||||
const movieTitle = movie ? movie.title : '';
|
||||
|
||||
const showMoviePlaceholder = isSelected && !movie;
|
||||
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
||||
const showQualityPlaceholder = isSelected && !quality;
|
||||
const showLanguagePlaceholder = isSelected && !languages;
|
||||
const showIndexerFlagsPlaceholder = isSelected && !indexerFlags;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
@@ -311,6 +346,28 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
) : null}
|
||||
</TableRowCell>
|
||||
|
||||
{isIndexerFlagsColumnVisible ? (
|
||||
<TableRowCellButton
|
||||
title={translate('ClickToChangeIndexerFlags')}
|
||||
onPress={onSelectIndexerFlagsPress}
|
||||
>
|
||||
{showIndexerFlagsPlaceholder ? (
|
||||
<InteractiveImportRowCellPlaceholder isOptional={true} />
|
||||
) : (
|
||||
<>
|
||||
{indexerFlags ? (
|
||||
<Popover
|
||||
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||
title={translate('IndexerFlags')}
|
||||
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</TableRowCellButton>
|
||||
) : null}
|
||||
|
||||
<TableRowCell>
|
||||
{rejections.length ? (
|
||||
<Popover
|
||||
@@ -361,6 +418,14 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
|
||||
onLanguagesSelect={onLanguagesSelect}
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
|
||||
<SelectIndexerFlagsModal
|
||||
isOpen={selectModalOpen === 'indexerFlags'}
|
||||
indexerFlags={indexerFlags ?? 0}
|
||||
modalTitle={modalTitle}
|
||||
onIndexerFlagsSelect={onIndexerFlagsSelect}
|
||||
onModalClose={onSelectModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface InteractiveImportCommandOptions {
|
||||
releaseGroup?: string;
|
||||
quality: QualityModel;
|
||||
languages: Language[];
|
||||
indexerFlags: number;
|
||||
downloadId?: string;
|
||||
movieFileId?: number;
|
||||
}
|
||||
@@ -27,6 +28,7 @@ interface InteractiveImport extends ModelBase {
|
||||
movie?: Movie;
|
||||
qualityWeight: number;
|
||||
customFormats: object[];
|
||||
indexerFlags: number;
|
||||
rejections: Rejection[];
|
||||
movieFileId?: number;
|
||||
}
|
||||
|
||||
@@ -88,13 +88,6 @@ const columns = [
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, {
|
||||
@@ -104,6 +97,13 @@ const columns = [
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
}
|
||||
|
||||
.rejected,
|
||||
.indexerFlags,
|
||||
.download {
|
||||
.indexerFlags {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 50px;
|
||||
|
||||
@@ -90,8 +90,8 @@ interface InteractiveSearchRowProps {
|
||||
customFormats: CustomFormat[];
|
||||
customFormatScore: number;
|
||||
mappedMovieId?: number;
|
||||
rejections: string[];
|
||||
indexerFlags: string[];
|
||||
rejections: string[];
|
||||
downloadAllowed: boolean;
|
||||
isGrabbing: boolean;
|
||||
isGrabbed: boolean;
|
||||
@@ -125,8 +125,8 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||
customFormatScore,
|
||||
customFormats,
|
||||
mappedMovieId,
|
||||
rejections = [],
|
||||
indexerFlags = [],
|
||||
rejections = [],
|
||||
downloadAllowed,
|
||||
isGrabbing = false,
|
||||
isGrabbed = false,
|
||||
@@ -276,7 +276,7 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||
customFormats.length
|
||||
)}
|
||||
tooltip={<MovieFormats formats={customFormats} />}
|
||||
position={tooltipPositions.TOP}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class DeleteMovieModalContent extends Component {
|
||||
type={inputTypes.CHECK}
|
||||
name="addImportExclusion"
|
||||
value={addImportExclusion}
|
||||
helpText={translate('AddImportExclusionHelpText')}
|
||||
helpText={translate('AddListExclusionMovieHelpText')}
|
||||
kind={kinds.DANGER}
|
||||
onChange={onDeleteOptionChange}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -142,10 +143,10 @@ class MovieCastPoster extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.title}>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{personName}
|
||||
</div>
|
||||
<div className={styles.title}>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{character}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -142,10 +143,10 @@ class MovieCrewPoster extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.title}>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{personName}
|
||||
</div>
|
||||
<div className={styles.title}>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{job}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
.links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => {
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('RSSSync')}
|
||||
label={translate('RssSync')}
|
||||
iconName={icons.RSS}
|
||||
isSpinning={isRssSyncExecuting}
|
||||
isDisabled={hasNoMovie}
|
||||
|
||||
@@ -98,7 +98,7 @@ function DeleteMovieModalContent(props: DeleteMovieModalContentProps) {
|
||||
type={inputTypes.CHECK}
|
||||
name="addImportExclusion"
|
||||
value={addImportExclusion}
|
||||
helpText={translate('AddImportExclusionHelpText')}
|
||||
helpText={translate('AddListExclusionMovieHelpText')}
|
||||
onChange={onDeleteOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
26
frontend/src/Movie/IndexerFlags.tsx
Normal file
26
frontend/src/Movie/IndexerFlags.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import createIndexerFlagsSelector from 'Store/Selectors/createIndexerFlagsSelector';
|
||||
|
||||
interface IndexerFlagsProps {
|
||||
indexerFlags: number;
|
||||
}
|
||||
|
||||
function IndexerFlags({ indexerFlags = 0 }: IndexerFlagsProps) {
|
||||
const allIndexerFlags = useSelector(createIndexerFlagsSelector);
|
||||
|
||||
const flags = allIndexerFlags.items.filter(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
(item) => (indexerFlags & item.id) === item.id
|
||||
);
|
||||
|
||||
return flags.length ? (
|
||||
<ul>
|
||||
{flags.map((flag, index) => {
|
||||
return <li key={index}>{flag.name}</li>;
|
||||
})}
|
||||
</ul>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default IndexerFlags;
|
||||
@@ -57,3 +57,9 @@
|
||||
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.indexerFlags {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ interface CssExports {
|
||||
'dateAdded': string;
|
||||
'download': string;
|
||||
'formats': string;
|
||||
'indexerFlags': string;
|
||||
'language': string;
|
||||
'languages': string;
|
||||
'quality': string;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import IndexerFlags from 'Movie/IndexerFlags';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
@@ -82,6 +85,7 @@ class MovieFileEditorRow extends Component {
|
||||
qualityCutoffNotMet,
|
||||
customFormats,
|
||||
customFormatScore,
|
||||
indexerFlags,
|
||||
languages,
|
||||
dateAdded,
|
||||
columns
|
||||
@@ -143,12 +147,30 @@ class MovieFileEditorRow extends Component {
|
||||
customFormats.length
|
||||
)}
|
||||
tooltip={<MovieFormats formats={customFormats} />}
|
||||
position={tooltipPositions.TOP}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'indexerFlags') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.indexerFlags}
|
||||
>
|
||||
{indexerFlags ? (
|
||||
<Popover
|
||||
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
|
||||
title={translate('IndexerFlags')}
|
||||
body={<IndexerFlags indexerFlags={indexerFlags} />}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
) : null}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'languages') {
|
||||
return (
|
||||
<TableRowCell
|
||||
@@ -363,6 +385,7 @@ MovieFileEditorRow.propTypes = {
|
||||
releaseGroup: PropTypes.string,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFormatScore: PropTypes.number.isRequired,
|
||||
indexerFlags: PropTypes.number.isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
mediaInfo: PropTypes.object,
|
||||
@@ -372,7 +395,8 @@ MovieFileEditorRow.propTypes = {
|
||||
};
|
||||
|
||||
MovieFileEditorRow.defaultProps = {
|
||||
customFormats: []
|
||||
customFormats: [],
|
||||
indexerFlags: 0
|
||||
};
|
||||
|
||||
export default MovieFileEditorRow;
|
||||
|
||||
@@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import MovieFileEditorRow from './MovieFileEditorRow';
|
||||
import styles from './MovieFileEditorTableContent.css';
|
||||
|
||||
@@ -14,6 +16,9 @@ class MovieFileEditorTableContent extends Component {
|
||||
const {
|
||||
items,
|
||||
columns,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
onSortPress,
|
||||
onTableOptionChange
|
||||
} = this.props;
|
||||
|
||||
@@ -22,7 +27,7 @@ class MovieFileEditorTableContent extends Component {
|
||||
{
|
||||
!items.length &&
|
||||
<div className={styles.blankpad}>
|
||||
No movie files to manage.
|
||||
{translate('NoMovieFilesToManage')}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -30,6 +35,9 @@ class MovieFileEditorTableContent extends Component {
|
||||
!!items.length &&
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<TableBody>
|
||||
@@ -59,7 +67,10 @@ MovieFileEditorTableContent.propTypes = {
|
||||
isDeleting: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string.isRequired,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onDeletePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { deleteMovieFile, setMovieFilesTableOption, updateMovieFiles } from 'Store/Actions/movieFileActions';
|
||||
import { deleteMovieFile, setMovieFilesSort, setMovieFilesTableOption } from 'Store/Actions/movieFileActions';
|
||||
import { fetchLanguages, fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||
import getQualities from 'Utilities/Quality/getQualities';
|
||||
import MovieFileEditorTableContent from './MovieFileEditorTableContent';
|
||||
@@ -11,7 +12,7 @@ import MovieFileEditorTableContent from './MovieFileEditorTableContent';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { movieId }) => movieId,
|
||||
(state) => state.movieFiles,
|
||||
createClientSideCollectionSelector('movieFiles'),
|
||||
(state) => state.settings.languages,
|
||||
(state) => state.settings.qualityProfiles,
|
||||
createMovieSelector(),
|
||||
@@ -23,13 +24,13 @@ function createMapStateToProps() {
|
||||
) => {
|
||||
const languages = languageProfiles.items;
|
||||
const qualities = getQualities(qualityProfiles.schema.items);
|
||||
const filesForMovie = movieFiles.items.filter((obj) => {
|
||||
return obj.movieId === movieId;
|
||||
});
|
||||
const filesForMovie = movieFiles.items.filter((file) => file.movieId === movieId);
|
||||
|
||||
return {
|
||||
items: filesForMovie,
|
||||
columns: movieFiles.columns,
|
||||
sortKey: movieFiles.sortKey,
|
||||
sortDirection: movieFiles.sortDirection,
|
||||
isDeleting: movieFiles.isDeleting,
|
||||
isSaving: movieFiles.isSaving,
|
||||
error: null,
|
||||
@@ -40,31 +41,13 @@ function createMapStateToProps() {
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchFetchQualityProfileSchema(name, path) {
|
||||
dispatch(fetchQualityProfileSchema());
|
||||
},
|
||||
|
||||
dispatchFetchLanguages(name, path) {
|
||||
dispatch(fetchLanguages());
|
||||
},
|
||||
|
||||
dispatchUpdateMovieFiles(updateProps) {
|
||||
dispatch(updateMovieFiles(updateProps));
|
||||
},
|
||||
|
||||
onTableOptionChange(payload) {
|
||||
dispatch(setMovieFilesTableOption(payload));
|
||||
},
|
||||
|
||||
onDeletePress(movieFileId) {
|
||||
dispatch(deleteMovieFile({
|
||||
id: movieFileId
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = {
|
||||
fetchQualityProfileSchema,
|
||||
fetchLanguages,
|
||||
deleteMovieFile,
|
||||
setMovieFilesTableOption,
|
||||
setMovieFilesSort
|
||||
};
|
||||
|
||||
class MovieFileEditorTableContentConnector extends Component {
|
||||
|
||||
@@ -72,24 +55,40 @@ class MovieFileEditorTableContentConnector extends Component {
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatchFetchLanguages();
|
||||
this.props.dispatchFetchQualityProfileSchema();
|
||||
this.props.fetchLanguages();
|
||||
this.props.fetchQualityProfileSchema();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onDeletePress = (movieFileId) => {
|
||||
this.props.deleteMovieFile({
|
||||
id: movieFileId
|
||||
});
|
||||
};
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setMovieFilesTableOption(payload);
|
||||
};
|
||||
|
||||
onSortPress = (sortKey, sortDirection) => {
|
||||
this.props.setMovieFilesSort({
|
||||
sortKey,
|
||||
sortDirection
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatchFetchLanguages,
|
||||
dispatchFetchQualityProfileSchema,
|
||||
dispatchUpdateMovieFiles,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<MovieFileEditorTableContent
|
||||
{...otherProps}
|
||||
{...this.props}
|
||||
onDeletePress={this.onDeletePress}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onSortPress={this.onSortPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -99,9 +98,11 @@ MovieFileEditorTableContentConnector.propTypes = {
|
||||
movieId: PropTypes.number.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
dispatchFetchLanguages: PropTypes.func.isRequired,
|
||||
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||
dispatchUpdateMovieFiles: PropTypes.func.isRequired
|
||||
fetchLanguages: PropTypes.func.isRequired,
|
||||
fetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||
deleteMovieFile: PropTypes.func.isRequired,
|
||||
setMovieFilesTableOption: PropTypes.func.isRequired,
|
||||
setMovieFilesSort: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieFileEditorTableContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieFileEditorTableContentConnector);
|
||||
|
||||
@@ -46,7 +46,7 @@ class ExtraFileTableContent extends Component {
|
||||
{
|
||||
!items.length &&
|
||||
<div className={styles.blankpad}>
|
||||
No extra files to manage.
|
||||
{translate('NoExtraFilesToManage')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,7 @@ function createMapStateToProps() {
|
||||
movieId,
|
||||
extraFiles
|
||||
) => {
|
||||
const filesForMovie = extraFiles.items.filter((obj) => {
|
||||
return obj.movieId === movieId;
|
||||
});
|
||||
const filesForMovie = extraFiles.items.filter((file) => file.movieId === movieId);
|
||||
|
||||
return {
|
||||
items: filesForMovie,
|
||||
@@ -26,11 +24,6 @@ function createMapStateToProps() {
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
};
|
||||
}
|
||||
|
||||
class ExtraFileTableContentConnector extends Component {
|
||||
|
||||
//
|
||||
@@ -53,4 +46,4 @@ ExtraFileTableContentConnector.propTypes = {
|
||||
movieId: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(ExtraFileTableContentConnector);
|
||||
export default connect(createMapStateToProps, null)(ExtraFileTableContentConnector);
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface MovieFile extends ModelBase {
|
||||
languages: Language[];
|
||||
quality: QualityModel;
|
||||
customFormats: CustomFormat[];
|
||||
indexerFlags: number;
|
||||
mediaInfo: MediaInfo;
|
||||
qualityCutoffNotMet: boolean;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ class OrganizePreviewModalContent extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>{translate('OrganizeLoadError')}</div>
|
||||
<Alert kind={kinds.DANGER}>{translate('OrganizeLoadError')}</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -6,11 +6,12 @@ import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import ParseToolbarButton from 'Parse/ParseToolbarButton';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
|
||||
|
||||
function CustomFormatSettingsPage() {
|
||||
return (
|
||||
<PageContent title="Custom Format Settings">
|
||||
<PageContent title={translate('CustomFormatsSettings')}>
|
||||
<SettingsToolbarConnector
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
||||
@@ -152,7 +152,7 @@ class CustomFormat extends Component {
|
||||
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteCustomFormat')}
|
||||
message={translate('DeleteCustomFormatMessageText', { customFormatName: name })}
|
||||
message={translate('DeleteCustomFormatMessageText', { name })}
|
||||
confirmLabel={translate('Delete')}
|
||||
isSpinning={isDeleting}
|
||||
onConfirm={this.onConfirmDeleteCustomFormat}
|
||||
|
||||
@@ -61,8 +61,8 @@ class CustomFormats extends Component {
|
||||
return (
|
||||
<FieldSet legend={translate('CustomFormats')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadCustomFormats')}
|
||||
{...otherProps}c={true}
|
||||
errorMessage={translate('CustomFormatsLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.customFormats}>
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Card from 'Components/Card';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
@@ -112,9 +113,9 @@ class EditCustomFormatModalContent extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToAddANewCustomFormatPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddCustomFormatError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ class ExportCustomFormatModalContent extends Component {
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadCustomFormats')}
|
||||
{translate('CustomFormatsLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class ImportCustomFormatModalContent extends Component {
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadCustomFormats')}
|
||||
{translate('CustomFormatsLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,9 @@ class ImportCustomFormatModalContentConnector extends Component {
|
||||
const selectedImplementation = _.find(this.props.specificationSchema, { implementation: spec.implementation });
|
||||
|
||||
if (!selectedImplementation) {
|
||||
throw new Error(translate('CustomFormatUnknownCondition', [spec.implementation]));
|
||||
throw new Error(translate('CustomFormatUnknownCondition', {
|
||||
implementation: spec.implementation
|
||||
}));
|
||||
}
|
||||
|
||||
this.props.selectCustomFormatSpecificationSchema({ implementation: spec.implementation });
|
||||
@@ -109,7 +111,10 @@ class ImportCustomFormatModalContentConnector extends Component {
|
||||
for (const [key, value] of Object.entries(fields)) {
|
||||
const field = _.find(schema.fields, { name: key });
|
||||
if (!field) {
|
||||
throw new Error(translate('CustomFormatUnknownConditionOption', [key, schema.implementationName]));
|
||||
throw new Error(translate('CustomFormatUnknownConditionOption', {
|
||||
key,
|
||||
implementation: schema.implementationName
|
||||
}));
|
||||
}
|
||||
|
||||
this.props.setCustomFormatSpecificationFieldValue({ name: key, value });
|
||||
|
||||
@@ -42,9 +42,9 @@ class AddSpecificationModalContent extends Component {
|
||||
|
||||
{
|
||||
!isSchemaFetching && !!schemaError &&
|
||||
<div>
|
||||
{translate('UnableToAddANewConditionPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddConditionError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -53,10 +53,10 @@ class AddSpecificationModalContent extends Component {
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
{translate('RadarrSupportsCustomConditionsAgainstTheReleasePropertiesBelow')}
|
||||
{translate('SupportedCustomConditions')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('VisitGithubCustomFormatsAphrodite')}
|
||||
{translate('VisitTheWikiForMoreDetails')}
|
||||
<Link to="https://wiki.servarr.com/radarr/settings#custom-formats-2">{translate('Wiki')}</Link>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
@@ -7,8 +7,8 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Link from 'Components/Link/Link';
|
||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
@@ -52,12 +52,13 @@ function EditSpecificationModalContent(props) {
|
||||
fields && fields.some((x) => x.label === translate('CustomFormatsSpecificationRegularExpression')) &&
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{ __html: translate('ThisConditionMatchesUsingRegularExpressions', ['<code>\\^$.|?*+()[{</code>', '<code>\\</code>']) }} />
|
||||
{translate('MoreDetails')} <Link to="https://www.regular-expressions.info/tutorial.html">{translate('LinkHere')}</Link>
|
||||
<InlineMarkdown data={translate('ConditionUsingRegularExpressions')} />
|
||||
</div>
|
||||
<div>
|
||||
{translate('RegularExpressionsCanBeTested')}
|
||||
<Link to="http://regexstorm.net/tester">{translate('LinkHere')}</Link>
|
||||
<InlineMarkdown data={translate('RegularExpressionsTutorialLink')} />
|
||||
</div>
|
||||
<div>
|
||||
<InlineMarkdown data={translate('RegularExpressionsCanBeTested')} />
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
@@ -99,7 +100,7 @@ function EditSpecificationModalContent(props) {
|
||||
type={inputTypes.CHECK}
|
||||
name="negate"
|
||||
{...negate}
|
||||
helpText={translate('NegateHelpText', [implementationName])}
|
||||
helpText={translate('NegateHelpText', { implementationName })}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -113,7 +114,7 @@ function EditSpecificationModalContent(props) {
|
||||
type={inputTypes.CHECK}
|
||||
name="required"
|
||||
{...required}
|
||||
helpText={translate('RequiredHelpText', [implementationName, implementationName])}
|
||||
helpText={translate('RequiredHelpText', { implementationName })}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -43,9 +43,9 @@ class AddDownloadClientModalContent extends Component {
|
||||
|
||||
{
|
||||
!isSchemaFetching && !!schemaError &&
|
||||
<div>
|
||||
{translate('UnableToAddANewDownloadClientPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddDownloadClientError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -54,10 +54,10 @@ class AddDownloadClientModalContent extends Component {
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
{translate('RadarrSupportsAnyDownloadClient')}
|
||||
{translate('SupportedDownloadClients')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('ForMoreInformationOnTheIndividualDownloadClients')}
|
||||
{translate('SupportedDownloadClientsMoreInfo')}
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class DownloadClient extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
onDeleteDownloadClientModalClose= () => {
|
||||
onDeleteDownloadClientModalClose = () => {
|
||||
this.setState({ isDeleteDownloadClientModalOpen: false });
|
||||
};
|
||||
|
||||
|
||||
@@ -69,9 +69,9 @@ class EditDownloadClientModalContent extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToAddANewDownloadClientPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddDownloadClientError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -147,7 +147,7 @@ class EditDownloadClientModalContent extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
helpText={translate('DownloadClientTagHelpText')}
|
||||
helpText={translate('DownloadClientMovieTagHelpText')}
|
||||
{...tags}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
@@ -184,7 +184,6 @@ class EditDownloadClientModalContent extends Component {
|
||||
</FormGroup>
|
||||
}
|
||||
</FieldSet>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
@@ -30,7 +30,7 @@ function DownloadClientOptions(props) {
|
||||
{
|
||||
!isFetching && error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadDownloadClientOptions')}
|
||||
{translate('DownloadClientOptionsLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ function DownloadClientOptions(props) {
|
||||
hasSettings && !isFetching && !error && advancedSettings &&
|
||||
<div>
|
||||
<FieldSet legend={translate('CompletedDownloadHandling')}>
|
||||
|
||||
<Form>
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -52,9 +53,9 @@ function EditRemotePathMappingModalContent(props) {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToAddANewRemotePathMappingPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddRemotePathMappingError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -66,7 +67,7 @@ function EditRemotePathMappingModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="host"
|
||||
helpText={translate('SettingsRemotePathMappingHostHelpText')}
|
||||
helpText={translate('RemotePathMappingHostHelpText')}
|
||||
{...host}
|
||||
values={downloadClientHosts}
|
||||
onChange={onInputChange}
|
||||
@@ -74,24 +75,24 @@ function EditRemotePathMappingModalContent(props) {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SettingsRemotePathMappingRemotePath')}</FormLabel>
|
||||
<FormLabel>{translate('RemotePath')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="remotePath"
|
||||
helpText={translate('SettingsRemotePathMappingRemotePathHelpText')}
|
||||
helpText={translate('RemotePathMappingRemotePathHelpText')}
|
||||
{...remotePath}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SettingsRemotePathMappingLocalPath')}</FormLabel>
|
||||
<FormLabel>{translate('LocalPath')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PATH}
|
||||
name="localPath"
|
||||
helpText={translate('SettingsRemotePathMappingLocalPathHelpText')}
|
||||
helpText={translate('RemotePathMappingLocalPathHelpText')}
|
||||
{...localPath}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -55,7 +54,7 @@ function createRemotePathMappingSelector() {
|
||||
items
|
||||
} = remotePathMappings;
|
||||
|
||||
const mapping = id ? _.find(items, { id }) : newRemotePathMapping;
|
||||
const mapping = id ? items.find((i) => i.id === id) : newRemotePathMapping;
|
||||
const settings = selectSettings(mapping, pendingChanges, saveError);
|
||||
|
||||
return {
|
||||
|
||||
@@ -49,12 +49,12 @@ class RemotePathMappings extends Component {
|
||||
return (
|
||||
<FieldSet legend={translate('RemotePathMappings')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadRemotePathMappings')}
|
||||
errorMessage={translate('RemotePathMappingsLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<InlineMarkdown data={translate('RemotePathMappingsInfo', { app: 'Radarr', wikiLink: 'https://wiki.servarr.com/radarr/settings#remote-path-mappings' })} />
|
||||
<InlineMarkdown data={translate('RemotePathMappingsInfo', { wikiLink: 'https://wiki.servarr.com/radarr/settings#remote-path-mappings' })} />
|
||||
</Alert>
|
||||
|
||||
<div className={styles.remotePathMappingsHeader}>
|
||||
|
||||
@@ -22,6 +22,7 @@ const requiresRestartKeys = [
|
||||
'bindAddress',
|
||||
'port',
|
||||
'urlBase',
|
||||
'instanceName',
|
||||
'enableSsl',
|
||||
'sslPort',
|
||||
'sslCertPath',
|
||||
@@ -125,7 +126,7 @@ class GeneralSettings extends Component {
|
||||
{
|
||||
!isFetching && error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadGeneralSettings')}
|
||||
{translate('GeneralSettingsLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
@@ -186,10 +187,8 @@ class GeneralSettings extends Component {
|
||||
isOpen={this.state.isRestartRequiredModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('RestartRadarr')}
|
||||
message={
|
||||
`Radarr requires a restart to apply changes, do you want to restart now? ${isWindowsService ? 'Depending which user is running the Radarr service you may need to restart Radarr as admin once before the service will start automatically.' : ''}`
|
||||
}
|
||||
cancelLabel={translate('IllRestartLater')}
|
||||
message={`${translate('RestartRequiredToApplyChanges')} ${isWindowsService ? translate('RestartRequiredWindowsService') : ''}`}
|
||||
cancelLabel={translate('RestartLater')}
|
||||
confirmLabel={translate('RestartNow')}
|
||||
onConfirm={this.onConfirmRestart}
|
||||
onCancel={this.onCloseRestartRequiredModalOpen}
|
||||
|
||||
@@ -63,7 +63,7 @@ function HostSettings(props) {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('URLBase')}</FormLabel>
|
||||
<FormLabel>{translate('UrlBase')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
@@ -111,7 +111,7 @@ function HostSettings(props) {
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>{translate('EnableSSL')}</FormLabel>
|
||||
<FormLabel>{translate('EnableSsl')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
@@ -128,7 +128,7 @@ function HostSettings(props) {
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>{translate('SSLPort')}</FormLabel>
|
||||
<FormLabel>{translate('SslPort')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
@@ -149,12 +149,12 @@ function HostSettings(props) {
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>{translate('SSLCertPath')}</FormLabel>
|
||||
<FormLabel>{translate('SslCertPath')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="sslCertPath"
|
||||
helpText={translate('SSLCertPathHelpText')}
|
||||
helpText={translate('SslCertPathHelpText')}
|
||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
{...sslCertPath}
|
||||
@@ -169,12 +169,12 @@ function HostSettings(props) {
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>{translate('SSLCertPassword')}</FormLabel>
|
||||
<FormLabel>{translate('SslCertPassword')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PASSWORD}
|
||||
name="sslCertPassword"
|
||||
helpText={translate('SSLCertPasswordHelpText')}
|
||||
helpText={translate('SslCertPasswordHelpText')}
|
||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
{...sslCertPassword}
|
||||
@@ -184,18 +184,19 @@ function HostSettings(props) {
|
||||
}
|
||||
|
||||
{
|
||||
isWindows && mode !== 'service' &&
|
||||
isWindows && mode !== 'service' ?
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>{translate('OpenBrowserOnStart')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="launchBrowser"
|
||||
helpText={translate('LaunchBrowserHelpText')}
|
||||
helpText={translate('OpenBrowserOnStartHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...launchBrowser}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
</FieldSet>
|
||||
|
||||
@@ -25,9 +25,18 @@ function ProxySettings(props) {
|
||||
} = settings;
|
||||
|
||||
const proxyTypeOptions = [
|
||||
{ key: 'http', value: translate('HttpHttps') },
|
||||
{ key: 'socks4', value: translate('Socks4') },
|
||||
{ key: 'socks5', value: translate('Socks5') }
|
||||
{
|
||||
key: 'http',
|
||||
value: translate('HttpHttps')
|
||||
},
|
||||
{
|
||||
key: 'socks4',
|
||||
value: translate('Socks4')
|
||||
},
|
||||
{
|
||||
key: 'socks5',
|
||||
value: translate('Socks5')
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -70,7 +70,8 @@ function UpdateSettings(props) {
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
!isWindows &&
|
||||
isWindows ?
|
||||
null :
|
||||
<div>
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -39,7 +40,7 @@ function EditImportListExclusionModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{id ? translate('EditListExclusion') : translate('AddListExclusion')}
|
||||
{id ? translate('EditImportListExclusion') : translate('AddImportListExclusion')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody className={styles.body}>
|
||||
@@ -50,9 +51,9 @@ function EditImportListExclusionModalContent(props) {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToAddANewListExclusionPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddImportListExclusionError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -7,7 +6,7 @@ import { saveImportExclusion, setImportExclusionValue } from 'Store/Actions/sett
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import EditImportListExclusionModalContent from './EditImportListExclusionModalContent';
|
||||
|
||||
const newImportExclusion = {
|
||||
const newImportListExclusion = {
|
||||
movieTitle: '',
|
||||
tmdbId: 0,
|
||||
movieYear: 0
|
||||
@@ -27,7 +26,7 @@ function createImportExclusionSelector() {
|
||||
items
|
||||
} = importExclusions;
|
||||
|
||||
const mapping = id ? _.find(items, { id }) : newImportExclusion;
|
||||
const mapping = id ? items.find((i) => i.id === id) : newImportListExclusion;
|
||||
const settings = selectSettings(mapping, pendingChanges, saveError);
|
||||
|
||||
return {
|
||||
@@ -66,10 +65,10 @@ class EditImportExclusionModalContentConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.id) {
|
||||
Object.keys(newImportExclusion).forEach((name) => {
|
||||
Object.keys(newImportListExclusion).forEach((name) => {
|
||||
this.props.setImportExclusionValue({
|
||||
name,
|
||||
value: newImportExclusion[name]
|
||||
value: newImportListExclusion[name]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
.movieTitle {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 0 0 600px;
|
||||
flex: 0 1 600px;
|
||||
}
|
||||
|
||||
.tmdbId,
|
||||
|
||||
@@ -67,7 +67,7 @@ class ImportListExclusion extends Component {
|
||||
)}
|
||||
>
|
||||
<div className={styles.tmdbId}>{tmdbId}</div>
|
||||
<div className={styles.movieTitle}>{movieTitle}</div>
|
||||
<div className={styles.movieTitle} title={movieTitle}>{movieTitle}</div>
|
||||
<div className={styles.movieYear}>{movieYear}</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 0 0 600px;
|
||||
flex: 0 1 600px;
|
||||
}
|
||||
|
||||
.tmdbId,
|
||||
|
||||
@@ -45,14 +45,14 @@ class ImportListExclusions extends Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('ListExclusions')}>
|
||||
<FieldSet legend={translate('ImportListExclusions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadListExclusions')}
|
||||
errorMessage={translate('ImportListExclusionsLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.importListExclusionsHeader}>
|
||||
<div className={styles.tmdbId}>
|
||||
TMDb Id
|
||||
{translate('TMDBId')}
|
||||
</div>
|
||||
<div className={styles.title}>
|
||||
{translate('Title')}
|
||||
|
||||
@@ -70,7 +70,7 @@ class ImportListSettings extends Component {
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('ListSettings')}>
|
||||
<PageContent title={translate('ImportListSettings')}>
|
||||
<SettingsToolbarConnector
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
|
||||
@@ -37,42 +37,46 @@ class AddImportListModalContent extends Component {
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
isSchemaFetching &&
|
||||
<LoadingIndicator />
|
||||
isSchemaFetching ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isSchemaFetching && !!schemaError &&
|
||||
<div>
|
||||
{translate('UnableToAddANewListPleaseTryAgain')}
|
||||
</div>
|
||||
!isSchemaFetching && !!schemaError ?
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddListError')}
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isSchemaPopulated && !schemaError &&
|
||||
isSchemaPopulated && !schemaError ?
|
||||
<div>
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
{translate('RadarrSupportsAnyRSSMovieListsAsWellAsTheOneStatedBelow')}
|
||||
{translate('SupportedListsMovie')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('ForMoreInformationOnTheIndividualImportListsClinkOnTheInfoButtons')}
|
||||
{translate('SupportedListsMoreInfo')}
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
{
|
||||
Object.keys(listGroups).map((key) => {
|
||||
return (
|
||||
<FieldSet legend={`${titleCase(key)} List`} key={key}>
|
||||
<FieldSet key={key} legend={translate('TypeOfList', {
|
||||
typeOfList: titleCase(key)
|
||||
})}
|
||||
>
|
||||
<div className={styles.importLists}>
|
||||
{
|
||||
listGroups[key].map((importList) => {
|
||||
listGroups[key].map((list) => {
|
||||
return (
|
||||
<AddImportListItem
|
||||
key={importList.implementation}
|
||||
implementation={importList.implementation}
|
||||
{...importList}
|
||||
key={list.implementation}
|
||||
implementation={list.implementation}
|
||||
{...list}
|
||||
onImportListSelect={onImportListSelect}
|
||||
/>
|
||||
);
|
||||
@@ -83,7 +87,8 @@ class AddImportListModalContent extends Component {
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -67,10 +67,11 @@ function EditImportListModalContent(props) {
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && (!!error || !!rootFolderError) &&
|
||||
<div>
|
||||
{translate('UnableToAddANewListPleaseTryAgain')}
|
||||
</div>
|
||||
!isFetching && (!!error || !!rootFolderError) ?
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddListError')}
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
@@ -92,7 +93,9 @@ function EditImportListModalContent(props) {
|
||||
kind={kinds.INFO}
|
||||
className={styles.message}
|
||||
>
|
||||
{translate('ListWillRefreshEveryInterp', [formatShortTimeSpan(minRefreshInterval.value)])}
|
||||
{translate('ListWillRefreshEveryInterval', {
|
||||
refreshInterval: formatShortTimeSpan(minRefreshInterval.value)
|
||||
})}
|
||||
</Alert>
|
||||
|
||||
<FormGroup>
|
||||
@@ -112,7 +115,7 @@ function EditImportListModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enabled"
|
||||
helpText={translate('EnabledHelpText')}
|
||||
helpText={translate('ListEnabledHelpText')}
|
||||
{...enabled}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
@@ -124,7 +127,7 @@ function EditImportListModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableAuto"
|
||||
helpText={translate('EnableAutoHelpText')}
|
||||
helpText={translate('EnableAutomaticAddMovieHelpText')}
|
||||
{...enableAuto}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
@@ -136,7 +139,7 @@ function EditImportListModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.MOVIE_MONITORED_SELECT}
|
||||
name="monitor"
|
||||
helpText={translate('ShouldMonitorHelpText')}
|
||||
helpText={translate('ListMonitorMovieHelpText')}
|
||||
{...monitor}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
@@ -148,7 +151,7 @@ function EditImportListModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="searchOnAdd"
|
||||
helpText={translate('SearchOnAddHelpText')}
|
||||
helpText={translate('ListSearchOnAddMovieHelpText')}
|
||||
{...searchOnAdd}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
@@ -170,6 +173,7 @@ function EditImportListModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
helpText={translate('ListQualityProfileHelpText')}
|
||||
{...qualityProfileId}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
@@ -181,6 +185,7 @@ function EditImportListModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
name="rootFolderPath"
|
||||
helpText={translate('ListRootFolderHelpText')}
|
||||
{...rootFolderPath}
|
||||
includeMissingValue={true}
|
||||
onChange={onInputChange}
|
||||
|
||||
@@ -41,7 +41,7 @@ class ImportList extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
onDeleteImportListModalClose= () => {
|
||||
onDeleteImportListModalClose = () => {
|
||||
this.setState({ isDeleteImportListModalOpen: false });
|
||||
};
|
||||
|
||||
@@ -89,7 +89,7 @@ class ImportList extends Component {
|
||||
{
|
||||
enableAuto &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('Auto')}
|
||||
{translate('AutomaticAdd')}
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -59,9 +59,9 @@ class ImportLists extends Component {
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('Lists')}>
|
||||
<FieldSet legend={translate('ImportLists')} >
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadLists')}
|
||||
errorMessage={translate('ImportListsLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.importLists}>
|
||||
|
||||
@@ -39,7 +39,7 @@ function ImportListOptions(props) {
|
||||
{
|
||||
!isFetching && error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadListOptions')}
|
||||
{translate('ListOptionsLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
|
||||
@@ -43,9 +43,9 @@ class AddIndexerModalContent extends Component {
|
||||
|
||||
{
|
||||
!isSchemaFetching && !!schemaError &&
|
||||
<div>
|
||||
{translate('UnableToAddANewIndexerPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddIndexerError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -54,10 +54,10 @@ class AddIndexerModalContent extends Component {
|
||||
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>
|
||||
{translate('RadarrSupportsAnyIndexer')}
|
||||
{translate('SupportedIndexers')}
|
||||
</div>
|
||||
<div>
|
||||
{translate('ForMoreInformationOnTheIndividualIndexers')}
|
||||
{translate('SupportedIndexersMoreInfo')}
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -66,9 +67,9 @@ function EditIndexerModalContent(props) {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToAddANewIndexerPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddIndexerError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -86,13 +87,13 @@ function EditIndexerModalContent(props) {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('EnableRSS')}</FormLabel>
|
||||
<FormLabel>{translate('EnableRss')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableRss"
|
||||
helpText={supportsRss.value ? translate('RSSHelpText') : undefined}
|
||||
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
|
||||
helpText={supportsRss.value ? translate('EnableRssHelpText') : undefined}
|
||||
helpTextWarning={supportsRss.value ? undefined : translate('RssIsNotSupportedWithThisIndexer')}
|
||||
isDisabled={!supportsRss.value}
|
||||
{...enableRss}
|
||||
onChange={onInputChange}
|
||||
@@ -106,7 +107,7 @@ function EditIndexerModalContent(props) {
|
||||
type={inputTypes.CHECK}
|
||||
name="enableAutomaticSearch"
|
||||
helpText={supportsSearch.value ? translate('EnableAutomaticSearchHelpText') : undefined}
|
||||
helpTextWarning={supportsSearch.value ? undefined : translate('EnableAutomaticSearchHelpTextWarning')}
|
||||
helpTextWarning={supportsSearch.value ? undefined : translate('SearchIsNotSupportedWithThisIndexer')}
|
||||
isDisabled={!supportsSearch.value}
|
||||
{...enableAutomaticSearch}
|
||||
onChange={onInputChange}
|
||||
@@ -120,7 +121,7 @@ function EditIndexerModalContent(props) {
|
||||
type={inputTypes.CHECK}
|
||||
name="enableInteractiveSearch"
|
||||
helpText={supportsSearch.value ? translate('EnableInteractiveSearchHelpText') : undefined}
|
||||
helpTextWarning={supportsSearch.value ? undefined : translate('EnableInteractiveSearchHelpTextWarning')}
|
||||
helpTextWarning={supportsSearch.value ? undefined : translate('SearchIsNotSupportedWithThisIndexer')}
|
||||
isDisabled={!supportsSearch.value}
|
||||
{...enableInteractiveSearch}
|
||||
onChange={onInputChange}
|
||||
@@ -182,7 +183,7 @@ function EditIndexerModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
helpText={translate('IndexerTagHelpText')}
|
||||
helpText={translate('IndexerTagMovieHelpText')}
|
||||
{...tags}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
||||
@@ -42,7 +42,7 @@ class Indexer extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
onDeleteIndexerModalClose= () => {
|
||||
onDeleteIndexerModalClose = () => {
|
||||
this.setState({ isDeleteIndexerModalOpen: false });
|
||||
};
|
||||
|
||||
@@ -101,7 +101,7 @@ class Indexer extends Component {
|
||||
{
|
||||
supportsRss && enableRss &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('RSS')}
|
||||
{translate('Rss')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class Indexers extends Component {
|
||||
return (
|
||||
<FieldSet legend={translate('Indexers')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadIndexers')}
|
||||
errorMessage={translate('IndexersLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.indexers}>
|
||||
|
||||
@@ -127,7 +127,7 @@ function ManageIndexersEditModalContent(
|
||||
|
||||
<ModalBody>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('EnableRSS')}</FormLabel>
|
||||
<FormLabel>{translate('EnableRss')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
|
||||
@@ -50,7 +50,7 @@ const COLUMNS = [
|
||||
},
|
||||
{
|
||||
name: 'enableRss',
|
||||
label: () => translate('EnableRSS'),
|
||||
label: () => translate('EnableRss'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ function IndexerOptions(props) {
|
||||
{
|
||||
!isFetching && error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadIndexerOptions')}
|
||||
{translate('IndexerOptionsLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ function IndexerOptions(props) {
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>{translate('RSSSyncInterval')}</FormLabel>
|
||||
<FormLabel>{translate('RssSyncInterval')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
@@ -118,8 +118,8 @@ function IndexerOptions(props) {
|
||||
min={0}
|
||||
max={120}
|
||||
unit="minutes"
|
||||
helpText={translate('RssSyncHelpText')}
|
||||
helpTextWarning={translate('RSSSyncIntervalHelpTextWarning')}
|
||||
helpText={translate('RssSyncIntervalHelpText')}
|
||||
helpTextWarning={translate('RssSyncIntervalHelpTextWarning')}
|
||||
helpLink="https://wiki.servarr.com/radarr/faq#how-does-radarr-work"
|
||||
onChange={onInputChange}
|
||||
{...settings.rssSyncInterval}
|
||||
|
||||
@@ -119,7 +119,7 @@ class MediaManagement extends Component {
|
||||
!isFetching && error ?
|
||||
<FieldSet legend={translate('NamingSettings')}>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadMediaManagementSettings')}
|
||||
{translate('MediaManagementSettingsLoadError')}
|
||||
</Alert>
|
||||
</FieldSet> : null
|
||||
}
|
||||
@@ -204,7 +204,7 @@ class MediaManagement extends Component {
|
||||
type={inputTypes.NUMBER}
|
||||
unit='MB'
|
||||
name="minimumFreeSpaceWhenImporting"
|
||||
helpText={translate('MinimumFreeSpaceWhenImportingHelpText')}
|
||||
helpText={translate('MinimumFreeSpaceHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.minimumFreeSpaceWhenImporting}
|
||||
/>
|
||||
@@ -220,7 +220,7 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="copyUsingHardlinks"
|
||||
helpText={translate('CopyUsingHardlinksHelpText')}
|
||||
helpText={translate('CopyUsingHardlinksMovieHelpText')}
|
||||
helpTextWarning={translate('CopyUsingHardlinksHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
{...settings.copyUsingHardlinks}
|
||||
@@ -237,7 +237,7 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="useScriptImport"
|
||||
helpText={translate('UseScriptImportHelpText')}
|
||||
helpText={translate('ImportUsingScriptHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.useScriptImport}
|
||||
/>
|
||||
@@ -255,7 +255,7 @@ class MediaManagement extends Component {
|
||||
type={inputTypes.PATH}
|
||||
includeFiles={true}
|
||||
name="scriptImportPath"
|
||||
helpText={translate('ScriptImportPathHelpText')}
|
||||
helpText={translate('ImportScriptPathHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.scriptImportPath}
|
||||
/>
|
||||
@@ -268,7 +268,7 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="importExtraFiles"
|
||||
helpText={translate('ImportExtraFilesHelpText')}
|
||||
helpText={translate('ImportExtraFilesMovieHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.importExtraFiles}
|
||||
/>
|
||||
@@ -286,8 +286,8 @@ class MediaManagement extends Component {
|
||||
type={inputTypes.TEXT}
|
||||
name="extraFileExtensions"
|
||||
helpTexts={[
|
||||
translate('ExtraFileExtensionsHelpTexts1'),
|
||||
translate('ExtraFileExtensionsHelpTexts2')
|
||||
translate('ExtraFileExtensionsHelpText'),
|
||||
translate('ExtraFileExtensionsHelpTextsExamples')
|
||||
]}
|
||||
onChange={onInputChange}
|
||||
{...settings.extraFileExtensions}
|
||||
@@ -323,8 +323,8 @@ class MediaManagement extends Component {
|
||||
type={inputTypes.SELECT}
|
||||
name="downloadPropersAndRepacks"
|
||||
helpTexts={[
|
||||
translate('DownloadPropersAndRepacksHelpText1'),
|
||||
translate('DownloadPropersAndRepacksHelpText2')
|
||||
translate('DownloadPropersAndRepacksHelpText'),
|
||||
translate('DownloadPropersAndRepacksHelpTextCustomFormat')
|
||||
]}
|
||||
helpTextWarning={
|
||||
settings.downloadPropersAndRepacks.value === 'doNotPrefer' ?
|
||||
@@ -347,7 +347,7 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableMediaInfo"
|
||||
helpText={translate('EnableMediaInfoHelpText')}
|
||||
helpText={translate('AnalyseVideoFilesHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.enableMediaInfo}
|
||||
/>
|
||||
@@ -362,7 +362,7 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="rescanAfterRefresh"
|
||||
helpText={translate('RescanAfterRefreshHelpText')}
|
||||
helpText={translate('RescanAfterRefreshMovieHelpText')}
|
||||
helpTextWarning={translate('RescanAfterRefreshHelpTextWarning')}
|
||||
values={rescanAfterRefreshOptions}
|
||||
onChange={onInputChange}
|
||||
@@ -379,7 +379,7 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="fileDate"
|
||||
helpText={translate('FileDateHelpText')}
|
||||
helpText={translate('ChangeFileDateHelpText')}
|
||||
values={fileDateOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.fileDate}
|
||||
@@ -395,7 +395,7 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.PATH}
|
||||
name="recycleBin"
|
||||
helpText={translate('RecycleBinHelpText')}
|
||||
helpText={translate('RecyclingBinHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.recycleBin}
|
||||
/>
|
||||
@@ -410,8 +410,8 @@ class MediaManagement extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="recycleBinCleanupDays"
|
||||
helpText={translate('RecycleBinCleanupDaysHelpText')}
|
||||
helpTextWarning={translate('RecycleBinCleanupDaysHelpTextWarning')}
|
||||
helpText={translate('RecyclingBinCleanupHelpText')}
|
||||
helpTextWarning={translate('RecyclingBinCleanupHelpTextWarning')}
|
||||
min={0}
|
||||
onChange={onInputChange}
|
||||
{...settings.recycleBinCleanupDays}
|
||||
@@ -461,13 +461,13 @@ class MediaManagement extends Component {
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>{translate('ChmodGroup')}</FormLabel>
|
||||
<FormLabel>{translate('ChownGroup')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="chownGroup"
|
||||
helpText={translate('ChmodGroupHelpText')}
|
||||
helpTextWarning={translate('ChmodGroupHelpTextWarning')}
|
||||
helpText={translate('ChownGroupHelpText')}
|
||||
helpTextWarning={translate('ChownGroupHelpTextWarning')}
|
||||
values={fileDateOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.chownGroup}
|
||||
|
||||
@@ -110,7 +110,7 @@ class Naming extends Component {
|
||||
|
||||
if (examplesPopulated) {
|
||||
if (examples.movieExample) {
|
||||
standardMovieFormatHelpTexts.push(`Movie: ${examples.movieExample}`);
|
||||
standardMovieFormatHelpTexts.push(`${translate('Movie')}: ${examples.movieExample}`);
|
||||
} else {
|
||||
standardMovieFormatErrors.push({ get message() {
|
||||
return translate('MovieInvalidFormat');
|
||||
@@ -118,7 +118,7 @@ class Naming extends Component {
|
||||
}
|
||||
|
||||
if (examples.movieFolderExample) {
|
||||
movieFolderFormatHelpTexts.push(`Example: ${examples.movieFolderExample}`);
|
||||
movieFolderFormatHelpTexts.push(`${translate('Example')}: ${examples.movieFolderExample}`);
|
||||
} else {
|
||||
movieFolderFormatErrors.push({ get message() {
|
||||
return translate('InvalidFormat');
|
||||
@@ -136,7 +136,7 @@ class Naming extends Component {
|
||||
{
|
||||
!isFetching && error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadNamingSettings')}
|
||||
{translate('NamingSettingsLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ class Naming extends Component {
|
||||
buttons={<FormInputButton onPress={this.onMovieFolderNamingModalOpenClick}>?</FormInputButton>}
|
||||
onChange={onInputChange}
|
||||
{...settings.movieFolderFormat}
|
||||
helpTexts={['Used when adding a new movie or moving movies via the editor', ...movieFolderFormatHelpTexts]}
|
||||
helpTexts={[translate('MovieFolderFormatHelpText'), ...movieFolderFormatHelpTexts]}
|
||||
errors={[...movieFolderFormatErrors, ...settings.movieFolderFormat.errors]}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -5,6 +5,7 @@ import SelectInput from 'Components/Form/SelectInput';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
@@ -16,10 +17,30 @@ import NamingOption from './NamingOption';
|
||||
import styles from './NamingModal.css';
|
||||
|
||||
const separatorOptions = [
|
||||
{ key: ' ', value: 'Space ( )' },
|
||||
{ key: '.', value: 'Period (.)' },
|
||||
{ key: '_', value: 'Underscore (_)' },
|
||||
{ key: '-', value: 'Dash (-)' }
|
||||
{
|
||||
key: ' ',
|
||||
get value() {
|
||||
return `${translate('Space')} ( )`;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '.',
|
||||
get value() {
|
||||
return `${translate('Period')} (.)`;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '_',
|
||||
get value() {
|
||||
return `${translate('Underscore')} (_)`;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '-',
|
||||
get value() {
|
||||
return `${translate('Dash')} (-)`;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const caseOptions = [
|
||||
@@ -32,13 +53,13 @@ const caseOptions = [
|
||||
{
|
||||
key: 'lower',
|
||||
get value() {
|
||||
return translate('LowerCase');
|
||||
return translate('Lowercase');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'upper',
|
||||
get value() {
|
||||
return translate('UpperCase');
|
||||
return translate('Uppercase');
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -336,10 +357,7 @@ class NamingModal extends Component {
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<div>
|
||||
MediaInfo Full/AudioLanguages/SubtitleLanguages support a <code>:EN+DE</code> suffix allowing you to filter the languages included in the filename. Use <code>-DE</code> to exclude specific languages.
|
||||
Appending <code>+</code> (eg <code>:EN+</code>) will output <code>[EN]</code>/<code>[EN+--]</code>/<code>[--]</code> depending on excluded languages. For example <code>{'{'}MediaInfo Full:EN+DE{'}'}</code>.
|
||||
</div>
|
||||
<InlineMarkdown data={translate('MediaInfoFootNote')} />
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ function EditMetadataModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Edit {name.value} Metadata
|
||||
{translate('EditMetadata', { metadataType: name.value })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
@@ -47,7 +47,7 @@ function EditMetadataModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enable"
|
||||
helpText={translate('EnableHelpText')}
|
||||
helpText={translate('EnableMetadataHelpText')}
|
||||
{...enable}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,7 @@ function Metadatas(props) {
|
||||
return (
|
||||
<FieldSet legend={translate('Metadata')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadMetadata')}
|
||||
errorMessage={translate('MetadataLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.metadatas}>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddNotificationItem from './AddNotificationItem';
|
||||
import styles from './AddNotificationModalContent.css';
|
||||
@@ -39,9 +41,9 @@ class AddNotificationModalContent extends Component {
|
||||
|
||||
{
|
||||
!isSchemaFetching && !!schemaError &&
|
||||
<div>
|
||||
{translate('UnableToAddANewNotificationPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddNotificationError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -59,9 +59,9 @@ function EditNotificationModalContent(props) {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToAddANewNotificationPleaseTryAgain')}
|
||||
</div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddNotificationError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -99,7 +99,7 @@ function EditNotificationModalContent(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
helpText={translate('TagsHelpText')}
|
||||
helpText={translate('NotificationsTagsMovieHelpText')}
|
||||
{...tags}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
||||
@@ -191,7 +191,7 @@ class Notification extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!onGrab && !onDownload && !onRename && !onHealthIssue && !onHealthRestored && !onApplicationUpdate && !onMovieDelete && !onMovieFileDelete && !onManualInteractionRequired ?
|
||||
!onGrab && !onDownload && !onRename && !onHealthIssue && !onHealthRestored && !onApplicationUpdate && !onMovieAdded && !onMovieDelete && !onMovieFileDelete && !onManualInteractionRequired ?
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
|
||||
@@ -55,7 +55,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onGrab"
|
||||
helpText={translate('OnGrabHelpText')}
|
||||
helpText={translate('OnGrab')}
|
||||
isDisabled={!supportsOnGrab.value}
|
||||
{...onGrab}
|
||||
onChange={onInputChange}
|
||||
@@ -66,7 +66,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onDownload"
|
||||
helpText={translate('OnDownloadHelpText')}
|
||||
helpText={translate('OnImport')}
|
||||
isDisabled={!supportsOnDownload.value}
|
||||
{...onDownload}
|
||||
onChange={onInputChange}
|
||||
@@ -79,7 +79,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onUpgrade"
|
||||
helpText={translate('OnUpgradeHelpText')}
|
||||
helpText={translate('OnUpgrade')}
|
||||
isDisabled={!supportsOnUpgrade.value}
|
||||
{...onUpgrade}
|
||||
onChange={onInputChange}
|
||||
@@ -91,7 +91,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onRename"
|
||||
helpText={translate('OnRenameHelpText')}
|
||||
helpText={translate('OnRename')}
|
||||
isDisabled={!supportsOnRename.value}
|
||||
{...onRename}
|
||||
onChange={onInputChange}
|
||||
@@ -102,7 +102,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onMovieAdded"
|
||||
helpText={translate('OnMovieAddedHelpText')}
|
||||
helpText={translate('OnMovieAdded')}
|
||||
isDisabled={!supportsOnMovieAdded.value}
|
||||
{...onMovieAdded}
|
||||
onChange={onInputChange}
|
||||
@@ -113,7 +113,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onMovieDelete"
|
||||
helpText={translate('OnMovieDeleteHelpText')}
|
||||
helpText={translate('OnMovieDelete')}
|
||||
isDisabled={!supportsOnMovieDelete.value}
|
||||
{...onMovieDelete}
|
||||
onChange={onInputChange}
|
||||
@@ -124,7 +124,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onMovieFileDelete"
|
||||
helpText={translate('OnMovieFileDeleteHelpText')}
|
||||
helpText={translate('OnMovieFileDelete')}
|
||||
isDisabled={!supportsOnMovieFileDelete.value}
|
||||
{...onMovieFileDelete}
|
||||
onChange={onInputChange}
|
||||
@@ -137,7 +137,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onMovieFileDeleteForUpgrade"
|
||||
helpText={translate('OnMovieFileDeleteForUpgradeHelpText')}
|
||||
helpText={translate('OnMovieFileDeleteForUpgrade')}
|
||||
isDisabled={!supportsOnMovieFileDeleteForUpgrade.value}
|
||||
{...onMovieFileDeleteForUpgrade}
|
||||
onChange={onInputChange}
|
||||
@@ -149,7 +149,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onHealthIssue"
|
||||
helpText={translate('OnHealthIssueHelpText')}
|
||||
helpText={translate('OnHealthIssue')}
|
||||
isDisabled={!supportsOnHealthIssue.value}
|
||||
{...onHealthIssue}
|
||||
onChange={onInputChange}
|
||||
@@ -160,7 +160,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onHealthRestored"
|
||||
helpText={translate('OnHealthRestoredHelpText')}
|
||||
helpText={translate('OnHealthRestored')}
|
||||
isDisabled={!supportsOnHealthRestored.value}
|
||||
{...onHealthRestored}
|
||||
onChange={onInputChange}
|
||||
@@ -173,7 +173,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="includeHealthWarnings"
|
||||
helpText={translate('IncludeHealthWarningsHelpText')}
|
||||
helpText={translate('IncludeHealthWarnings')}
|
||||
isDisabled={!supportsOnHealthIssue.value}
|
||||
{...includeHealthWarnings}
|
||||
onChange={onInputChange}
|
||||
@@ -185,7 +185,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onApplicationUpdate"
|
||||
helpText={translate('OnApplicationUpdateHelpText')}
|
||||
helpText={translate('OnApplicationUpdate')}
|
||||
isDisabled={!supportsOnApplicationUpdate.value}
|
||||
{...onApplicationUpdate}
|
||||
onChange={onInputChange}
|
||||
@@ -196,7 +196,7 @@ function NotificationEventItems(props) {
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="onManualInteractionRequired"
|
||||
helpText={translate('OnManualInteractionRequiredHelpText')}
|
||||
helpText={translate('OnManualInteractionRequired')}
|
||||
isDisabled={!supportsOnManualInteractionRequired.value}
|
||||
{...onManualInteractionRequired}
|
||||
onChange={onInputChange}
|
||||
|
||||
@@ -62,7 +62,7 @@ class Notifications extends Component {
|
||||
return (
|
||||
<FieldSet legend={translate('Connections')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadNotifications')}
|
||||
errorMessage={translate('NotificationsLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.notifications}>
|
||||
|
||||
@@ -15,12 +15,17 @@ function PendingChangesModal(props) {
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
bindShortcut
|
||||
bindShortcut,
|
||||
unbindShortcut
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
bindShortcut('enter', onConfirm);
|
||||
}, [bindShortcut, onConfirm]);
|
||||
if (isOpen) {
|
||||
bindShortcut('enter', onConfirm);
|
||||
|
||||
return () => unbindShortcut('enter', onConfirm);
|
||||
}
|
||||
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -61,7 +66,8 @@ PendingChangesModal.propTypes = {
|
||||
kind: PropTypes.oneOf(kinds.all),
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
bindShortcut: PropTypes.func.isRequired
|
||||
bindShortcut: PropTypes.func.isRequired,
|
||||
unbindShortcut: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
PendingChangesModal.defaultProps = {
|
||||
|
||||
@@ -17,15 +17,15 @@ function getDelay(enabled, delay) {
|
||||
}
|
||||
|
||||
if (!delay) {
|
||||
return 'No Delay';
|
||||
return translate('NoDelay');
|
||||
}
|
||||
|
||||
if (delay === 1) {
|
||||
return '1 Minute';
|
||||
return translate('OneMinute');
|
||||
}
|
||||
|
||||
// TODO: use better units of time than just minutes
|
||||
return `${delay} Minutes`;
|
||||
return translate('DelayMinutes', { delay });
|
||||
}
|
||||
|
||||
class DelayProfile extends Component {
|
||||
@@ -85,7 +85,7 @@ class DelayProfile extends Component {
|
||||
connectDragSource
|
||||
} = this.props;
|
||||
|
||||
let preferred = `Prefer ${titleCase(preferredProtocol)}`;
|
||||
let preferred = titleCase(translate('PreferProtocol', { preferredProtocol }));
|
||||
|
||||
if (!enableUsenet) {
|
||||
preferred = translate('OnlyTorrent');
|
||||
|
||||
@@ -70,7 +70,7 @@ class DelayProfiles extends Component {
|
||||
<Measure onMeasure={this.onMeasure}>
|
||||
<FieldSet legend={translate('DelayProfiles')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadDelayProfiles')}
|
||||
errorMessage={translate('DelayProfilesLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<Scroller
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user