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

Compare commits

..

141 Commits

Author SHA1 Message Date
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
Bogdan 4eb4128a89 Remove BOM from download clients 2024-01-28 00:01:00 +02:00
Stevie Robinson f90cdbb112 Translate Download Clients on the backend
(cherry picked from commit 48b12f5b00429a7cd218d23f0544641b0da62a06)

Closes #9391
2024-01-27 23:49:48 +02:00
Stevie Robinson a8dbc97921 Fixed: Validating DownloadStation output path
(cherry picked from commit 07cbd7c8d29f2e0731f0a11c685f898f7e83b0c8)

Closes #9695
2024-01-27 18:38:44 +02:00
Bogdan f93e136386 Use string interpolation for test string
Closes #9696
2024-01-27 18:13:37 +02:00
Bogdan a70fa0fcfe Fix improve parsing logging 2024-01-27 17:58:43 +02:00
Stevie Robinson c8931784a7 Translate Calendar Frontend
(cherry picked from commit bf43453c040ba0474e0cd71e1c5aa3b98edf13ff)

Closes #8984
2024-01-27 17:57:38 +02:00
Weblate f601448a65 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lars <lars.erik.heloe@gmail.com>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: diaverso <alexito_perez.95@hotmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2024-01-27 17:37:43 +02:00
bakerboy448 64125a31b6 Improve Parsing Logging 2024-01-27 17:37:17 +02:00
Weblate 2f4da90d8a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Crocmou <slaanesh8854@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: diaverso <alexito_perez.95@hotmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2024-01-27 16:36:08 +02:00
Bogdan 20d9db2cde New: Date added for files in movie details 2024-01-27 13:18:13 +02:00
Mark McDowall 5b7c0a94fb Fixed: History retention for Newsbin
(cherry picked from commit 0ea189d03c8c5e02c00b96a4281dd9e668d6a9ae)
2024-01-27 10:16:38 +02:00
Servarr 1416f7898e Automated API Docs update 2024-01-27 10:16:21 +02:00
Bogdan f9cd9f3204 Language comment in Manual Import
Closes #9612
2024-01-26 11:58:51 +02:00
Mark McDowall 99ab65f790 New: Add recycle bin path for deleted movies to webhook/custom script
(cherry picked from commit a71d40edba1388d67e4deefd8bfc354a7a83c6b1)

Closes #9674
2024-01-26 11:44:41 +02:00
Bogdan 82fb355930 New: External link to TMDb for Cast/Crew
Fixes #9667
Closes #9668
2024-01-24 14:03:56 +02:00
Servarr 83d437cbb3 Automated API Docs update 2024-01-24 12:28:42 +02:00
Bogdan 4beb5b328b New: Option to disable Email encryption
* New: Option to disable Email encryption

(cherry picked from commit 7be5732a3a6679120b0f01bd1eb1207194f57f5e)

* Fix possible NullRef in Email Encryption migration

(cherry picked from commit 271266b10ac51ee6dd7a7024d346b631bd5397c2)

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-24 12:11:51 +02:00
Bogdan 23830f50ac Fixed: Testing for disabled Notifications 2024-01-24 12:10:57 +02:00
Weblate b808a92cdf 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-01-24 09:41:04 +02:00
Mark McDowall 3185c73659 New: Optionally remove from queue by changing category to 'Post-Import Category' when configured
(cherry picked from commit 345854d0fe9b65a561fdab12aac688782a420aa5)

Closes #9680
2024-01-24 09:37:39 +02:00
Weblate 7dc9ec03a5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alexander <a.burdun@gmail.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: wilfriedarma <wilfriedarma.collet@gmail.com>
Co-authored-by: zichichi <sollami@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
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/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translation: Servarr/Radarr
2024-01-24 08:59:10 +02:00
Servarr 33228335e3 Automated API Docs update 2024-01-24 08:58:09 +02:00
Mark McDowall 833340f8bd New: Add size to more history events
(cherry picked from commit 0d064181941fc6d149fc2f891661e059758d5428)

Closes #9672
2024-01-24 08:46:54 +02:00
Mark McDowall 0ecb1d0706 New: Add download client name to pending items waiting for a specific client
(cherry picked from commit 3cd4c67ba12cd5e8cc00d3df8929555fc0ad5918)

Closes #9676
2024-01-24 08:41:27 +02:00
Bogdan 25b77eb4a2 Update database migration version translation token
(cherry picked from commit 7d0d503a5e132cda3c03d6f7cd7b51c9c80740de)

Closes #9679
2024-01-24 08:30:52 +02:00
Stevie Robinson b946173c05 Add Regular Expression Custom Format translation
(cherry picked from commit 9f50166fa62a71d0a23e2c2d331651792285dc0e)

Closes #9678
2024-01-24 08:28:10 +02:00
Bogdan e5ccc32a37 Fixed: Movie status after using movie editor
Fixes #9681
2024-01-24 08:20:02 +02:00
Bogdan 3aeb52c3fd Fixed: Sorting by name in Manage Indexer and Download Client modals 2024-01-22 14:13:22 +02:00
Servarr c717989034 Automated API Docs update 2024-01-21 16:24:34 +02:00
Bogdan 806b89abbe Fixed: Use movie file from state for status label in add search results 2024-01-21 16:17:46 +02:00
Bogdan cc7104a814 Movie stats for Calendar 2024-01-21 16:17:46 +02:00
Bogdan 84c2d7f69d Revert "Add SizeOnDisk and HasFile to MovieResource"
This reverts commit 0ae8952b38.
2024-01-21 16:17:46 +02:00
bakerboy448 fcd187970c Improve Release Title Custom Format debugging
(cherry picked from commit ec40bc6eea1eb282cb804b8dd5461bf5ade332e9)

