1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

...

79 Commits

Author SHA1 Message Date
Bogdan 4ad7b60d9d New: Add 'cn' language code as Chinese language 2024-02-25 04:09:45 +02:00
Bogdan 7e4231fc0e Fixed: Selection of last added custom filter
Plus some translations and typos
2024-02-23 05:43:48 +02:00
bakerboy448 94287d9427 Update name for errors with metadata API 2024-02-22 01:03:08 +02:00
Servarr 8ec6b5dd4d Automated API Docs update 2024-02-21 22:05:02 +02:00
Mark McDowall 4be43c9f2b Fixed: Multi-word genres in Auto Tags
Fixed #6488

(cherry picked from commit 5c4f82999368edfedd038a0a27d323e04b81a400)
2024-02-21 20:53:34 +02:00
servarr[bot] c388cf968b Bump node to v20.x on builder 2024-02-21 20:52:43 +02:00
Bogdan b6b809f473 Fixed: Reprocessing custom formats for file in Manual Import 2024-02-21 20:46:16 +02:00
Bogdan 9dd31be7b3 New: Set Indexer flags in Manual Import 2024-02-21 20:46:16 +02:00
Bogdan 25ab396a2c Fixed: Notifications with only On Movie Add enabled being labeled as disabled 2024-02-21 17:05:39 +02:00
Bogdan 145cd74969 Avoid saving Indexer Flags in Last RSS Release info 2024-02-19 20:55:50 +02:00
Bogdan b9c76d9bed Bump version to 5.4.0 2024-02-18 19:27:49 +02:00
Weblate 63f16924b1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Chaoshuai Lü <lcs@meta.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-02-17 21:35:15 +02:00
Bogdan a91a9f7fd9 Fixed: Movie titles using default language after using movie editor 2024-02-17 09:08:56 +02:00
Bogdan 4c8e9f204e Fix movie queue status for download client unavailable 2024-02-16 03:49:23 +02:00
Bogdan d64ee6681f Fixed: Avoid upgrades for custom formats cut-off already met 2024-02-15 02:21:21 +02:00
Bogdan 2ecc57cd31 Translations for Discover list 2024-02-15 00:34:35 +02:00
Bogdan 9620207503 Improve add/loading error notices
(cherry picked from commit dd704579df43b0dd835f8bb618c4b4412561a888)

Closes #9767
2024-02-15 00:06:28 +02:00
bakerboy448 0b090e5f39 Improve Custom Format rejection messaging
(cherry picked from commit cac97c057faa44c1656e02681cb9ba668faca488)

Closes #9747
2024-02-14 19:21:11 +02:00
Bogdan 51cb0920ed Fix translation token for DL client directory help text
Closes #9769
2024-02-14 19:15:31 +02:00
Bogdan a90d6682d3 Update Custom Format Deletion confirmation message
Consistency with the rest of the Delete*MessageText

Closes #9766
2024-02-14 19:12:58 +02:00
Bogdan db62eddf5a Fixed: Allow selection of Cast/Crew names
Fixed #9781
2024-02-14 19:09:19 +02:00
Weblate ac2b2e6215 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Steve Hansen <steve@hansenconsultancy.be>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2024-02-14 19:09:05 +02:00
Bogdan 9581dd9764 Show download client ID as hint in select options
(cherry picked from commit c0b17d9345367ab6500b7cca6bb70c1e3b930284)
2024-02-14 02:55:47 +02:00
Bogdan 6c459c744a Improve messaging on indexer specified download client is not available
(cherry picked from commit 84e657482d37eed35f09c6dab3c2b8b5ebd5bac4)
2024-02-14 02:55:31 +02:00
abcasada 4676ecfce9 Hints for week column and short dates in UI settings
(cherry picked from commit 4558f552820b52bb1f9cd97fdabe03654ce9924a)

