1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-16 21:15:33 -04:00

Compare commits

...

356 Commits

Author SHA1 Message Date
Bogdan
2e97e09f44 Fail build on missing test results
Ignore missing test results failure on FreeBSD
2025-05-10 13:46:35 +03:00
Bogdan
ccfb9c0dad Bump SixLabors.ImageSharp to 3.1.8 2025-05-10 13:43:52 +03:00
Weblate
b655d97e9e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Robi Korb <robi.korb@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
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/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2025-05-04 21:05:34 +03:00
Bogdan
3afcb91db6 Bump version to 5.23.2 2025-05-04 21:04:29 +03:00
Bogdan
704e2d6176 Fixed: (PTP) Sorting releases by time added 2025-05-01 19:22:00 +03:00
Mark McDowall
8314c37b1d Improve messaging when NZB contains invalid XML
(cherry picked from commit 728df146ada115a367bf1ce808482a4625e6098d)
2025-04-29 11:36:09 +03:00
Bogdan
c2c3dfe917 Avoid varying logging message template between calls 2025-04-29 11:35:57 +03:00
Bogdan
c58a9b3f2c Pass messages with arguments to NLog in LoggerExtensions
(cherry picked from commit 9683b0af35220bb0af801779a06d73feaeba809a)
2025-04-29 11:32:12 +03:00
Bogdan
65a532a7fd Fixed: Sidebar flickering on mobile 2025-04-28 11:56:16 +03:00
Bogdan
704d920dab Remove unused preload.js 2025-04-27 21:34:21 +03:00
Bogdan
025cb0788f Update default log level message 2025-04-27 21:10:35 +03:00
Mark McDowall
82c21d8bb1 Convert Log FIles to TypeScript
(cherry picked from commit 95929dd9c2b4460ec58b63136d64bd584e7dd263)
2025-04-27 21:08:26 +03:00
Mark McDowall
96f973c961 Convert Spinner button components to TypeScript
(cherry picked from commit a1d4bb53997748ef348af50dd967bae8e23e234d)
2025-04-27 20:42:38 +03:00
Mark McDowall
a1ed440945 Convert Messages to TypeScript
(cherry picked from commit 0fdeb0566305311dcec344074b5de9e38b8d8090)
2025-04-27 20:38:21 +03:00
Mark McDowall
8caa839d99 Convert Table to TypeScript
(cherry picked from commit 699120a8fd54be9e70fb9a83298f94c8cb6a80bb)
2025-04-27 20:29:10 +03:00
Bogdan
9228e5dea0 Convert ImportListList component to TypeScript 2025-04-27 19:45:03 +03:00
Mark McDowall
371ac0921d Convert TagList components to TypeScript
(cherry picked from commit 20e1a8d116cf60d619f9525cc82867483d38df84)
2025-04-27 19:45:03 +03:00
Mark McDowall
937557e214 Convert Page components to TypeScript
(cherry picked from commit f35a27449d253260ba9c9fae28909cec8a87b4fe)
2025-04-27 19:45:03 +03:00
Mark McDowall
7fdaf41325 useMeasure instead of Measure in TypeScript components
(cherry picked from commit ee1a0a1f7175839c63595bef6d0221d3787189f4)
2025-04-27 19:45:03 +03:00
Bogdan
577eb4f4ca Bump version to 5.23.1 2025-04-27 11:47:54 +03:00
Weblate
311f41b306 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: zefir6 <zefir@mj12.pl>
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/hu/
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/pl/
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/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2025-04-26 12:22:27 +03:00
Mark McDowall
78f3b1f403 Convert Menu components to TypeScript
(cherry picked from commit 12a1ef038753cdab89ae7137d06e1ba3810166b1)
2025-04-25 18:10:02 +03:00
Bogdan
4dc02dcb80 Bump core-js to 3.41 2025-04-24 17:01:45 +03:00
Bogdan
2f649e413d Bump caniuse db 2025-04-24 16:57:21 +03:00
Bogdan
107ddd3826 Fix maximum typo and clean unused CSS files 2025-04-24 16:53:36 +03:00
Bogdan
dfdd2cba99 Page titles for collections and discover 2025-04-24 16:16:23 +03:00
Bogdan
c57d68c3dd Remove unused register page populator 2025-04-24 15:21:20 +03:00
Bogdan
6cc02b734e Fixed: Refresh collections to clear stale state on bulk movies removal 2025-04-24 15:08:31 +03:00
Bogdan
c5fa09dd86 Fixed: Restore scroll position for collections and discover on go back 2025-04-24 15:08:31 +03:00
Bogdan
29d59315b2 Bump version to 5.23.0 2025-04-23 22:04:25 +03:00
Bogdan
981a3c2db3 Fixed: Selecting No Change for quality profile inputs 2025-04-23 20:21:58 +03:00
Bogdan
3f2ea56bf9 Clear collection changes on add movie modal close 2025-04-23 11:33:40 +03:00
Servarr
1679ed1327 Automated API Docs update 2025-04-20 14:15:50 +03:00
Bogdan
69a1c1b21b Custom format scoring is not usable on movies index 2025-04-20 12:57:34 +03:00
Bogdan
5bd51832a0 Bump version to 5.22.4 2025-04-20 08:59:55 +03:00
Bogdan
52a69b662d Convert Add Movie from collection to TypeScript 2025-04-19 16:36:53 +03:00
Bogdan
7e34d89069 Convert Movie Collection Menus to TypeScript 2025-04-19 13:52:57 +03:00
Bogdan
b0024b28a5 Movie file is optional on movie resources 2025-04-18 23:35:43 +03:00
Bogdan
ae5450f75d Convert Edit Movie Collection modal to TypeScript 2025-04-18 23:15:28 +03:00
Bogdan
1d1aca1a04 Convert Collection Footer to TypeScript 2025-04-18 16:02:31 +03:00
Bogdan
3a55316ada Improve typings for select options 2025-04-18 12:11:55 +03:00
Bogdan
9ef7c2a0b4 Fixed: Autotagging using tag specification 2025-04-18 10:14:38 +03:00
Weblate
e759f3fd0b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@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/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/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
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/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2025-04-17 12:09:12 +03:00
Bogdan
03429db877 Fixed: Prevent new imports without deleting old movie files 2025-04-16 21:09:27 +03:00
Bogdan
bb5f421e38 Log when expected movie file is missing from disk on upgrade 2025-04-16 18:18:58 +03:00
Mark McDowall
7dd3ed815a Convert Modal components to TypeScript
(cherry picked from commit 9d0acba00065d78dab2fcd73cad6883bb6539fc7)
2025-04-16 12:56:29 +03:00
Bogdan
cc56482819 Translate settings for Radarr import list 2025-04-16 12:27:47 +03:00
Bogdan
40f41847fd Fixed: Selected value for empty root folder inputs 2025-04-16 12:19:04 +03:00
Bogdan
8485fc8c75 Fix various typos 2025-04-15 23:48:07 +03:00
Servarr
f3026df65d Automated API Docs update 2025-04-15 23:06:49 +03:00
Bogdan
cfd25e974f Fixed: Free space and missing for selected root folder value 2025-04-15 22:30:42 +03:00
Mark McDowall
c52f9c5ec4 New: Ability to change root folder when editing movie
(cherry picked from commit 417af2b91542e709e4b99aa5ca55b0501ba426ad)
2025-04-15 22:30:42 +03:00
Mark McDowall
b91517afd5 Add React Query
(cherry picked from commit 4491df3ae7530f2167beebc3548dd01fd2cc1a12)
2025-04-15 22:30:42 +03:00
Bogdan
ee8aaadb29 Fix editing import lists 2025-04-15 22:30:42 +03:00
Mark McDowall
0694f2fa76 Convert ProviderFieldFormGroup to TypeScript
(cherry picked from commit 2935d148a8ee2717421609eb26e2005eda963110)
2025-04-15 22:30:42 +03:00
Mark McDowall
2c81f3be0f Improve typings in FormInputGroup
(cherry picked from commit 6838f068bcd04b770cd9c53873f160be97ea745f)
2025-04-15 22:30:42 +03:00
Mark McDowall
8fb2f64e98 Fixed: Tooltips for detailed error messages
(cherry picked from commit c589c4f85e49d9d30c48e778e9ea08e692544730)
2025-04-15 22:30:42 +03:00
Mark McDowall
efd2b80e10 Fixed: Truncate long text in tag values
(cherry picked from commit 93c3f6d1d6b50a7ae06aca083aba5297d8d8b6e8)
2025-04-15 22:30:42 +03:00
Stevie Robinson
a9bbe06966 Fix: Adding a new root folder from add movie modal
(cherry picked from commit 983b079c8257790b52d7f63b2aa8051e88c7ceb2)
2025-04-15 22:30:42 +03:00
Mark McDowall
4c6f80b308 Fixed: Closing on click outside select input and styling on Library Import
(cherry picked from commit 3e99917e9d2ba486355e80ccba9e199f73a4f5af)
2025-04-15 22:30:42 +03:00
Bogdan
c8299f7e57 Convert Form Components to TypeScript
Co-authored-by: Mark McDowall <mark@mcdowall.ca>

Remove defaultProps from TypeScript components

(cherry picked from commit a90c13e86f798841cb6db038bb6b6d1408a00585)

Fix multi-select checkboxes not appearing