Closes #9653
2024-01-21 07:34:12 +02:00
Bogdan 34eb59dde4 Bump version to 5.3.3 2024-01-21 07:32:09 +02:00
Weblate 31b66c6673 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Blair Noctis <fqmxz5hyfba7ft85@neon.casa>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Julian Baquero <julian-baquero@upc.edu.co>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: MaddionMax <kovacs.tamas@ius.hu>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blabla <romcrack56@gmail.com>
Co-authored-by: brn <barantsenkul@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: horvi28 <horvi28@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
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/pt_BR/
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-01-21 03:50:25 +02:00
servarr[bot] 06a96ef2d1 Fixed: Movies poster view on mobile devices (#9659)
(cherry picked from commit c0b30a50281c38a103c2f800064d0ec964fae6e0)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-21 03:44:01 +02:00
Bogdan c77ce2459c Transpile logical assignment operators with babel
(cherry picked from commit 3cf4d2907e32e81050f35cda042dcc2b4641d40d)
2024-01-21 03:43:33 +02:00
servarr[bot] 083989d151 New: Log warning if less than 1 GB free space during update
* New: Log warning if less than 1 GB free space during update

(cherry picked from commit e66ba84fc0b5b120dd4e87f6b8ae1b3c038ee72b)

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-21 03:43:10 +02:00
Mark McDowall c003fe16de Fixed: Don't clone indexer API Key
(cherry picked from commit d336aaf3f04136471970155b5a7cc876770c64ff)
2024-01-20 07:43:06 +02:00
Mark McDowall bc9b2cd283 Refactor icons on full color calendar events
(cherry picked from commit 9884f6f282560ff2a0ea193e9306c6284cf8672c)

Closes #9646
2024-01-19 16:10:55 +02:00
Bogdan d0e400c55a Wrap values in log messages in FileListParser
Closes #9644
2024-01-19 09:51:12 +02:00
Stevie Robinson 77863dc2cf New: Drop commands table content before postgres migration
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>

(cherry picked from commit 8dd3b45c90209136c0bd0a861061c6d20837d62f)
2024-01-19 09:51:04 +02:00
Stevie Robinson 18dc6f60b0 Round off the seeded ratio when checking for removal candidates
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>

(cherry picked from commit c6bb6ad8788fb1c20ed466a495f2b47034947145)
2024-01-19 08:15:59 +02:00
Bogdan 49501a55ae Check paged requests using PageSize for Import Lists 2024-01-18 09:30:28 +02:00
Weblate d5d77a4f1a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Blair Noctis <fqmxz5hyfba7ft85@neon.casa>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Deleted User <noreply+2593@weblate.org>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
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/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-01-18 09:24:07 +02:00
Bogdan 0ae8952b38 Add SizeOnDisk and HasFile to MovieResource 2024-01-17 06:25:50 +02:00
Bogdan 6292ff76b0 Rename episode to movie 2024-01-17 05:21:25 +02:00
Bogdan 646d271e81 Add title to invalid Plex RSS item log
Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2024-01-17 01:15:57 +02:00
Bogdan 3d2ca830bc Fixed: Importing Plex RSS lists with invalid items 2024-01-17 01:13:21 +02:00
Bogdan da02ec3b04 Add missing import needed for Added column 2024-01-17 00:49:17 +02:00
bakerboy448 cc9a443473 Update logging to indicate a hardlink is being attempted
(cherry picked from commit 16e5ffa467f72e52c750143c835f6ee1c1c2460b)

Closes #9611
2024-01-17 00:12:17 +02:00
Stevie Robinson 81b6bf521d Add missing translation keys from Indexer Settings
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>
(cherry picked from commit 07fbb0d1f464513ed28721d6c91d428dd54818cf)

Closes #9627
2024-01-17 00:10:56 +02:00
Bogdan 7edb892eb4 Throw download as failed for invalid magnet links
(cherry picked from commit 091449d9bff9023ca27a85cc1048296f7d5ea37b)

Closes #9625
Fixes #9125
2024-01-17 00:07:04 +02:00
Blair Noctis 3b36921787 Fixed: Improve help text for download client priority
(cherry picked from commit 1bba7e177b5b4173e620cd014ffdc231023309a0)

Closes #9622
2024-01-17 00:06:12 +02:00
Rubicj c2d8bc85d0 New: Added column in Queue
(cherry picked from commit 57445bbe57a84990e284ef97d42455a06587e1ee)

Closes #9621
2024-01-17 00:04:30 +02:00
Bogdan 3e55b1cf25 Fix Content-Type in FileList fixture 2024-01-16 21:51:55 +02:00
Bogdan 0b0c93081d Check Content-Type in FileList parser 2024-01-16 21:39:03 +02:00
Servarr 91fbad72c0 Automated API Docs update 2024-01-16 20:58:27 +02:00
Bogdan 35651ac59b New: Release Groups for movie table index
* New: Release Group for movie table index

Co-authored-by: Qstick <qstick@gmail.com>

* fixup! New: Release Group for movie table index

---------

Co-authored-by: Qstick <qstick@gmail.com>
2024-01-16 20:52:07 +02:00
Qstick 1932aec131 Improved http timeout handling
(cherry picked from commit f87a66fcba6ca9ca972fa1c747a940b216e0e5e3)
2024-01-16 08:48:21 +02:00
Stevie Robinson ea470b4ee9 Sort Custom Filters
(cherry picked from commit e4b5d559df2d5f3d55e16aae5922509e84f31e64)
2024-01-16 08:05:18 +02:00
Qstick 1bb404a912 Fixed: Only use frames for Primary video stream for analysis 2024-01-15 23:16:22 -06:00
Weblate 374d20634d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Daniele Prevedello <dprevedello86@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Petr Vojar <vojar.petr@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: crayon3shawn <crayon3shawn@gmail.com>
Co-authored-by: hansaudun <hans@n5.no>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2024-01-15 01:44:06 +02:00
Qstick 60d9aacac6 Build report can get sent before installer finished 2024-01-14 13:27:52 -06:00
Qstick c5992ed944 Bump Inno version to 6.2.2 2024-01-14 12:59:58 -06:00
Mark McDowall 4c4073ce1c New: Support SABnzb's new format for sorters
(cherry picked from commit d484553b310af4257c841c37a503ef54650c1426)

Closes #9351
2024-01-14 18:08:18 +02:00
Jendrik Weise d72f78d979 New: Custom import scripts can communicate information back
(cherry picked from commit b4ac495983d61819d9ab84f49c880957ba57418b)
2024-01-14 16:15:48 +02:00
Bogdan dca9d69aaa Bump version to 5.3.2 2024-01-14 07:10:07 +02:00
Stevie Robinson 5a64826868 Add: New icon for deleted episodes with status missing from disk
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>
(cherry picked from commit 79907c881cc92ce9ee3973d5cf21749fe5fc58da)

Closes #9604
2024-01-14 03:47:30 +02:00
Mark McDowall cda40312e0 New: Optional directory setting for Aria2
(cherry picked from commit fd17df0dd03a5feb088c3241a247eac20f0e8c6c)

Closes #9602
2024-01-14 03:43:37 +02:00
Bogdan 907779b4ce Fetch movie file entity from database to broadcast 2024-01-14 03:42:03 +02:00
Mark McDowall cc03651af5 Don't use TestCase for single test
(cherry picked from commit 541d3307e1466b0353dc4149f502a4b62b4de616)
2024-01-14 03:41:26 +02:00
servarr[bot] 1ae98d618c Fixed: Movie posters flickering when width changes repeatedly
(cherry picked from commit 53cf5308931069638c23925596a3fd8aaccc5d98)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-14 03:40:54 +02:00
Bogdan f5914da2f9 Remove double filtering in entity history repository 2024-01-12 22:05:51 +02:00
Bogdan f7816aa5cd Fixed: Filter history by multiple event types in PG 2024-01-12 22:05:51 +02:00
Andrejs Ķīlis a652ce50a9 Fixed: Latvian and Russian language parsing
Improved support for Latvian with test cases I have encountered in the wild and fixed a case where Russian is not recognized (RU instead of RUS).
2024-01-12 03:00:51 +02:00
Bogdan 58b726a292 Fixed: Improve torrent blocklist matching
Closes #9585
2024-01-12 02:56:32 +02:00
Bogdan 1d8cf6a7f5 Fixed: Persist release source for pending releases
Closes #9583
2024-01-12 02:54:31 +02:00
ilike2burnthing 2c3ad380ef Remove unsupported pagination for Nyaa
(cherry picked from commit fef525ddb8b5f91bb36b3c9e652663fccb098a00)

Closes #9582
2024-01-12 02:52:53 +02:00
Stevie Robinson 0e7874aacf Fix Missing HelpText Translation Keys
(cherry picked from commit 587b600d6c6bac64c99d12225360810ef283f0aa)

Closes #9576
2024-01-12 02:45:57 +02:00
Weblate 8638d82ad3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aleksandr <alyarmak@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Bradley BARBIER <bradley.barbier@outlook.fr>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: HuaBing <admin@hbcraft.cn>
Co-authored-by: JJonttuu <oikeaihminen@protonmail.com>
Co-authored-by: Juan Lores <juan.lores@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Piotr Komborski <piotr+github@kombor.ski>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Watashi <drazy24@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: boan51204 <je.991707@gmail.com>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: liangyi <994302767@qq.com>
Co-authored-by: marius-tu <marius.tubbesing94@gmail.com>
Co-authored-by: ragote <ragote@pm.me>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: twobuttonbob <madinlol@gmail.com>
Co-authored-by: 饶志华 <879467666@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2024-01-12 02:41:40 +02:00
Bogdan f3d6a1f99d Fixed: Release source for release/push 2024-01-11 02:07:03 +02:00
Bogdan fa036f5807 Sorting movie list by tags 2024-01-11 00:41:53 +02:00
Bogdan a931f8a69f Fixed: Skip fewer slides with cast/crew on smaller screens
Fixes #9571
2024-01-10 20:51:40 +02:00
Bogdan a491c9a4a0 Fixed: Parsing custom formats for releases titles containing colon 2024-01-10 20:34:24 +02:00
Bogdan 2aafb6369c Fix app name in healthcheck 2024-01-10 01:35:08 +02:00
Mark McDowall ef8253044e Fixed: Blocklisting torrents from indexers that do not provide torrent hash
(cherry picked from commit 3541cd7ba877fb785c7f97123745abf51162eb8e)
2024-01-09 00:30:32 +02:00
Bogdan c1feeb72ee New: Year specification for custom formats 2024-01-08 02:29:29 +02:00
Servarr 21560cd6cc Automated API Docs update 2024-01-07 16:36:16 +02:00
Bogdan bda2b9b0b8 Fixed: Filter history by multiple event types 2024-01-07 16:07:36 +02:00
Bogdan 4630de9616 Bump version to 5.3.1 2024-01-07 11:10:52 +02:00
468 changed files with 10589 additions and 7089 deletions
+3 -2
View File
@@ -9,7 +9,7 @@ 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.0' majorVersion: '5.3.5'
minorVersion: $[counter('minorVersion', 2000)] minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)' radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
@@ -17,7 +17,7 @@ variables:
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.417' dotnetVersion: '6.0.417'
nodeVersion: '16.X' nodeVersion: '16.X'
innoVersion: '6.2.0' innoVersion: '6.2.2'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04' linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11' macImage: 'macOS-11'
@@ -1242,6 +1242,7 @@ stages:
- stage: Report_Out - stage: Report_Out
dependsOn: dependsOn:
- Analyze - Analyze
- Installer
- Unit_Test - Unit_Test
- Integration - Integration
- Automation - Automation
+1 -1
View File
@@ -254,7 +254,7 @@ InstallInno()
ProgressStart "Installing portable Inno Setup" ProgressStart "Installing portable Inno Setup"
rm -rf _inno rm -rf _inno
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe" curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
mkdir _inno mkdir _inno
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno ./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
rm innosetup.exe rm innosetup.exe
+2
View File
@@ -2,6 +2,8 @@ const loose = true;
module.exports = { module.exports = {
plugins: [ plugins: [
'@babel/plugin-transform-logical-assignment-operators',
// Stage 1 // Stage 1
'@babel/plugin-proposal-export-default-from', '@babel/plugin-proposal-export-default-from',
['@babel/plugin-transform-optional-chaining', { loose }], ['@babel/plugin-transform-optional-chaining', { loose }],
@@ -6,7 +6,7 @@ import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './HistoryEventTypeCell.css'; import styles from './HistoryEventTypeCell.css';
function getIconName(eventType) { function getIconName(eventType, data) {
switch (eventType) { switch (eventType) {
case 'grabbed': case 'grabbed':
return icons.DOWNLOADING; return icons.DOWNLOADING;
@@ -17,7 +17,7 @@ function getIconName(eventType) {
case 'downloadFailed': case 'downloadFailed':
return icons.DOWNLOADING; return icons.DOWNLOADING;
case 'movieFileDeleted': case 'movieFileDeleted':
return icons.DELETE; return data.reason === 'MissingFromDisk' ? icons.FILE_MISSING : icons.DELETE;
case 'movieFileRenamed': case 'movieFileRenamed':
return icons.ORGANIZE; return icons.ORGANIZE;
case 'downloadIgnored': case 'downloadIgnored':
@@ -47,7 +47,7 @@ function getTooltip(eventType, data) {
case 'downloadFailed': case 'downloadFailed':
return translate('MovieDownloadFailedTooltip'); return translate('MovieDownloadFailedTooltip');
case 'movieFileDeleted': case 'movieFileDeleted':
return translate('MovieFileDeletedTooltip'); return data.reason === 'MissingFromDisk' ? translate('MovieFileMissingTooltip') : translate('MovieFileDeletedTooltip');
case 'movieFileRenamed': case 'movieFileRenamed':
return translate('MovieFileRenamedTooltip'); return translate('MovieFileRenamedTooltip');
case 'downloadIgnored': case 'downloadIgnored':
@@ -58,7 +58,7 @@ function getTooltip(eventType, data) {
} }
function HistoryEventTypeCell({ eventType, data }) { function HistoryEventTypeCell({ eventType, data }) {
const iconName = getIconName(eventType); const iconName = getIconName(eventType, data);
const iconKind = getIconKind(eventType); const iconKind = getIconKind(eventType);
const tooltip = getTooltip(eventType, data); const tooltip = getTooltip(eventType, data);
+10 -3
View File
@@ -25,7 +25,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected';
import QueueFilterModal from './QueueFilterModal'; import QueueFilterModal from './QueueFilterModal';
import QueueOptionsConnector from './QueueOptionsConnector'; import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector'; import QueueRowConnector from './QueueRowConnector';
import RemoveQueueItemsModal from './RemoveQueueItemsModal'; import RemoveQueueItemModal from './RemoveQueueItemModal';
class Queue extends Component { class Queue extends Component {
@@ -307,9 +307,16 @@ class Queue extends Component {
} }
</PageContentBody> </PageContentBody>
<RemoveQueueItemsModal <RemoveQueueItemModal
isOpen={isConfirmRemoveModalOpen} isOpen={isConfirmRemoveModalOpen}
selectedCount={selectedCount} selectedCount={selectedCount}
canChangeCategory={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
return !!(item && item.downloadClientHasPostImportCategory);
})
)}
canIgnore={isConfirmRemoveModalOpen && ( canIgnore={isConfirmRemoveModalOpen && (
selectedIds.every((id) => { selectedIds.every((id) => {
const item = items.find((i) => i.id === id); const item = items.find((i) => i.id === id);
@@ -317,7 +324,7 @@ class Queue extends Component {
return !!(item && item.movieId); return !!(item && item.movieId);
}) })
)} )}
allPending={isConfirmRemoveModalOpen && ( pending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => { selectedIds.every((id) => {
const item = items.find((i) => i.id === id); const item = items.find((i) => i.id === id);
+15 -1
View File
@@ -4,7 +4,7 @@ import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import ProgressBar from 'Components/ProgressBar'; import ProgressBar from 'Components/ProgressBar';
// 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 TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
@@ -96,7 +96,9 @@ class QueueRow extends Component {
indexer, indexer,
outputPath, outputPath,
downloadClient, downloadClient,
downloadClientHasPostImportCategory,
estimatedCompletionTime, estimatedCompletionTime,
added,
timeleft, timeleft,
size, size,
sizeleft, sizeleft,
@@ -315,6 +317,15 @@ class QueueRow extends Component {
); );
} }
if (name === 'added') {
return (
<RelativeDateCellConnector
key={name}
date={added}
/>
);
}
if (name === 'actions') { if (name === 'actions') {
return ( return (
<TableRowCell <TableRowCell
@@ -363,6 +374,7 @@ class QueueRow extends Component {
<RemoveQueueItemModal <RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen} isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title} sourceTitle={title}
canChangeCategory={!!downloadClientHasPostImportCategory}
canIgnore={!!movie} canIgnore={!!movie}
isPending={isPending} isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed} onRemovePress={this.onRemoveQueueItemModalConfirmed}
@@ -392,7 +404,9 @@ QueueRow.propTypes = {
indexer: PropTypes.string, indexer: PropTypes.string,
outputPath: PropTypes.string, outputPath: PropTypes.string,
downloadClient: PropTypes.string, downloadClient: PropTypes.string,
downloadClientHasPostImportCategory: PropTypes.bool,
estimatedCompletionTime: PropTypes.string, estimatedCompletionTime: PropTypes.string,
added: PropTypes.string,
timeleft: PropTypes.string, timeleft: PropTypes.string,
size: PropTypes.number, size: PropTypes.number,
year: PropTypes.number, year: PropTypes.number,
@@ -1,171 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
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 Modal from 'Components/Modal/Modal';
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, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class RemoveQueueItemModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
});
};
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
};
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
this.resetState();
this.props.onRemovePress(state);
};
onModalClose = () => {
this.resetState();
this.props.onModalClose();
};
//
// Render
render() {
const {
isOpen,
sourceTitle,
canIgnore,
isPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={this.onModalClose}
>
<ModalContent
onModalClose={this.onModalClose}
>
<ModalHeader>
{translate('RemoveQueueItem', { sourceTitle })}
</ModalHeader>
<ModalBody>
<div>
{translate('RemoveQueueItemConfirmation', { sourceTitle })}
</div>
{
isPending ?
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist ?
<FormGroup>
<FormLabel>{translate('SkipRedownload')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup> :
null
}
</ModalBody>
<ModalFooter>
<Button onPress={this.onModalClose}>
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired,
isPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RemoveQueueItemModal;
@@ -0,0 +1,230 @@
import React, { useCallback, useMemo, useState } from 'react';
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 Modal from 'Components/Modal/Modal';
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, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemModal.css';
interface RemovePressProps {
remove: boolean;
changeCategory: boolean;
blocklist: boolean;
skipRedownload: boolean;
}
interface RemoveQueueItemModalProps {
isOpen: boolean;
sourceTitle: string;
canChangeCategory: boolean;
canIgnore: boolean;
isPending: boolean;
selectedCount?: number;
onRemovePress(props: RemovePressProps): void;
onModalClose: () => void;
}
type RemovalMethod = 'removeFromClient' | 'changeCategory' | 'ignore';
type BlocklistMethod =
| 'doNotBlocklist'
| 'blocklistAndSearch'
| 'blocklistOnly';
function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
const {
isOpen,
sourceTitle,
canIgnore,
canChangeCategory,
isPending,
selectedCount,
onRemovePress,
onModalClose,
} = props;
const multipleSelected = selectedCount && selectedCount > 1;
const [removalMethod, setRemovalMethod] =
useState<RemovalMethod>('removeFromClient');
const [blocklistMethod, setBlocklistMethod] =
useState<BlocklistMethod>('doNotBlocklist');
const { title, message } = useMemo(() => {
if (!selectedCount) {
return {
title: translate('RemoveQueueItem', { sourceTitle }),
message: translate('RemoveQueueItemConfirmation', { sourceTitle }),
};
}
if (selectedCount === 1) {
return {
title: translate('RemoveSelectedItem'),
message: translate('RemoveSelectedItemQueueMessageText'),
};
}
return {
title: translate('RemoveSelectedItems'),
message: translate('RemoveSelectedItemsQueueMessageText', {
selectedCount,
}),
};
}, [sourceTitle, selectedCount]);
const removalMethodOptions = useMemo(() => {
return [
{
key: 'removeFromClient',
value: translate('RemoveFromDownloadClient'),
hint: multipleSelected
? translate('RemoveMultipleFromDownloadClientHint')
: translate('RemoveFromDownloadClientHint'),
},
{
key: 'changeCategory',
value: translate('ChangeCategory'),
isDisabled: !canChangeCategory,
hint: multipleSelected
? translate('ChangeCategoryMultipleHint')
: translate('ChangeCategoryHint'),
},
{
key: 'ignore',
value: multipleSelected
? translate('IgnoreDownloads')
: translate('IgnoreDownload'),
isDisabled: !canIgnore,
hint: multipleSelected
? translate('IgnoreDownloadsHint')
: translate('IgnoreDownloadHint'),
},
];
}, [canChangeCategory, canIgnore, multipleSelected]);
const blocklistMethodOptions = useMemo(() => {
return [
{
key: 'doNotBlocklist',
value: translate('DoNotBlocklist'),
hint: translate('DoNotBlocklistHint'),
},
{
key: 'blocklistAndSearch',
value: translate('BlocklistAndSearch'),
hint: multipleSelected
? translate('BlocklistAndSearchMultipleHint')
: translate('BlocklistAndSearchHint'),
},
{
key: 'blocklistOnly',
value: translate('BlocklistOnly'),
hint: multipleSelected
? translate('BlocklistMultipleOnlyHint')
: translate('BlocklistOnlyHint'),
},
];
}, [multipleSelected]);
const handleRemovalMethodChange = useCallback(
({ value }: { value: RemovalMethod }) => {
setRemovalMethod(value);
},
[setRemovalMethod]
);
const handleBlocklistMethodChange = useCallback(
({ value }: { value: BlocklistMethod }) => {
setBlocklistMethod(value);
},
[setBlocklistMethod]
);
const handleConfirmRemove = useCallback(() => {
onRemovePress({
remove: removalMethod === 'removeFromClient',
changeCategory: removalMethod === 'changeCategory',
blocklist: blocklistMethod !== 'doNotBlocklist',
skipRedownload: blocklistMethod === 'blocklistOnly',
});
setRemovalMethod('removeFromClient');
setBlocklistMethod('doNotBlocklist');
}, [
removalMethod,
blocklistMethod,
setRemovalMethod,
setBlocklistMethod,
onRemovePress,
]);
const handleModalClose = useCallback(() => {
setRemovalMethod('removeFromClient');
setBlocklistMethod('doNotBlocklist');
onModalClose();
}, [setRemovalMethod, setBlocklistMethod, onModalClose]);
return (
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
<ModalContent onModalClose={handleModalClose}>
<ModalHeader>{title}</ModalHeader>
<ModalBody>
<div className={styles.message}>{message}</div>
{isPending ? null : (
<FormGroup>
<FormLabel>{translate('RemoveQueueItemRemovalMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="removalMethod"
value={removalMethod}
values={removalMethodOptions}
isDisabled={!canChangeCategory && !canIgnore}
helpTextWarning={translate(
'RemoveQueueItemRemovalMethodHelpTextWarning'
)}
onChange={handleRemovalMethodChange}
/>
</FormGroup>
)}
<FormGroup>
<FormLabel>
{multipleSelected
? translate('BlocklistReleases')
: translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="blocklistMethod"
value={blocklistMethod}
values={blocklistMethodOptions}
helpText={translate('BlocklistReleaseHelpText')}
onChange={handleBlocklistMethodChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button onPress={handleModalClose}>{translate('Close')}</Button>
<Button kind={kinds.DANGER} onPress={handleConfirmRemove}>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
export default RemoveQueueItemModal;
@@ -1,174 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
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 Modal from 'Components/Modal/Modal';
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, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemsModal.css';
class RemoveQueueItemsModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
});
};
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
};
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
this.resetState();
this.props.onRemovePress(state);
};
onModalClose = () => {
this.resetState();
this.props.onModalClose();
};
//
// Render
render() {
const {
isOpen,
selectedCount,
canIgnore,
allPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={this.onModalClose}
>
<ModalContent
onModalClose={this.onModalClose}
>
<ModalHeader>
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
</ModalHeader>
<ModalBody>
<div className={styles.message}>
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', { selectedCount }) : translate('RemoveSelectedItemQueueMessageText')}
</div>
{
allPending ?
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist ?
<FormGroup>
<FormLabel>{translate('SkipRedownload')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup> :
null
}
</ModalBody>
<ModalFooter>
<Button onPress={this.onModalClose}>
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired,
allPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RemoveQueueItemsModal;
@@ -3,10 +3,13 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions'; import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions'; import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions'; import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import parseUrl from 'Utilities/String/parseUrl'; import parseUrl from 'Utilities/String/parseUrl';
import AddNewMovie from './AddNewMovie'; import AddNewMovie from './AddNewMovie';
@@ -35,7 +38,9 @@ const mapDispatchToProps = {
fetchRootFolders, fetchRootFolders,
fetchImportExclusions, fetchImportExclusions,
fetchQueueDetails, fetchQueueDetails,
clearQueueDetails clearQueueDetails,
fetchMovieFiles,
clearMovieFiles
}; };
class AddNewMovieConnector extends Component { class AddNewMovieConnector extends Component {
@@ -55,6 +60,20 @@ class AddNewMovieConnector extends Component {
this.props.fetchQueueDetails(); this.props.fetchQueueDetails();
} }
componentDidUpdate(prevProps) {
const {
items
} = this.props;
if (hasDifferentItems(prevProps.items, items)) {
const movieIds = selectUniqueIds(items, 'internalId');
if (movieIds.length) {
this.props.fetchMovieFiles({ movieId: movieIds });
}
}
}
componentWillUnmount() { componentWillUnmount() {
if (this._movieLookupTimeout) { if (this._movieLookupTimeout) {
clearTimeout(this._movieLookupTimeout); clearTimeout(this._movieLookupTimeout);
@@ -62,6 +81,7 @@ class AddNewMovieConnector extends Component {
this.props.clearAddMovie(); this.props.clearAddMovie();
this.props.clearQueueDetails(); this.props.clearQueueDetails();
this.props.clearMovieFiles();
} }
// //
@@ -107,12 +127,15 @@ class AddNewMovieConnector extends Component {
AddNewMovieConnector.propTypes = { AddNewMovieConnector.propTypes = {
term: PropTypes.string, term: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
lookupMovie: PropTypes.func.isRequired, lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired, clearAddMovie: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired, fetchRootFolders: PropTypes.func.isRequired,
fetchImportExclusions: PropTypes.func.isRequired, fetchImportExclusions: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired, fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired clearQueueDetails: PropTypes.func.isRequired,
fetchMovieFiles: PropTypes.func.isRequired,
clearMovieFiles: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector); export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
@@ -75,7 +75,6 @@ class AddNewMovieSearchResult extends Component {
colorImpairedMode, colorImpairedMode,
id, id,
monitored, monitored,
hasFile,
isAvailable, isAvailable,
movieFile, movieFile,
queueItem, queueItem,
@@ -88,6 +87,8 @@ class AddNewMovieSearchResult extends Component {
isNewAddMovieModalOpen isNewAddMovieModalOpen
} = this.state; } = this.state;
const hasMovieFile = !!movieFile;
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
const posterWidth = 167; const posterWidth = 167;
const posterHeight = 250; const posterHeight = 250;
@@ -126,7 +127,7 @@ class AddNewMovieSearchResult extends Component {
movieId={existingMovieId} movieId={existingMovieId}
movieFile={movieFile} movieFile={movieFile}
monitored={monitored} monitored={monitored}
hasFile={hasFile} hasFile={hasMovieFile}
status={status} status={status}
width={posterWidth} width={posterWidth}
detailedProgressBar={true} detailedProgressBar={true}
@@ -270,7 +271,7 @@ class AddNewMovieSearchResult extends Component {
{ {
isExistingMovie && isSmallScreen && isExistingMovie && isSmallScreen &&
<MovieStatusLabel <MovieStatusLabel
hasMovieFiles={hasFile} hasMovieFiles={hasMovieFile}
monitored={monitored} monitored={monitored}
isAvailable={isAvailable} isAvailable={isAvailable}
queueItem={queueItem} queueItem={queueItem}
@@ -322,7 +323,6 @@ AddNewMovieSearchResult.propTypes = {
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
id: PropTypes.number, id: PropTypes.number,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired, isAvailable: PropTypes.bool.isRequired,
movieFile: PropTypes.object, movieFile: PropTypes.object,
queueItem: PropTypes.object, queueItem: PropTypes.object,
@@ -11,10 +11,12 @@ function createMapStateToProps() {
createExclusionMovieSelector(), createExclusionMovieSelector(),
createDimensionsSelector(), createDimensionsSelector(),
(state) => state.queue.details.items, (state) => state.queue.details.items,
(state) => state.movieFiles.items,
(state, { internalId }) => internalId, (state, { internalId }) => internalId,
(state) => state.settings.ui.item.movieRuntimeFormat, (state) => state.settings.ui.item.movieRuntimeFormat,
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId, movieRuntimeFormat) => { (isExistingMovie, isExclusionMovie, dimensions, queueItems, movieFiles, internalId, movieRuntimeFormat) => {
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId); const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
const movieFile = movieFiles.find((item) => internalId > 0 && item.movieId === internalId);
return { return {
existingMovieId: internalId, existingMovieId: internalId,
@@ -22,6 +24,7 @@ function createMapStateToProps() {
isExclusionMovie, isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
queueItem, queueItem,
movieFile,
movieRuntimeFormat movieRuntimeFormat
}; };
} }
@@ -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
})); }));
}, },
+9
View File
@@ -44,7 +44,16 @@ export interface CustomFilter {
filers: PropertyFilter[]; filers: PropertyFilter[];
} }
export interface AppSectionState {
dimensions: {
isSmallScreen: boolean;
width: number;
height: number;
};
}
interface AppState { interface AppState {
app: AppSectionState;
calendar: CalendarAppState; calendar: CalendarAppState;
commands: CommandAppState; commands: CommandAppState;
history: HistoryAppState; history: HistoryAppState;
+1 -1
View File
@@ -147,7 +147,7 @@ class AgendaEvent extends Component {
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')} title={translate('QualityCutoffNotMet')}
/> />
} }
</div> </div>
+1 -3
View File
@@ -33,9 +33,7 @@ class Calendar extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<Alert kind={kinds.DANGER}> <Alert kind={kinds.DANGER}>{translate('CalendarLoadError')}</Alert>
{translate('UnableToLoadTheCalendar')}
</Alert>
} }
{ {
+3 -3
View File
@@ -104,7 +104,7 @@ class CalendarPage extends Component {
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
label={translate('iCalLink')} label={translate('ICalLink')}
iconName={icons.CALENDAR} iconName={icons.CALENDAR}
onPress={this.onGetCalendarLinkPress} onPress={this.onGetCalendarLinkPress}
/> />
@@ -112,7 +112,7 @@ class CalendarPage extends Component {
<PageToolbarSeparator /> <PageToolbarSeparator />
<PageToolbarButton <PageToolbarButton
label={translate('RSSSync')} label={translate('RssSync')}
iconName={icons.RSS} iconName={icons.RSS}
isSpinning={isRssSyncExecuting} isSpinning={isRssSyncExecuting}
onPress={onRssSyncPress} onPress={onRssSyncPress}
@@ -180,7 +180,7 @@ class CalendarPage extends Component {
{ {
!movieError && movieIsPopulated && !hasMovie && !movieError && movieIsPopulated && !hasMovie &&
<NoMovie /> <NoMovie totalItems={0} />
} }
{ {
@@ -48,6 +48,10 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
.statusContainer { .statusContainer {
display: flex; display: flex;
align-items: center; align-items: center;
&:global(.fullColor) {
filter: var(--calendarFullColorFilter)
}
} }
.statusIcon { .statusIcon {
+11 -3
View File
@@ -76,12 +76,18 @@ class CalendarEvent extends Component {
{title} {title}
</div> </div>
<div className={styles.statusContainer}> <div
className={classNames(
styles.statusContainer,
fullColorEvents && 'fullColor'
)}
>
{ {
queueItem ? queueItem ?
<span className={styles.statusIcon}> <span className={styles.statusIcon}>
<CalendarEventQueueDetails <CalendarEventQueueDetails
{...queueItem} {...queueItem}
fullColorEvents={fullColorEvents}
/> />
</span> : </span> :
null null
@@ -98,12 +104,14 @@ class CalendarEvent extends Component {
} }
{ {
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ? showCutoffUnmetIcon &&
!!movieFile &&
movieFile.qualityCutoffNotMet ?
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')} title={translate('QualityCutoffNotMet')}
/> : /> :
null null
} }
@@ -126,7 +126,7 @@ class CalendarHeader extends Component {
isDisabled={view === calendarViews.AGENDA} isDisabled={view === calendarViews.AGENDA}
onPress={onTodayPress} onPress={onTodayPress}
> >
Today {translate('Today')}
</Button> </Button>
</div> </div>
+4 -3
View File
@@ -20,10 +20,11 @@ function Legend(props) {
if (showCutoffUnmetIcon) { if (showCutoffUnmetIcon) {
iconsToShow.push( iconsToShow.push(
<LegendIconItem <LegendIconItem
name={translate('CutoffUnmet')} name={translate('CutoffNotMet')}
icon={icons.MOVIE_FILE} icon={icons.MOVIE_FILE}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING} kind={kinds.WARNING}
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')} fullColorEvents={fullColorEvents}
tooltip={translate('QualityCutoffNotMet')}
/> />
); );
} }
@@ -4,4 +4,8 @@
.icon { .icon {
margin-right: 5px; margin-right: 5px;
&:global(.fullColorEvents) {
filter: var(--calendarFullColorFilter)
}
} }
@@ -1,3 +1,4 @@
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
@@ -6,9 +7,9 @@ import styles from './LegendIconItem.css';
function LegendIconItem(props) { function LegendIconItem(props) {
const { const {
name, name,
fullColorEvents,
icon, icon,
kind, kind,
darken,
tooltip tooltip
} = props; } = props;
@@ -18,9 +19,11 @@ function LegendIconItem(props) {
title={tooltip} title={tooltip}
> >
<Icon <Icon
className={styles.icon} className={classNames(
styles.icon,
fullColorEvents && 'fullColorEvents'
)}
name={icon} name={icon}
darken={darken}
kind={kind} kind={kind}
/> />
@@ -31,14 +34,10 @@ function LegendIconItem(props) {
LegendIconItem.propTypes = { LegendIconItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
icon: PropTypes.object.isRequired, icon: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired, kind: PropTypes.string.isRequired,
darken: PropTypes.bool.isRequired,
tooltip: PropTypes.string.isRequired tooltip: PropTypes.string.isRequired
}; };
LegendIconItem.defaultProps = {
darken: false
};
export default LegendIconItem; export default LegendIconItem;
@@ -135,7 +135,7 @@ class CalendarOptionsModalContent extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="showCutoffUnmetIcon" name="showCutoffUnmetIcon"
value={showCutoffUnmetIcon} value={showCutoffUnmetIcon}
helpText={translate('ShowCutoffUnmetIconHelpText')} helpText={translate('IconForCutoffUnmetHelpText')}
onChange={this.onOptionInputChange} onChange={this.onOptionInputChange}
/> />
</FormGroup> </FormGroup>
@@ -177,7 +177,7 @@ class CalendarOptionsModalContent extends Component {
values={weekColumnOptions} values={weekColumnOptions}
value={calendarWeekColumnHeader} value={calendarWeekColumnHeader}
onChange={this.onGlobalInputChange} onChange={this.onGlobalInputChange}
helpText={translate('SettingsWeekColumnHeaderHelpText')} helpText={translate('WeekColumnHeaderHelpText')}
/> />
</FormGroup> </FormGroup>
@@ -109,7 +109,7 @@ class CalendarLinkModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{translate('RadarrCalendarFeed')} {translate('CalendarFeed')}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@@ -121,19 +121,19 @@ class CalendarLinkModalContent extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="unmonitored" name="unmonitored"
value={unmonitored} value={unmonitored}
helpText={translate('UnmonitoredHelpText')} helpText={translate('ICalIncludeUnmonitoredMoviesHelpText')}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('ShowAsAllDayEvents')}</FormLabel> <FormLabel>{translate('ICalShowAsAllDayEvents')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="asAllDay" name="asAllDay"
value={asAllDay} value={asAllDay}
helpText={translate('AsAllDayHelpText')} helpText={translate('ICalShowAsAllDayEventsHelpText')}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
@@ -145,7 +145,7 @@ class CalendarLinkModalContent extends Component {
type={inputTypes.TAG} type={inputTypes.TAG}
name="tags" name="tags"
value={tags} value={tags}
helpText={translate('TagsHelpText')} helpText={translate('ICalTagsMoviesHelpText')}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
@@ -160,7 +160,7 @@ class CalendarLinkModalContent extends Component {
name="iCalHttpUrl" name="iCalHttpUrl"
value={iCalHttpUrl} value={iCalHttpUrl}
readOnly={true} readOnly={true}
helpText={translate('ICalHttpUrlHelpText')} helpText={translate('ICalFeedHelpText')}
buttons={[ buttons={[
<ClipboardButton <ClipboardButton
key="copy" key="copy"
+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}
/> />
@@ -74,11 +74,7 @@ CollectionMovieLabel.propTypes = {
CollectionMovieLabel.defaultProps = { CollectionMovieLabel.defaultProps = {
isSaving: false, isSaving: false,
statistics: { statistics: {}
episodeFileCount: 0,
totalEpisodeCount: 0,
percentOfEpisodes: 0
}
}; };
export default CollectionMovieLabel; export default CollectionMovieLabel;
@@ -30,22 +30,24 @@ function CustomFiltersModalContent(props) {
<ModalBody> <ModalBody>
{ {
customFilters.map((customFilter) => { customFilters
return ( .sort((a, b) => a.label.localeCompare(b.label))
<CustomFilter .map((customFilter) => {
key={customFilter.id} return (
id={customFilter.id} <CustomFilter
label={customFilter.label} key={customFilter.id}
filters={customFilter.filters} id={customFilter.id}
selectedFilterKey={selectedFilterKey} label={customFilter.label}
isDeleting={isDeleting} filters={customFilter.filters}
deleteError={deleteError} selectedFilterKey={selectedFilterKey}
dispatchSetFilter={dispatchSetFilter} isDeleting={isDeleting}
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter} deleteError={deleteError}
onEditPress={onEditCustomFilter} dispatchSetFilter={dispatchSetFilter}
/> dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
); onEditPress={onEditCustomFilter}
}) />
);
})
} }
<div className={styles.addButtonContainer}> <div className={styles.addButtonContainer}>
@@ -267,6 +267,7 @@ FormInputGroup.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.any, value: PropTypes.any,
values: PropTypes.arrayOf(PropTypes.any), values: PropTypes.arrayOf(PropTypes.any),
isDisabled: PropTypes.bool,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all), kind: PropTypes.oneOf(kinds.all),
min: PropTypes.number, min: PropTypes.number,
-8
View File
@@ -12,18 +12,10 @@
.info { .info {
color: var(--infoColor); color: var(--infoColor);
&:global(.darken) {
color: color(var(--infoColor) shade(30%));
}
} }
.pink { .pink {
color: var(--pink); color: var(--pink);
&:global(.darken) {
color: color(var(--pink) shade(30%));
}
} }
.success { .success {
+1 -5
View File
@@ -18,7 +18,6 @@ class Icon extends PureComponent {
kind, kind,
size, size,
title, title,
darken,
isSpinning, isSpinning,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -27,8 +26,7 @@ class Icon extends PureComponent {
<FontAwesomeIcon <FontAwesomeIcon
className={classNames( className={classNames(
className, className,
styles[kind], styles[kind]
darken && 'darken'
)} )}
icon={name} icon={name}
spin={isSpinning} spin={isSpinning}
@@ -61,7 +59,6 @@ Icon.propTypes = {
kind: PropTypes.string.isRequired, kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
darken: PropTypes.bool.isRequired,
isSpinning: PropTypes.bool.isRequired, isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired fixedWidth: PropTypes.bool.isRequired
}; };
@@ -69,7 +66,6 @@ Icon.propTypes = {
Icon.defaultProps = { Icon.defaultProps = {
kind: kinds.DEFAULT, kind: kinds.DEFAULT,
size: 14, size: 14,
darken: false,
isSpinning: false, isSpinning: false,
fixedWidth: false fixedWidth: false
}; };
+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}
@@ -40,18 +40,26 @@ class FilterMenuContent extends Component {
} }
{ {
customFilters.map((filter) => { customFilters.length > 0 ?
return ( <MenuItemSeparator /> :
<FilterMenuItem null
key={filter.id} }
filterKey={filter.id}
selectedFilterKey={selectedFilterKey} {
onPress={onFilterSelect} customFilters
> .sort((a, b) => a.label.localeCompare(b.label))
{filter.label} .map((filter) => {
</FilterMenuItem> return (
); <FilterMenuItem
}) key={filter.id}
filterKey={filter.id}
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
</FilterMenuItem>
);
})
} }
{ {
@@ -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;
@@ -103,6 +103,40 @@ class DiscoverMovieHeader extends Component {
); );
} }
if (name === 'isTrending') {
return (
<VirtualTableHeaderCell
key={name}
className={styles[name]}
name={name}
isSortable={true}
{...otherProps}
>
<Icon
name={icons.TRENDING}
size={12}
/>
</VirtualTableHeaderCell>
);
}
if (name === 'isPopular') {
return (
<VirtualTableHeaderCell
key={name}
className={styles[name]}
name={name}
isSortable={true}
{...otherProps}
>
<Icon
name={icons.POPULAR}
size={12}
/>
</VirtualTableHeaderCell>
);
}
return ( return (
<VirtualTableHeaderCell <VirtualTableHeaderCell
key={name} key={name}
@@ -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
}; };
+4
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,
@@ -59,6 +60,7 @@ import {
faEye as fasEye, faEye as fasEye,
faFastBackward as fasFastBackward, faFastBackward as fasFastBackward,
faFastForward as fasFastForward, faFastForward as fasFastForward,
faFileCircleQuestion as fasFileCircleQuestion,
faFileExport as fasFileExport, faFileExport as fasFileExport,
faFileInvoice as farFileInvoice, faFileInvoice as farFileInvoice,
faFilm as fasFilm, faFilm as fasFilm,
@@ -159,6 +161,7 @@ export const EXPORT = fasFileExport;
export const EXTERNAL_LINK = fasExternalLinkAlt; export const EXTERNAL_LINK = fasExternalLinkAlt;
export const FATAL = fasTimesCircle; export const FATAL = fasTimesCircle;
export const FILE = farFile; export const FILE = farFile;
export const FILE_MISSING = fasFileCircleQuestion;
export const FILM = fasFilm; export const FILM = fasFilm;
export const FILTER = fasFilter; export const FILTER = fasFilter;
export const FLAG = fasFlag; export const FLAG = fasFlag;
@@ -231,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;
@@ -242,25 +242,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();
@@ -283,19 +264,39 @@ function InteractiveImportModalContent(
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'),
},
];
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,
@@ -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
} }
]; ];
@@ -50,12 +50,16 @@ class DeleteMovieModalContent extends Component {
title, title,
path, path,
hasFile, hasFile,
statistics,
deleteOptions, deleteOptions,
sizeOnDisk,
onModalClose, onModalClose,
onDeleteOptionChange onDeleteOptionChange
} = this.props; } = this.props;
const {
sizeOnDisk = 0
} = statistics;
const deleteFiles = this.state.deleteFiles; const deleteFiles = this.state.deleteFiles;
const addImportExclusion = deleteOptions.addImportExclusion; const addImportExclusion = deleteOptions.addImportExclusion;
@@ -151,12 +155,16 @@ class DeleteMovieModalContent extends Component {
DeleteMovieModalContent.propTypes = { DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired,
hasFile: PropTypes.bool.isRequired, hasFile: PropTypes.bool.isRequired,
sizeOnDisk: PropTypes.number.isRequired,
deleteOptions: PropTypes.object.isRequired, deleteOptions: PropTypes.object.isRequired,
onDeleteOptionChange: PropTypes.func.isRequired, onDeleteOptionChange: PropTypes.func.isRequired,
onDeletePress: PropTypes.func.isRequired, onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
DeleteMovieModalContent.defaultProps = {
statistics: {}
};
export default DeleteMovieModalContent; export default DeleteMovieModalContent;
@@ -1,8 +1,14 @@
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 Label from 'Components/Label';
import Link from 'Components/Link/Link';
import MonitorToggleButton from 'Components/MonitorToggleButton'; import MonitorToggleButton from 'Components/MonitorToggleButton';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, sizes } from 'Helpers/Props';
import MovieHeadshot from 'Movie/MovieHeadshot'; import MovieHeadshot from 'Movie/MovieHeadshot';
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector'; import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
import translate from 'Utilities/String/translate';
import styles from '../MovieCreditPoster.css'; import styles from '../MovieCreditPoster.css';
class MovieCastPoster extends Component { class MovieCastPoster extends Component {
@@ -52,6 +58,7 @@ class MovieCastPoster extends Component {
render() { render() {
const { const {
tmdbId,
personName, personName,
character, character,
images, images,
@@ -83,15 +90,35 @@ class MovieCastPoster extends Component {
style={contentStyle} style={contentStyle}
> >
<div className={styles.posterContainer}> <div className={styles.posterContainer}>
<div className={styles.controls}> <div className={styles.toggleMonitoredContainer}>
<MonitorToggleButton <MonitorToggleButton
className={styles.action} className={styles.monitorToggleButton}
monitored={monitored} monitored={monitored}
size={20} size={20}
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress} onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
/> />
</div> </div>
<Label className={styles.controls}>
<span className={styles.externalLinks}>
<Popover
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
title={translate('Links')}
body={
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
<Label
className={styles.externalLinkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
{translate('TMDb')}
</Label>
</Link>
}
/>
</span>
</Label>
<div <div
style={elementStyle} style={elementStyle}
> >
@@ -1,8 +1,14 @@
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 Label from 'Components/Label';
import Link from 'Components/Link/Link';
import MonitorToggleButton from 'Components/MonitorToggleButton'; import MonitorToggleButton from 'Components/MonitorToggleButton';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, sizes } from 'Helpers/Props';
import MovieHeadshot from 'Movie/MovieHeadshot'; import MovieHeadshot from 'Movie/MovieHeadshot';
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector'; import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
import translate from 'Utilities/String/translate';
import styles from '../MovieCreditPoster.css'; import styles from '../MovieCreditPoster.css';
class MovieCrewPoster extends Component { class MovieCrewPoster extends Component {
@@ -52,6 +58,7 @@ class MovieCrewPoster extends Component {
render() { render() {
const { const {
tmdbId,
personName, personName,
job, job,
images, images,
@@ -83,15 +90,35 @@ class MovieCrewPoster extends Component {
style={contentStyle} style={contentStyle}
> >
<div className={styles.posterContainer}> <div className={styles.posterContainer}>
<div className={styles.controls}> <div className={styles.toggleMonitoredContainer}>
<MonitorToggleButton <MonitorToggleButton
className={styles.action} className={styles.monitorToggleButton}
monitored={monitored} monitored={monitored}
size={20} size={20}
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress} onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
/> />
</div> </div>
<Label className={styles.controls}>
<span className={styles.externalLinks}>
<Popover
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
title={translate('Links')}
body={
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
<Label
className={styles.externalLinkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
{translate('TMDb')}
</Label>
</Link>
}
/>
</span>
</Label>
<div <div
style={elementStyle} style={elementStyle}
> >
@@ -1,13 +1,17 @@
$hoverScale: 1.05; $hoverScale: 1.05;
.content { .content {
border-radius: '5px';
transition: all 200ms ease-in; transition: all 200ms ease-in;
&:hover { &:hover {
z-index: 2; z-index: 2;
box-shadow: 0 0 12px var(--black); box-shadow: 0 0 12px var(--black);
transition: all 200ms ease-in; transition: all 200ms ease-in;
.controls {
opacity: 0.9;
transition: opacity 200ms linear 150ms;
}
} }
} }
@@ -44,13 +48,13 @@ $hoverScale: 1.05;
font-size: $smallFontSize; font-size: $smallFontSize;
} }
.controls { .toggleMonitoredContainer {
position: absolute; position: absolute;
top: 10px; top: 10px;
z-index: 3; z-index: 3;
} }
.action { .monitorToggleButton {
composes: toggleButton from '~Components/MonitorToggleButton.css'; composes: toggleButton from '~Components/MonitorToggleButton.css';
width: 25px; width: 25px;
@@ -61,8 +65,39 @@ $hoverScale: 1.05;
} }
} }
.controls {
position: absolute;
bottom: 10px;
left: 10px;
z-index: 3;
border-radius: 4px;
background-color: #707070;
color: var(--white);
font-size: $smallFontSize;
opacity: 0;
transition: opacity 0;
}
.action {
composes: button from '~Components/Link/IconButton.css';
&:hover {
color: var(--iconButtonHoverLightColor);
}
}
@media only screen and (max-width: $breakpointSmall) { @media only screen and (max-width: $breakpointSmall) {
.container { .container {
padding: 5px; padding: 5px;
} }
} }
.externalLinks {
margin: 0 2px;
}
.externalLinkLabel {
composes: label from '~Components/Label.css';
cursor: pointer;
}
@@ -5,10 +5,14 @@ interface CssExports {
'container': string; 'container': string;
'content': string; 'content': string;
'controls': string; 'controls': string;
'externalLinkLabel': string;
'externalLinks': string;
'monitorToggleButton': string;
'overlayTitle': string; 'overlayTitle': string;
'poster': string; 'poster': string;
'posterContainer': string; 'posterContainer': string;
'title': string; 'title': string;
'toggleMonitoredContainer': string;
} }
export const cssExports: CssExports; export const cssExports: CssExports;
export default cssExports; export default cssExports;
@@ -52,7 +52,8 @@ class MovieCreditPosters extends Component {
render() { render() {
const { const {
items, items,
itemComponent itemComponent,
isSmallScreen
} = this.props; } = this.props;
const { const {
@@ -67,7 +68,7 @@ class MovieCreditPosters extends Component {
<Swiper <Swiper
slidesPerView='auto' slidesPerView='auto'
spaceBetween={10} spaceBetween={10}
slidesPerGroup={3} slidesPerGroup={isSmallScreen ? 1 : 3}
navigation={true} navigation={true}
loop={false} loop={false}
loopFillGroupWithBlank={true} loopFillGroupWithBlank={true}
+8 -4
View File
@@ -238,7 +238,7 @@ class MovieDetails extends Component {
certification, certification,
ratings, ratings,
path, path,
sizeOnDisk, statistics,
qualityProfileId, qualityProfileId,
monitored, monitored,
studio, studio,
@@ -267,6 +267,10 @@ class MovieDetails extends Component {
movieRuntimeFormat movieRuntimeFormat
} = this.props; } = this.props;
const {
sizeOnDisk = 0
} = statistics;
const { const {
isOrganizeModalOpen, isOrganizeModalOpen,
isEditMovieModalOpen, isEditMovieModalOpen,
@@ -734,7 +738,7 @@ MovieDetails.propTypes = {
certification: PropTypes.string, certification: PropTypes.string,
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
sizeOnDisk: PropTypes.number.isRequired, statistics: PropTypes.object.isRequired,
qualityProfileId: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
@@ -773,9 +777,9 @@ MovieDetails.propTypes = {
MovieDetails.defaultProps = { MovieDetails.defaultProps = {
genres: [], genres: [],
statistics: {},
tags: [], tags: [],
isSaving: false, isSaving: false
sizeOnDisk: 0
}; };
export default MovieDetails; export default MovieDetails;
@@ -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}
+10 -6
View File
@@ -17,13 +17,13 @@ function createUnoptimizedSelector() {
createClientSideCollectionSelector('movies', 'movieIndex'), createClientSideCollectionSelector('movies', 'movieIndex'),
(movies: MoviesAppState) => { (movies: MoviesAppState) => {
return movies.items.map((m) => { return movies.items.map((m) => {
const { monitored, status, hasFile, sizeOnDisk } = m; const { monitored, status, hasFile, statistics } = m;
return { return {
monitored, monitored,
status, status,
hasFile, hasFile,
sizeOnDisk, statistics,
}; };
}); });
} }
@@ -44,16 +44,20 @@ export default function MovieIndexFooter() {
let monitored = 0; let monitored = 0;
let totalFileSize = 0; let totalFileSize = 0;
movies.forEach((s) => { movies.forEach((m) => {
if (s.hasFile) { const { statistics = { sizeOnDisk: 0 } } = m;
const { sizeOnDisk = 0 } = statistics;
if (m.hasFile) {
movieFiles += 1; movieFiles += 1;
} }
if (s.monitored) { if (m.monitored) {
monitored++; monitored++;
} }
totalFileSize += s.sizeOnDisk; totalFileSize += sizeOnDisk;
}); });
return ( return (
@@ -13,6 +13,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector'; import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar'; import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect'; import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
import { Statistics } from 'Movie/Movie';
import MoviePoster from 'Movie/MoviePoster'; import MoviePoster from 'Movie/MoviePoster';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
@@ -66,17 +67,19 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
status, status,
path, path,
overview, overview,
statistics = {} as Statistics,
images, images,
hasFile, hasFile,
isAvailable, isAvailable,
tmdbId, tmdbId,
imdbId, imdbId,
studio, studio,
sizeOnDisk,
added, added,
youTubeTrailerId, youTubeTrailerId,
} = movie; } = movie;
const { sizeOnDisk = 0 } = statistics;
const dispatch = useDispatch(); const dispatch = useDispatch();
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false); const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false); const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
@@ -16,6 +16,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector'; import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar'; import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect'; import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
import { Statistics } from 'Movie/Movie';
import MoviePoster from 'Movie/MoviePoster'; import MoviePoster from 'Movie/MoviePoster';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
@@ -75,12 +76,14 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
path, path,
movieFile, movieFile,
ratings, ratings,
sizeOnDisk, statistics = {} as Statistics,
certification, certification,
originalTitle, originalTitle,
originalLanguage, originalLanguage,
} = movie; } = movie;
const { sizeOnDisk = 0 } = statistics;
const dispatch = useDispatch(); const dispatch = useDispatch();
const [hasPosterError, setHasPosterError] = useState(false); const [hasPosterError, setHasPosterError] = useState(false);
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false); const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
@@ -244,11 +244,15 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
if (isSmallScreen) { if (isSmallScreen) {
const padding = bodyPaddingSmallScreen - 5; const padding = bodyPaddingSmallScreen - 5;
const width = window.innerWidth - padding * 2;
const height = window.innerHeight;
setSize({ if (width !== size.width || height !== size.height) {
width: window.innerWidth - padding * 2, setSize({
height: window.innerHeight, width,
}); height,
});
}
return; return;
} }
@@ -256,13 +260,18 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
if (current) { if (current) {
const width = current.clientWidth; const width = current.clientWidth;
const padding = bodyPadding - 5; const padding = bodyPadding - 5;
const finalWidth = width - padding * 2;
if (Math.abs(size.width - finalWidth) < 20 || size.width === finalWidth) {
return;
}
setSize({ setSize({
width: width - padding * 2, width: finalWidth,
height: window.innerHeight, height: window.innerHeight,
}); });
} }
}, [isSmallScreen, scrollerRef, bounds]); }, [isSmallScreen, size, scrollerRef, bounds]);
useEffect(() => { useEffect(() => {
const currentScrollerRef = scrollerRef.current as HTMLElement; const currentScrollerRef = scrollerRef.current as HTMLElement;
@@ -38,6 +38,7 @@
flex: 1 0 125px; flex: 1 0 125px;
} }
.releaseGroups,
.inCinemas, .inCinemas,
.physicalRelease, .physicalRelease,
.digitalRelease, .digitalRelease,
+1
View File
@@ -20,6 +20,7 @@ interface CssExports {
'physicalRelease': string; 'physicalRelease': string;
'popularity': string; 'popularity': string;
'qualityProfileId': string; 'qualityProfileId': string;
'releaseGroups': string;
'rottenTomatoesRating': string; 'rottenTomatoesRating': string;
'runtime': string; 'runtime': string;
'sizeOnDisk': string; 'sizeOnDisk': string;
@@ -19,6 +19,7 @@ import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks'; import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector'; import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector'; import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector';
import { Statistics } from 'Movie/Movie';
import MoviePopularityIndex from 'Movie/MoviePopularityIndex'; import MoviePopularityIndex from 'Movie/MoviePopularityIndex';
import MovieTitleLink from 'Movie/MovieTitleLink'; import MovieTitleLink from 'Movie/MovieTitleLink';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
@@ -60,6 +61,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
originalLanguage, originalLanguage,
originalTitle, originalTitle,
added, added,
statistics = {} as Statistics,
year, year,
inCinemas, inCinemas,
digitalRelease, digitalRelease,
@@ -67,7 +69,6 @@ function MovieIndexRow(props: MovieIndexRowProps) {
runtime, runtime,
minimumAvailability, minimumAvailability,
path, path,
sizeOnDisk,
genres = [], genres = [],
ratings, ratings,
popularity, popularity,
@@ -82,6 +83,8 @@ function MovieIndexRow(props: MovieIndexRowProps) {
isSaving = false, isSaving = false,
} = movie; } = movie;
const { sizeOnDisk = 0, releaseGroups = [] } = statistics;
const dispatch = useDispatch(); const dispatch = useDispatch();
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false); const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false); const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
@@ -380,6 +383,20 @@ function MovieIndexRow(props: MovieIndexRowProps) {
); );
} }
if (name === 'releaseGroups') {
const joinedReleaseGroups = releaseGroups.join(', ');
const truncatedReleaseGroups =
releaseGroups.length > 3
? `${releaseGroups.slice(0, 3).join(', ')}...`
: joinedReleaseGroups;
return (
<VirtualTableRowCell key={name} className={styles[name]}>
<span title={joinedReleaseGroups}>{truncatedReleaseGroups}</span>
</VirtualTableRowCell>
);
}
if (name === 'tags') { if (name === 'tags') {
return ( return (
<VirtualTableRowCell key={name} className={styles[name]}> <VirtualTableRowCell key={name} className={styles[name]}>
@@ -31,6 +31,7 @@
flex: 1 0 125px; flex: 1 0 125px;
} }
.releaseGroups,
.inCinemas, .inCinemas,
.physicalRelease, .physicalRelease,
.digitalRelease, .digitalRelease,
@@ -17,6 +17,7 @@ interface CssExports {
'physicalRelease': string; 'physicalRelease': string;
'popularity': string; 'popularity': string;
'qualityProfileId': string; 'qualityProfileId': string;
'releaseGroups': string;
'rottenTomatoesRating': string; 'rottenTomatoesRating': string;
'runtime': string; 'runtime': string;
'sizeOnDisk': string; 'sizeOnDisk': string;
+7 -1
View File
@@ -12,6 +12,12 @@ export interface Collection {
title: string; title: string;
} }
export interface Statistics {
movieFileCount: number;
releaseGroups: string[];
sizeOnDisk: number;
}
export interface Ratings { export interface Ratings {
imdb: object; imdb: object;
tmdb: object; tmdb: object;
@@ -42,11 +48,11 @@ interface Movie extends ModelBase {
runtime: number; runtime: number;
minimumAvailability: string; minimumAvailability: string;
path: string; path: string;
sizeOnDisk: number;
genres: string[]; genres: string[];
ratings: Ratings; ratings: Ratings;
popularity: number; popularity: number;
certification: string; certification: string;
statistics: Statistics;
tags: number[]; tags: number[];
images: Image[]; images: Image[];
movieFile: MovieFile; movieFile: MovieFile;
@@ -45,7 +45,8 @@
width: 165px; width: 165px;
} }
.releaseGroup { .releaseGroup,
.dateAdded {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 120px; width: 120px;
@@ -6,6 +6,7 @@ interface CssExports {
'audio': string; 'audio': string;
'audioLanguages': string; 'audioLanguages': string;
'customFormatScore': string; 'customFormatScore': string;
'dateAdded': string;
'download': string; 'download': string;
'formats': string; 'formats': string;
'language': string; 'language': string;
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
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 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 Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
@@ -82,6 +83,7 @@ class MovieFileEditorRow extends Component {
customFormats, customFormats,
customFormatScore, customFormatScore,
languages, languages,
dateAdded,
columns columns
} = this.props; } = this.props;
@@ -287,6 +289,16 @@ class MovieFileEditorRow extends Component {
); );
} }
if (name === 'dateAdded') {
return (
<RelativeDateCellConnector
key={name}
className={styles.dateAdded}
date={dateAdded}
/>
);
}
if (name === 'actions') { if (name === 'actions') {
return ( return (
<TableRowCell key={name} className={styles.actions}> <TableRowCell key={name} className={styles.actions}>
@@ -354,6 +366,7 @@ MovieFileEditorRow.propTypes = {
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,
dateAdded: PropTypes.string,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onDeletePress: PropTypes.func.isRequired onDeletePress: PropTypes.func.isRequired
}; };
@@ -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);
@@ -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
@@ -61,7 +61,7 @@ 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}c={true}
> >
<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 });
@@ -43,7 +43,7 @@ class AddSpecificationModalContent extends Component {
{ {
!isSchemaFetching && !!schemaError && !isSchemaFetching && !!schemaError &&
<div> <div>
{translate('UnableToAddANewConditionPleaseTryAgain')} {translate('AddConditionError')}
</div> </div>
} }
@@ -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';
@@ -49,15 +49,16 @@ function EditSpecificationModalContent(props) {
{...otherProps} {...otherProps}
> >
{ {
fields && fields.some((x) => x.label === 'Regular Expression') && 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>
@@ -69,9 +69,9 @@ class EditDownloadClientModalContent extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<div> <Alert kind={kinds.DANGER}>
{translate('UnableToAddANewDownloadClientPleaseTryAgain')} {translate('AddDownloadClientError')}
</div> </Alert>
} }
{ {
@@ -133,7 +133,7 @@ class EditDownloadClientModalContent extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.NUMBER} type={inputTypes.NUMBER}
name="priority" name="priority"
helpText={translate('PriorityHelpText')} helpText={translate('DownloadClientPriorityHelpText')}
min={1} min={1}
max={50} max={50}
{...priority} {...priority}
@@ -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}
/> />
@@ -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>
} }
{ {
@@ -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}

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