(cherry picked from commit f1d343218cdbd5a63abeb2eb97bba1105dc8035d)
2024-02-14 02:55:21 +02:00
Bogdan ef92af9dd8 Fix translation for list exclusion on movie deletion 2024-02-14 02:54:06 +02:00
Qstick b144482d68 Bump version to 5.3.6 2024-02-11 18:21:31 -06:00
Qstick 173b1d6a4c Fixed: Align DownloadClientInfo in ManualInteractionRequiredMessage with DownloadMessage
Also handle null ref exceptions when DownloadClientInfo is null in notification service
2024-02-11 17:56:35 -06:00
Servarr 5f624a147b Automated API Docs update 2024-02-10 23:10:20 -05:00
Stevie Robinson af066da4ff New: Ignore 'Other' subfolder when scanning disk
Closes #9718

(cherry picked from commit e1c6722aad1e35a1e2371bc47f399b57e4f96f27)
2024-02-10 22:05:06 -06:00
Alex Herbig 937ebcdac3 New: Add RZeroX to release group parsing exceptions
Closes #9569
Clsoes #9719

(cherry picked from commit e2210228b34a4d98ef64965e810689d39733734e)
2024-02-10 21:54:59 -06:00
Mark McDowall 67f5199667 Fixed: Parsing Hungarian anime releases
Closes #9673

(cherry picked from commit 9ba5850fcaf0a5fb73dec7d7f8f1d8d3de0b3fb9)
2024-02-10 21:40:46 -06:00
Qstick 38cd130da5 Fixed: Remove old naming config from API responses
Closes #9741
2024-02-10 21:32:55 -06:00
Weblate ed340be2b1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: bogdan-rgb <b.hmelniczky@yandex.ru>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translation: Servarr/Radarr
2024-02-11 05:12:22 +02:00
Weblate 34cfb58b39 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hicabi Erdem <bilgi@hicabierdem.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: bai0012 <baicongrui@gmail.com>
Co-authored-by: gr0sz <joshuatg727@gmail.com>
Co-authored-by: savin-msk <ns@a77.io>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-02-09 15:26:25 +02:00
Bogdan 3d0f22ca7c Fixed: Ignore invalid tags in CustomScript/Discord/Webhook 2024-02-09 15:17:05 +02:00
Bogdan 2510f44c25 Fixed: Refresh tags state to clear removed tags by housekeeping 2024-02-07 23:20:25 +02:00
Bogdan c0bf75cae3 Fixed: Recommendations on Postgres 2024-02-07 18:22:45 +02:00
Mark McDowall a63ab1ddd6 Fixed: Don't use sub folder to check for free disk space for update
(cherry picked from commit f722d49b3a9efefa65bef1b24d90be9332ca62ea)

Closes #9748
2024-02-07 08:53:37 +02:00
Mark McDowall 41cb020ff0 New: Log database engine version on startup
(cherry picked from commit 6ab1d8e16b29e98b4d2ebb68e0356f6f2d3a2c10)
2024-02-07 08:51:39 +02:00
Mark McDowall d660309b5a Fixed: Redirecting after login
(cherry picked from commit 745b92daf4bf4b9562ffe52dad84a12a5561add5)
2024-02-07 08:51:08 +02:00
Bogdan 222c19e4b3 Fixed: Edit button for import list exclusion on mobile
Fixes #9736
2024-02-06 01:19:58 +02:00
Mark McDowall b08981dee0 Sort movie files on movie details page
(cherry picked from commit 113b0864b8e92b7b768acc8341bdf4c9e2e1a47f)
2024-02-05 00:24:21 +02:00
Bogdan 4a9c0b2240 Bump version to 5.3.5 2024-02-04 12:54:19 +02:00
Weblate 8970b1276f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2024-02-03 22:43:43 +02:00
Bogdan e868dbf911 Fixed: Naming validation when using max token length 2024-02-03 01:38:54 +02:00
Bogdan e38b31a220 Fix table columns order for Interactive Search 2024-02-02 23:21:19 +02:00
Bogdan 9b1dac4b57 Tests for Movie Statistics
Closes #7891
2024-02-02 22:34:14 +02:00
Bogdan 20ac0bb0e1 Avoid import loop for already imported movies
(cherry picked from commit b183743d9f0a0b15e4a9db0a9d3d2d1c238b0d9c)