(cherry picked from commit e199710c15fbfa643a9f71c7a20f70b1722d0df6)
2025-04-15 22:30:42 +03:00
Bogdan
445babbca8 Fixed: Parse JAP instead of JPN as Japanese 2025-04-13 13:06:32 +03:00
Bogdan
e5137d13e9 Bump version to 5.22.3 2025-04-13 09:47:07 +03:00
Bogdan
fb8f8f4dd3 Ignore version check on non-Sqlite platforms for recommendations 2025-04-12 10:20:46 +03:00
Bogdan
2b8ca4746a Eager load metadata for movies 2025-04-12 10:20:46 +03:00
Bogdan
9231a0e526 Fixed: Improve times for refreshing movies
Co-authored-by: Kevin LAMBERT <kev993@gmail.com>
2025-04-11 20:18:35 +03:00
brunhildaV
9fa75f0539 Fixed: Updating minimum size on sliding quality size limits 2025-04-09 23:30:36 +03:00
Bogdan
76b5568129 Bump IPAddressRange, System.Memory and System.ValueTuple 2025-04-09 18:15:15 +03:00
Bogdan
27efe506a7 Fixed: Sending Manual Interaction Needed for Custom Script with unparsed movie 2025-04-09 09:19:20 +03:00
Bogdan
d9be54575a Bump Selenium.WebDriver.ChromeDriver 2025-04-08 12:34:14 +03:00
Servarr
a825b96518 Automated API Docs update 2025-04-08 12:34:02 +03:00
Weblate
221b7a4300 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Ste <stefanucciu@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@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/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
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/
Translation: Servarr/Radarr
2025-04-08 11:51:56 +03:00
Mark McDowall
1ac784e323 Update WikiUrl type in API docs
(cherry picked from commit 9bd619ccfe074abe396bbf043a36a5be18a7ba4b)
2025-04-08 11:37:07 +03:00
Bogdan
aae34f4c43 Simplify props for MovieInteractiveSearchModal 2025-04-08 11:30:34 +03:00
Mark McDowall
7219648fea Fixed: Set output encoding to UTF-8 when running external processes
(cherry picked from commit f8e57b09856278a6d0c65f18704e96a33459687d)
2025-04-08 11:23:38 +03:00
Mark McDowall
b7be80744c New: Prevent Remote Path Mapping local folder being set to System folder or '/'
(cherry picked from commit 0f904e091702a2ac53771ee3aeb5aafe62688035)
2025-04-08 11:23:22 +03:00
Bogdan
29ca18d3f3 Fixed: Parse EN from release titles as English 2025-04-07 16:33:49 +03:00
Bogdan
d9704a999d Fixed: Remove support for IMDb Lists of the form 'ls12345678' 2025-04-07 16:32:11 +03:00
Bogdan
a23983032a Log delete statements only once 2025-04-07 16:08:13 +03:00
Bogdan
99d68cfd91 Fixed: Disallow tags creation with empty label 2025-04-07 16:08:13 +03:00
Bogdan
9c009a84f2 Bump version to 5.22.2 2025-04-06 15:43:07 +03:00
Daniel
e8ca64fabc Fix typo in IMDb List validation message (#11024) 2025-04-06 15:41:28 +03:00
Bogdan
c821541a2f Fixed: (PTP) Parse neutral leech releases 2025-04-04 21:54:43 +03:00
Jamie Bartlett
c10aadcc7b New: Auto tag movies based on studio 2025-04-03 18:59:29 +03:00
Mark McDowall
4a2202ed7f New: Parse original from release name when specified
(cherry picked from commit 88f4016fe0ed768f4206d04479156c45517f15b7)

Closes #10673
2025-04-03 18:45:15 +03:00
Mark McDowall
78c009d6fa New: Parsing releases with 4K in square brackets as 2160p
(cherry picked from commit 9e6b32ca3ec1f16e54803f6419365b1c68ea18e0)

Closes #7848
2025-04-03 18:45:15 +03:00
Mark McDowall
e03289abe7 Fixed: Prevent exception when grabbing unparsable release
(cherry picked from commit 9a69222c9a1c42c2571f21e2d4a2e02b90216248)

Closes #10789
2025-04-03 18:45:15 +03:00
Stevie Robinson
da2ce10c68 Refine localization string for IndexerSettingsFailDownloadsHelpText
(cherry picked from commit 34ae65c087f52a65533d301b88ae9f50a564f6b8)
2025-04-02 16:04:57 +03:00
Bogdan
6d34f2afb1 Clean up media cover service fixture 2025-04-02 15:50:50 +03:00
Bogdan
49b0c9639c Fix media covers test 2025-04-02 15:49:19 +03:00
Bogdan
c281e68b9f Simplify last write time for media covers 2025-04-02 15:09:35 +03:00
Bogdan
740d3ce88c Bump linux agent to ubuntu-22.04 2025-04-01 17:31:39 +03:00
Bogdan
ad7b85f76d New: Indexer flags in webhook for grabbed releases 2025-04-01 17:27:30 +03:00
Bogdan
3aa93e7946 Fixed: Improve error message for queue items from Transmission 2025-04-01 17:16:30 +03:00
Bogdan
bc08b0b2e1 Fixed: Avoid requests without categories for FileList 2025-04-01 17:14:47 +03:00
ojmaster
107f843303 New: Add Urdu Language (#10957) 2025-04-01 00:49:47 +03:00
Bogdan
16b6997b14 Fixed: Deprecate Media Browser / Legacy Emby metadata 2025-03-31 21:28:10 +03:00
Weblate
a5bcac5de9 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alex <despedo@gmail.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@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/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
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/uk/
Translation: Servarr/Radarr
2025-03-30 10:12:46 +03:00
Bogdan
1e10d569c8 Bump version to 5.22.1 2025-03-30 10:12:10 +03:00
Bogdan
74d2259f67 Avoid fetching movies twice on initial load 2025-03-29 19:43:30 +02:00
Bogdan
6e68a91922 Fixed: Avoid stale movie statistics on movies index 2025-03-29 17:50:03 +02:00
Bogdan
a962de776b Movie updates already done in MovieControllerWithSignalR 2025-03-29 17:46:54 +02:00
Mark McDowall
e8afde2e90 Add XML declaration and clean up Kodi metadata generation
(cherry picked from commit b103005aa23baffcf95ade6a2fa3b9923cddc167)
2025-03-25 15:01:30 +02:00
Bogdan
4633a834f3 Save Publish Dates as UTC for grabbed movies 2025-03-25 15:01:30 +02:00
Mark McDowall
cd021961f0 Fixed: Deleting movie folder fails when files/folders aren't instantly removed
(cherry picked from commit c84699ed5d5a2f59f236c26a8999d25a1102ec02)
2025-03-25 09:27:23 +02:00
Bogdan
456ea3d57c Fixed: Manual importing queued items with movieId to avoid title parsing
Fixes #10931
2025-03-25 09:26:03 +02:00
Bogdan
d09fa6f880 Cleanup unused sorting fields for bulk manage providers
(cherry picked from commit 6115236d3853f70a18b73aef15ebe4e18ab48e40)
2025-03-25 09:23:58 +02:00
Bogdan
bcd4fe1f08 Fixed: Priority validation for indexers and download clients
(cherry picked from commit f0e320f3aa501f120721503b8256f464a31be783)
2025-03-25 09:23:30 +02:00
Mark McDowall
8efce68922 Fixed: Trakt yearly lists no longer supported
(cherry picked from commit bdd975da0f1587a058c9b44871dd62eaada02467)
2025-03-25 09:22:41 +02:00
Mark McDowall
4b3c29ed93 Fixed: Allow tables to scroll on tablets in portrait mode
(cherry picked from commit 5fb632eb46cf77ea4f61d407f6429d9c32dba766)
2025-03-25 09:19:13 +02:00
Bogdan
7ea9161779 Bump browserslist-db 2025-03-24 20:26:51 +02:00
Stevie Robinson
f5c66c5093 New: Show size in history details
(cherry picked from commit ce6536f8abab584c365dcccbc31a780b51f6b02d)
2025-03-24 20:24:40 +02:00
Mark McDowall
a3515db9f7 Show Remove Failed option in torrent download clients
(cherry picked from commit 24ce10006eda8563b74c25a54dea92e966e6d786)
2025-03-24 20:05:24 +02:00
Bogdan
d4bb318253 Fixed: Prevent exception for seed configuration provider with invalid indexer ID
(cherry picked from commit f7b54f9d6b88330e04f127b6ee2ed9ee19404ec9)
2025-03-24 20:05:24 +02:00
Stevie Robinson
64e865f296 Enhance failed download warning for items not grabbed by Radarr
(cherry picked from commit c9027359271690f7d374d1330fa39776e2b8ec40)
2025-03-24 20:05:24 +02:00
Mark McDowall
982f9062bd Fixed: Downloads failed for file contents will be removed from client
(cherry picked from commit c034282f45c5e83ab9881356edba6f1e08f3cafe)
2025-03-24 20:05:24 +02:00
Mark McDowall
48075e33ac New: Option to treat downloads with non-media extensions as failed
(cherry picked from commit 776143cc813ec1b5fa31fbf8667c3ab174b71f5c)

New: Treat .scr as dangerous file

(cherry picked from commit 103ccd74f30830944e9e9f06d02be096f476ae34)

New: .arj and .lzh extensions are potentially dangerous

(cherry picked from commit a72288a14e67f62b00f921dbeb1a0f57a61e5ba7)

Fixed: Failing dangerous and executable single file downloads

(cherry picked from commit e37684e045310ca543aa6a22b38a325cd8a8e84d)

Fixed: Rejected Imports with no associated release or indexer

(cherry picked from commit 31e02bdeada8c85d67a75b69e57d3e7ea46989c6)

Fixed: Don't return warning in title field for rejected downloads

(cherry picked from commit 1fa532dd3eaaee01ac6a049e43fcdbd44357d617)

Fixed: Improve rejected download handling

(cherry picked from commit 4db43882361232eb8fe9ee5331c3d77ea3aa8dfa)
2025-03-24 20:05:24 +02:00
Bogdan
91f08a83cd Bump version to 5.22.0 2025-03-24 18:29:45 +02:00
Weblate
886db23c58 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Maxime Surrel <maxime.surrel@live.fr>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2025-03-24 15:48:02 +02:00
Bogdan
b646386e77 Update state with the filtered movie file languages
Due to filtering the languages in /moviefile/bulk to not allow Any or Original, we're updating the state with the actual languages returned by the API.
2025-03-24 12:56:30 +02:00
Servarr
4aa259a666 Automated API Docs update 2025-03-23 18:42:37 +02:00
Bogdan
35f1a61bf8 Fixed: Updating movie files via Manage Files 2025-03-23 18:33:53 +02:00
Bogdan
1d855aed00 Deprecate /api/v3/moviefile/editor 2025-03-23 18:33:53 +02:00
Bogdan
f7da5b0866 Bump version to 5.21.1 2025-03-23 09:46:34 +02:00
Mark McDowall
682cc70acf Fixed: Drop downs flickering in some cases
(cherry picked from commit 3b024443c5447b7638a69a99809bf44b2419261f)

Closes #10869
2025-03-22 19:49:12 +02:00
Weblate
9d624b07ce Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alex <despedo@gmail.com>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
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/uk/
Translation: Servarr/Radarr
2025-03-22 16:34:03 +02:00
Bogdan
2afb41498d Fixed: Improve Movie Details loading 2025-03-22 16:32:47 +02:00
Bogdan
a0679fcf11 Fixed: Don't allow Any or Original for movie files 2025-03-22 15:03:43 +02:00
Weblate
df4a69ac02 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alex <despedo@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: theman824 <h.confirmation@gmx.de>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
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
2025-03-21 18:49:59 +02:00
Bogdan
2c8d8ff2d6 Translate indexer settings
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
2025-03-19 16:32:21 +02:00
Bogdan
0593568065 Fixed: Close modal when deleting movie from index
Fixes #10937
2025-03-19 01:03:39 +02:00
Bogdan
25aa719ad6 Bump NLog, Npgsql, System.Memory and System.ValueTuple 2025-03-18 14:18:00 +02:00
Bogdan
3ab61a2fee Bump version to 5.21.0 2025-03-18 14:13:07 +02:00
Weblate
954a040d6e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
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/
Translation: Servarr/Radarr
2025-03-18 12:33:26 +02:00
Mark McDowall
905b23618a Improve logging when login fails due to CryptographicException
(cherry picked from commit 1449941471cbb8885e9298317b9a30f2576d7941)
2025-03-16 13:12:41 +02:00
Bogdan
8decd5d8e1 Bump version to 5.20.2 2025-03-16 10:45:58 +02:00
Bogdan
8b5b177d16 New: Display indexer in download failed details 2025-03-16 01:41:37 +02:00
Bogdan
e6c6fceff8 Fixed: Inherit indexer, size and release group for marked as failed history 2025-03-16 01:41:37 +02:00
Weblate
dcc8b28a07 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
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/he/
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/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
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
2025-03-15 21:06:54 +02:00
Stevie Robinson
02baf4d7a4 Translate Frontend Components and Helpers
(cherry picked from commit e777b7018481b18ef18f1116f75983a037bf0849)

Closes #8995
2025-03-15 18:15:29 +02:00
Servarr
22ec1fe492 Multiple Translations updated by Weblate (#10925)
ignore-downstream






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/tr/
Translation: Servarr/Radarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
2025-03-15 14:13:27 +02:00
fezster
a7dbdadd21 New: Add HDR Type to XBMC metadata video stream details (#10906)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2025-03-12 14:33:46 +02:00
Bogdan
93581e4a2f Fixed: Spinning icon on toggling movie monitoring 2025-03-11 23:07:08 +02:00
Bogdan
4c8da09df6 Fixed: Movie Details crashing on invalid collection 2025-03-11 16:14:29 +02:00
Bogdan
89666175a6 Update recommendation message against using uTorrent
(cherry picked from commit 6d8c3f15b343a24fc31a212463af8ed2b5792508)
2025-03-11 11:32:13 +02:00
Mark McDowall
7a33e156a3 New: Truncate button text
(cherry picked from commit 093ee5b88db0470426f6132e66a5893e5cf89bab)
2025-03-11 11:29:31 +02:00
Mark McDowall
c7c07404b0 Improve wrapping of text in sidebar
(cherry picked from commit f58dfc5605738ebccdd6adc6f1ca2a7843c086b2)
2025-03-11 11:29:20 +02:00
Mark McDowall
abeeee9363 Upgrade 'eslint-plugin-react-hooks' to 5.2.0
(cherry picked from commit c86822b114bd0b7276b40cdf2bb6181ef35db3dc)
2025-03-11 11:27:52 +02:00
Bogdan
23c30734d2 Convert QualityProfileName to TypeScript
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-03-09 22:02:04 +02:00
Bogdan
939e45e646 Disable left/right arrow navigation when a modal is open on Movie Details 2025-03-09 21:46:08 +02:00
Weblate
16ceba2392 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
2025-03-09 21:20:18 +02:00
Bogdan
94d620d878 Clear search results when switching between movies 2025-03-09 21:16:48 +02:00
hhjuhl
ee0db93a0a Use 'text-wrap: balance' for text wrapping on overview
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
(cherry picked from commit 160151c6e000c6620ba2e04ca6245317c5c9ba16)

Closes #10729
2025-03-09 19:27:24 +02:00
Bogdan
f815b31c33 Convert Movie Details to TypeScript
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-03-09 19:26:44 +02:00
Bogdan
c078191b3d Bump version to 5.20.1 2025-03-09 11:53:09 +02:00
Bogdan
653b358fd3 Convert Delete Movie Modal to TypeScript
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-03-08 16:36:12 +02:00
Bogdan
6a7ed22b44 Convert Movie History to TypeScript
Closes #10755

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-03-08 16:04:08 +02:00
Mark McDowall
779292490a Convert SelectMovieRow to TypeScript
(cherry picked from commit 32ce09648cb9eb13c46b060f2665f3ce837261f2)
2025-03-08 15:16:12 +02:00
Mark McDowall
e4e96fc7f9 Convert Preview Rename to TypeScript
(cherry picked from commit a2fd23c84d0a9d01864119d2e643970845c9e49e)
2025-03-08 15:16:12 +02:00
Weblate
049bf7715e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
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/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/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
2025-03-08 15:16:04 +02:00
Bogdan
df4dfaac0b Bump SixLabors.ImageSharp to 3.1.7 2025-03-07 19:17:58 +02:00
Bogdan
89c96b0a80 Increase input sizes in edit movie modal
Closes #10749
2025-03-07 19:03:28 +02:00
Bogdan
7db12b6e58 Convert EditMovieModal to TypeScript
Towards #10700

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-03-07 19:03:28 +02:00
Bogdan
28dee7bc01 Convert MoveMovieModal to TypeScript
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-03-07 19:03:19 +02:00
Bogdan
8ec60eb0a6 Convert Movie Formats/Status/CollectionLabel to TypeScript 2025-03-07 16:43:37 +02:00
Weblate
102849a697 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Chong Yao Jun <yaojun12345678910@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Stan Ulbrych <stanulbrych@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: pbarone <pbarone@live.com>
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/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
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/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_Hans/
Translation: Servarr/Radarr
2025-03-07 14:03:05 +02:00
Bogdan
95da7d7b47 Convert Interactive Search to TypeScript 2025-03-04 15:15:04 +02:00
Weblate
22b5739967 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Eduardo045 <eduardomeirelles045@gmail.com>
Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Volodymyr <minecrafter7893@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: corwin007x <skopal.ondrej@gmail.com>
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/pt/
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/uk/
Translation: Servarr/Radarr
2025-03-04 14:57:54 +02:00
Bogdan
cfba047d80 Fixed: Parsing some titles with FRA as French 2025-03-04 14:46:11 +02:00
Bogdan
576d404e70 Fixed: Replace diacritics in Clean Title naming tokens 2025-03-02 07:19:13 +02:00
Bogdan
5959d4e51a Fixed: Instance name must contain application name 2025-02-26 03:59:22 +02:00
Chaz Harris
2aca6c6e1d Bump devcontainer nodejs version to 20
(cherry picked from commit d8222c066c04d5219a21a6e7f9f3571a67e8dcca)
2025-02-25 20:06:02 +02:00
Bogdan
e8bbe0ee9f Bump Polly to 8.5.2 2025-02-25 19:51:19 +02:00
Bogdan
66332a110a Bump version to 5.20.0 2025-02-25 19:50:48 +02:00
Bogdan
36c66deb4b Recommend against using uTorrent 2025-02-25 12:50:11 +02:00
Weblate
edec432244 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
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/hu/
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/
Translation: Servarr/Radarr
2025-02-24 19:28:00 +02:00
Bogdan
554e15d438 New: Watch list sorting and rate limit for Trakt Import Lists 2025-02-23 14:59:38 +02:00
Bogdan
553645a07c Bump version to 5.19.2 2025-02-23 12:14:28 +02:00
Bogdan
7de7e83c5b New: Add Blu-ray link to movie details 2025-02-23 00:03:48 +02:00
Bogdan
b7a46bedb0 Fixed: Avoid checking for free space if other specifications fail first 2025-02-22 21:33:38 +02:00
Weblate
0925769377 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Al3xPdx007 <constantin.pdx@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Pablo <pablo@pabloarraiz.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: pelnoph <pierre.regnier.1984@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
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
2025-02-22 18:34:51 +02:00
Servarr
72244362fe Automated API Docs update 2025-02-22 18:33:37 +02:00
Mark McDowall
c6526c34e9 Cleanse console log messages
(cherry picked from commit 609e964794e17343f63e1ecff3fef323e3d284ff)
2025-02-19 15:44:15 +02:00
Bogdan
efa2913dbc Translate Trakt popular list types 2025-02-19 15:28:05 +02:00
Mark McDowall
35c22a4ffa Fixed: Only show Additional Parameters on Trakt Popular list
(cherry picked from commit b122ee967009d53432f3d1dd196132487f3999e1)
2025-02-19 15:15:17 +02:00
Stevie Robinson
66d96e21da Fixed: Fallback to Instance Name for Discord notifications
(cherry picked from commit b99e06acc0a3ecae2857d9225b35424c82c67a2b)
2025-02-19 14:52:30 +02:00
Bogdan
36d4e9e6cd New: Movie Requested filter for interactive search 2025-02-18 04:42:10 +02:00
Weblate
7189d7b15c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: haru4a <haru4as95@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2025-02-16 23:04:54 +02:00
Servarr
6e80113987 Automated API Docs update 2025-02-16 23:03:09 +02:00
Bogdan
bb8a0dda63 Fixed: Processing existing movie files via Manage Files 2025-02-16 21:36:10 +02:00
Bogdan
525ed65687 Fix download links for FileList when passkey contains spaces 2025-02-16 12:19:17 +02:00
Bogdan
3fbccc6af3 Bump version to 5.19.2 2025-02-16 12:19:04 +02:00
Bogdan
8e10eecfac Fixed: Close Metadata settings modal on saving 2025-02-15 13:34:21 +02:00
Bogdan
a3b1512552 Fixed: Parsing some titles with FRE as French and ITA as Italian 2025-02-13 17:31:39 +02:00
epmt7w3ugk
d375b5ffbe Fixed: Parse GER/DE releases as German language
Fix parsing for German language to correctly detect "GER" and "DE"
Update test for GER/DE language parsing.
2025-02-10 17:17:43 +02:00
Bogdan
884abc0368 Bump version to 5.19.1 2025-02-09 17:50:50 +02:00
Weblate
f8da7aae03 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gallyam Biktashev <gallyamb@gmail.com>
Co-authored-by: Gionatan Spedicato <natanoig444@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
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/it/
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/
Translation: Servarr/Radarr
2025-02-07 19:14:05 -06:00
Connor Gallopo
c165118d4d Update README.md
Update Copyright Date
2025-02-07 14:01:42 -06:00
Robin Dadswell
b3dd571a92 New: Migrated StevenLu URL to new URL 2025-02-06 03:41:33 +02:00
Bogdan
dd900eb739 Building docs on ARM
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
(cherry picked from commit 147e732c9ca7a4c289d4f6386f1277650e11f15b)
2025-02-06 00:43:53 +02:00
Bogdan
66aae0c91c Fixed: Reject multi-part files with P1, P2, etc. 2025-02-04 03:40:06 +02:00
Servarr
d888a0a2b3 Automated API Docs update 2025-02-03 21:31:13 +02:00
Bogdan
cb5416a18c Improve message for unknown movie rejection in release searching 2025-02-03 18:06:48 +02:00
Bogdan
7977e0be05 Add reason enum to decision engine rejections
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2025-02-03 17:46:13 +02:00
Bogdan
cd836fef38 Bump version to 5.19.0 2025-02-03 14:12:21 +02:00
Mark McDowall
b0bfbe767c Add MediaInfo AudioLanguagesAll and update styling
(cherry picked from commit 572b8620c9693f6824ae0919d6a37ba69c7590b1)
2025-02-02 15:16:41 +02:00
Bogdan
528b93dabe Fixed: Format bitrate for primary streams in media info
Co-authored-by: Mark McDowall <markus.mcd5@gmail.com>
2025-02-02 13:58:44 +02:00
Bogdan
1edcbee5e1 Bump version to 5.18.4 2025-02-02 12:49:50 +02:00
Bogdan
8853dced9f Fixed: Health warning for downloading inside root folders
(cherry picked from commit 1e9fd02e9d2bf57247adcac5728e2a0d2b084b86)
2025-02-01 23:36:10 +02:00
Mark McDowall
c7aa1bae5e Fixed: Ignore special folders inside Blackhole watch folders
(cherry picked from commit e79dd6f8e689617b1fd9f96c639ac300669112c5)
2025-02-01 23:35:45 +02:00
Bogdan
405ae77070 New: Prefer newer Usenet releases
(cherry picked from commit 6a439f03273b376feda713ef04a6912fc3af9d0a)
2025-02-01 23:35:31 +02:00
Weblate
6236bc9b4f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.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/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translation: Servarr/Radarr
2025-01-31 23:30:40 +02:00
Bogdan
743c977e5b New: Refresh cache for tracked queue on movies update 2025-01-31 23:29:41 +02:00
Bogdan
c0e5646f07 Bump Polly and NLog.Layouts.ClefJsonLayout 2025-01-26 15:42:43 +02:00
Weblate
10094b4e66 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Craze <christian.strey@gmail.com>
Co-authored-by: Dimitar \"Topper\" Maznekov <d.maznekov@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: ηg <jonas.konrath@icloud.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fa/
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2025-01-26 15:00:33 +02:00
Bogdan
d923406f08 Bump version to 5.18.3 2025-01-26 14:48:34 +02:00
Bogdan
69a9c72286 Fixed: Loading movies with duplicated translations 2025-01-26 14:47:13 +02:00
Bogdan
55b9477a01 Fixed: Cleanup duplicated movie translations 2025-01-26 14:47:13 +02:00
Bogdan
6b81f92137 Fixed: Import Movies page crashing on console.error with non-string values 2025-01-21 16:11:22 +02:00
Bogdan
3ceda1bcda New: Parse releases with JPN as Japanese and KOR as Korean 2025-01-20 03:59:38 +02:00
Luke Anderson
f1f1921517 Update Trakt ratings logo (#10822)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2025-01-19 17:05:55 +02:00
Weblate
af0c96538a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dimitar \"Topper\" Maznekov <d.maznekov@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: ahgharaghani <ah.gharaghani@gmail.com>
Co-authored-by: keysuck <joshkkim@gmail.com>
Co-authored-by: warkurre86 <tom.novo.86@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fa/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
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/tr/
Translation: Servarr/Radarr
2025-01-19 16:56:42 +02:00
jcassette
3d52f45b6a New: reflink support for ZFS
(cherry picked from commit a840bb542362d58006b6cc27affd58ee6b965b80)
2025-01-19 16:55:06 +02:00
Bogdan
d4715f119d Bump version to 5.18.2 2025-01-19 16:54:39 +02:00
kephasdev
d58135bf17 Fixed: Augmenting languages for releases with MULTI and other languages (#10842) 2025-01-17 20:32:09 +02:00
Bogdan
b452c10da3 Bump SonarCloud azure extension for UI analysis to 3.X
(cherry picked from commit 396b2ae7c10c7df749ea23ea93608b56482175a1)
2025-01-14 11:43:51 +02:00
Stevie Robinson
f6b364725d Additional logging for delay profile decisions
(cherry picked from commit fa0f77659cbd3e9efdae55bbedb30fd8288622a6)

Closes #10831
2025-01-12 20:40:59 +02:00
Stevie Robinson
99f6be3f3d New: Show release source in history grab details
(cherry picked from commit 1609f0c9647b89bf55b8c043eeffc8a61653a1e5)

Closes #10830
2025-01-12 20:40:59 +02:00
Stevie Robinson
c2ac49a873 Additional logging for custom format score
(cherry picked from commit 3c8268c428688cc703af76b648c9b3385858274f)

Closes #10828
2025-01-12 20:40:58 +02:00
Weblate
0e24a3e8bc Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ano10 <Ano10@users.noreply.translate.servarr.com>
Co-authored-by: Dimitar \"Topper\" Maznekov <d.maznekov@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mickaël O <mickael.ouillon@ac-bordeaux.fr>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: xumei51201314 <xumei51201314@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_Hans/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2025-01-12 15:19:47 +02:00
Weblate
18032cc83b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ano10 <Ano10@users.noreply.translate.servarr.com>
Co-authored-by: Dimitar \"Topper\" Maznekov <d.maznekov@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mickaël O <mickael.ouillon@ac-bordeaux.fr>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
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
2025-01-12 15:19:09 +02:00
Bogdan
927eb38945 Bump version to 5.18.1 2025-01-12 15:16:00 +02:00
Qstick
5fac348613 Bump SonarCloud azure extension to 3.X
(cherry picked from commit 7b8e352d876cd8f8e5b6296f0c3938bed4db8bb8)
2025-01-11 17:44:00 -06:00
Bogdan
7ba9603449 Fixed: Sending Discord notifications with images without absolute links 2025-01-06 13:27:14 +02:00
Bogdan
e36de8ab8d New: Auto tag based on movie status 2025-01-06 04:24:29 +02:00
Stevie Robinson
f8704a1655 Translate backend: Autotagging + CF specs
Signed-off-by: Stevie Robinson <stevie.robinson@gmail.com>
(cherry picked from commit de1cc25c903924fecbca79fedb458d729ae584fd)

Towards #9647
2025-01-06 04:06:34 +02:00
Stevie Robinson
f507d5154e Fixed: Listening on all IPv4 Addresses
(cherry picked from commit 035c474f10c257331a5f47e863d24af82537e335)
2025-01-05 13:53:29 +02:00
Stevie Robinson
5f03e7142a Fixed: qBittorrent Ratio Limit Check
(cherry picked from commit 4dcc015fb19ceb57d2e8f4985c5137e765829d1c)
2025-01-05 13:53:17 +02:00
Bogdan
c0ebbee7c9 Bump version to 5.18.0 2025-01-05 13:52:57 +02:00
Bogdan
4051cf3d80 Fixed: Increase rate limit for PassThePopcorn 2025-01-02 23:16:48 +02:00
Weblate
9876ed64e2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: 1 <1228553526@qq.com>
Co-authored-by: Alexander Balya <alexander.balya@gmail.com>
Co-authored-by: Ano10 <Ano10@users.noreply.translate.servarr.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Matti Meikäläinen <diefor-93@hotmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Tommy Au <smarttommyau@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: marapavelka <mara.pavelka@gmail.com>
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/ro/
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_TW/
Translation: Servarr/Radarr
2025-01-02 23:13:41 +02:00
Bogdan
2f26974ecc New: Add Tagalog language 2025-01-02 21:16:30 +02:00
Siddhant Naik
25f66a3029 New: Add Marathi language 2025-01-02 19:41:08 +02:00
Bogdan
0e25b2708c Fixed: Sending Manual Interaction Required notifications to Discord for unknown movies 2024-12-31 18:20:16 +02:00
Bogdan
410870d21e Check if backup folder is writable on backup
(cherry picked from commit 8aad79fd3e14eb885724a5e5790803c289be2f25)

Closes #10806
2024-12-31 12:16:28 +02:00
Bogdan
a64d931904 Suggest adding IP to RPC whitelist for on failed Transmission auth
(cherry picked from commit f05e552e8e6dc02cd26444073ab9a678dcb36492)
2024-12-31 12:12:52 +02:00
Bogdan
f0a9e76cfc Bump version to 5.17.2 2024-12-30 00:58:56 +02:00
Mark McDowall
6f23c465ee Don't send session information to Sentry
(cherry picked from commit fae24e98fb9230c2f3701caef457332952c6723f)
2024-12-28 03:32:11 +02:00
Bogdan
af60cca9ae Fixed: Advanced settings for Metadata consumers 2024-12-23 12:15:04 +02:00
Mark McDowall
d34d23a052 Fixed: Movies updated during Import List Sync not reflected in the UI
(cherry picked from commit 1c30ecd66dd0fd1dafaf9ab0e41a11a54eaac132)

Closes #10794
2024-12-23 12:08:25 +02:00
Bogdan
0a0da42543 Bump version to 5.17.1 2024-12-22 13:23:52 +02:00
Bogdan
e5419f6f06 Bump System.Memory
Closes #10791
2024-12-21 11:20:28 +02:00
Bogdan
88d9c08f1a Bump MailKit to 4.8.0 and Microsoft.Data.SqlClient to 2.1.7
Closes #10790
2024-12-21 11:15:14 +02:00
Bogdan
6b4259757c Add test for do not prefer repacks/propers 2024-12-20 20:33:18 +02:00
Mark McDowall
f1d7c56d94 Fixed: Custom Format score bypassing upgrades not being allowed
(cherry picked from commit ebe23104d4b29a3c900a982fb84e75c27ed531ab)

Co-authored-by: CeruleanRed <toni.suta@gmail.com>
2024-12-20 20:33:18 +02:00
Mark McDowall
c81b2e80ee Convert MediaInfo to TypeScript
(cherry picked from commit 4e4bf3507f20c0f8581c66804f8ef406c41952d8)

Closes #10753
2024-12-20 15:37:16 +02:00
Mark McDowall
5efefd804b Upgrade @typescript-eslint packages to 8.181.1
(cherry picked from commit ed10b63fa0c161cac7e0a2084e53785ab1798208)
2024-12-17 13:15:06 +02:00
Mark McDowall
38f9543526 Upgrade Font Awesome to 6.7.1
(cherry picked from commit 016b5718386593c030f14fcac307c93ee1ceeca6)
2024-12-17 13:11:07 +02:00
Mark McDowall
aae68e681e Upgrade babel to 7.26.0
(cherry picked from commit bfcd017012730c97eb587ae2d2e91f72ee7a1de3)
2024-12-17 13:08:36 +02:00
Bogdan
1d21bbf78f Bump version to 5.17.0 2024-12-16 20:24:54 +02:00
Stevie Robinson
99c3c8ce5b Replace URLs in translations with tokens
(cherry picked from commit 98d60e1a8e9abce6b31b3cdd745eff0fed181458)
2024-12-16 15:40:58 +02:00
Weblate
85171e40a5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
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/ko/
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-12-16 15:28:28 +02:00
Bogdan
86b656d323 Use minor version for core-js in babel/preset-env 2024-12-16 12:40:37 +02:00
Bogdan
5ae5d1043a Improve opening add movie modal for Discover Overview 2024-12-16 01:25:03 +02:00
Mark McDowall
b801aa0935 New: Add metadata links to telegram messages
Co-authored-by: Ivar Stangeby <istangeby@gmail.com>

Fixed errors sending Telegram notifications when links aren't available

(cherry picked from commit 4eab168267db716a9e897a992e3a7f6889571f9f)
(cherry picked from commit 4d7a3d0909437268b4ad0a0dbeb59d45b4435118)

Closes #10242
Closes #10489
2024-12-15 15:20:16 +02:00
Stevie Robinson
b2b5aa1f79 New: Optionally as Instance Name to Telegram notifications
(cherry picked from commit 36633b5d08c19158f185c0fa5faabbaec607fcb5)

Closes #10757
2024-12-15 14:43:23 +02:00
Mark McDowall
8c6ba9a543 Fixed: Augmenting languages from indexer for release with stale indexer ID
(cherry picked from commit cb7489ce8fe933920ea04297bd2941496a0c07c6)

Closes #10768
2024-12-15 14:43:23 +02:00
Mark McDowall
4e024c51d3 Fixed: Movies without tags bypassing tags on Download Client
(cherry picked from commit c0e264cfc520ee387bfc882c95a5822c655e0d9b)

Closes #10765
2024-12-15 14:43:23 +02:00
Mark McDowall
e4106f0ede Upgrade TypeScript and core-js
(cherry picked from commit 148480909917f69ff3b2ca547ccb4716dd56606e)

Closes #10763
2024-12-15 14:43:20 +02:00
Bogdan
9032ac20ff Bump version to 5.16.3 2024-12-15 10:04:38 +02:00
Bogdan
23fce4bf2e Fixed: Refresh backup list on deletion
(cherry picked from commit 3b00112447361b19c04851a510e63f812597a043)
2024-12-15 05:29:12 +02:00
Mark McDowall
64fd8552f8 Fixed: Error getting processes in some cases
(cherry picked from commit b552d4e9f7ca7388404aa0d52566010a54cb0244)
2024-12-15 05:28:39 +02:00
Weblate
e016410c10 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Rodion <rodyon009@gmail.com>
Co-authored-by: Tomer Horowitz <tomerh2001@gmail.com>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: farebyting <farelbyting@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: hhjuhl <hans@kopula.dk>
Co-authored-by: kaisernet <afimark7@gmail.com>
Co-authored-by: keysuck <joshkkim@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
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/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-12-14 02:33:47 +02:00
Bogdan
bea943adf8 New: Tooltip with extra genres on search and collections 2024-12-13 19:17:59 +02:00
Bogdan
9780d20f8a Improve is visible property check for discover movies 2024-12-13 19:14:41 +02:00
Bogdan
62722d45b0 Fixed: Using all movie genres for collection filters 2024-12-13 19:13:55 +02:00
Bogdan
27dd8e8cd5 New: Tooltip with extra genres on movie details page 2024-12-12 21:59:19 +02:00
Bogdan
6c47ede76b Fixed: Refreshing movie genres 2024-12-12 21:58:54 +02:00
Mark McDowall
7b9562bb38 Update React
(cherry picked from commit 4491df3ae7530f2167beebc3548dd01fd2cc1a12)

Towards #10703
2024-12-12 21:01:19 +02:00
Stevie Robinson
8b0b7c1cb0 New: Reactive search button on Wanted pages
(cherry picked from commit e8c3aa20bd92701a16dcd97c5e103b79b3683105)

Closes #10750
2024-12-12 20:42:55 +02:00
Mark McDowall
7ebd341cd6 Sync TimeSpanConverter with upstream
(cherry picked from commit 1374240321f08d1400faf95e84217e4b7a2d116b)

Closes #10756
2024-12-09 14:03:12 +02:00
Bogdan
6c85f166ff Bump version to 5.16.2 2024-12-08 11:22:50 +02:00
Servarr
45aabce107 Automated API Docs update 2024-12-04 14:09:02 +02:00
soup
0caa793df4 New: Add config file setting for CGNAT authentication bypass
(cherry picked from commit 4c41a4f368046f73f82306bbd73bec992392938b)
2024-12-04 13:30:56 +02:00
Stevie Robinson
9a107cc8d7 New: Add Languages to Webhook Notifications
(cherry picked from commit e039dc45e267cf717e00c0a49ba637012f37e3d7)

Closes #10733
2024-12-02 16:59:45 +02:00
Mark McDowall
a6d727fe2a New: Kometa metadata file creation disabled
(cherry picked from commit c62fc9d05bb9e1fe51b454d78e80bd9250e31f89)

Closes #10738
2024-12-02 16:56:12 +02:00
Servarr
01a53d3624 Automated API Docs update 2024-12-02 16:03:25 +02:00
Weblate
348c29c9d7 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ardenet <1213193613@qq.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Robin Dadswell <robin@robindadswell.tech>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mryx007 <mryx@mail.de>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_Hans/
Translation: Servarr/Radarr
2024-12-02 15:34:43 +02:00
Bogdan
64739712c6 Add return type for movie lookup and import endpoints
Closes #10737
2024-12-02 15:29:50 +02:00
Bogdan
6ac9cca953 Avoid default category on existing Transmission configurations
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
(cherry picked from commit bd656ae7f66fc9224ef2a57857152ee5d54d54f8)
2024-12-02 15:12:07 +02:00
Bogdan
a2b38c5b7d New: Labels support for Transmission 4.0
(cherry picked from commit 675e3cd38a14ea33c27f2d66a4be2bf802e17d88)
2024-12-02 15:12:07 +02:00
Bogdan
3cc4105d71 Bump NLog, Npgsql, Ical.Net, IPAddressRange, ImageSharp and Polly 2024-12-02 14:30:38 +02:00
Mark McDowall
3449a5d3fe Fixed: Don't fail import if symlink target can't be resolved
(cherry picked from commit 8cb58a63d8ec1b290bc57ad2cf1e90809ceebce9)
2024-12-02 02:32:21 +02:00
Gylesie
5bac157d36 Remove unnecessary heap allocations in local IP check
(cherry picked from commit ed536a85ad5f2062bf6f01f80efddb19fa935f63)
2024-12-02 02:31:28 +02:00
Mark McDowall
114d260f42 Deprecate Sizeleft and Timeleft queue item properties
Rename SizeLeft and TimeLeft queue item properties

(cherry picked from commit b51a49097941e5f306cae5785c63985b319784fd)

Fixed: Error loading queue

(cherry picked from commit f9606518eef78117f1e06a8bcc34af57ab0d2454)
2024-12-01 19:25:45 +02:00
Bogdan
617b9c5d35 Console warnings for missing translations on development builds
(cherry picked from commit 67a1ecb0fea4e6c7dfdb68fbe3ef30d4c22398d8)

Closes #10669
2024-12-01 18:54:53 +02:00
Mark McDowall
ba4ccbb0bd Deluge communication improvements
(cherry picked from commit 183b8b574a4dd948b5fada94d0e645b87710f223)
2024-12-01 14:01:41 +02:00
Mark McDowall
b845268b3d New: Support for new SABnzbd history retention values
Closes #10699

(cherry picked from commit e361f18837d98c089f7dc9c0190221ca8e2cf225)
2024-12-01 14:01:09 +02:00
Bogdan
0fee552074 Bump version to 5.16.1 2024-12-01 14:00:15 +02:00
Mark McDowall
828b994ef4 Support Postgres with non-standard version string
(cherry picked from commit 40f4ef27b22113c1dae0d0cbdee8205132bed68a)
2024-12-01 11:47:19 +00:00
Weblate
7952fd325b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Albrt9527 <2563009889@qq.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_HANS/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2024-11-30 02:30:22 +02:00
Barry Mieny
4b4e598b67 New: Add Afrikaans language 2024-11-30 02:29:04 +02:00
Bogdan
71ccebd0f5 Fix cutoff fixture 2024-11-30 02:12:26 +02:00
Mark McDowall
2607c67912 Fixed: Prevent lack of internet from stopping all health checks from running
(cherry picked from commit dba3a8243988d3e9870b841696303191e1703a0d)

Closes #10694
2024-11-30 02:05:47 +02:00
Bogdan
a626b4f3c4 Fixed: Custom Format upgrading not respecting 'Upgrades Allowed'
(cherry picked from commit 91c5e6f12292e522ceb9094825525fb3684b97c6)

Closes #10691
2024-11-30 02:01:46 +02:00
Bogdan
1526bf29f4 Fixed path in downloading to root folder check message 2024-11-30 01:59:16 +02:00
bpoxy
2194772736 New: Add Albanian language (#10663) 2024-11-28 01:00:33 +02:00
Gauthier
cd490d6334 New: Add headers setting in webhook connection
(cherry picked from commit 78fb20282de73c0ea47375895a807235385d90e3)
2024-11-28 00:58:27 +02:00
bakerboy448
ff609848d8 New: Replace 'Ben the Man' release group parsing with 'Ben the Men'
Closes #10676

(cherry picked from commit 202190d032257b3cd19e42606385db7052b2aae4)
2024-11-27 15:59:51 -06:00
Weblate
15b6f7212d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mizuyoru_TW <mizuyoru.tw@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: hebian <zyf200913@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
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-11-27 14:57:43 -06:00
Mark McDowall
af06a9f70d Webpack web target
(cherry picked from commit a90866a73e6cff9a286c23e60c74672f4c0d317a)
2024-11-27 11:12:43 +02:00
Servarr
c3fa440cf8 Multiple Translations updated by Weblate (#10688)
ignore-downstream


Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
2024-11-19 20:37:07 -06:00
Bogdan
0411d66520 Bump version to 5.16.0 2024-11-19 03:10:17 +02:00
Bogdan
179637fe8b Fixed: Release dates for Discover Movie posters 2024-11-19 00:40:44 +02:00
Weblate
09b4bf15cf Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr
2024-11-17 14:00:33 +02:00
Mark McDowall
ea86d14ca7 Fixed: Normalize unicode characters when comparing paths for equality
(cherry picked from commit ceeec091f85d0094e07537b7f62f18292655a710)
2024-11-17 11:28:51 +02:00
Elias Benbourenane
2429dd91c6 Allow GetFileSize to follow symlinks
(cherry picked from commit ca0bb14027f3409014e7cf9ffa8e04e577001d77)
2024-11-17 11:28:35 +02:00
Mark McDowall
a752476cdb Fixed: Allow files to be moved from Torrent Blackhole even when remove is disabled
(cherry picked from commit f739fd0900695e2ff312d13985c87d84ae00ea75)
2024-11-17 11:27:35 +02:00
Bogdan
50ce480abf Pin ReportGenerator in Azure Pipelines for .NET 6 2024-11-15 22:18:40 +02:00
Weblate
0ef6e56e5d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Ardenet <1213193613@qq.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
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/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
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-11-07 17:04:54 +02:00
Bogdan
12d5014125 New: Track Kometa metadata files
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>

Fixes #10059
Fixes #10419
Closes #10311
2024-11-05 14:31:08 +02:00
Bogdan
c8301d425c Fix translation token for Mount Health Check 2024-11-04 19:26:46 +02:00
Servarr
b1df9b2401 Automated API Docs update 2024-11-04 15:41:58 +02:00
Mark McDowall
ff09da3a69 New: Filter queue by status
(cherry picked from commit fb540040ef66e90c55b82539b85df378d6c76bd3)

Closes #10648
2024-11-04 15:05:57 +02:00
Mark McDowall
3b9bd696fb New: Favorite folders in Manual Import
(cherry picked from commit 3ddc6ac6de5c27a9aab915672321c8818dc5da48)

Closes #10630
2024-11-04 14:36:38 +02:00
Weblate
9ab3e6bab7 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lars <lars.erik.heloe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mytelegrambot <lacsonluxur@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
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/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
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/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-11-04 13:26:17 +02:00
Mark McDowall
86f4f86a0a Fixed: Filtering queue by multiple qualities
(cherry picked from commit b8af3af9f16db96337832c2989f4e7ff3dc2ed30)

Closes #10647
2024-11-04 13:12:31 +02:00
Bogdan
40d95a04e3 Sync coding style with upstream for join methods
Towards #10637
2024-11-04 13:01:40 +02:00
Mark McDowall
ca724836ce Rename Manage Custom Formats to Manage Formats
(cherry picked from commit 0f225b05c00add562c9a6aa8cc4cf494e83176c1)

Closes #10629
2024-11-04 12:57:55 +02:00
Mark McDowall
10e3964111 New: Use instance name in PWA manifest
(cherry picked from commit 1fcfb88d2aa0126c4b3c878c8e310311ea57d04d)

Closes #10625
2024-11-04 12:54:59 +02:00
Mark McDowall
b22a86e1d7 New: Include source path with Webhook import event movie file
(cherry picked from commit 73208e2f60263b1236f094a2bf6c47ebd5a8a271)

Closes #10635
2024-11-04 12:53:12 +02:00
Mark McDowall
5976d66511 New: Reject files during import that have no audio tracks
(cherry picked from commit 978349e24135572889095c743d0e7fac734ba7e0)

Closes #10643
2024-11-04 12:51:14 +02:00
Bogdan
b4eff4d4f9 Show a movie path as example in Mount Health Check
Closes #10649
2024-11-04 12:38:48 +02:00
Mark McDowall
1414a09111 New: Add individual edit to Manage Custom Formats
(cherry picked from commit e006b405323c276eb5b7f2dd97b97c80394a6930)
2024-11-04 12:38:30 +02:00
Mark McDowall
b30efd0c62 Use current time for cache break in development
(cherry picked from commit 020ed32fcfab1c6fbe57af5ea650300272c93fd7)
2024-11-04 12:37:12 +02:00
Mark McDowall
def6950db4 Fixed: Use download client name for history column
(cherry picked from commit 1df0ba9e5aef2d2745a45c546c869837ac8e68db)
2024-11-04 12:36:55 +02:00
Mark McDowall
f23c2dbaba Increase retries for DebouncerFixture
(cherry picked from commit 78cf13d341e6690bf6079dd1819d060d002155a7)
2024-11-04 12:36:26 +02:00
Bogdan
186e9cdd23 Bump version to 5.15.1 2024-11-03 11:41:20 +02:00
Bogdan
394f34eb2a Fixed: Root folder existence for import lists and movie collections 2024-11-02 21:59:32 +02:00
Weblate
d9f508280d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Daniel <statoxxl@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: HUi <huynguyeexn@gmail.com>
Co-authored-by: Kuzmich <kuzmich55@gmail.com>
Co-authored-by: Moon55 <dylan.gurdak@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2024-11-02 21:12:23 +02:00
Bogdan
b5505800de Fix file browser translations 2024-11-02 21:07:10 +02:00
Bogdan
48a79eb7d3 Fixed: Loading queue with pending releases for deleted movies 2024-10-31 18:33:04 +02:00
Bogdan
b42f7e09f9 Fixed: Cleaning the French preposition 'à' from titles 2024-10-31 11:33:08 +02:00
Bogdan
8f507ac726 Fixed: Parse "Català" and "Catalán" as Catalan 2024-10-29 20:49:00 +02:00
Bogdan
06d54e0ec2 Update JetBrains logos
Closes #10603
2024-10-29 10:00:57 +02:00
Bogdan
3708d58847 Fixed: Custom filtering movies by year
Fixes #10610
2024-10-28 07:49:55 +02:00
Bogdan
0049ccd39f Inherit trigger from pushed command models
(cherry picked from commit 0bc4903954b955fce0c368ef7fd2a6f3761d6a93)

Closes #10592
2024-10-27 08:20:57 +02:00
Bogdan
ab8a2d190e Improve message for grab errors due to no matching tags
Co-authored-by: zakary <zak@ary.dev>
(cherry picked from commit df672487cf1d5f067849367a2bfb0068defc315d)

Closes #10593
2024-10-27 08:10:38 +02:00
Hadrien Patte
25bb52b206 Use OperatingSystem class to get OS information
(cherry picked from commit 135b5c2ddd8f0a274b0d59eb07f75aaf1446b9da)
2024-10-27 08:08:01 +02:00
Bogdan
63c6f70e67 Fixed: Changing movies to another root folder without moving files 2024-10-27 08:07:44 +02:00
Bogdan
79cd6269f4 Fixed: Status check for completed directories in Deluge
(cherry picked from commit 33139d4b53c1adad769c7e2b0510e8990c66b84a)
2024-10-27 08:06:29 +02:00
Bogdan
879c872179 Cleanse exceptions in event logs
(cherry picked from commit 404e6d68ea526ab521cd39ecda1bf3b02285765d)
2024-10-27 08:06:11 +02:00
Bogdan
d4993cf69b Bump version to 5.15.0 2024-10-26 17:41:27 +03:00
Bogdan
781e0c9d1c Fixed: Optional square and round brackets for "{Release Year}" 2024-10-26 15:02:46 +03:00
Bogdan
c946ed83f9 Fixed: Stopped/Started as initial state for qBittorrent v5.0 2024-10-26 10:32:20 +03:00
Bogdan
9aecf94e8e Bump version to 5.14.0 2024-10-26 09:10:14 +03:00
1039 changed files with 31007 additions and 25981 deletions

View File

@@ -6,7 +6,7 @@
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"version": "20",
"nvmVersion": "latest"
}
},

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-1.3318" y1="43.7371" x2="67.0419" y2="26.0967">
<stop offset="0.1237" style="stop-color:#7866FF"/>
<stop offset="0.5376" style="stop-color:#FE2EB6"/>
<stop offset="0.8548" style="stop-color:#FD0486"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="67.3,16 43.7,0 0,31.1 11.1,70 58.9,60.3 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="45.9148" y1="38.9098" x2="67.6577" y2="9.0989">
<stop offset="0.1237" style="stop-color:#FF0080"/>
<stop offset="0.2587" style="stop-color:#FE0385"/>
<stop offset="0.4109" style="stop-color:#FA0C92"/>
<stop offset="0.5713" style="stop-color:#F41BA9"/>
<stop offset="0.7363" style="stop-color:#EB2FC8"/>
<stop offset="0.8656" style="stop-color:#E343E6"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="67.3,16 43.7,0 38,15.7 38,47.8 70,47.8 "/>
</g>
<g>
<rect x="13.4" y="13.4" style="fill:#000000;" width="43.2" height="43.2"/>
<rect x="17.4" y="48.5" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
<g>
<path style="fill:#FFFFFF;" d="M17.4,19.1h6.9c5.6,0,9.5,3.8,9.5,8.9V28c0,5-3.9,8.9-9.5,8.9h-6.9V19.1z M21.4,22.7v10.7h3
c3.2,0,5.4-2.2,5.4-5.3V28c0-3.2-2.2-5.4-5.4-5.4H21.4z"/>
<polygon style="fill:#FFFFFF;" points="40.3,22.7 34.9,22.7 34.9,19.1 49.6,19.1 49.6,22.7 44.2,22.7 44.2,37 40.3,37 "/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="120.1px" height="130.2px" viewBox="0 0 120.1 130.2" style="enable-background:new 0 0 120.1 130.2;" xml:space="preserve"
>
<g>
<linearGradient id="XMLID_2_" gradientUnits="userSpaceOnUse" x1="31.8412" y1="120.5578" x2="110.2402" y2="73.24">
<stop offset="0" style="stop-color:#FCEE39"/>
<stop offset="1" style="stop-color:#F37B3D"/>
</linearGradient>
<path id="XMLID_3041_" style="fill:url(#XMLID_2_);" d="M118.6,71.8c0.9-0.8,1.4-1.9,1.5-3.2c0.1-2.6-1.8-4.7-4.4-4.9
c-1.2-0.1-2.4,0.4-3.3,1.1l0,0l-83.8,45.9c-1.9,0.8-3.6,2.2-4.7,4.1c-2.9,4.8-1.3,11,3.6,13.9c3.4,2,7.5,1.8,10.7-0.2l0,0l0,0
c0.2-0.2,0.5-0.3,0.7-0.5l78-54.8C117.3,72.9,118.4,72.1,118.6,71.8L118.6,71.8L118.6,71.8z"/>
<linearGradient id="XMLID_3_" gradientUnits="userSpaceOnUse" x1="48.3607" y1="6.9083" x2="119.9179" y2="69.5546">
<stop offset="0" style="stop-color:#EF5A6B"/>
<stop offset="0.57" style="stop-color:#F26F4E"/>
<stop offset="1" style="stop-color:#F37B3D"/>
</linearGradient>
<path id="XMLID_3049_" style="fill:url(#XMLID_3_);" d="M118.8,65.1L118.8,65.1L55,2.5C53.6,1,51.6,0,49.3,0
c-4.3,0-7.7,3.5-7.7,7.7v0c0,2.1,0.8,3.9,2.1,5.3l0,0l0,0c0.4,0.4,0.8,0.7,1.2,1l67.4,57.7l0,0c0.8,0.7,1.8,1.2,3,1.3
c2.6,0.1,4.7-1.8,4.9-4.4C120.2,67.3,119.7,66,118.8,65.1z"/>
<linearGradient id="XMLID_4_" gradientUnits="userSpaceOnUse" x1="52.9467" y1="63.6407" x2="10.5379" y2="37.1562">
<stop offset="0" style="stop-color:#7C59A4"/>
<stop offset="0.3852" style="stop-color:#AF4C92"/>
<stop offset="0.7654" style="stop-color:#DC4183"/>
<stop offset="0.957" style="stop-color:#ED3D7D"/>
</linearGradient>
<path id="XMLID_3042_" style="fill:url(#XMLID_4_);" d="M57.1,59.5C57,59.5,17.7,28.5,16.9,28l0,0l0,0c-0.6-0.3-1.2-0.6-1.8-0.9
c-5.8-2.2-12.2,0.8-14.4,6.6c-1.9,5.1,0.2,10.7,4.6,13.4l0,0l0,0C6,47.5,6.6,47.8,7.3,48c0.4,0.2,45.4,18.8,45.4,18.8l0,0
c1.8,0.8,3.9,0.3,5.1-1.2C59.3,63.7,59,61,57.1,59.5z"/>
<linearGradient id="XMLID_5_" gradientUnits="userSpaceOnUse" x1="52.1736" y1="3.7019" x2="10.7706" y2="37.8971">
<stop offset="0" style="stop-color:#EF5A6B"/>
<stop offset="0.364" style="stop-color:#EE4E72"/>
<stop offset="1" style="stop-color:#ED3D7D"/>
</linearGradient>
<path id="XMLID_3057_" style="fill:url(#XMLID_5_);" d="M49.3,0c-1.7,0-3.3,0.6-4.6,1.5L4.9,28.3c-0.1,0.1-0.2,0.1-0.2,0.2l-0.1,0
l0,0c-1.7,1.2-3.1,3-3.9,5.1C-1.5,39.4,1.5,45.9,7.3,48c3.6,1.4,7.5,0.7,10.4-1.4l0,0l0,0c0.7-0.5,1.3-1,1.8-1.6l34.6-31.2l0,0
c1.8-1.4,3-3.6,3-6.1v0C57.1,3.5,53.6,0,49.3,0z"/>
<g id="XMLID_3008_">
<rect id="XMLID_3033_" x="34.6" y="37.4" style="fill:#000000;" width="51" height="51"/>
<rect id="XMLID_3032_" x="39" y="78.8" style="fill:#FFFFFF;" width="19.1" height="3.2"/>
<g id="XMLID_3009_">
<path id="XMLID_3030_" style="fill:#FFFFFF;" d="M38.8,50.8l1.5-1.4c0.4,0.5,0.8,0.8,1.3,0.8c0.6,0,0.9-0.4,0.9-1.2l0-5.3l2.3,0
l0,5.3c0,1-0.3,1.8-0.8,2.3c-0.5,0.5-1.3,0.8-2.3,0.8C40.2,52.2,39.4,51.6,38.8,50.8z"/>
<path id="XMLID_3028_" style="fill:#FFFFFF;" d="M45.3,43.8l6.7,0v1.9l-4.4,0V47l4,0l0,1.8l-4,0l0,1.3l4.5,0l0,2l-6.7,0
L45.3,43.8z"/>
<path id="XMLID_3026_" style="fill:#FFFFFF;" d="M55,45.8l-2.5,0l0-2l7.3,0l0,2l-2.5,0l0,6.3l-2.3,0L55,45.8z"/>
<path id="XMLID_3022_" style="fill:#FFFFFF;" d="M39,54l4.3,0c1,0,1.8,0.3,2.3,0.7c0.3,0.3,0.5,0.8,0.5,1.4v0
c0,1-0.5,1.5-1.3,1.9c1,0.3,1.6,0.9,1.6,2v0c0,1.4-1.2,2.3-3.1,2.3l-4.3,0L39,54z M43.8,56.6c0-0.5-0.4-0.7-1-0.7l-1.5,0l0,1.5
l1.4,0C43.4,57.3,43.8,57.1,43.8,56.6L43.8,56.6z M43,59l-1.8,0l0,1.5H43c0.7,0,1.1-0.3,1.1-0.8v0C44.1,59.2,43.7,59,43,59z"/>
<path id="XMLID_3019_" style="fill:#FFFFFF;" d="M46.8,54l3.9,0c1.3,0,2.1,0.3,2.7,0.9c0.5,0.5,0.7,1.1,0.7,1.9v0
c0,1.3-0.7,2.1-1.7,2.6l2,2.9l-2.6,0l-1.7-2.5h-1l0,2.5l-2.3,0L46.8,54z M50.6,58c0.8,0,1.2-0.4,1.2-1v0c0-0.7-0.5-1-1.2-1
l-1.5,0v2H50.6z"/>
<path id="XMLID_3016_" style="fill:#FFFFFF;" d="M56.8,54l2.2,0l3.5,8.4l-2.5,0l-0.6-1.5l-3.2,0l-0.6,1.5l-2.4,0L56.8,54z
M58.8,59l-0.9-2.3L57,59L58.8,59z"/>
<path id="XMLID_3014_" style="fill:#FFFFFF;" d="M62.8,54l2.3,0l0,8.3l-2.3,0L62.8,54z"/>
<path id="XMLID_3012_" style="fill:#FFFFFF;" d="M65.7,54l2.1,0l3.4,4.4l0-4.4l2.3,0l0,8.3l-2,0L68,57.8l0,4.6l-2.3,0L65.7,54z"
/>
<path id="XMLID_3010_" style="fill:#FFFFFF;" d="M73.7,61.1l1.3-1.5c0.8,0.7,1.7,1,2.7,1c0.6,0,1-0.2,1-0.6v0
c0-0.4-0.3-0.5-1.4-0.8c-1.8-0.4-3.1-0.9-3.1-2.6v0c0-1.5,1.2-2.7,3.2-2.7c1.4,0,2.5,0.4,3.4,1.1l-1.2,1.6
c-0.8-0.5-1.6-0.8-2.3-0.8c-0.6,0-0.8,0.2-0.8,0.5v0c0,0.4,0.3,0.5,1.4,0.8c1.9,0.4,3.1,1,3.1,2.6v0c0,1.7-1.3,2.7-3.4,2.7
C76.1,62.5,74.7,62,73.7,61.1z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
<g>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="22.9451" y1="75.7869" x2="74.7868" y2="20.6415">
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
<stop offset="0.4044" style="stop-color:#C41E57"/>
<stop offset="0.4677" style="stop-color:#C41E57"/>
<stop offset="0.6505" style="stop-color:#EB8523"/>
<stop offset="0.9516" style="stop-color:#FEBD11"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="49.8,15.2 36,36.7 58.4,70 70,23.1 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="17.7187" y1="73.2922" x2="69.5556" y2="18.1519">
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
<stop offset="0.4044" style="stop-color:#C41E57"/>
<stop offset="0.4677" style="stop-color:#C41E57"/>
<stop offset="0.7043" style="stop-color:#EB8523"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="51.1,15.7 49,0 18.8,33.6 27.6,42.3 20.8,70 58.4,70 "/>
</g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="1.8281" y1="53.4275" x2="48.8245" y2="9.2255">
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
<stop offset="0.6613" style="stop-color:#C41E57"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="49,0 11.6,0 0,47.1 55.6,47.1 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="49.8935" y1="-11.5569" x2="48.8588" y2="24.0352">
<stop offset="0.5" style="stop-color:#C41E57"/>
<stop offset="0.6668" style="stop-color:#D13F48"/>
<stop offset="0.7952" style="stop-color:#D94F39"/>
<stop offset="0.8656" style="stop-color:#DD5433"/>
</linearGradient>
<polygon style="fill:url(#SVGID_4_);" points="55.3,47.1 51.1,15.7 49,0 41.7,23 "/>
</g>
<g>
<rect x="13.4" y="13.5" transform="matrix(-1 2.577289e-003 -2.577289e-003 -1 70.0288 70.081)" style="fill:#000000;" width="43.2" height="43.2"/>
<rect x="17.6" y="48.6" transform="matrix(1 -2.577289e-003 2.577289e-003 1 -0.1287 6.634109e-002)" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
<path style="fill:#FFFFFF;" d="M17.4,19.1l8.2,0c2.3,0,4,0.6,5.2,1.8c1,1,1.5,2.4,1.5,4.1l0,0.1c0,1.5-0.3,2.6-1.1,3.5
c-0.7,0.9-1.6,1.6-2.8,2l4.4,6.4l-4.6,0l-3.7-5.5l-3.3,0l0,5.5l-3.9,0L17.4,19.1z M25.3,27.8c1,0,1.7-0.2,2.2-0.7
c0.5-0.5,0.8-1.1,0.8-1.8l0-0.1c0-0.9-0.3-1.5-0.8-1.9c-0.5-0.4-1.3-0.6-2.3-0.6l-3.9,0l0,5.1L25.3,27.8z"/>
<path style="fill:#FFFFFF;" d="M36,33.2l-1.9,0l0-3.3l2.5,0l0.6-3.8l-2.3,0l0-3.3l2.8,0l0.6-3.7l3.4,0l-0.6,3.7l3.7,0l0.6-3.7
l3.4,0l-0.6,3.7l1.9,0l0,3.3l-2.5,0L47,29.9l2.3,0l0,3.3l-2.8,0L45.8,37l-3.4,0l0.7-3.8l-3.7,0L38.7,37l-3.4,0L36,33.2z
M43.7,29.9l0.6-3.8l-3.7,0L40,29.9L43.7,29.9z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
<defs>
<linearGradient id="linear-gradient" x1="70.22612" y1="27.79912" x2="-5.13024" y2="63.12242" gradientTransform="matrix(1, 0, 0, -1, 0, 71.27997)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#c90f5e"/>
<stop offset="0.22111" stop-color="#c90f5e"/>
<stop offset="0.2356" stop-color="#c90f5e"/>
<stop offset="0.35559" stop-color="#ca135c"/>
<stop offset="0.46633" stop-color="#ce1e57"/>
<stop offset="0.5735" stop-color="#d4314e"/>
<stop offset="0.67844" stop-color="#dc4b41"/>
<stop offset="0.78179" stop-color="#e66d31"/>
<stop offset="0.88253" stop-color="#f3961d"/>
<stop offset="0.94241" stop-color="#fcb20f"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="24.65904" y1="61.99608" x2="46.04762" y2="2.93445" gradientTransform="matrix(1, 0, 0, -1, 0, 71.27997)" gradientUnits="userSpaceOnUse">
<stop offset="0.04188" stop-color="#077cfb"/>
<stop offset="0.44503" stop-color="#c90f5e"/>
<stop offset="0.95812" stop-color="#077cfb"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="17.39552" y1="63.34592" x2="33.19389" y2="7.20092" gradientTransform="matrix(1, 0, 0, -1, 0, 71.27997)" gradientUnits="userSpaceOnUse">
<stop offset="0.27749" stop-color="#c90f5e"/>
<stop offset="0.97382" stop-color="#fcb20f"/>
</linearGradient>
</defs>
<title>rider</title>
<g>
<polygon points="70 27.237 63.391 23.75 20.926 0 3.827 17.921 21.619 41.068 60.537 44.397 70 27.237" fill="url(#linear-gradient)"/>
<polygon points="50.423 16.132 44.271 1.107 27.643 17.471 11.768 50.194 49.411 70 70 57.98 50.423 16.132" fill="url(#linear-gradient-2)"/>
<polygon points="20.926 0 0 14.095 7.779 62.172 27.848 69.889 53.78 48.823 20.926 0" fill="url(#linear-gradient-3)"/>
</g>
<g>
<rect x="13.30219" y="13.19311" width="43.61371" height="43.61371"/>
<g>
<path d="M17.22741,18.86293h8.39564a7.38416,7.38416,0,0,1,5.34268,1.85358,5.86989,5.86989,0,0,1,1.52648,4.1433h0A5.74339,5.74339,0,0,1,28.567,30.5296l4.47041,6.54206H28.34891L24.42368,31.1838h-3.162v5.88785H17.22741V18.86293h0ZM25.296,27.69471c1.96262,0,3.053-1.09034,3.053-2.61682h0c0-1.74455-1.19938-2.61682-3.162-2.61682H21.15265v5.23365H25.296Z" fill="#fff"/>
<path d="M36.09034,18.86293H43.2866c5.77882,0,9.70405,3.92523,9.70405,9.15888h0c0,5.12461-3.92523,9.15888-9.70405,9.15888H36.09034V18.86293Zm4.03427,3.59813V33.47352h3.162a5.23727,5.23727,0,0,0,5.56075-5.45171h0a5.26493,5.26493,0,0,0-5.56075-5.56075h-3.162Z" fill="#fff"/>
</g>
<rect x="17.22741" y="48.62925" width="16.35514" height="2.72586" fill="#fff"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="25.0676" y1="1.4599" x2="43.1829" y2="66.675">
<stop offset="0.2849" style="stop-color:#00CDD7"/>
<stop offset="0.9409" style="stop-color:#2086D7"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="9.4,63.3 0,7.3 17.5,0.1 28.6,6.7 38.8,1.2 60.1,9.4 48.1,70 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="30.7199" y1="9.7343" x2="61.365" y2="54.6713">
<stop offset="0.1398" style="stop-color:#FFF045"/>
<stop offset="0.3656" style="stop-color:#00CDD7"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="70,23.7 61,1.4 44.6,0 19.3,24.3 26.1,55.6 38.8,64.6 70,46 62.3,31.7 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="61.0819" y1="15.2899" x2="65.1065" y2="29.5436">
<stop offset="0.2849" style="stop-color:#00CDD7"/>
<stop offset="0.9409" style="stop-color:#2086D7"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="56,20.4 62.3,31.7 70,23.7 64.4,9.8 "/>
</g>
<g>
<g>
<rect x="13.4" y="13.4" style="fill:#000000;" width="43.2" height="43.2"/>
<rect x="17.5" y="48.5" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
<path style="fill:#FFFFFF;" d="M38.7,34.3l2.3-2.8c1.6,1.3,3.3,2.2,5.3,2.2c1.6,0,2.5-0.6,2.5-1.7v-0.1c0-1-0.6-1.5-3.6-2.3
c-3.6-0.9-5.8-1.9-5.8-5.5v-0.1c0-3.3,2.6-5.4,6.2-5.4c2.6,0,4.8,0.8,6.6,2.3l-2,3c-1.6-1.1-3.1-1.8-4.6-1.8
c-1.5,0-2.3,0.7-2.3,1.6v0.1c0,1.2,0.8,1.6,3.8,2.4c3.6,1,5.6,2.3,5.6,5.4v0.1c0,3.6-2.7,5.6-6.5,5.6
C43.5,37.2,40.8,36.2,38.7,34.3"/>
</g>
<polygon style="fill:#FFFFFF;" points="35.2,19 32.5,29.4 29.5,19 26.5,19 23.4,29.4 20.7,19 16.6,19 21.7,36.9 25,36.9 28,26.5
30.9,36.9 34.3,36.9 39.4,19 "/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -15,7 +15,7 @@ Note that only one type of a given movie is supported. If you want both a 4k ver
* Adding new movies with lots of information, such as trailers, ratings, etc.
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
* Can watch for better quality of the movies you have and do an automatic upgrade. *e.g. from DVD to Blu-Ray*
* Can watch for better quality of the movies you have and do an automatic upgrade. _eg. from DVD to Blu-Ray_
* Automatic failed download handling will try another release if one fails
* Manual search so you can pick any release or to see why a release was not downloaded automatically
* Full integration with SABnzbd and NZBGet
@@ -68,12 +68,12 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
## JetBrains
Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
Thank you to [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png" alt="JetBrains" width="96">](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
* [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/ReSharper_icon.png" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/WebStorm_icon.png" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/Rider_icon.png" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/dotTrace_icon.png" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
## DigitalOcean
@@ -87,4 +87,4 @@ This project is also supported by DigitalOcean
### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
* Copyright 2010-2022
* Copyright 2010-2025

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.13.1'
majorVersion: '5.23.2'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
@@ -19,7 +19,7 @@ variables:
nodeVersion: '20.X'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
linuxImage: 'ubuntu-22.04'
macImage: 'macOS-13'
trigger:
@@ -481,6 +481,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: ne(variables['testName'], 'freebsd-x64')
- job: Unit_Docker
displayName: Unit Docker
@@ -540,7 +541,8 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: true
- job: Unit_LinuxCore_Postgres14
displayName: Unit Native LinuxCore with Postgres14 Database
dependsOn: Prepare
@@ -596,6 +598,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: true
- job: Unit_LinuxCore_Postgres15
displayName: Unit Native LinuxCore with Postgres15 Database
@@ -652,6 +655,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: true
- stage: Integration
displayName: Integration
@@ -734,6 +738,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(testName) Integration Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: true
displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres14
@@ -796,6 +801,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: true
displayName: Publish Test Results
@@ -859,6 +865,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: true
displayName: Publish Test Results
- job: Integration_FreeBSD
@@ -905,6 +912,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'FreeBSD Integration Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: false
displayName: Publish Test Results
- job: Integration_Docker
@@ -974,6 +982,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(testName) Integration Tests'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: true
displayName: Publish Test Results
- stage: Automation
@@ -1055,6 +1064,7 @@ stages:
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(osName) Automation Tests'
failTaskOnFailedTests: $(failBuild)
failTaskOnMissingResultsFile: $(failBuild)
displayName: Publish Test Results
- stage: Analyze
@@ -1116,20 +1126,20 @@ stages:
vmImage: ${{ variables.windowsImage }}
steps:
- checkout: self # Need history for Sonar analysis
- task: SonarCloudPrepare@2
- task: SonarCloudPrepare@3
env:
SONAR_SCANNER_OPTS: ''
inputs:
SonarCloud: 'SonarCloud'
organization: 'radarr'
scannerMode: 'CLI'
scannerMode: 'cli'
configMode: 'manual'
cliProjectKey: 'Radarr_Radarr.UI'
cliProjectName: 'RadarrUI'
cliProjectVersion: '$(radarrVersion)'
cliSources: './frontend'
- task: SonarCloudAnalyze@2
- task: SonarCloudAnalyze@3
- job: Api_Docs
displayName: API Docs
dependsOn: Prepare
@@ -1205,12 +1215,12 @@ stages:
submodules: true
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
- task: SonarCloudPrepare@2
- task: SonarCloudPrepare@3
condition: eq(variables['System.PullRequest.IsFork'], 'False')
inputs:
SonarCloud: 'SonarCloud'
organization: 'radarr'
scannerMode: 'MSBuild'
scannerMode: 'dotnet'
projectKey: 'Radarr_Radarr'
projectName: 'Radarr'
projectVersion: '$(radarrVersion)'
@@ -1223,10 +1233,10 @@ stages:
./build.sh --backend -f net6.0 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@2
- task: SonarCloudAnalyze@3
condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results
- task: reportgenerator@5
- task: reportgenerator@5.3.11
displayName: Generate Coverage Report
inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'

15
docs.sh
View File

@@ -1,13 +1,18 @@
#!/bin/bash
set -e
FRAMEWORK="net6.0"
PLATFORM=$1
ARCHITECTURE="${2:-x64}"
if [ "$PLATFORM" = "Windows" ]; then
RUNTIME="win-x64"
RUNTIME="win-$ARCHITECTURE"
elif [ "$PLATFORM" = "Linux" ]; then
RUNTIME="linux-x64"
RUNTIME="linux-$ARCHITECTURE"
elif [ "$PLATFORM" = "Mac" ]; then
RUNTIME="osx-x64"
RUNTIME="osx-$ARCHITECTURE"
else
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
echo "Platform must be provided as first argument: Windows, Linux or Mac"
exit 1
fi
@@ -35,7 +40,7 @@ dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p
dotnet new tool-manifest
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/net6.0/$RUNTIME/$application" v3 &
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v3 &
sleep 45

View File

@@ -210,7 +210,6 @@ module.exports = {
'no-undef-init': 'off',
'no-undefined': 'off',
'no-unused-vars': ['error', { args: 'none', ignoreRestSiblings: true }],
'no-use-before-define': 'error',
// Node.js and CommonJS

View File

@@ -14,7 +14,6 @@ module.exports = (env) => {
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = !!env.production;
const isProfiling = isProduction && !!env.profile;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
@@ -26,6 +25,7 @@ module.exports = (env) => {
const config = {
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-source-map',
target: 'web',
stats: {
children: false
@@ -159,16 +159,6 @@ module.exports = (env) => {
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: [/\.jsx?$/, /\.tsx?$/],
exclude: /(node_modules|JsLibraries)/,
@@ -186,7 +176,7 @@ module.exports = (env) => {
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
corejs: '3.41'
}
]
]

View File

@@ -145,7 +145,7 @@ function Blocklist() {
});
const handleFilterSelect = useCallback(
(selectedFilterKey: string) => {
(selectedFilterKey: string | number) => {
dispatch(setBlocklistFilter({ selectedFilterKey }));
},
[dispatch]

View File

@@ -18,6 +18,7 @@ import {
} from 'typings/History';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import formatBytes from 'Utilities/Number/formatBytes';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import styles from './HistoryDetails.css';
@@ -41,6 +42,7 @@ function HistoryDetails(props: HistoryDetailsProps) {
indexer,
releaseGroup,
movieMatchType,
releaseSource,
customFormatScore,
nzbInfoUrl,
downloadClient,
@@ -49,10 +51,36 @@ function HistoryDetails(props: HistoryDetailsProps) {
ageHours,
ageMinutes,
publishedDate,
size,
} = data as GrabbedHistoryData;
const downloadClientNameInfo = downloadClientName ?? downloadClient;
let releaseSourceMessage = '';
switch (releaseSource) {
case 'Unknown':
releaseSourceMessage = translate('Unknown');
break;
case 'Rss':
releaseSourceMessage = translate('Rss');
break;
case 'Search':
releaseSourceMessage = translate('Search');
break;
case 'UserInvokedSearch':
releaseSourceMessage = translate('UserInvokedSearch');
break;
case 'InteractiveSearch':
releaseSourceMessage = translate('InteractiveSearch');
break;
case 'ReleasePush':
releaseSourceMessage = translate('ReleasePush');
break;
default:
releaseSourceMessage = '';
}
return (
<DescriptionList>
<DescriptionListItem
@@ -88,6 +116,14 @@ function HistoryDetails(props: HistoryDetailsProps) {
/>
) : null}
{releaseSource ? (
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('ReleaseSource')}
data={releaseSourceMessage}
/>
) : null}
{nzbInfoUrl ? (
<span>
<DescriptionListItemTitle>
@@ -126,12 +162,19 @@ function HistoryDetails(props: HistoryDetailsProps) {
})}
/>
) : null}
{size ? (
<DescriptionListItem
title={translate('Size')}
data={formatBytes(size)}
/>
) : null}
</DescriptionList>
);
}
if (eventType === 'downloadFailed') {
const { message } = data as DownloadFailedHistory;
const { message, indexer } = data as DownloadFailedHistory;
return (
<DescriptionList>
@@ -145,6 +188,10 @@ function HistoryDetails(props: HistoryDetailsProps) {
<DescriptionListItem title={translate('GrabId')} data={downloadId} />
) : null}
{indexer ? (
<DescriptionListItem title={translate('Indexer')} data={indexer} />
) : null}
{message ? (
<DescriptionListItem title={translate('Message')} data={message} />
) : null}
@@ -153,7 +200,7 @@ function HistoryDetails(props: HistoryDetailsProps) {
}
if (eventType === 'downloadFolderImported') {
const { customFormatScore, droppedPath, importedPath } =
const { customFormatScore, droppedPath, importedPath, size } =
data as DownloadFolderImportedHistory;
return (
@@ -186,12 +233,19 @@ function HistoryDetails(props: HistoryDetailsProps) {
data={formatCustomFormatScore(parseInt(customFormatScore))}
/>
) : null}
{size ? (
<DescriptionListItem
title={translate('FileSize')}
data={formatBytes(size)}
/>
) : null}
</DescriptionList>
);
}
if (eventType === 'movieFileDeleted') {
const { reason, customFormatScore } = data as MovieFileDeletedHistory;
const { reason, customFormatScore, size } = data as MovieFileDeletedHistory;
let reasonMessage = '';
@@ -221,6 +275,13 @@ function HistoryDetails(props: HistoryDetailsProps) {
data={formatCustomFormatScore(parseInt(customFormatScore))}
/>
) : null}
{size ? (
<DescriptionListItem
title={translate('FileSize')}
data={formatBytes(size)}
/>
) : null}
</DescriptionList>
);
}

View File

@@ -37,7 +37,7 @@ interface HistoryDetailsModalProps {
sourceTitle: string;
data: HistoryData;
downloadId?: string;
isMarkingAsFailed: boolean;
isMarkingAsFailed?: boolean;
onMarkAsFailedPress: () => void;
onModalClose: () => void;
}

View File

@@ -77,7 +77,7 @@ function History() {
});
const handleFilterSelect = useCallback(
(selectedFilterKey: string) => {
(selectedFilterKey: string | number) => {
dispatch(setHistoryFilter({ selectedFilterKey }));
},
[dispatch]

View File

@@ -154,9 +154,14 @@ function HistoryRow(props: HistoryRowProps) {
}
if (name === 'downloadClient') {
const downloadClientName =
'downloadClientName' in data ? data.downloadClientName : null;
const downloadClient =
'downloadClient' in data ? data.downloadClient : null;
return (
<TableRowCell key={name} className={styles.downloadClient}>
{'downloadClient' in data ? data.downloadClient : ''}
{downloadClientName ?? downloadClient ?? ''}
</TableRowCell>
);
}

View File

@@ -183,7 +183,7 @@ function Queue() {
});
const handleFilterSelect = useCallback(
(selectedFilterKey: string) => {
(selectedFilterKey: string | number) => {
dispatch(setQueueFilter({ selectedFilterKey }));
},
[dispatch]

View File

@@ -6,7 +6,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import { gotoQueuePage, setQueueOption } from 'Store/Actions/queueActions';
import { CheckInputChanged } from 'typings/inputs';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
function QueueOptions() {
@@ -16,7 +16,7 @@ function QueueOptions() {
);
const handleOptionChange = useCallback(
({ name, value }: CheckInputChanged) => {
({ name, value }: InputChanged<boolean>) => {
dispatch(
setQueueOption({
[name]: value,

View File

@@ -1,5 +1,5 @@
import React from 'react';
import Icon, { IconProps } from 'Components/Icon';
import Icon, { IconKind } from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds } from 'Helpers/Props';
import { TooltipPosition } from 'Helpers/Props/tooltipPositions';
@@ -61,7 +61,7 @@ function QueueStatus(props: QueueStatusProps) {
// status === 'downloading'
let iconName = icons.DOWNLOADING;
let iconKind: IconProps['kind'] = kinds.DEFAULT;
let iconKind: IconKind = kinds.DEFAULT;
let title = translate('Downloading');
if (status === 'paused') {

View File

@@ -82,8 +82,7 @@ class AddNewMovie extends Component {
const {
error,
items,
hasExistingMovies,
colorImpairedMode
hasExistingMovies
} = this.props;
const term = this.state.term;
@@ -150,7 +149,6 @@ class AddNewMovie extends Component {
return (
<AddNewMovieSearchResultConnector
key={item.tmdbId}
colorImpairedMode={colorImpairedMode}
{...item}
/>
);
@@ -223,8 +221,7 @@ AddNewMovie.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
hasExistingMovies: PropTypes.bool.isRequired,
onMovieLookupChange: PropTypes.func.isRequired,
onClearMovieLookup: PropTypes.func.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
onClearMovieLookup: PropTypes.func.isRequired
};
export default AddNewMovie;

View File

@@ -6,7 +6,6 @@ import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import parseUrl from 'Utilities/String/parseUrl';
@@ -17,15 +16,13 @@ function createMapStateToProps() {
(state) => state.addMovie,
(state) => state.movies.items.length,
(state) => state.router.location,
createUISettingsSelector(),
(addMovie, existingMoviesCount, location, uiSettings) => {
(addMovie, existingMoviesCount, location) => {
const { params } = parseUrl(location.search);
return {
...addMovie,
term: params.term,
hasExistingMovies: existingMoviesCount > 0,
colorImpairedMode: uiSettings.enableColorImpairedMode
hasExistingMovies: existingMoviesCount > 0
};
}
);

View File

@@ -79,9 +79,9 @@ class AddNewMovieModalContent extends Component {
}
<div className={styles.info}>
<div className={styles.overview}>
{overview}
</div>
{overview ? (
<div className={styles.overview}>{overview}</div>
) : null}
<Form>
<FormGroup>
@@ -98,7 +98,9 @@ class AddNewMovieModalContent extends Component {
movieFolder: folder,
isWindows
}}
helpText={translate('SubfolderWillBeCreatedAutomaticallyInterp', [folder])}
helpText={translate('AddNewMovieRootFolderHelpText', {
folder
})}
onChange={onInputChange}
{...rootFolderPath}
/>
@@ -110,7 +112,7 @@ class AddNewMovieModalContent extends Component {
</FormLabel>
<FormInputGroup
type={inputTypes.MOVIE_MONITORED_SELECT}
type={inputTypes.MONITOR_MOVIES_SELECT}
name="monitor"
onChange={onInputChange}
{...monitor}

View File

@@ -91,6 +91,10 @@
margin-left: 5px;
}
.genres {
pointer-events: all;
}
.links {
margin-left: 5px;
pointer-events: all;

View File

@@ -10,6 +10,7 @@ import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import MovieStatusLabel from 'Movie/Details/MovieStatusLabel';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MovieGenres from 'Movie/MovieGenres';
import MoviePoster from 'Movie/MoviePoster';
import formatRuntime from 'Utilities/Date/formatRuntime';
import translate from 'Utilities/String/translate';
@@ -73,12 +74,9 @@ class AddNewMovieSearchResult extends Component {
isExistingMovie,
isExcluded,
isSmallScreen,
colorImpairedMode,
id,
monitored,
isAvailable,
movieFile,
queueItem,
runtime,
movieRuntimeFormat,
certification
@@ -249,9 +247,7 @@ class AddNewMovieSearchResult extends Component {
name={icons.GENRE}
size={13}
/>
<span className={styles.genres}>
{genres.slice(0, 3).join(', ')}
</span>
<MovieGenres className={styles.genres} genres={genres} />
</Label> :
null
}
@@ -280,20 +276,18 @@ class AddNewMovieSearchResult extends Component {
}
canFlip={true}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
position={tooltipPositions.TOP}
/>
{
isExistingMovie && isSmallScreen &&
<MovieStatusLabel
status={status}
hasMovieFiles={hasMovieFile}
movieId={existingMovieId}
monitored={monitored}
isAvailable={isAvailable}
queueItem={queueItem}
id={id}
hasMovieFiles={hasMovieFile}
status={status}
useLabel={true}
colorImpairedMode={colorImpairedMode}
/>
}
</div>
@@ -338,12 +332,9 @@ AddNewMovieSearchResult.propTypes = {
isExistingMovie: PropTypes.bool.isRequired,
isExcluded: PropTypes.bool,
isSmallScreen: PropTypes.bool.isRequired,
id: PropTypes.number,
monitored: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired,
movieFile: PropTypes.object,
queueItem: PropTypes.object,
colorImpairedMode: PropTypes.bool,
runtime: PropTypes.number.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired,
certification: PropTypes.string

View File

@@ -8,19 +8,16 @@ function createMapStateToProps() {
return createSelector(
createExistingMovieSelector(),
createDimensionsSelector(),
(state) => state.queue.details.items,
(state) => state.movieFiles.items,
(state, { internalId }) => internalId,
(state) => state.settings.ui.item.movieRuntimeFormat,
(isExistingMovie, dimensions, queueItems, movieFiles, internalId, movieRuntimeFormat) => {
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
(isExistingMovie, dimensions, movieFiles, internalId, movieRuntimeFormat) => {
const movieFile = movieFiles.find((item) => internalId > 0 && item.movieId === internalId);
return {
existingMovieId: internalId,
isExistingMovie,
isSmallScreen: dimensions.isSmallScreen,
queueItem,
movieFile,
movieRuntimeFormat
};

View File

@@ -1,18 +1,10 @@
.inputContainer {
margin-right: 20px;
min-width: 150px;
div {
margin-top: 10px;
&:first-child {
margin-top: 0;
}
}
}
.label {
margin-bottom: 3px;
margin-bottom: 10px;
font-weight: bold;
}

View File

@@ -117,7 +117,7 @@ class ImportMovieFooter extends Component {
</div>
<FormInputGroup
type={inputTypes.MOVIE_MONITORED_SELECT}
type={inputTypes.MONITOR_MOVIES_SELECT}
name="monitor"
value={monitor}
isDisabled={!selectedCount}

View File

@@ -44,7 +44,7 @@ function ImportMovieRow(props) {
<VirtualTableRowCell className={styles.monitor}>
<FormInputGroup
type={inputTypes.MOVIE_MONITORED_SELECT}
type={inputTypes.MONITOR_MOVIES_SELECT}
name="monitor"
value={monitor}
onChange={onInputChange}
@@ -81,7 +81,6 @@ ImportMovieRow.propTypes = {
selectedMovie: PropTypes.object,
isExistingMovie: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
queued: PropTypes.bool.isRequired,
isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired

View File

@@ -131,7 +131,7 @@ class ImportMovieSelectMovie extends Component {
id={this._buttonId}
>
<Link
ref={ref}
// ref={ref}
className={styles.button}
component="div"
onPress={this.onPress}
@@ -255,7 +255,7 @@ class ImportMovieSelectMovie extends Component {
items.map((item) => {
return (
<ImportMovieSearchResultConnector
key={item.tvdbId}
key={item.tmdbId}
tmdbId={item.tmdbId}
title={item.title}
year={item.year}

View File

@@ -1,9 +1,10 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ConnectedRouter, ConnectedRouterProps } from 'connected-react-router';
import React from 'react';
import DocumentTitle from 'react-document-title';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import PageConnector from 'Components/Page/PageConnector';
import Page from 'Components/Page/Page';
import ApplyTheme from './ApplyTheme';
import AppRoutes from './AppRoutes';
@@ -12,17 +13,21 @@ interface AppProps {
history: ConnectedRouterProps['history'];
}
const queryClient = new QueryClient();
function App({ store, history }: AppProps) {
return (
<DocumentTitle title={window.Radarr.instanceName}>
<Provider store={store}>
<ConnectedRouter history={history}>
<ApplyTheme />
<PageConnector>
<AppRoutes />
</PageConnector>
</ConnectedRouter>
</Provider>
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<ConnectedRouter history={history}>
<ApplyTheme />
<Page>
<AppRoutes />
</Page>
</ConnectedRouter>
</Provider>
</QueryClientProvider>
</DocumentTitle>
);
}

View File

@@ -10,7 +10,7 @@ import CollectionConnector from 'Collection/CollectionConnector';
import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch';
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
import MovieDetailsPage from 'Movie/Details/MovieDetailsPage';
import MovieIndex from 'Movie/Index/MovieIndex';
import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage';
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
@@ -67,7 +67,7 @@ function AppRoutes() {
<Route path="/add/discover" component={DiscoverMovieConnector} />
<Route path="/movie/:titleSlug" component={MovieDetailsPageConnector} />
<Route path="/movie/:titleSlug" component={MovieDetailsPage} />
{/*
Calendar

View File

@@ -9,13 +9,13 @@ export type SelectContextAction =
| { type: 'unselectAll' }
| {
type: 'toggleSelected';
id: number;
isSelected: boolean;
id: number | string;
isSelected: boolean | null;
shiftKey: boolean;
}
| {
type: 'removeItem';
id: number;
id: number | string;
}
| {
type: 'updateItems';

View File

@@ -1,11 +1,16 @@
import Column from 'Components/Table/Column';
import { SortDirection } from 'Helpers/Props/sortDirections';
import { FilterBuilderProp, PropertyFilter } from './AppState';
import { ValidationFailure } from 'typings/pending';
import { Filter, FilterBuilderProp } from './AppState';
export interface Error {
responseJSON: {
message: string;
};
status?: number;
responseJSON:
| {
message: string | undefined;
}
| ValidationFailure[]
| undefined;
}
export interface AppSectionDeleteState {
@@ -30,7 +35,7 @@ export interface TableAppSectionState {
export interface AppSectionFilterState<T> {
selectedFilterKey: string;
filters: PropertyFilter[];
filters: Filter[];
filterBuilderProps: FilterBuilderProp<T>[];
}
@@ -51,6 +56,16 @@ export interface AppSectionItemState<T> {
item: T;
}
export interface AppSectionProviderState<T>
extends AppSectionDeleteState,
AppSectionSaveState {
isFetching: boolean;
isPopulated: boolean;
error: Error;
items: T[];
pendingChanges: Partial<T>;
}
interface AppSectionState<T> {
isFetching: boolean;
isPopulated: boolean;

View File

@@ -1,15 +1,25 @@
import { Error } from './AppSectionState';
import BlocklistAppState from './BlocklistAppState';
import CalendarAppState from './CalendarAppState';
import CaptchaAppState from './CaptchaAppState';
import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState';
import CustomFiltersAppState from './CustomFiltersAppState';
import ExtraFilesAppState from './ExtraFilesAppState';
import HistoryAppState, { MovieHistoryAppState } from './HistoryAppState';
import InteractiveImportAppState from './InteractiveImportAppState';
import MessagesAppState from './MessagesAppState';
import MovieBlocklistAppState from './MovieBlocklistAppState';
import MovieCollectionAppState from './MovieCollectionAppState';
import MovieCreditAppState from './MovieCreditAppState';
import MovieFilesAppState from './MovieFilesAppState';
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
import OAuthAppState from './OAuthAppState';
import OrganizePreviewAppState from './OrganizePreviewAppState';
import ParseAppState from './ParseAppState';
import PathsAppState from './PathsAppState';
import ProviderOptionsAppState from './ProviderOptionsAppState';
import QueueAppState from './QueueAppState';
import ReleasesAppState from './ReleasesAppState';
import RootFolderAppState from './RootFolderAppState';
import SettingsAppState from './SettingsAppState';
import SystemAppState from './SystemAppState';
@@ -36,7 +46,7 @@ export interface PropertyFilter {
export interface Filter {
key: string;
label: string;
label: string | (() => string);
filers: PropertyFilter[];
}
@@ -48,8 +58,11 @@ export interface CustomFilter {
}
export interface AppSectionState {
isUpdated: boolean;
isConnected: boolean;
isDisconnected: boolean;
isReconnecting: boolean;
isSidebarVisible: boolean;
version: string;
prevVersion?: string;
dimensions: {
@@ -57,23 +70,37 @@ export interface AppSectionState {
width: number;
height: number;
};
translations: {
error?: Error;
isPopulated: boolean;
};
messages: MessagesAppState;
}
interface AppState {
app: AppSectionState;
blocklist: BlocklistAppState;
calendar: CalendarAppState;
captcha: CaptchaAppState;
commands: CommandAppState;
customFilters: CustomFiltersAppState;
extraFiles: ExtraFilesAppState;
history: HistoryAppState;
interactiveImport: InteractiveImportAppState;
movieBlocklist: MovieBlocklistAppState;
movieCollections: MovieCollectionAppState;
movieCredits: MovieCreditAppState;
movieFiles: MovieFilesAppState;
movieHistory: MovieHistoryAppState;
movieIndex: MovieIndexAppState;
movies: MoviesAppState;
oAuth: OAuthAppState;
organizePreview: OrganizePreviewAppState;
parse: ParseAppState;
paths: PathsAppState;
providerOptions: ProviderOptionsAppState;
queue: QueueAppState;
releases: ReleasesAppState;
rootFolders: RootFolderAppState;
settings: SettingsAppState;
system: SystemAppState;

View File

@@ -0,0 +1,11 @@
interface CaptchaAppState {
refreshing: false;
token: string;
siteKey: unknown;
secretToken: unknown;
ray: unknown;
stoken: unknown;
responseUrl: unknown;
}
export default CaptchaAppState;

View File

@@ -0,0 +1,6 @@
import AppSectionState from 'App/State/AppSectionState';
import { ExtraFile } from 'MovieFile/ExtraFile';
type ExtraFilesAppState = AppSectionState<ExtraFile>;
export default ExtraFilesAppState;

View File

@@ -5,6 +5,8 @@ import AppSectionState, {
} from 'App/State/AppSectionState';
import History from 'typings/History';
export type MovieHistoryAppState = AppSectionState<History>;
interface HistoryAppState
extends AppSectionState<History>,
AppSectionFilterState<History>,

View File

@@ -1,11 +1,20 @@
import AppSectionState from 'App/State/AppSectionState';
import RecentFolder from 'InteractiveImport/Folder/RecentFolder';
import ImportMode from 'InteractiveImport/ImportMode';
import InteractiveImport from 'InteractiveImport/InteractiveImport';
interface FavoriteFolder {
folder: string;
}
interface RecentFolder {
folder: string;
lastUsed: string;
}
interface InteractiveImportAppState extends AppSectionState<InteractiveImport> {
originalItems: InteractiveImport[];
importMode: ImportMode;
favoriteFolders: FavoriteFolder[];
recentFolders: RecentFolder[];
}

View File

@@ -0,0 +1,15 @@
import ModelBase from 'App/ModelBase';
import AppSectionState from 'App/State/AppSectionState';
export type MessageType = 'error' | 'info' | 'success' | 'warning';
export interface Message extends ModelBase {
hideAfter: number;
message: string;
name: string;
type: MessageType;
}
type MessagesAppState = AppSectionState<Message>;
export default MessagesAppState;

View File

@@ -0,0 +1,6 @@
import { AppSectionProviderState } from 'App/State/AppSectionState';
import Metadata from 'typings/Metadata';
type MetadataAppState = AppSectionProviderState<Metadata>;
export default MetadataAppState;

View File

@@ -0,0 +1,6 @@
import AppSectionState from 'App/State/AppSectionState';
import Blocklist from 'typings/Blocklist';
type MovieBlocklistAppState = AppSectionState<Blocklist>;
export default MovieBlocklistAppState;

View File

@@ -1,8 +1,20 @@
import AppSectionState from 'App/State/AppSectionState';
import AppSectionState, {
AppSectionFilterState,
AppSectionSaveState,
Error,
} from 'App/State/AppSectionState';
import MovieCollection from 'typings/MovieCollection';
interface MovieCollectionAppState extends AppSectionState<MovieCollection> {
interface MovieCollectionAppState
extends AppSectionState<MovieCollection>,
AppSectionFilterState<MovieCollection>,
AppSectionSaveState {
itemMap: Record<number, number>;
isAdding: boolean;
addError: Error;
pendingChanges: Partial<MovieCollection>;
}
export default MovieCollectionAppState;

View File

@@ -1,6 +1,6 @@
import AppSectionState from 'App/State/AppSectionState';
import MovieCredit from 'typings/MovieCredit';
interface MovieCreditAppState extends AppSectionState<MovieCredit> {}
type MovieCreditAppState = AppSectionState<MovieCredit>;
export default MovieCreditAppState;

View File

@@ -64,6 +64,8 @@ interface MoviesAppState
deleteOptions: {
addImportExclusion: boolean;
};
pendingChanges: Partial<Movie>;
}
export default MoviesAppState;

View File

@@ -0,0 +1,9 @@
import { Error } from './AppSectionState';
interface OAuthAppState {
authorizing: boolean;
result: Record<string, unknown> | null;
error: Error;
}
export default OAuthAppState;

View File

@@ -0,0 +1,13 @@
import ModelBase from 'App/ModelBase';
import AppSectionState from 'App/State/AppSectionState';
export interface OrganizePreviewModel extends ModelBase {
movieId: number;
movieFileId: number;
existingPath: string;
newPath: string;
}
type OrganizePreviewAppState = AppSectionState<OrganizePreviewModel>;
export default OrganizePreviewAppState;

View File

@@ -0,0 +1,22 @@
import AppSectionState from 'App/State/AppSectionState';
import Field, { FieldSelectOption } from 'typings/Field';
export interface ProviderOptions {
fields?: Field[];
}
interface ProviderOptionsDevice {
id: string;
name: string;
}
interface ProviderOptionsAppState {
devices: AppSectionState<ProviderOptionsDevice>;
servers: AppSectionState<FieldSelectOption<unknown>>;
newznabCategories: AppSectionState<FieldSelectOption<unknown>>;
getProfiles: AppSectionState<FieldSelectOption<unknown>>;
getTags: AppSectionState<FieldSelectOption<unknown>>;
getRootFolders: AppSectionState<FieldSelectOption<unknown>>;
}
export default ProviderOptionsAppState;

View File

@@ -0,0 +1,10 @@
import AppSectionState, {
AppSectionFilterState,
} from 'App/State/AppSectionState';
import Release from 'typings/Release';
interface ReleasesAppState
extends AppSectionState<Release>,
AppSectionFilterState<Release> {}
export default ReleasesAppState;

View File

@@ -20,6 +20,7 @@ import NamingConfig from 'typings/Settings/NamingConfig';
import NamingExample from 'typings/Settings/NamingExample';
import ReleaseProfile from 'typings/Settings/ReleaseProfile';
import UiSettings from 'typings/Settings/UiSettings';
import MetadataAppState from './MetadataAppState';
export interface DownloadClientAppState
extends AppSectionState<DownloadClient>,
@@ -36,8 +37,7 @@ export interface NamingAppState
extends AppSectionItemState<NamingConfig>,
AppSectionSaveState {}
export interface NamingExamplesAppState
extends AppSectionItemState<NamingExample> {}
export type NamingExamplesAppState = AppSectionItemState<NamingExample>;
export interface ImportListAppState
extends AppSectionState<ImportList>,
@@ -97,6 +97,7 @@ interface SettingsAppState {
indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState;
languages: LanguageSettingsAppState;
metadata: MetadataAppState;
naming: NamingAppState;
namingExamples: NamingExamplesAppState;
notifications: NotificationAppState;

View File

@@ -1,5 +1,6 @@
import DiskSpace from 'typings/DiskSpace';
import Health from 'typings/Health';
import LogFile from 'typings/LogFile';
import SystemStatus from 'typings/SystemStatus';
import Task from 'typings/Task';
import Update from 'typings/Update';
@@ -9,13 +10,16 @@ export type DiskSpaceAppState = AppSectionState<DiskSpace>;
export type HealthAppState = AppSectionState<Health>;
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
export type TaskAppState = AppSectionState<Task>;
export type LogFilesAppState = AppSectionState<LogFile>;
export type UpdateAppState = AppSectionState<Update>;
interface SystemAppState {
diskSpace: DiskSpaceAppState;
health: HealthAppState;
logFiles: LogFilesAppState;
status: SystemStatusAppState;
tasks: TaskAppState;
updateLogFiles: LogFilesAppState;
updates: UpdateAppState;
}

View File

@@ -1,31 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import AddNewCollectionMovieModalContentConnector from './AddNewCollectionMovieModalContentConnector';
function AddNewCollectionMovieModal(props) {
const {
isOpen,
onModalClose,
...otherProps
} = props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<AddNewCollectionMovieModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
AddNewCollectionMovieModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default AddNewCollectionMovieModal;

View File

@@ -1,204 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import SpinnerButton from 'Components/Link/SpinnerButton';
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 } from 'Helpers/Props';
import MoviePoster from 'Movie/MoviePoster';
import translate from 'Utilities/String/translate';
import styles from './AddNewCollectionMovieModalContent.css';
class AddNewCollectionMovieModalContent extends Component {
//
// Listeners
onQualityProfileIdChange = ({ value }) => {
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
};
onAddMoviePress = () => {
this.props.onAddMoviePress();
};
//
// Render
render() {
const {
title,
year,
overview,
images,
isAdding,
folder,
tags,
isSmallScreen,
isWindows,
onModalClose,
onInputChange,
rootFolderPath,
monitor,
qualityProfileId,
minimumAvailability,
searchForMovie
} = this.props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{title}
{
!title.contains(year) && !!year &&
<span className={styles.year}>({year})</span>
}
</ModalHeader>
<ModalBody>
<div className={styles.container}>
{
!isSmallScreen &&
<div className={styles.poster}>
<MoviePoster
className={styles.poster}
images={images}
size={250}
/>
</div>
}
<div className={styles.info}>
<div className={styles.overview}>
{overview}
</div>
<Form>
<FormGroup>
<FormLabel>{translate('RootFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
valueOptions={{
movieFolder: folder,
isWindows
}}
selectedValueOptions={{
movieFolder: folder,
isWindows
}}
helpText={translate('SubfolderWillBeCreatedAutomaticallyInterp', [folder])}
onChange={onInputChange}
{...rootFolderPath}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('Monitor')}
</FormLabel>
<FormInputGroup
type={inputTypes.MOVIE_MONITORED_SELECT}
name="monitor"
onChange={onInputChange}
{...monitor}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
<FormInputGroup
type={inputTypes.AVAILABILITY_SELECT}
name="minimumAvailability"
onChange={onInputChange}
{...minimumAvailability}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('QualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
onChange={this.onQualityProfileIdChange}
{...qualityProfileId}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
onChange={onInputChange}
{...tags}
/>
</FormGroup>
</Form>
</div>
</div>
</ModalBody>
<ModalFooter className={styles.modalFooter}>
<label className={styles.searchForMissingMovieLabelContainer}>
<span className={styles.searchForMissingMovieLabel}>
{translate('StartSearchForMissingMovie')}
</span>
<CheckInput
containerClassName={styles.searchForMissingMovieContainer}
className={styles.searchForMissingMovieInput}
name="searchForMovie"
onChange={onInputChange}
{...searchForMovie}
/>
</label>
<SpinnerButton
className={styles.addButton}
kind={kinds.SUCCESS}
isSpinning={isAdding}
onPress={this.onAddMoviePress}
>
{translate('AddMovie')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
}
AddNewCollectionMovieModalContent.propTypes = {
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
overview: PropTypes.string,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isAdding: PropTypes.bool.isRequired,
addError: PropTypes.object,
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object,
minimumAvailability: PropTypes.object.isRequired,
searchForMovie: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
tags: PropTypes.object.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
isWindows: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired,
onAddMoviePress: PropTypes.func.isRequired
};
export default AddNewCollectionMovieModalContent;

View File

@@ -1,121 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { addMovie, setMovieCollectionValue } from 'Store/Actions/movieCollectionActions';
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import AddNewMovieModalContent from './AddNewCollectionMovieModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.movieCollections,
createCollectionSelector(),
createDimensionsSelector(),
createSystemStatusSelector(),
(discoverMovieState, collection, dimensions, systemStatus) => {
const {
isAdding,
addError,
pendingChanges
} = discoverMovieState;
const collectionDefaults = {
rootFolderPath: collection.rootFolderPath,
monitor: 'movieOnly',
qualityProfileId: collection.qualityProfileId,
minimumAvailability: collection.minimumAvailability,
searchForMovie: collection.searchOnAdd,
tags: collection.tags || []
};
const {
settings,
validationErrors,
validationWarnings
} = selectSettings(collectionDefaults, pendingChanges, addError);
return {
isAdding,
addError,
isSmallScreen: dimensions.isSmallScreen,
validationErrors,
validationWarnings,
isWindows: systemStatus.isWindows,
...settings
};
}
);
}
const mapDispatchToProps = {
addMovie,
setMovieCollectionValue
};
class AddNewCollectionMovieModalContentConnector extends Component {
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.setMovieCollectionValue({ name, value });
};
onAddMoviePress = () => {
const {
tmdbId,
title,
rootFolderPath,
monitor,
qualityProfileId,
minimumAvailability,
searchForMovie,
tags
} = this.props;
this.props.addMovie({
tmdbId,
title,
rootFolderPath: rootFolderPath.value,
monitor: monitor.value,
qualityProfileId: qualityProfileId.value,
minimumAvailability: minimumAvailability.value,
searchForMovie: searchForMovie.value,
tags: tags.value
});
this.props.onModalClose(true);
};
//
// Render
render() {
return (
<AddNewMovieModalContent
{...this.props}
onInputChange={this.onInputChange}
onAddMoviePress={this.onAddMoviePress}
/>
);
}
}
AddNewCollectionMovieModalContentConnector.propTypes = {
tmdbId: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object,
minimumAvailability: PropTypes.object.isRequired,
searchForMovie: PropTypes.object.isRequired,
tags: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired,
addMovie: PropTypes.func.isRequired,
setMovieCollectionValue: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewCollectionMovieModalContentConnector);

View File

@@ -0,0 +1,45 @@
import React, { useCallback, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import Modal from 'Components/Modal/Modal';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import AddNewMovieCollectionMovieModalContent, {
AddNewMovieCollectionMovieModalContentProps,
} from './AddNewMovieCollectionMovieModalContent';
interface AddNewCollectionMovieModalProps
extends AddNewMovieCollectionMovieModalContentProps {
isOpen: boolean;
}
function AddNewMovieCollectionMovieModal({
isOpen,
onModalClose,
...otherProps
}: AddNewCollectionMovieModalProps) {
const dispatch = useDispatch();
const wasOpen = usePrevious(isOpen);
const handleModalClose = useCallback(() => {
dispatch(clearPendingChanges({ section: 'movieCollections' }));
onModalClose();
}, [dispatch, onModalClose]);
useEffect(() => {
if (wasOpen && !isOpen) {
dispatch(clearPendingChanges({ section: 'movieCollections' }));
}
}, [wasOpen, isOpen, dispatch]);
return (
<Modal isOpen={isOpen} onModalClose={handleModalClose}>
<AddNewMovieCollectionMovieModalContent
{...otherProps}
onModalClose={handleModalClose}
/>
</Modal>
);
}
export default AddNewMovieCollectionMovieModal;

View File

@@ -0,0 +1,269 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import MovieMinimumAvailabilityPopoverContent from 'AddMovie/MovieMinimumAvailabilityPopoverContent';
import AppState from 'App/State/AppState';
import useMovieCollection from 'Collection/useMovieCollection';
import CheckInput from 'Components/Form/CheckInput';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Icon from 'Components/Icon';
import SpinnerButton from 'Components/Link/SpinnerButton';
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 Popover from 'Components/Tooltip/Popover';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import { Image } from 'Movie/Movie';
import MoviePoster from 'Movie/MoviePoster';
import {
addMovie,
setMovieCollectionValue,
} from 'Store/Actions/movieCollectionActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import useIsWindows from 'System/useIsWindows';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import styles from './AddNewMovieCollectionMovieModalContent.css';
export interface AddNewMovieCollectionMovieModalContentProps {
tmdbId: number;
title: string;
year: number;
overview?: string;
images: Image[];
collectionId: number;
folder: string;
onModalClose: () => void;
}
function AddNewMovieCollectionMovieModalContent({
tmdbId,
title,
year,
overview,
images,
collectionId,
folder,
onModalClose,
}: AddNewMovieCollectionMovieModalContentProps) {
const dispatch = useDispatch();
const collection = useMovieCollection(collectionId)!;
const { isSmallScreen } = useSelector(createDimensionsSelector());
const isWindows = useIsWindows();
const { isAdding, addError, pendingChanges } = useSelector(
(state: AppState) => state.movieCollections
);
const wasAdding = usePrevious(isAdding);
const { settings, validationErrors, validationWarnings } = useMemo(() => {
const options = {
rootFolderPath: collection.rootFolderPath,
monitor: collection.monitored ? 'movieOnly' : 'none',
qualityProfileId: collection.qualityProfileId,
minimumAvailability: collection.minimumAvailability,
searchForMovie: collection.searchOnAdd,
tags: collection.tags || [],
};
return selectSettings(options, pendingChanges, addError);
}, [collection, pendingChanges, addError]);
const {
monitor,
qualityProfileId,
minimumAvailability,
rootFolderPath,
searchForMovie,
tags,
} = settings;
const handleInputChange = useCallback(
({ name, value }: InputChanged) => {
// @ts-expect-error actions aren't typed
dispatch(setMovieCollectionValue({ name, value }));
},
[dispatch]
);
const handleAddMoviePress = useCallback(() => {
dispatch(
addMovie({
tmdbId,
title,
rootFolderPath: rootFolderPath.value,
monitor: monitor.value,
qualityProfileId: qualityProfileId.value,
minimumAvailability: minimumAvailability.value,
searchForMovie: searchForMovie.value,
tags: tags.value,
})
);
}, [
tmdbId,
title,
rootFolderPath,
monitor,
qualityProfileId,
minimumAvailability,
searchForMovie,
tags,
dispatch,
]);
useEffect(() => {
if (!isAdding && wasAdding && !addError) {
onModalClose();
}
}, [isAdding, wasAdding, addError, onModalClose]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{title}
{!title.includes(String(year)) && year ? (
<span className={styles.year}>({year})</span>
) : null}
</ModalHeader>
<ModalBody>
<div className={styles.container}>
{isSmallScreen ? null : (
<div className={styles.poster}>
<MoviePoster
className={styles.poster}
images={images}
size={250}
/>
</div>
)}
<div className={styles.info}>
{overview ? (
<div className={styles.overview}>{overview}</div>
) : null}
<Form
validationErrors={validationErrors}
validationWarnings={validationWarnings}
>
<FormGroup>
<FormLabel>{translate('RootFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
valueOptions={{
movieFolder: folder,
isWindows,
}}
selectedValueOptions={{
movieFolder: folder,
isWindows,
}}
helpText={translate('AddNewMovieRootFolderHelpText', {
folder,
})}
{...rootFolderPath}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Monitor')}</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_MOVIES_SELECT}
name="monitor"
{...monitor}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MinimumAvailability')}
<Popover
anchor={
<Icon className={styles.labelIcon} name={icons.INFO} />
}
title={translate('MinimumAvailability')}
body={<MovieMinimumAvailabilityPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.AVAILABILITY_SELECT}
name="minimumAvailability"
{...minimumAvailability}
helpLink="https://wiki.servarr.com/radarr/faq#what-is-minimum-availability"
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('QualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
{...qualityProfileId}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
{...tags}
onChange={handleInputChange}
/>
</FormGroup>
</Form>
</div>
</div>
</ModalBody>
<ModalFooter className={styles.modalFooter}>
<label className={styles.searchForMissingMovieLabelContainer}>
<span className={styles.searchForMissingMovieLabel}>
{translate('StartSearchForMissingMovie')}
</span>
<CheckInput
containerClassName={styles.searchForMissingMovieContainer}
className={styles.searchForMissingMovieInput}
name="searchForMovie"
{...searchForMovie}
onChange={handleInputChange}
/>
</label>
<SpinnerButton
className={styles.addButton}
kind={kinds.SUCCESS}
isSpinning={isAdding}
onPress={handleAddMoviePress}
>
{translate('AddMovie')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
export default AddNewMovieCollectionMovieModalContent;

View File

@@ -18,9 +18,9 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import CollectionFooter from './CollectionFooter';
import CollectionFilterMenu from './Menus/CollectionFilterMenu';
import CollectionSortMenu from './Menus/CollectionSortMenu';
import NoCollection from './NoCollection';
import MovieCollectionFilterMenu from './Menus/MovieCollectionFilterMenu';
import MovieCollectionSortMenu from './Menus/MovieCollectionSortMenu';
import NoMovieCollections from './NoMovieCollections';
import CollectionOverviewsConnector from './Overview/CollectionOverviewsConnector';
import CollectionOverviewOptionsModal from './Overview/Options/CollectionOverviewOptionsModal';
@@ -224,6 +224,7 @@ class Collection extends Component {
view,
onSortSelect,
onFilterSelect,
initialScrollTop,
onScroll,
isRefreshingCollections,
isSaving,
@@ -247,7 +248,7 @@ class Collection extends Component {
const hasNoCollection = !totalItems;
return (
<PageContent>
<PageContent title={translate('Collections')}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
@@ -284,14 +285,14 @@ class Collection extends Component {
<PageToolbarSeparator />
}
<CollectionSortMenu
<MovieCollectionSortMenu
sortKey={sortKey}
sortDirection={sortDirection}
isDisabled={hasNoCollection}
onSortSelect={onSortSelect}
/>
<CollectionFilterMenu
<MovieCollectionFilterMenu
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
@@ -306,6 +307,7 @@ class Collection extends Component {
ref={this.scrollerRef}
className={styles.contentBody}
innerClassName={styles[`${view}InnerContentBody`]}
onScroll={onScroll}
>
{
isFetching && !isPopulated &&
@@ -334,6 +336,7 @@ class Collection extends Component {
onSelectedChange={this.onSelectedChange}
onSelectAllChange={this.onSelectAllChange}
selectedState={selectedState}
scrollTop={initialScrollTop}
{...otherProps}
/>
</div>
@@ -341,7 +344,7 @@ class Collection extends Component {
{
!error && isPopulated && !items.length &&
<NoCollection totalItems={totalItems} />
<NoMovieCollections totalItems={totalItems} />
}
</PageContentBody>
@@ -374,6 +377,7 @@ class Collection extends Component {
}
Collection.propTypes = {
initialScrollTop: PropTypes.number,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,

View File

@@ -5,14 +5,17 @@ import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import withScrollPosition from 'Components/withScrollPosition';
import { executeCommand } from 'Store/Actions/commandActions';
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
import {
fetchMovieCollections,
saveMovieCollections,
setMovieCollectionsFilter,
setMovieCollectionsSort
} from 'Store/Actions/movieCollectionActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import scrollPositions from 'Store/scrollPositions';
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import Collection from './Collection';
function createMapStateToProps() {
@@ -36,8 +39,8 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
dispatchFetchRootFolders() {
dispatch(fetchRootFolders());
dispatchFetchMovieCollections() {
dispatch(fetchMovieCollections());
},
dispatchFetchQueueDetails() {
dispatch(fetchQueueDetails());
@@ -68,13 +71,11 @@ class CollectionConnector extends Component {
// Lifecycle
componentDidMount() {
registerPagePopulator(this.repopulate);
this.props.dispatchFetchRootFolders();
this.props.dispatchFetchMovieCollections();
this.props.dispatchFetchQueueDetails();
}
componentWillUnmount() {
unregisterPagePopulator(this.repopulate);
this.props.dispatchClearQueueDetails();
}
@@ -93,9 +94,16 @@ class CollectionConnector extends Component {
// Render
render() {
const {
dispatchFetchMovieCollections,
dispatchFetchQueueDetails,
dispatchClearQueueDetails,
...otherProps
} = this.props;
return (
<Collection
{...this.props}
{...otherProps}
onViewSelect={this.onViewSelect}
onScroll={this.onScroll}
onUpdateSelectedPress={this.onUpdateSelectedPress}
@@ -108,7 +116,7 @@ CollectionConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
view: PropTypes.string.isRequired,
onUpdateSelectedPress: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired,
dispatchFetchMovieCollections: PropTypes.func.isRequired,
dispatchFetchQueueDetails: PropTypes.func.isRequired,
dispatchClearQueueDetails: PropTypes.func.isRequired
};

View File

@@ -1,24 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import FilterModal from 'Components/Filter/FilterModal';
import { setMovieCollectionsFilter } from 'Store/Actions/movieCollectionActions';
function createMapStateToProps() {
return createSelector(
(state) => state.movieCollections.items,
(state) => state.movieCollections.filterBuilderProps,
(sectionItems, filterBuilderProps) => {
return {
sectionItems,
filterBuilderProps,
customFilterType: 'movieCollections'
};
}
);
}
const mapDispatchToProps = {
dispatchSetFilter: setMovieCollectionsFilter
};
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);

View File

@@ -1,296 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput';
import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector';
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import CollectionFooterLabel from './CollectionFooterLabel';
import styles from './CollectionFooter.css';
const NO_CHANGE = 'noChange';
const monitoredOptions = [
{
key: NO_CHANGE,
get value() {
return translate('NoChange');
},
isDisabled: 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');
},
isDisabled: true
},
{
key: 'yes',
get value() {
return translate('Yes');
}
},
{
key: 'no',
get value() {
return translate('No');
}
}
];
class CollectionFooter extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
monitored: NO_CHANGE,
monitor: NO_CHANGE,
qualityProfileId: NO_CHANGE,
minimumAvailability: NO_CHANGE,
rootFolderPath: NO_CHANGE,
searchOnAdd: NO_CHANGE
};
}
componentDidUpdate(prevProps) {
const {
isSaving,
saveError
} = this.props;
const newState = {};
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitored: NO_CHANGE,
monitor: NO_CHANGE,
qualityProfileId: NO_CHANGE,
minimumAvailability: NO_CHANGE,
rootFolderPath: NO_CHANGE,
searchOnAdd: NO_CHANGE
});
}
if (!_.isEmpty(newState)) {
this.setState(newState);
}
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.setState({ [name]: value });
};
onUpdateSelectedPress = () => {
const {
monitored,
monitor,
qualityProfileId,
minimumAvailability,
rootFolderPath,
searchOnAdd
} = this.state;
const changes = {};
if (monitored !== NO_CHANGE) {
changes.monitored = monitored === 'monitored';
}
if (monitor !== NO_CHANGE) {
changes.monitor = monitor;
}
if (qualityProfileId !== NO_CHANGE) {
changes.qualityProfileId = qualityProfileId;
}
if (minimumAvailability !== NO_CHANGE) {
changes.minimumAvailability = minimumAvailability;
}
if (rootFolderPath !== NO_CHANGE) {
changes.rootFolderPath = rootFolderPath;
}
if (searchOnAdd !== NO_CHANGE) {
changes.searchOnAdd = searchOnAdd === 'yes';
}
this.props.onUpdateSelectedPress(changes);
};
//
// Render
render() {
const {
selectedIds,
isSaving
} = this.props;
const {
monitored,
monitor,
qualityProfileId,
minimumAvailability,
rootFolderPath,
searchOnAdd
} = this.state;
const selectedCount = selectedIds.length;
return (
<PageContentFooter>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MonitorCollection')}
isSaving={isSaving && monitored !== NO_CHANGE}
/>
<SelectInput
name="monitored"
value={monitored}
values={monitoredOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MonitorMovies')}
isSaving={isSaving && monitor !== NO_CHANGE}
/>
<SelectInput
name="monitor"
value={monitor}
values={monitoredOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('QualityProfile')}
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
<QualityProfileSelectInputConnector
name="qualityProfileId"
value={qualityProfileId}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MinimumAvailability')}
isSaving={isSaving && minimumAvailability !== NO_CHANGE}
/>
<AvailabilitySelectInput
name="minimumAvailability"
value={minimumAvailability}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('RootFolder')}
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
<RootFolderSelectInputConnector
name="rootFolderPath"
value={rootFolderPath}
includeNoChange={true}
isDisabled={!selectedCount}
selectedValueOptions={{ includeFreeSpace: false }}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('SearchMoviesOnAdd')}
isSaving={isSaving && searchOnAdd !== NO_CHANGE}
/>
<SelectInput
name="searchOnAdd"
value={searchOnAdd}
values={searchOnAddOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<CollectionFooterLabel
label={translate('CountCollectionsSelected', { count: selectedCount })}
isSaving={false}
/>
<div className={styles.buttons}>
<div>
<SpinnerButton
className={styles.addSelectedButton}
kind={kinds.PRIMARY}
isSpinning={isSaving}
isDisabled={!selectedCount || isSaving}
onPress={this.onUpdateSelectedPress}
>
{translate('UpdateSelected')}
</SpinnerButton>
</div>
</div>
</div>
</div>
</PageContentFooter>
);
}
}
CollectionFooter.propTypes = {
selectedIds: PropTypes.arrayOf(PropTypes.number).isRequired,
isAdding: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
onUpdateSelectedPress: PropTypes.func.isRequired
};
export default CollectionFooter;

View File

@@ -0,0 +1,317 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Error } from 'App/State/AppSectionState';
import FormInputGroup from 'Components/Form/FormInputGroup';
import { EnhancedSelectInputValue } from 'Components/Form/Select/EnhancedSelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { inputTypes, kinds } from 'Helpers/Props';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import CollectionFooterLabel from './CollectionFooterLabel';
import styles from './CollectionFooter.css';
interface SavePayload {
monitored?: boolean;
monitor?: string;
qualityProfileId?: number;
minimumAvailability?: string;
rootFolderPath?: string;
searchOnAdd?: boolean;
}
interface CollectionFooterProps {
selectedIds: number[];
isAdding: boolean;
isSaving: boolean;
saveError: Error;
onUpdateSelectedPress(payload: object): void;
}
const NO_CHANGE = 'noChange';
const monitoredOptions: EnhancedSelectInputValue<string>[] = [
{
key: NO_CHANGE,
get value() {
return translate('NoChange');
},
},
{
key: 'monitored',
get value() {
return translate('Monitored');
},
},
{
key: 'unmonitored',
get value() {
return translate('Unmonitored');
},
},
];
const searchOnAddOptions: EnhancedSelectInputValue<string>[] = [
{
key: NO_CHANGE,
get value() {
return translate('NoChange');
},
},
{
key: 'yes',
get value() {
return translate('Yes');
},
},
{
key: 'no',
get value() {
return translate('No');
},
},
];
function CollectionFooter({
selectedIds,
isSaving,
saveError,
onUpdateSelectedPress,
}: CollectionFooterProps) {
const [monitored, setMonitored] = useState(NO_CHANGE);
const [monitor, setMonitor] = useState(NO_CHANGE);
const [qualityProfileId, setQualityProfileId] = useState<string | number>(
NO_CHANGE
);
const [minimumAvailability, setMinimumAvailability] = useState(NO_CHANGE);
const [rootFolderPath, setRootFolderPath] = useState(NO_CHANGE);
const [searchOnAdd, setSearchOnAdd] = useState(NO_CHANGE);
const wasSaving = usePrevious(isSaving);
const handleSavePress = useCallback(() => {
let hasChanges = false;
const payload: SavePayload = {};
if (monitored !== NO_CHANGE) {
hasChanges = true;
payload.monitored = monitored === 'monitored';
}
if (monitor !== NO_CHANGE) {
hasChanges = true;
payload.monitor = monitor;
}
if (qualityProfileId !== NO_CHANGE) {
hasChanges = true;
payload.qualityProfileId = qualityProfileId as number;
}
if (minimumAvailability !== NO_CHANGE) {
hasChanges = true;
payload.minimumAvailability = minimumAvailability as string;
}
if (rootFolderPath !== NO_CHANGE) {
hasChanges = true;
payload.rootFolderPath = rootFolderPath;
}
if (searchOnAdd !== NO_CHANGE) {
hasChanges = true;
payload.searchOnAdd = searchOnAdd === 'yes';
}
if (hasChanges) {
onUpdateSelectedPress(payload);
}
}, [
monitor,
monitored,
qualityProfileId,
minimumAvailability,
rootFolderPath,
searchOnAdd,
onUpdateSelectedPress,
]);
const handleInputChange = useCallback(({ name, value }: InputChanged) => {
switch (name) {
case 'monitored':
setMonitored(value as string);
break;
case 'monitor':
setMonitor(value as string);
break;
case 'qualityProfileId':
setQualityProfileId(value as string);
break;
case 'minimumAvailability':
setMinimumAvailability(value as string);
break;
case 'rootFolderPath':
setRootFolderPath(value as string);
break;
case 'searchOnAdd':
setSearchOnAdd(value as string);
break;
default:
console.warn(`CollectionFooter Unknown Input: '${name}'`);
}
}, []);
useEffect(() => {
if (!isSaving && wasSaving && !saveError) {
setMonitored(NO_CHANGE);
setMonitor(NO_CHANGE);
setQualityProfileId(NO_CHANGE);
setMinimumAvailability(NO_CHANGE);
setRootFolderPath(NO_CHANGE);
setSearchOnAdd(NO_CHANGE);
}
}, [
isSaving,
wasSaving,
saveError,
setMonitored,
setMonitor,
setQualityProfileId,
setMinimumAvailability,
setRootFolderPath,
setSearchOnAdd,
]);
const selectedCount = selectedIds.length;
return (
<PageContentFooter>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MonitorCollection')}
isSaving={isSaving && monitored !== NO_CHANGE}
/>
<FormInputGroup
type={inputTypes.SELECT}
name="monitored"
value={monitored}
values={monitoredOptions}
isDisabled={!selectedCount}
onChange={handleInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MonitorMovies')}
isSaving={isSaving && monitor !== NO_CHANGE}
/>
<FormInputGroup
type={inputTypes.SELECT}
name="monitor"
value={monitor}
values={monitoredOptions}
isDisabled={!selectedCount}
onChange={handleInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('QualityProfile')}
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
value={qualityProfileId}
includeNoChange={true}
includeNoChangeDisabled={false}
isDisabled={!selectedCount}
onChange={handleInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('MinimumAvailability')}
isSaving={isSaving && minimumAvailability !== NO_CHANGE}
/>
<FormInputGroup
type={inputTypes.AVAILABILITY_SELECT}
name="minimumAvailability"
value={minimumAvailability}
includeNoChange={true}
includeNoChangeDisabled={false}
isDisabled={!selectedCount}
onChange={handleInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('RootFolder')}
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
value={rootFolderPath}
includeNoChange={true}
includeNoChangeDisabled={false}
selectedValueOptions={{ includeFreeSpace: false }}
isDisabled={!selectedCount}
onChange={handleInputChange}
/>
</div>
<div className={styles.inputContainer}>
<CollectionFooterLabel
label={translate('SearchMoviesOnAdd')}
isSaving={isSaving && searchOnAdd !== NO_CHANGE}
/>
<FormInputGroup
type={inputTypes.SELECT}
name="searchOnAdd"
value={searchOnAdd}
values={searchOnAddOptions}
isDisabled={!selectedCount}
onChange={handleInputChange}
/>
</div>
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<CollectionFooterLabel
label={translate('CountCollectionsSelected', {
count: selectedCount,
})}
isSaving={false}
/>
<div className={styles.buttons}>
<div>
<SpinnerButton
className={styles.addSelectedButton}
kind={kinds.PRIMARY}
isSpinning={isSaving}
isDisabled={!selectedCount || isSaving}
onPress={handleSavePress}
>
{translate('UpdateSelected')}
</SpinnerButton>
</div>
</div>
</div>
</div>
</PageContentFooter>
);
}
export default CollectionFooter;

View File

@@ -1,40 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import SpinnerIcon from 'Components/SpinnerIcon';
import { icons } from 'Helpers/Props';
import styles from './CollectionFooterLabel.css';
function CollectionFooterLabel(props) {
const {
className,
label,
isSaving
} = props;
return (
<div className={className}>
{label}
{
isSaving &&
<SpinnerIcon
className={styles.savingIcon}
name={icons.SPINNER}
isSpinning={true}
/>
}
</div>
);
}
CollectionFooterLabel.propTypes = {
className: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
isSaving: PropTypes.bool.isRequired
};
CollectionFooterLabel.defaultProps = {
className: styles.label
};
export default CollectionFooterLabel;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import SpinnerIcon from 'Components/SpinnerIcon';
import { icons } from 'Helpers/Props';
import styles from './CollectionFooterLabel.css';
interface CollectionFooterLabelProps {
className?: string;
label: string;
isSaving: boolean;
}
function CollectionFooterLabel({
className = styles.label,
label,
isSaving,
}: CollectionFooterLabelProps) {
return (
<div className={className}>
{label}
{isSaving ? (
<SpinnerIcon
className={styles.savingIcon}
name={icons.SPINNER}
isSpinning={true}
/>
) : null}
</div>
);
}
export default CollectionFooterLabel;

View File

@@ -9,7 +9,7 @@ function createMapStateToProps() {
createCollectionSelector(),
(collection) => {
// If a movie is deleted this selector may fire before the parent
// selecors, which will result in an undefined movie, if that happens
// selectors, which will result in an undefined movie, if that happens
// we want to return early here and again in the render function to avoid
// trying to show a movie that has no information available.
@@ -22,7 +22,7 @@ function createMapStateToProps() {
return {
...collection,
movies: [...collection.movies].sort((a, b) => b.year - a.year),
genres: Array.from(new Set(allGenres)).slice(0, 3)
genres: Array.from(new Set(allGenres))
};
}
);

View File

@@ -1,25 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import EditCollectionModalContentConnector from './EditCollectionModalContentConnector';
function EditCollectionModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditCollectionModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
EditCollectionModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditCollectionModal;

View File

@@ -1,39 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditCollectionModal from './EditCollectionModal';
const mapDispatchToProps = {
clearPendingChanges
};
class EditCollectionModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.clearPendingChanges({ section: 'movieCollections' });
this.props.onModalClose();
};
//
// Render
render() {
return (
<EditCollectionModal
{...this.props}
onModalClose={this.onModalClose}
/>
);
}
}
EditCollectionModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(undefined, mapDispatchToProps)(EditCollectionModalConnector);

View File

@@ -1,190 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
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 } from 'Helpers/Props';
import MoviePoster from 'Movie/MoviePoster';
import translate from 'Utilities/String/translate';
import styles from './EditCollectionModalContent.css';
class EditCollectionModalContent extends Component {
//
// Listeners
onSavePress = () => {
const {
onSavePress
} = this.props;
onSavePress(false);
};
//
// Render
render() {
const {
title,
images,
overview,
item,
isSaving,
onInputChange,
onModalClose,
isSmallScreen,
...otherProps
} = this.props;
const {
monitored,
qualityProfileId,
minimumAvailability,
// Id,
rootFolderPath,
tags,
searchOnAdd
} = item;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('Edit')} - {title}
</ModalHeader>
<ModalBody>
<div className={styles.container}>
{
!isSmallScreen &&
<div className={styles.poster}>
<MoviePoster
className={styles.poster}
images={images}
size={250}
/>
</div>
}
<div className={styles.info}>
<div className={styles.overview}>
{overview}
</div>
<Form
{...otherProps}
>
<FormGroup>
<FormLabel>{translate('Monitored')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="monitored"
helpText={translate('MonitoredCollectionHelpText')}
{...monitored}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
<FormInputGroup
type={inputTypes.AVAILABILITY_SELECT}
name="minimumAvailability"
{...minimumAvailability}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('QualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
{...qualityProfileId}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('RootFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
{...rootFolderPath}
includeMissingValue={true}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
onChange={onInputChange}
{...tags}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('SearchOnAdd')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="searchOnAdd"
helpText={translate('SearchOnAddCollectionHelpText')}
{...searchOnAdd}
onChange={onInputChange}
/>
</FormGroup>
</Form>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
{translate('Cancel')}
</Button>
<SpinnerButton
isSpinning={isSaving}
onPress={this.onSavePress}
>
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
}
EditCollectionModalContent.propTypes = {
collectionId: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
item: PropTypes.object.isRequired,
isSaving: PropTypes.bool.isRequired,
isPathChanging: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditCollectionModalContent;

View File

@@ -1,120 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveMovieCollection, setMovieCollectionValue } from 'Store/Actions/movieCollectionActions';
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import EditCollectionModalContent from './EditCollectionModalContent';
function createIsPathChangingSelector() {
return createSelector(
(state) => state.movieCollections.pendingChanges,
createCollectionSelector(),
(pendingChanges, collection) => {
const rootFolderPath = pendingChanges.rootFolderPath;
if (rootFolderPath == null) {
return false;
}
return collection.rootFolderPath !== rootFolderPath;
}
);
}
function createMapStateToProps() {
return createSelector(
(state) => state.movieCollections,
createCollectionSelector(),
createIsPathChangingSelector(),
createDimensionsSelector(),
(moviesState, collection, isPathChanging, dimensions) => {
const {
isSaving,
saveError,
pendingChanges
} = moviesState;
const movieSettings = {
monitored: collection.monitored,
qualityProfileId: collection.qualityProfileId,
minimumAvailability: collection.minimumAvailability,
rootFolderPath: collection.rootFolderPath,
tags: collection.tags,
searchOnAdd: collection.searchOnAdd
};
const settings = selectSettings(movieSettings, pendingChanges, saveError);
return {
title: collection.title,
images: collection.images,
overview: collection.overview,
isSaving,
saveError,
isPathChanging,
originalPath: collection.path,
item: settings.settings,
isSmallScreen: dimensions.isSmallScreen,
...settings
};
}
);
}
const mapDispatchToProps = {
dispatchSetMovieCollectionValue: setMovieCollectionValue,
dispatchSaveMovieCollection: saveMovieCollection
};
class EditCollectionModalContentConnector extends Component {
//
// Lifecycle
componentDidUpdate(prevProps, prevState) {
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
this.props.onModalClose();
}
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.dispatchSetMovieCollectionValue({ name, value });
};
onSavePress = () => {
this.props.dispatchSaveMovieCollection({
id: this.props.collectionId
});
};
//
// Render
render() {
return (
<EditCollectionModalContent
{...this.props}
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
onMoveMoviePress={this.onMoveMoviePress}
/>
);
}
}
EditCollectionModalContentConnector.propTypes = {
collectionId: PropTypes.number,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
dispatchSetMovieCollectionValue: PropTypes.func.isRequired,
dispatchSaveMovieCollection: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(EditCollectionModalContentConnector);

View File

@@ -0,0 +1,36 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import Modal from 'Components/Modal/Modal';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditMovieCollectionModalContent, {
EditMovieCollectionModalContentProps,
} from './EditMovieCollectionModalContent';
interface EditMovieCollectionModalProps
extends EditMovieCollectionModalContentProps {
isOpen: boolean;
}
function EditMovieCollectionModal({
isOpen,
onModalClose,
...otherProps
}: EditMovieCollectionModalProps) {
const dispatch = useDispatch();
const handleModalClose = useCallback(() => {
dispatch(clearPendingChanges({ section: 'movieCollections' }));
onModalClose();
}, [dispatch, onModalClose]);
return (
<Modal isOpen={isOpen} onModalClose={handleModalClose}>
<EditMovieCollectionModalContent
{...otherProps}
onModalClose={handleModalClose}
/>
</Modal>
);
}
export default EditMovieCollectionModal;

View File

@@ -0,0 +1,214 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import useMovieCollection from 'Collection/useMovieCollection';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
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 usePrevious from 'Helpers/Hooks/usePrevious';
import { inputTypes } from 'Helpers/Props';
import MoviePoster from 'Movie/MoviePoster';
import {
saveMovieCollection,
setMovieCollectionValue,
} from 'Store/Actions/movieCollectionActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import styles from './EditMovieCollectionModalContent.css';
export interface EditMovieCollectionModalContentProps {
collectionId: number;
onModalClose: () => void;
}
function EditMovieCollectionModalContent({
collectionId,
onModalClose,
}: EditMovieCollectionModalContentProps) {
const dispatch = useDispatch();
const {
title,
overview,
monitored,
qualityProfileId,
minimumAvailability,
rootFolderPath,
searchOnAdd,
images,
tags,
} = useMovieCollection(collectionId)!;
const { isSaving, saveError, pendingChanges } = useSelector(
(state: AppState) => state.movieCollections
);
const { isSmallScreen } = useSelector(createDimensionsSelector());
const wasSaving = usePrevious(isSaving);
const { settings, ...otherSettings } = useMemo(() => {
return selectSettings(
{
monitored,
minimumAvailability,
qualityProfileId,
rootFolderPath,
searchOnAdd,
tags,
},
pendingChanges,
saveError
);
}, [
monitored,
minimumAvailability,
qualityProfileId,
rootFolderPath,
searchOnAdd,
tags,
pendingChanges,
saveError,
]);
const handleInputChange = useCallback(
({ name, value }: InputChanged) => {
// @ts-expect-error actions aren't typed
dispatch(setMovieCollectionValue({ name, value }));
},
[dispatch]
);
const handleSavePress = useCallback(() => {
dispatch(
saveMovieCollection({
id: collectionId,
})
);
}, [collectionId, dispatch]);
useEffect(() => {
if (!isSaving && wasSaving && !saveError) {
onModalClose();
}
}, [isSaving, wasSaving, saveError, onModalClose]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('EditMovieCollectionModalHeader', { title })}
</ModalHeader>
<ModalBody>
<div className={styles.container}>
{isSmallScreen ? null : (
<div className={styles.poster}>
<MoviePoster
className={styles.poster}
images={images}
size={250}
/>
</div>
)}
<div className={styles.info}>
<div className={styles.overview}>{overview}</div>
<Form {...otherSettings}>
<FormGroup>
<FormLabel>{translate('Monitored')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="monitored"
helpText={translate('MonitoredCollectionHelpText')}
{...settings.monitored}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
<FormInputGroup
type={inputTypes.AVAILABILITY_SELECT}
name="minimumAvailability"
{...settings.minimumAvailability}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('QualityProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
{...settings.qualityProfileId}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('RootFolder')}</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
{...settings.rootFolderPath}
includeMissingValue={true}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
{...settings.tags}
onChange={handleInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('SearchOnAdd')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="searchOnAdd"
helpText={translate('SearchOnAddCollectionHelpText')}
{...settings.searchOnAdd}
onChange={handleInputChange}
/>
</FormGroup>
</Form>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<SpinnerErrorButton
error={saveError}
isSpinning={isSaving}
onPress={handleSavePress}
>
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
export default EditMovieCollectionModalContent;

View File

@@ -1,41 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import CollectionFilterModalConnector from 'Collection/CollectionFilterModalConnector';
import FilterMenu from 'Components/Menu/FilterMenu';
import { align } from 'Helpers/Props';
function CollectionFilterMenu(props) {
const {
selectedFilterKey,
filters,
customFilters,
isDisabled,
onFilterSelect
} = props;
return (
<FilterMenu
alignMenu={align.RIGHT}
isDisabled={isDisabled}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={CollectionFilterModalConnector}
onFilterSelect={onFilterSelect}
/>
);
}
CollectionFilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
onFilterSelect: PropTypes.func.isRequired
};
CollectionFilterMenu.defaultProps = {
showCustomFilters: false
};
export default CollectionFilterMenu;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { CustomFilter, Filter } from 'App/State/AppState';
import MovieCollectionFilterModal from 'Collection/MovieCollectionFilterModal';
import FilterMenu from 'Components/Menu/FilterMenu';
interface MovieCollectionFilterMenuProps {
selectedFilterKey: string | number;
filters: Filter[];
customFilters: CustomFilter[];
isDisabled: boolean;
onFilterSelect: (filter: number | string) => void;
}
function MovieCollectionFilterMenu({
selectedFilterKey,
filters,
customFilters,
isDisabled,
onFilterSelect,
}: MovieCollectionFilterMenuProps) {
return (
<FilterMenu
alignMenu="right"
isDisabled={isDisabled}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={MovieCollectionFilterModal}
onFilterSelect={onFilterSelect}
/>
);
}
export default MovieCollectionFilterMenu;

View File

@@ -1,24 +1,26 @@
import PropTypes from 'prop-types';
import React from 'react';
import MenuContent from 'Components/Menu/MenuContent';
import SortMenu from 'Components/Menu/SortMenu';
import SortMenuItem from 'Components/Menu/SortMenuItem';
import { align, sortDirections } from 'Helpers/Props';
import { align } from 'Helpers/Props';
import { SortDirection } from 'Helpers/Props/sortDirections';
import translate from 'Utilities/String/translate';
function CollectionSortMenu(props) {
const {
sortKey,
sortDirection,
isDisabled,
onSortSelect
} = props;
interface MovieCollectionSortMenuProps {
sortKey?: string;
sortDirection?: SortDirection;
isDisabled: boolean;
onSortSelect(sortKey: string): void;
}
function MovieCollectionSortMenu({
sortKey,
sortDirection,
isDisabled,
onSortSelect,
}: MovieCollectionSortMenuProps) {
return (
<SortMenu
isDisabled={isDisabled}
alignMenu={align.RIGHT}
>
<SortMenu isDisabled={isDisabled} alignMenu={align.RIGHT}>
<MenuContent>
<SortMenuItem
name="sortTitle"
@@ -28,6 +30,7 @@ function CollectionSortMenu(props) {
>
{translate('Title')}
</SortMenuItem>
<SortMenuItem
name="missingMovies"
sortKey={sortKey}
@@ -41,11 +44,4 @@ function CollectionSortMenu(props) {
);
}
CollectionSortMenu.propTypes = {
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
isDisabled: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired
};
export default CollectionSortMenu;
export default MovieCollectionSortMenu;

View File

@@ -0,0 +1,39 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setMovieCollectionsFilter } from 'Store/Actions/movieCollectionActions';
interface MovieCollectionFilterModalProps {
isOpen: boolean;
}
export default function MovieCollectionFilterModal(
props: MovieCollectionFilterModalProps
) {
const sectionItems = useSelector(
(state: AppState) => state.movieCollections.items
);
const filterBuilderProps = useSelector(
(state: AppState) => state.movieCollections.filterBuilderProps
);
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: { selectedFilterKey: string | number }) => {
dispatch(setMovieCollectionsFilter(payload));
},
[dispatch]
);
return (
<FilterModal
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType="movieCollections"
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@@ -1,13 +1,14 @@
import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './NoCollection.css';
import styles from './NoMovieCollections.css';
function NoCollection(props) {
const { totalItems } = props;
interface NoMovieCollectionsProps {
totalItems: number;
}
function NoMovieCollections({ totalItems }: NoMovieCollectionsProps) {
if (totalItems > 0) {
return (
<div>
@@ -20,24 +21,16 @@ function NoCollection(props) {
return (
<div>
<div className={styles.message}>
{translate('NoCollections')}
</div>
<div className={styles.message}>{translate('NoCollections')}</div>
<div className={styles.buttonContainer}>
<Button
to="/add/import"
kind={kinds.PRIMARY}
>
<Button to="/add/import" kind={kinds.PRIMARY}>
{translate('ImportExistingMovies')}
</Button>
</div>
<div className={styles.buttonContainer}>
<Button
to="/add/new"
kind={kinds.PRIMARY}
>
<Button to="/add/new" kind={kinds.PRIMARY}>
{translate('AddNewMovie')}
</Button>
</div>
@@ -45,8 +38,4 @@ function NoCollection(props) {
);
}
NoCollection.propTypes = {
totalItems: PropTypes.number.isRequired
};
export default NoCollection;
export default NoMovieCollections;

View File

@@ -1,12 +1,12 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AddNewMovieCollectionMovieModal from 'Collection/AddNewMovieCollectionMovieModal';
import Link from 'Components/Link/Link';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import EditMovieModal from 'Movie/Edit/EditMovieModal';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MoviePoster from 'Movie/MoviePoster';
import translate from 'Utilities/String/translate';
import AddNewCollectionMovieModal from './../AddNewCollectionMovieModal';
import styles from './CollectionMovie.css';
class CollectionMovie extends Component {
@@ -160,7 +160,7 @@ class CollectionMovie extends Component {
</Link>
</div>
<AddNewCollectionMovieModal
<AddNewMovieCollectionMovieModal
isOpen={isNewAddMovieModalOpen && !isExistingMovie}
tmdbId={tmdbId}
title={title}
@@ -172,7 +172,7 @@ class CollectionMovie extends Component {
collectionId={collectionId}
/>
<EditMovieModalConnector
<EditMovieModal
isOpen={isEditMovieModalOpen}
movieId={id}
onModalClose={this.onEditMovieModalClose}
@@ -188,7 +188,7 @@ CollectionMovie.propTypes = {
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
overview: PropTypes.string,
monitored: PropTypes.bool,
collectionId: PropTypes.number.isRequired,
hasFile: PropTypes.bool,

View File

@@ -3,14 +3,15 @@ import React, { Component } from 'react';
import TextTruncate from 'react-text-truncate';
import { Navigation } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/react';
import EditCollectionModalConnector from 'Collection/Edit/EditCollectionModalConnector';
import EditMovieCollectionModal from 'Collection/Edit/EditMovieCollectionModal';
import CheckInput from 'Components/Form/CheckInput';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import { icons, sizes } from 'Helpers/Props';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import MovieGenres from 'Movie/MovieGenres';
import QualityProfileName from 'Settings/Profiles/Quality/QualityProfileName';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
import translate from 'Utilities/String/translate';
@@ -211,7 +212,7 @@ class CollectionOverview extends Component {
/>
<span className={styles.qualityProfileName}>
{
<QualityProfileNameConnector
<QualityProfileName
qualityProfileId={qualityProfileId}
/>
}
@@ -242,12 +243,10 @@ class CollectionOverview extends Component {
size={sizes.MEDIUM}
>
<Icon
name={icons.PROFILE}
name={icons.GENRE}
size={13}
/>
<span className={styles.genres}>
{genres.join(', ')}
</span>
<MovieGenres className={styles.genres} genres={genres} />
</Label>
}
@@ -312,7 +311,7 @@ class CollectionOverview extends Component {
</div>
</div>
<EditCollectionModalConnector
<EditMovieCollectionModal
isOpen={isEditCollectionModalOpen}
collectionId={id}
onModalClose={this.onEditCollectionModalClose}

View File

@@ -14,17 +14,17 @@ const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
function calculatePosterWidth(posterSize, isSmallScreen) {
const maxiumPosterWidth = isSmallScreen ? 152 : 162;
const maximumPosterWidth = isSmallScreen ? 152 : 162;
if (posterSize === 'large') {
return maxiumPosterWidth;
return maximumPosterWidth;
}
if (posterSize === 'medium') {
return Math.floor(maxiumPosterWidth * 0.75);
return Math.floor(maximumPosterWidth * 0.75);
}
return Math.floor(maxiumPosterWidth * 0.5);
return Math.floor(maximumPosterWidth * 0.5);
}
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {

View File

@@ -0,0 +1,21 @@
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
export function createMovieCollectionSelector(collectionId?: number) {
return createSelector(
(state: AppState) => state.movieCollections.itemMap,
(state: AppState) => state.movieCollections.items,
(itemMap, allMovieCollections) => {
return collectionId
? allMovieCollections[itemMap[collectionId]]
: undefined;
}
);
}
function useMovieCollection(collectionId: number | undefined) {
return useSelector(createMovieCollectionSelector(collectionId));
}
export default useMovieCollection;

View File

@@ -4,6 +4,7 @@ import React, { Component, ErrorInfo } from 'react';
interface ErrorBoundaryProps {
children: React.ReactNode;
errorComponent: React.ElementType;
onModalClose?: () => void;
}
interface ErrorBoundaryState {
@@ -32,11 +33,17 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
}
render() {
const { children, errorComponent: ErrorComponent } = this.props;
const {
children,
errorComponent: ErrorComponent,
onModalClose,
} = this.props;
const { error, info } = this.state;
if (error) {
return <ErrorComponent error={error} info={info} />;
return (
<ErrorComponent error={error} info={info} onModalClose={onModalClose} />
);
}
return children;

View File

@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
import StackTrace from 'stacktrace-js';
import translate from 'Utilities/String/translate';
import styles from './ErrorBoundaryError.css';
interface ErrorBoundaryErrorProps {
export interface ErrorBoundaryErrorProps {
className: string;
messageClassName: string;
detailsClassName: string;
@@ -18,7 +19,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
className = styles.container,
messageClassName = styles.message,
detailsClassName = styles.details,
message = 'There was an error loading this content',
message = translate('ErrorLoadingContent'),
error,
info,
} = props;

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Alert from 'Components/Alert';
import PathInput from 'Components/Form/PathInput';
import { PathInputInternal } from 'Components/Form/PathInput';
import Button from 'Components/Link/Button';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
@@ -151,7 +151,7 @@ function FileBrowserModalContent(props: FileBrowserModalContentProps) {
</Alert>
) : null}
<PathInput
<PathInputInternal
className={styles.pathInput}
placeholder={translate('FileBrowserPlaceholderText')}
hasFileBrowser={false}

View File

@@ -16,6 +16,7 @@ import MovieFilterBuilderRowValue from './MovieFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
import QualityProfileFilterBuilderRowValue from './QualityProfileFilterBuilderRowValue';
import QueueStatusFilterBuilderRowValue from './QueueStatusFilterBuilderRowValue';
import ReleaseStatusFilterBuilderRowValue from './ReleaseStatusFilterBuilderRowValue';
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
import styles from './FilterBuilderRow.css';
@@ -80,6 +81,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.QUALITY_PROFILE:
return QualityProfileFilterBuilderRowValue;
case filterBuilderValueTypes.QUEUE_STATUS:
return QueueStatusFilterBuilderRowValue;
case filterBuilderValueTypes.MOVIE:
return MovieFilterBuilderRowValue;

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import TagInput from 'Components/Form/TagInput';
import TagInput from 'Components/Form/Tag/TagInput';
import { filterBuilderTypes, filterBuilderValueTypes, kinds } from 'Helpers/Props';
import tagShape from 'Helpers/Props/Shapes/tagShape';
import convertToBytes from 'Utilities/Number/convertToBytes';
@@ -58,7 +58,7 @@ function getValue(input, selectedFilterBuilderProp) {
if (selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER) {
const { numberFractionDigits = 0 } = selectedFilterBuilderProp;
return Number(input).toFixed(numberFractionDigits);
return Number(Number(input).toFixed(numberFractionDigits));
}
return input;

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import TagInputTag from 'Components/Form/TagInputTag';
import TagInputTag from 'Components/Form/Tag/TagInputTag';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './FilterBuilderRowValueTag.css';

View File

@@ -0,0 +1,67 @@
import React from 'react';
import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
const statusTagList = [
{
id: 'queued',
get name() {
return translate('Queued');
},
},
{
id: 'paused',
get name() {
return translate('Paused');
},
},
{
id: 'downloading',
get name() {
return translate('Downloading');
},
},
{
id: 'completed',
get name() {
return translate('Completed');
},
},
{
id: 'failed',
get name() {
return translate('Failed');
},
},
{
id: 'warning',
get name() {
return translate('Warning');
},
},
{
id: 'delay',
get name() {
return translate('Delay');
},
},
{
id: 'downloadClientUnavailable',
get name() {
return translate('DownloadClientUnavailable');
},
},
{
id: 'fallback',
get name() {
return translate('Fallback');
},
},
];
function QueueStatusFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
return <FilterBuilderRowValue {...props} tagList={statusTagList} />;
}
export default QueueStatusFilterBuilderRowValue;

View File

@@ -2,8 +2,11 @@ import React from 'react';
import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const protocols = [
{ id: 'tba', name: 'TBA' },
const statusTagList = [
{ id: 'tba',
get name() {
return translate('Tba');
} },
{
id: 'announced',
get name() {
@@ -33,7 +36,7 @@ const protocols = [
function ReleaseStatusFilterBuilderRowValue(props) {
return (
<FilterBuilderRowValue
tagList={protocols}
tagList={statusTagList}
{...props}
/>
);

View File

@@ -53,7 +53,7 @@ function CustomFiltersModalContent(props) {
<div className={styles.addButtonContainer}>
<Button onPress={onAddCustomFilter}>
Add Custom Filter
{translate('AddCustomFilter')}
</Button>
</div>
</ModalBody>

View File

@@ -1,98 +0,0 @@
import jdu from 'jdu';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AutoSuggestInput from './AutoSuggestInput';
class AutoCompleteInput extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
suggestions: []
};
}
//
// Control
getSuggestionValue(item) {
return item;
}
renderSuggestion(item) {
return item;
}
//
// Listeners
onInputChange = (event, { newValue }) => {
this.props.onChange({
name: this.props.name,
value: newValue
});
};
onInputBlur = () => {
this.setState({ suggestions: [] });
};
onSuggestionsFetchRequested = ({ value }) => {
const { values } = this.props;
const lowerCaseValue = jdu.replace(value).toLowerCase();
const filteredValues = values.filter((v) => {
return jdu.replace(v).toLowerCase().contains(lowerCaseValue);
});
this.setState({ suggestions: filteredValues });
};
onSuggestionsClearRequested = () => {
this.setState({ suggestions: [] });
};
//
// Render
render() {
const {
name,
value,
...otherProps
} = this.props;
const { suggestions } = this.state;
return (
<AutoSuggestInput
{...otherProps}
name={name}
value={value}
suggestions={suggestions}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
onInputBlur={this.onInputBlur}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
/>
);
}
}
AutoCompleteInput.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string,
values: PropTypes.arrayOf(PropTypes.string).isRequired,
onChange: PropTypes.func.isRequired
};
AutoCompleteInput.defaultProps = {
value: ''
};
export default AutoCompleteInput;

View File

@@ -0,0 +1,82 @@
import jdu from 'jdu';
import React, { SyntheticEvent, useCallback, useState } from 'react';
import {
ChangeEvent,
SuggestionsFetchRequestedParams,
} from 'react-autosuggest';
import { InputChanged } from 'typings/inputs';
import AutoSuggestInput from './AutoSuggestInput';
export interface AutoCompleteInputProps {
name: string;
readOnly?: boolean;
value?: string;
values: string[];
onChange: (change: InputChanged<string>) => unknown;
}
function AutoCompleteInput({
name,
value = '',
values,
onChange,
...otherProps
}: AutoCompleteInputProps) {
const [suggestions, setSuggestions] = useState<string[]>([]);
const getSuggestionValue = useCallback((item: string) => {
return item;
}, []);
const renderSuggestion = useCallback((item: string) => {
return item;
}, []);
const handleInputChange = useCallback(
(_event: SyntheticEvent, { newValue }: ChangeEvent) => {
onChange({
name,
value: newValue,
});
},
[name, onChange]
);
const handleInputBlur = useCallback(() => {
setSuggestions([]);
}, [setSuggestions]);
const handleSuggestionsFetchRequested = useCallback(
({ value: newValue }: SuggestionsFetchRequestedParams) => {
const lowerCaseValue = jdu.replace(newValue).toLowerCase();
const filteredValues = values.filter((v) => {
return jdu.replace(v).toLowerCase().includes(lowerCaseValue);
});
setSuggestions(filteredValues);
},
[values, setSuggestions]
);
const handleSuggestionsClearRequested = useCallback(() => {
setSuggestions([]);
}, [setSuggestions]);
return (
<AutoSuggestInput
{...otherProps}
name={name}
value={value}
suggestions={suggestions}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
onInputChange={handleInputChange}
onInputBlur={handleInputBlur}
onSuggestionsFetchRequested={handleSuggestionsFetchRequested}
onSuggestionsClearRequested={handleSuggestionsClearRequested}
/>
);
}
export default AutoCompleteInput;

View File

@@ -1,257 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Autosuggest from 'react-autosuggest';
import { Manager, Popper, Reference } from 'react-popper';
import Portal from 'Components/Portal';
import styles from './AutoSuggestInput.css';
class AutoSuggestInput extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._scheduleUpdate = null;
}
componentDidUpdate(prevProps) {
if (
this._scheduleUpdate &&
prevProps.suggestions !== this.props.suggestions
) {
this._scheduleUpdate();
}
}
//
// Control
renderInputComponent = (inputProps) => {
const { renderInputComponent } = this.props;
return (
<Reference>
{({ ref }) => {
if (renderInputComponent) {
return renderInputComponent(inputProps, ref);
}
return (
<div ref={ref}>
<input
{...inputProps}
/>
</div>
);
}}
</Reference>
);
};
renderSuggestionsContainer = ({ containerProps, children }) => {
return (
<Portal>
<Popper
placement='bottom-start'
modifiers={{
computeMaxHeight: {
order: 851,
enabled: true,
fn: this.onComputeMaxHeight
},
flip: {
padding: this.props.minHeight
}
}}
>
{({ ref: popperRef, style, scheduleUpdate }) => {
this._scheduleUpdate = scheduleUpdate;
return (
<div
ref={popperRef}
style={style}
className={children ? styles.suggestionsContainerOpen : undefined}
>
<div
{...containerProps}
style={{
maxHeight: style.maxHeight
}}
>
{children}
</div>
</div>
);
}}
</Popper>
</Portal>
);
};
//
// Listeners
onComputeMaxHeight = (data) => {
const {
top,
bottom,
width
} = data.offsets.reference;
const windowHeight = window.innerHeight;
if ((/^botton/).test(data.placement)) {
data.styles.maxHeight = windowHeight - bottom;
} else {
data.styles.maxHeight = top;
}
data.styles.width = width;
return data;
};
onInputChange = (event, { newValue }) => {
this.props.onChange({
name: this.props.name,
value: newValue
});
};
onInputKeyDown = (event) => {
const {
name,
value,
suggestions,
onChange
} = this.props;
if (
event.key === 'Tab' &&
suggestions.length &&
suggestions[0] !== this.props.value
) {
event.preventDefault();
if (value) {
onChange({
name,
value: suggestions[0]
});
}
}
};
//
// Render
render() {
const {
forwardedRef,
className,
inputContainerClassName,
name,
value,
placeholder,
suggestions,
hasError,
hasWarning,
getSuggestionValue,
renderSuggestion,
onInputChange,
onInputKeyDown,
onInputFocus,
onInputBlur,
onSuggestionsFetchRequested,
onSuggestionsClearRequested,
onSuggestionSelected,
...otherProps
} = this.props;
const inputProps = {
className: classNames(
className,
hasError && styles.hasError,
hasWarning && styles.hasWarning
),
name,
value,
placeholder,
autoComplete: 'off',
spellCheck: false,
onChange: onInputChange || this.onInputChange,
onKeyDown: onInputKeyDown || this.onInputKeyDown,
onFocus: onInputFocus,
onBlur: onInputBlur
};
const theme = {
container: inputContainerClassName,
containerOpen: styles.suggestionsContainerOpen,
suggestionsContainer: styles.suggestionsContainer,
suggestionsList: styles.suggestionsList,
suggestion: styles.suggestion,
suggestionHighlighted: styles.suggestionHighlighted
};
return (
<Manager>
<Autosuggest
{...otherProps}
ref={forwardedRef}
id={name}
inputProps={inputProps}
theme={theme}
suggestions={suggestions}
getSuggestionValue={getSuggestionValue}
renderInputComponent={this.renderInputComponent}
renderSuggestionsContainer={this.renderSuggestionsContainer}
renderSuggestion={renderSuggestion}
onSuggestionSelected={onSuggestionSelected}
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
/>
</Manager>
);
}
}
AutoSuggestInput.propTypes = {
forwardedRef: PropTypes.func,
className: PropTypes.string.isRequired,
inputContainerClassName: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
placeholder: PropTypes.string,
suggestions: PropTypes.array.isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
enforceMaxHeight: PropTypes.bool.isRequired,
minHeight: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
getSuggestionValue: PropTypes.func.isRequired,
renderInputComponent: PropTypes.elementType,
renderSuggestion: PropTypes.func.isRequired,
onInputChange: PropTypes.func,
onInputKeyDown: PropTypes.func,
onInputFocus: PropTypes.func,
onInputBlur: PropTypes.func.isRequired,
onSuggestionsFetchRequested: PropTypes.func.isRequired,
onSuggestionsClearRequested: PropTypes.func.isRequired,
onSuggestionSelected: PropTypes.func,
onChange: PropTypes.func.isRequired
};
AutoSuggestInput.defaultProps = {
className: styles.input,
inputContainerClassName: styles.inputContainer,
enforceMaxHeight: true,
minHeight: 50,
maxHeight: 200
};
export default AutoSuggestInput;

View File

@@ -0,0 +1,259 @@
import classNames from 'classnames';
import React, {
FocusEvent,
FormEvent,
KeyboardEvent,
KeyboardEventHandler,
MutableRefObject,
ReactNode,
Ref,
SyntheticEvent,
useCallback,
useEffect,
useRef,
} from 'react';
import Autosuggest, {
AutosuggestPropsBase,
BlurEvent,
ChangeEvent,
RenderInputComponentProps,
RenderSuggestionsContainerParams,
} from 'react-autosuggest';
import { Manager, Popper, Reference } from 'react-popper';
import Portal from 'Components/Portal';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { InputChanged } from 'typings/inputs';
import styles from './AutoSuggestInput.css';
interface AutoSuggestInputProps<T>
extends Omit<AutosuggestPropsBase<T>, 'renderInputComponent' | 'inputProps'> {
forwardedRef?: MutableRefObject<Autosuggest<T> | null>;
className?: string;
inputContainerClassName?: string;
name: string;
value?: string;
placeholder?: string;
suggestions: T[];
hasError?: boolean;
hasWarning?: boolean;
enforceMaxHeight?: boolean;
minHeight?: number;
maxHeight?: number;
renderInputComponent?: (
inputProps: RenderInputComponentProps,
ref: Ref<HTMLDivElement>
) => ReactNode;
onInputChange: (
event: FormEvent<HTMLElement>,
params: ChangeEvent
) => unknown;
onInputKeyDown?: KeyboardEventHandler<HTMLElement>;
onInputFocus?: (event: SyntheticEvent) => unknown;
onInputBlur: (
event: FocusEvent<HTMLElement>,
params?: BlurEvent<T>
) => unknown;
onChange?: (change: InputChanged<T>) => unknown;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function AutoSuggestInput<T = any>(props: AutoSuggestInputProps<T>) {
const {
// TODO: forwaredRef should be replaces with React.forwardRef
forwardedRef,
className = styles.input,
inputContainerClassName = styles.inputContainer,
name,
value = '',
placeholder,
suggestions,
enforceMaxHeight = true,
hasError,
hasWarning,
minHeight = 50,
maxHeight = 200,
getSuggestionValue,
renderSuggestion,
renderInputComponent,
onInputChange,
onInputKeyDown,
onInputFocus,
onInputBlur,
onSuggestionsFetchRequested,
onSuggestionsClearRequested,
onSuggestionSelected,
onChange,
...otherProps
} = props;
const updater = useRef<(() => void) | null>(null);
const previousSuggestions = usePrevious(suggestions);
const handleComputeMaxHeight = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(data: any) => {
const { top, bottom, width } = data.offsets.reference;
if (enforceMaxHeight) {
data.styles.maxHeight = maxHeight;
} else {
const windowHeight = window.innerHeight;
if (/^botton/.test(data.placement)) {
data.styles.maxHeight = windowHeight - bottom;
} else {
data.styles.maxHeight = top;
}
}
data.styles.width = width;
return data;
},
[enforceMaxHeight, maxHeight]
);
const createRenderInputComponent = useCallback(
(inputProps: RenderInputComponentProps) => {
return (
<Reference>
{({ ref }) => {
if (renderInputComponent) {
return renderInputComponent(inputProps, ref);
}
return (
<div ref={ref}>
<input {...inputProps} />
</div>
);
}}
</Reference>
);
},
[renderInputComponent]
);
const renderSuggestionsContainer = useCallback(
({ containerProps, children }: RenderSuggestionsContainerParams) => {
return (
<Portal>
<Popper
placement="bottom-start"
modifiers={{
computeMaxHeight: {
order: 851,
enabled: true,
fn: handleComputeMaxHeight,
},
flip: {
padding: minHeight,
},
}}
>
{({ ref: popperRef, style, scheduleUpdate }) => {
updater.current = scheduleUpdate;
return (
<div
ref={popperRef}
style={style}
className={
children ? styles.suggestionsContainerOpen : undefined
}
>
<div
{...containerProps}
style={{
maxHeight: style.maxHeight,
}}
>
{children}
</div>
</div>
);
}}
</Popper>
</Portal>
);
},
[minHeight, handleComputeMaxHeight]
);
const handleInputKeyDown = useCallback(
(event: KeyboardEvent<HTMLElement>) => {
if (
event.key === 'Tab' &&
suggestions.length &&
suggestions[0] !== value
) {
event.preventDefault();
if (value) {
onSuggestionSelected?.(event, {
suggestion: suggestions[0],
suggestionValue: value,
suggestionIndex: 0,
sectionIndex: null,
method: 'enter',
});
}
}
},
[value, suggestions, onSuggestionSelected]
);
const inputProps = {
className: classNames(
className,
hasError && styles.hasError,
hasWarning && styles.hasWarning
),
name,
value,
placeholder,
autoComplete: 'off',
spellCheck: false,
onChange: onInputChange,
onKeyDown: onInputKeyDown || handleInputKeyDown,
onFocus: onInputFocus,
onBlur: onInputBlur,
};
const theme = {
container: inputContainerClassName,
containerOpen: styles.suggestionsContainerOpen,
suggestionsContainer: styles.suggestionsContainer,
suggestionsList: styles.suggestionsList,
suggestion: styles.suggestion,
suggestionHighlighted: styles.suggestionHighlighted,
};
useEffect(() => {
if (updater.current && suggestions !== previousSuggestions) {
updater.current();
}
}, [suggestions, previousSuggestions]);
return (
<Manager>
<Autosuggest
{...otherProps}
ref={forwardedRef}
id={name}
inputProps={inputProps}
theme={theme}
suggestions={suggestions}
getSuggestionValue={getSuggestionValue}
renderInputComponent={createRenderInputComponent}
renderSuggestionsContainer={renderSuggestionsContainer}
renderSuggestion={renderSuggestion}
onSuggestionSelected={onSuggestionSelected}
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
/>
</Manager>
);
}
export default AutoSuggestInput;

View File

@@ -1,84 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import FormInputButton from './FormInputButton';
import TextInput from './TextInput';
import styles from './CaptchaInput.css';
function CaptchaInput(props) {
const {
className,
name,
value,
hasError,
hasWarning,
refreshing,
siteKey,
secretToken,
onChange,
onRefreshPress,
onCaptchaChange
} = props;
return (
<div>
<div className={styles.captchaInputWrapper}>
<TextInput
className={classNames(
className,
styles.hasButton,
hasError && styles.hasError,
hasWarning && styles.hasWarning
)}
name={name}
value={value}
onChange={onChange}
/>
<FormInputButton
onPress={onRefreshPress}
>
<Icon
name={icons.REFRESH}
isSpinning={refreshing}
/>
</FormInputButton>
</div>
{
!!siteKey && !!secretToken &&
<div className={styles.recaptchaWrapper}>
<ReCAPTCHA
sitekey={siteKey}
stoken={secretToken}
onChange={onCaptchaChange}
/>
</div>
}
</div>
);
}
CaptchaInput.propTypes = {
className: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
refreshing: PropTypes.bool.isRequired,
siteKey: PropTypes.string,
secretToken: PropTypes.string,
onChange: PropTypes.func.isRequired,
onRefreshPress: PropTypes.func.isRequired,
onCaptchaChange: PropTypes.func.isRequired
};
CaptchaInput.defaultProps = {
className: styles.input,
value: ''
};
export default CaptchaInput;

View File

@@ -0,0 +1,118 @@
import classNames from 'classnames';
import React, { useCallback, useEffect } from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import Icon from 'Components/Icon';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { icons } from 'Helpers/Props';
import {
getCaptchaCookie,
refreshCaptcha,
resetCaptcha,
} from 'Store/Actions/captchaActions';
import { InputChanged } from 'typings/inputs';
import FormInputButton from './FormInputButton';
import TextInput from './TextInput';
import styles from './CaptchaInput.css';
export interface CaptchaInputProps {
className?: string;
name: string;
value?: string;
provider: string;
providerData: object;
hasError?: boolean;
hasWarning?: boolean;
refreshing: boolean;
siteKey?: string;
secretToken?: string;
onChange: (change: InputChanged<string>) => unknown;
}
function CaptchaInput({
className = styles.input,
name,
value = '',
provider,
providerData,
hasError,
hasWarning,
refreshing,
siteKey,
secretToken,
onChange,
}: CaptchaInputProps) {
const { token } = useSelector((state: AppState) => state.captcha);
const dispatch = useDispatch();
const previousToken = usePrevious(token);
const handleCaptchaChange = useCallback(
(token: string | null) => {
// If the captcha has expired `captchaResponse` will be null.
// In the event it's null don't try to get the captchaCookie.
// TODO: Should we clear the cookie? or reset the captcha?
if (!token) {
return;
}
dispatch(
getCaptchaCookie({
provider,
providerData,
captchaResponse: token,
})
);
},
[provider, providerData, dispatch]
);
const handleRefreshPress = useCallback(() => {
dispatch(refreshCaptcha({ provider, providerData }));
}, [provider, providerData, dispatch]);
useEffect(() => {
if (token && token !== previousToken) {
onChange({ name, value: token });
}
}, [name, token, previousToken, onChange]);
useEffect(() => {
dispatch(resetCaptcha());
}, [dispatch]);
return (
<div>
<div className={styles.captchaInputWrapper}>
<TextInput
className={classNames(
className,
styles.hasButton,
hasError && styles.hasError,
hasWarning && styles.hasWarning
)}
name={name}
value={value}
onChange={onChange}
/>
<FormInputButton onPress={handleRefreshPress}>
<Icon name={icons.REFRESH} isSpinning={refreshing} />
</FormInputButton>
</div>
{siteKey && secretToken ? (
<div className={styles.recaptchaWrapper}>
<ReCAPTCHA
sitekey={siteKey}
stoken={secretToken}
onChange={handleCaptchaChange}
/>
</div>
) : null}
</div>
);
}
export default CaptchaInput;

View File

@@ -1,98 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { getCaptchaCookie, refreshCaptcha, resetCaptcha } from 'Store/Actions/captchaActions';
import CaptchaInput from './CaptchaInput';
function createMapStateToProps() {
return createSelector(
(state) => state.captcha,
(captcha) => {
return captcha;
}
);
}
const mapDispatchToProps = {
refreshCaptcha,
getCaptchaCookie,
resetCaptcha
};
class CaptchaInputConnector extends Component {
//
// Lifecycle
componentDidUpdate(prevProps) {
const {
name,
token,
onChange
} = this.props;
if (token && token !== prevProps.token) {
onChange({ name, value: token });
}
}
componentWillUnmount = () => {
this.props.resetCaptcha();
};
//
// Listeners
onRefreshPress = () => {
const {
provider,
providerData
} = this.props;
this.props.refreshCaptcha({ provider, providerData });
};
onCaptchaChange = (captchaResponse) => {
// If the captcha has expired `captchaResponse` will be null.
// In the event it's null don't try to get the captchaCookie.
// TODO: Should we clear the cookie? or reset the captcha?
if (!captchaResponse) {
return;
}
const {
provider,
providerData
} = this.props;
this.props.getCaptchaCookie({ provider, providerData, captchaResponse });
};
//
// Render
render() {
return (
<CaptchaInput
{...this.props}
onRefreshPress={this.onRefreshPress}
onCaptchaChange={this.onCaptchaChange}
/>
);
}
}
CaptchaInputConnector.propTypes = {
provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
token: PropTypes.string,
onChange: PropTypes.func.isRequired,
refreshCaptcha: PropTypes.func.isRequired,
getCaptchaCookie: PropTypes.func.isRequired,
resetCaptcha: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(CaptchaInputConnector);

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