Closes #9325
2024-02-02 22:34:09 +02:00
Mark McDowall 9ffa1cc2b9 Refactor select options in Manual Import
(cherry picked from commit 0685896ed8263ef6d05a933acaf584e6f4aa9f92)

Closes #9613
2024-02-02 20:45:07 +02:00
Mark McDowall 422db874f0 New: Accept ':##' on renaming tokens to allow specifying a maximum length for movie titles and release group
(cherry picked from commit 19db75b36beaa5e549d903b136dbda300f1f8562)

Closes #9713
2024-02-02 19:48:20 +02:00
Servarr adf647f3e1 Automated API Docs update 2024-02-02 18:03:05 +02:00
Bogdan dc81f51d40 New: Search Movies on Add for bulk manage collections
Fixes #8670
2024-02-02 17:40:08 +02:00
Mark McDowall c9da7ee0c9 New: Use Movie Folder Format to improve unmapped folders within root folders
(cherry picked from commit 81d2b18ce1c079c2a9dc3de037c9dceea16733fd)

Closes #8065
2024-02-02 17:40:08 +02:00
Bogdan 7198aa24a6 Refactor tags in WebhookMovie 2024-02-02 17:40:08 +02:00
Weblate 35c6fef2d1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2024-02-02 17:39:55 +02:00
Bogdan deac2bdf5c New: Tags field for Notifiarr and Webhook 2024-02-02 04:22:35 +02:00
bakerboy448 8837473ed8 Improve Release Grabbing & Failure Logging
(cherry picked from commit d7aea82e45a7c5fec9e72b534fc4c9fb8654c519)

Closes #9714
2024-02-02 01:12:35 +02:00
Bogdan 4ac538682d Fix ImportFixture test 2024-02-02 00:59:30 +02:00
Weblate 0277b2b201 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translation: Servarr/Radarr
2024-02-02 00:05:02 +02:00
Stevie Robinson e73015010e New: Send 'On Manual Interaction Required' notifications in more cases
(cherry picked from commit c5a724f14eec20acf565ac3a036944191b30cab0)

Closes #9722
2024-02-02 00:01:24 +02:00
Mark McDowall f704ab1512 Improve messaging if release is in queue because all movies in release were not imported
Sync with upstream

(cherry picked from commit 2728bf79ca41bc372de515cb09e1034a8c006c2b)
2024-02-01 23:54:09 +02:00
Bogdan 2f1e077e0d Bind shortcut for pending changes confirmation only when it's shown 2024-01-31 19:42:51 +02:00
Bogdan cd3397a7a1 Use movie specific translation for quality limits message 2024-01-31 19:42:36 +02:00
Mark McDowall b3517c14de New: Show error message for pending queue items without movies
(cherry picked from commit 5ca868b4b2d34ba65ba3d40144ed889ccf55343d)

Closes #8320
2024-01-30 22:39:39 +02:00
Bogdan 2d05708fa9 Wrap external links tooltip in Movie Details 2024-01-30 22:22:57 +02:00
Havok Dan 2ca581f2b6 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1700 of 1700 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
2024-01-29 23:48:52 +02:00
Magyar 8289b8978f Translated using Weblate (Hungarian) [skip ci]
Currently translated at 74.6% (1269 of 1700 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
2024-01-29 23:48:52 +02:00
Oskari Lavinto 54c1f54b13 Translated using Weblate (Finnish) [skip ci]
Currently translated at 93.8% (1596 of 1700 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
2024-01-29 23:48:51 +02:00
fordas 918fcfd86e Translated using Weblate (Spanish) [skip ci]
Currently translated at 75.1% (1278 of 1700 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
2024-01-29 23:48:18 +02:00
Crocmou f55206537c Translated using Weblate (French) [skip ci]
Currently translated at 90.4% (1538 of 1700 strings)

Translation: Servarr/Radarr
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
2024-01-29 23:47:28 +02:00
Weblate d2d9ac8b9d Multiple Translations updated by Weblate
ignore-downstream

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

Some files were not shown because too many files have changed in this diff Show More