Compare commits

...

152 Commits

Author SHA1 Message Date
Bogdan 638f92495c Bump version to 5.7.0 2024-05-14 20:18:27 +03:00
Bogdan 077b041d3f Fixed: Revert "Validate that folders in paths don't start or end with a space"
This reverts commit 0d0575f3a9.
2024-05-14 18:08:38 +03:00
Bogdan ff3dd3ae42 Tests for Wanted pages 2024-05-14 18:05:01 +03:00
Bogdan 2e3beddcbc Fixed: Sorting by movie titles in Missing/Cutoff Unmet under Postgres 2024-05-14 15:56:49 +03:00
Servarr dc068bbf3d Automated API Docs update 2024-05-14 03:07:05 +03:00
Bogdan 7a303c1ebf Remove not implemented endpoints from API docs 2024-05-14 02:53:51 +03:00
Bogdan 152f50a1ef New: Wanted Cutoff/Missing 2024-05-14 02:53:51 +03:00
Bogdan 9798202589 Add missing translation for External 2024-05-14 02:53:51 +03:00
Bogdan 7969776339 Rename file for getMovieStatusDetails 2024-05-14 02:53:51 +03:00
Bogdan 288982d7bd Bump Npgsql to 7.0.7 2024-05-13 15:14:57 +03:00
Servarr d39a3ade5b Automated API Docs update 2024-05-12 22:29:56 +03:00
Bogdan 1fc6e88bc4 New: Add `isExisting` flag for movies in collections API 2024-05-12 22:20:13 +03:00
Bogdan e8e1841e6c New: No Release Dates availability message
Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2024-05-12 17:16:15 +03:00
Bogdan d17eb4f33f Bump version to 5.6.0 2024-05-12 16:28:32 +03:00
Bogdan 685f462959 New: Include trending and popular options for Discover Movies 2024-05-11 16:29:42 +03:00
Servarr 7be8a34130 Automated API Docs update 2024-05-10 21:30:13 +03:00
Ivan Sanz Carasa 886711b496 New: LanguageId filter added to all movie endpoint 2024-05-10 20:54:57 +03:00
Servarr 5185e037da Automated API Docs update 2024-05-10 20:51:41 +03:00
Mark McDowall 38e7e37d57 Refactor movie tags for CustomScript, Webhook and Notifiarr events
(cherry picked from commit cc0a284660f139d5f47b27a2c389973e5e888587)

Closes #10003
2024-05-10 16:15:51 +03:00
Stevie Robinson 190c4c5893 New: Blocklist Custom Filters
(cherry picked from commit f81bb3ec1945d343dd0695a2826dac8833cb6346)

Closes #9997
2024-05-10 16:04:03 +03:00
Mark McDowall 0ec18ce4b3 New: Parse 480i Bluray/Remux as Bluray 480p
(cherry picked from commit 627b2a4289ecdd5558d37940624289708e01e10a)

Closes #10010
2024-05-10 14:59:24 +03:00
Weblate a08575b7bc Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Michael5564445 <michaelvelosk@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/ca/
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/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translation: Servarr/Radarr
2024-05-10 14:00:54 +03:00
Bogdan 556cc885ec Refactor PasswordInput to use type password
(cherry picked from commit c7c1e3ac9e5bffd4d92298fed70916e3808613fd)
2024-05-10 14:00:22 +03:00
Bogdan 586c0c6e13 Fixed: Notifications with only On Rename enabled 2024-05-10 00:47:31 +03:00
Bogdan cec569461d Fixed: Text color for inputs on login page 2024-05-09 20:58:49 +03:00
Mark McDowall 8b79b5afbf New: Dark theme for login screen
(cherry picked from commit cae134ec7b331d1c906343716472f3d043614b2c)

Closes #9998
2024-05-09 20:58:49 +03:00
Mickaël Thomas cd4552ce6f New: Support stoppedUP and stoppedDL states from qBittorrent
(cherry picked from commit 73a4bdea5247ee87e6bbae95f5325e1f03c88a7f)

Closes #9995
2024-05-09 20:58:49 +03:00
Bogdan 256439304b Use number input for seed ratio
(cherry picked from commit 1eddf3a152fae04142263c02a3e3b317ff2feeb2)

Plus translations

Closes #10000
2024-05-09 20:58:49 +03:00
Bogdan bb44fbc362 New: Root folder exists validation for import lists
Also moved the AppendArgument to avoid cases like `Invalid Path: '{path}'`.
2024-05-09 20:58:49 +03:00
Servarr cd401f72f5 Automated API Docs update 2024-05-08 04:31:35 +03:00
Bogdan c9624e7550 Fixed: Ignore invalid movie tags when writing XBMC metadata
Co-authored-by: Mark McDowall <mark@mcdowall.ca>

Fixes #9984
2024-05-08 04:05:34 +03:00
Bogdan 649702eaca Fixed: Indexer flags for torrent release pushes
(cherry picked from commit 47ba002806fe2c2004a649aa193ae318343a84e4)
2024-05-07 18:11:58 +03:00
Servarr 1c52f0f5bd Automated API Docs update 2024-05-06 23:54:09 +03:00
Bogdan dff85dc1f3 New: Display excluded label for movies in collections 2024-05-06 23:19:15 +03:00
Bogdan 1090aeff75 Fixed: Ignore exclusions in missing movies for collections
Fixes #9966
2024-05-06 23:18:02 +03:00
Jared 086a0addba
New: Config file setting to disable log database (#9943)
Co-authored-by: sillock1 <jprest97@gmail.com>
2024-05-06 21:51:19 +03:00
Bogdan 8b6cf34ce4 Fixed: Parsing long downloading/seeding values from Transmission
Fixes #9987
2024-05-06 21:26:36 +03:00
Jared 7f03a916f1
New: Optionally use Environment Variables for settings in config.xml (#9985)
Co-authored-by: sillock1 <jprest97@gmail.com>
2024-05-05 22:32:07 +03:00
Mika 3a6d603a9e Add file-count for Transmission RPC
(cherry picked from commit 23c741fd001582fa363c2723eff9facd3091618b)

Closes #9973
2024-05-05 13:03:21 +03:00
Weblate cd2c7dc7fb 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: Michael5564445 <michaelvelosk@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/
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/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translation: Servarr/Radarr
2024-05-05 12:50:06 +03:00
Bogdan f1d76c3483 Fix translations for SSL settings 2024-05-05 12:38:58 +03:00
Stevie Robinson 39eac4b5ad Add missing translation key
(cherry picked from commit 8be8c7f89cf4d40bee941c5ce768aa1a74ebe398)
2024-05-05 12:27:53 +03:00
Mark McDowall 71e1003358 Forward X-Forwarded-Host header
(cherry picked from commit 3fbe4361386e9fb8dafdf82ad9f00f02bec746cc)
2024-05-05 12:27:10 +03:00
Bogdan 89b6a5d51f Bump version to 5.5.3 2024-05-05 12:26:22 +03:00
Bogdan 711637c448 Fixed: Initialize databases after app folder migrations 2024-05-05 00:57:46 +03:00
Bruno Garcia 2677d25980 Update Sentry SDK add features
Co-authored-by: Stefan Jandl <reg@bitfox.at>
(cherry picked from commit 6377c688fc7b35749d608bf62796446bb5bcb11b)
2024-05-01 23:27:51 +03:00
Bogdan 56639bcd42 Fix translations for SSL settings 2024-04-30 12:41:38 +03:00
Bogdan 1ed62b9ced Use newer Node.js task for in pipelines 2024-04-29 14:40:14 +03:00
Servarr a596dda253 Automated API Docs update 2024-04-29 14:40:03 +03:00
Bogdan c0b354039d Parameter binding for API requests 2024-04-29 01:19:25 +03:00
Bogdan 3b5078d117 Fixed: Delay profiles reordering 2024-04-29 01:18:40 +03:00
Bogdan db1fee8d8a New: Use absolute timestamps for movie history 2024-04-28 20:22:32 +03:00
Mark McDowall 0d0575f3a9 New: Validate that folders in paths don't start or end with a space
(cherry picked from commit 316b5cbf75b45ef9a25f96ce1f2fbed25ad94296)

Closes #9958
2024-04-28 13:31:07 +03:00
Stevie Robinson 2d82347a66 New: Don't initially select 0 byte files in Interactive Import
(cherry picked from commit 04bd535cfca5e25c6a2d5417c6f18d5bf5180f67)

Closes #9960
2024-04-28 13:27:28 +03:00
Bogdan 25838df550 Fixed: Limit titles in task name to 10 movies
(cherry picked from commit c81ae6546118e954e481894d0b3fa6e9a20359c7)

Closes #9961
2024-04-28 13:22:25 +03:00
Mark McDowall b3a8b99f9a Fixed: Improve paths longer than 256 on Windows failing to hardlink
(cherry picked from commit a97fbcc40a6247bf59678425cf460588fd4dbecd)
2024-04-28 13:19:14 +03:00
Christopher 93a852841f New: Remove qBitorrent torrents that reach inactive seeding time
(cherry picked from commit d738035fed859eb475051f3df494b9c975a42e82)
2024-04-28 13:18:58 +03:00
Bogdan ead1ec43be Bump version to 5.5.2 2024-04-28 12:55:35 +03:00
Weblate 04b6dd44cb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: maodun96 <435795439@qq.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/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-04-27 21:13:22 +03:00
Bogdan 3db78079f3 Fixed: Retrying download on not suppressed HTTP errors 2024-04-25 17:22:49 +03:00
Bogdan c8a6b9f565 Database corruption message linking to wiki 2024-04-25 11:29:26 +03:00
Bogdan 811cafd9ae Bump dotnet to 6.0.29 2024-04-22 08:08:41 +03:00
fireph ac7039d651 New: Footnote to indicate some renaming tokens support truncation
(cherry picked from commit 7fc3bebc91db217a1c24ab2d01ebbc5bf03c918e)

Closes #9905
2024-04-21 18:36:51 +03:00
Bogdan a2d11cf684 Bump typescript eslint plugin and parser 2024-04-21 12:40:23 +03:00
Bogdan cc32635f6f Bump frontend dependencies 2024-04-21 10:31:56 +03:00
Bogdan 10f9cb64ac Bump version to 5.5.1 2024-04-21 09:16:33 +03:00
Weblate f77e27bace Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: Fonkio <maxime.fabre10@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jacopo Luca Maria Latrofa <jacopo.latrofa@gmail.com>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: YSLG <1451164040@qq.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: myrad2267 <myrad2267@gmail.com>
Co-authored-by: toeiazarothis <patrickdealmeida89000@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
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/it/
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-04-19 17:25:34 +03:00
Servarr 8ea6d59d59 Automated API Docs update 2024-04-19 17:24:01 +03:00
Bogdan 98668d0d25 Bump SixLabors.ImageSharp to 3.1.4 2024-04-19 07:59:50 +03:00
Gauthier 649d57a234 Improve Multi Language Regex and field translations
(cherry picked from commit 6c232b062c5c11b76a2f205fcd949619e4346d16)

Closes #9931
2024-04-16 12:53:04 +03:00
Josh McKinney dc7c8bf800 Add dev container workspace
Allows the linting and style settings for the frontend to be applied even when you load the main repo as a workspace

(cherry picked from commit d6278fced49b26be975c3a6039b38a94f700864b)

Closes #9929
2024-04-16 11:41:14 +03:00
Bogdan 8d90c7678f Fixed: Re-testing edited providers will forcibly test them
(cherry picked from commit e9662544621b2d1fb133ff9d96d0eb20b8198725)

Closes #9933
2024-04-16 11:39:43 +03:00
Bogdan 02518e2116 Fixed: Validate provider's settings in Test All endpoint 2024-04-16 11:39:35 +03:00
Mark McDowall 3191a883dc New: Improve multi-language negate Custom Format
(cherry picked from commit 42b11528b4699b8343887185c93a02b139192d83)

Closes #9720
2024-04-13 11:03:37 +03:00
Bogdan 31a714e6b3 Bump version to 5.5.0 2024-04-13 08:46:17 +03:00
Mark McDowall f7ca0b8b06 New: Auto tag movies based on tags present/absent on movies
(cherry picked from commit f4c19a384bd9bb4e35c9fa0ca5d9a448c04e409e)

Closes #9916
2024-04-10 23:40:26 +03:00
Josh McKinney 56be9502af Add DevContainer, VSCode config and extensions.json
(cherry picked from commit 5061dc4b5e5ea9925740496a5939a1762788b793)

Closes #9914
2024-04-10 22:59:13 +03:00
Mark McDowall 77381d3f72 New: Option to prefix app name on Telegram notification titles
(cherry picked from commit 37863a8deb339ef730b2dd5be61e1da1311fdd23)

Closes #9913
2024-04-10 22:34:52 +03:00
Bogdan 198e6324e0 Truncate long names for import lists 2024-04-09 18:19:26 +03:00
Alan Collins 81c9537e5a New: 'Custom Format:Format Name' rename token
cherry picked from commit 48cb5d227187a06930aad5ee1b4e7b76422d8421)

New: Update Custom Format renaming token to allow excluding specific formats

(cherry picked from commit 6584d95331d0e0763e1688a397a3ccaf5fa6ca38)

Closes #9835
Closes #9826
2024-04-09 08:04:57 +03:00
Bogdan d3cbb9be8d New: Detect shfs mounts 2024-04-08 22:25:42 +03:00
Bogdan 2e043c0cf7 Bump version to 5.4.6 2024-04-07 07:58:52 +03:00
Weblate ada33dc065 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Michael5564445 <michaelvelosk@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/
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/ro/
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-04-06 16:34:41 +03:00
Till Krüss badb68b817
Improve text for file deleted through UI/API (#9882) 2024-04-06 16:32:25 +03:00
Mark McDowall 3bd1b3e972 Fixed: Sending ntfy.sh notifications with unicode characters
(cherry picked from commit a169ebff2adda5c8585c6aae6249b1c1f7c12264)
2024-04-06 16:27:30 +03:00
Stevie Robinson 6851de42a7 New: Informational text on Custom Formats modal
(cherry picked from commit 238ba85f0a2639608d9890292dfe0b96c0212f10)
2024-04-06 16:27:19 +03:00
Cuki dd0b7c91f9 Fixed: Use widely supported display mode for PWA
(cherry picked from commit 1562d3bae3002947f9e428321d2b162ad69c3309)
2024-04-06 16:27:08 +03:00
Mark McDowall 45ac69e2d9 Fixed: Cleanse BHD RSS key in log files
(cherry picked from commit 60ee7cc716d344fc904fa6fb28f7be0386ae710d)
2024-04-06 16:26:49 +03:00
Bogdan 9ccf0ecdb1 Fix translation token for Include Health Warnings 2024-04-06 03:31:36 +03:00
Servarr 48a3467572 Automated API Docs update 2024-03-29 14:53:19 +02:00
Mark McDowall d0a10379f9 Fixed: Use custom formats from import during rename
(cherry picked from commit d338425951af50a710c6c4411a72f05d14737ddd)

Closes #9867
2024-03-28 12:42:21 +02:00
Bogdan caab5e3614 Add missing import after 4e4769 2024-03-28 12:38:28 +02:00
Alex Cortelyou 4e47695f89 New: Add additional fields to Webhook Manual Interaction Required events
(cherry picked from commit 1ec1ce58e9f095222e7fe4a8c74a0720fed71558)

Closes #9874
2024-03-28 11:15:56 +02:00
Bogdan 83bd4d0686 New: Advanced settings toggle in import list, notification and download client modals
(cherry picked from commit 13c925b3418d1d48ec041e3d97ab51aaf2b8977a)

Closes #9869
2024-03-28 11:14:26 +02:00
Mark McDowall a75619c8ef Fixed: Task with removed movie causing error
(cherry picked from commit fc6494c569324c839debdb1d08dde23b8f1b8d76)

Closes #9866
2024-03-28 11:08:32 +02:00
Louis R 28689006fb Fixed: Exceptions when checking for routable IPv4 addresses
(cherry picked from commit 060b789bc6f10f667795697eb536d4bd3851da49)
2024-03-28 10:29:30 +02:00
Carlos Gustavo Sarmiento 43b0589bea Fixed: qBittorrent not correctly handling retention during testing
(cherry picked from commit 588372fd950fc85f5e9a4275fbcb423b247ed0ee)
2024-03-28 10:29:17 +02:00
Stevie Robinson c4aad5800c Fixed: Handling torrents with relative path in rTorrent
(cherry picked from commit 35d0e6a6f806c68756450a7d199600d7fb49d6c5)
2024-03-28 10:28:53 +02:00
Bogdan 0c998dac5c New: Allow HEAD requests to ping endpoint
(cherry picked from commit 7353fe479dbb8d0dab76993ebed92d48e1b05524)
2024-03-28 10:28:41 +02:00
Bogdan d41c0f0ab7 Fixed: Movie search label on overflow views
Fixed #9865
2024-03-27 15:16:56 +02:00
Weblate 85b13b7e41 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Casselluu <jack10193@163.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Stanislav <prekop3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: shimmyx <shimmygodx@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
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-03-27 13:16:17 +02:00
Bogdan 2a545a84b4 Bump version to 5.4.5 2024-03-24 17:45:46 +02:00
Mark McDowall 280083f4d7 Fixed: Task progress messages in the UI
(cherry picked from commit c6417337812f3578a27f9dc1e44fdad80f557271)

Closes #9855
2024-03-22 11:15:16 +02:00
Mark McDowall d6dcae3d6a Fixed: Plex Watchlist import list
(cherry picked from commit 88de9274358d7005fa9c677bb8c86f046a2a23a9)
2024-03-22 11:06:39 +02:00
Bogdan ebde4d3bc8 New: Critic Rating for Kodi/Emby metadata 2024-03-21 19:22:51 +02:00
Yurii 1ee30290ef Use branded message title for Telegram nitifications 2024-03-17 13:20:57 +00:00
Bogdan d303eae7c6 New: Company filters for TMDb Popular List 2024-03-17 13:50:41 +02:00
Bogdan 584910514a Bump version to 5.4.4 2024-03-17 13:50:03 +02:00
Weblate a253181d7d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dennis Langthjem <dennis@langthjem.dk>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Gianmarco Novelli <rinogaetano94@live.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ihor Mudryi <mudryy33@gmail.com>
Co-authored-by: MadaxDeLuXe <madaxdeluxe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: infoaitek24 <info@aitekph.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: vfaergestad <vgf@hotmail.no>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
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/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translation: Servarr/Radarr
2024-03-16 18:15:57 +02:00
Bogdan 7ea6918327 Remove leftover QueuedTasks.js 2024-03-14 15:40:13 +02:00
Bogdan 953d3ad3fb Ensure not allowed cursor is shown for disabled select inputs 2024-03-14 14:31:09 +02:00
Mark McDowall b9f4073514 Fixed: Disabled select option still selectable
(cherry picked from commit 063dba22a803295adee4fdcbe42718af3e85ca78)

Closes #9838
2024-03-14 13:20:33 +02:00
Stevie Robinson 86a17e7984 Fixed: Wrapping of naming tokens with alternate separators
(cherry picked from commit 80630bf97f5bb3b49d4824dc039d2edfc74e4797)

Closes #9743
Closes #9836
2024-03-14 11:33:54 +02:00
Bogdan f38545f852 Ensure movies are populated in PageConnector 2024-03-14 11:21:56 +02:00
Mark McDowall a7720e829d New: Show movie titles after task name when applicable
(cherry picked from commit 6d552f2a60f44052079b5e8944f5e1bbabac56e0)

Closes #9837
2024-03-14 11:15:39 +02:00
Mark McDowall 3a4eac4d59 Fixed: Release push with only Magnet URL
(cherry picked from commit 9f705e4161af3f4dd55b399d56b0b9c5a36e181b)
2024-03-14 07:12:12 +02:00
Bogdan 04f792c55a Fixed: Map covers to local for Movie Editor 2024-03-12 22:48:27 +02:00
Stevie Robinson ada326e4dd Update release profile download client warning
(cherry picked from commit 2ec071a5ecab8f5056d179feaaef0147abb944ca)

Closes #9828
2024-03-10 20:16:09 +02:00
Bogdan cae58d620b New: Collection Refresh Complete Event to trigger root folder check for collections 2024-03-10 20:13:35 +02:00
Bogdan e84df18e8d Bump ImageSharp, Polly 2024-03-10 13:06:26 +02:00
Bogdan a51ae70938 Bump version to 5.4.3 2024-03-10 09:08:55 +02:00
Weblate 7cc04245ec Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Mark Martines <mark-martines@hotmail.com>
Co-authored-by: Maxence Winandy <maxence.winandy@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: linkin931 <931linkin@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
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/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/hu/
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/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/zh_CN/
Translation: Servarr/Radarr
2024-03-08 11:08:04 +02:00
Mark McDowall 2caf3c6725 Fixed: Error sending Manual Interaction Required notification
(cherry picked from commit a12cdb34bc0ab78937e3c3677012bf030923aebf)
2024-03-08 08:52:48 +02:00
Bogdan 41ff9352b9 Prevent NullRef in naming when truncating a null Release Group
(cherry picked from commit 13e29bd257ccfccb09e66c940ffabeb6503c05b5)
2024-03-08 08:52:48 +02:00
Helvio Pedreschi d7b9b2ccb2 Fixed: WebApp functionality on Apple devices
(cherry picked from commit c7dd7abf892eead7796fcc482aa2f2aabaf88712)
2024-03-08 08:52:48 +02:00
Mark McDowall e90a50a3aa Fixed: Overly aggressive exception release group parsing
(cherry picked from commit 0183812cc58dad0e555125ddd8b33a85cbdecbf2)
2024-03-08 08:52:48 +02:00
Bogdan a0dd26c353 Configurable URL Base setting for Kodi connections 2024-03-04 03:16:23 +02:00
Bogdan 2286055d6a Fixed: URL Base setting for Kodi connections 2024-03-03 13:48:25 +02:00
Mark McDowall 0a5a4e0a6f New: URL Base setting for Media Server connections
(cherry picked from commit 9fd193d2a82d5c2cdc0f36c1f984e4b6b68aaa8d)
2024-03-03 12:29:47 +02:00
Mark McDowall 619c38c493 Queue Manual Import commands at high priority
(cherry picked from commit 64c6a8879beb1b17122c8f6f74bf7b3cf4dd1570)
2024-03-03 12:29:45 +02:00
Louis R 0b8694c627 Fixed: Don't disable IPv6 in IPv6-only Environment
(cherry picked from commit 13af6f57796e54c3949cf340e03f020e6f8575c4)
2024-03-03 12:23:59 +02:00
Bogdan e2793e56e9 Bump version to 5.4.2 2024-03-03 12:23:41 +02:00
nopoz 68f61da321
New: Add download directory & move completed for Deluge 2024-03-03 12:23:22 +02:00
Weblate 8edb541e21 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Nicolò Castagnola <nipica@outlook.it>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translation: Servarr/Radarr
2024-03-03 02:34:23 +02:00
Mark McDowall d441becc74 New: Bypass archived history for failed downloads in SABnzbd
(cherry picked from commit c99d81e79ba5e6ecec01ddd942440d8a48a1c23b)
2024-03-02 04:01:40 +02:00
Mark McDowall a97b2ee2ed Increase migration timeout to 5 minutes
(cherry picked from commit 086d3b5afaa7680d22835ca66da2afcb6dd5865e)
2024-03-02 04:00:35 +02:00
Bogdan e70c61e24e Update caniuse-lite 2024-02-29 02:35:06 +02:00
Bogdan d1f96746e0 Fix RSS translation token 2024-02-29 02:26:35 +02:00
Weblate 35893697bd Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: EDUYO <eduardoestabiel@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Sadi A. Nogueira <contato@sadi.eti.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Xupix <colinaubert25@gmail.com>
Co-authored-by: 闫锦彪 <yanjinbiaohere@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
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/zh_CN/
Translation: Servarr/Radarr
2024-02-29 02:25:08 +02:00
Bogdan 540c150b93 Wrap external links tooltip in Movie Details with flex
Co-authored-by: ricci2511 <ricardo.christmann@protonmail.com>

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

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

View File

@ -0,0 +1,13 @@
// This file is used to open the backend and frontend in the same workspace, which is necessary as
// the frontend has vscode settings that are distinct from the backend
{
"folders": [
{
"path": ".."
},
{
"path": "../frontend"
}
],
"settings": {}
}

View File

@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "Radarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"nvmVersion": "latest"
}
},
"forwardPorts": [7878],
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode"]
}
}
}

12
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

1
.gitignore vendored
View File

@ -126,6 +126,7 @@ coverage*.xml
coverage*.json
setup/Output/
*.~is
.mono
# VS outout folders
bin

7
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"ms-dotnettools.csdevkit",
"ms-vscode-remote.remote-containers"
]
}

26
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": "Run Radarr",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/_output/net6.0/Radarr",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

44
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,44 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build dotnet",
"command": "dotnet",
"type": "process",
"args": [
"msbuild",
"-restore",
"${workspaceFolder}/src/Radarr.sln",
"-p:GenerateFullPaths=true",
"-p:Configuration=Debug",
"-p:Platform=Posix",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Radarr.sln",
"-property:GenerateFullPaths=true",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/Radarr.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -9,14 +9,14 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.3.6'
majorVersion: '5.7.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.417'
nodeVersion: '16.X'
dotnetVersion: '6.0.421'
nodeVersion: '20.X'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
@ -166,10 +166,10 @@ stages:
pool:
vmImage: $(imageName)
steps:
- task: NodeTool@0
- task: UseNode@1
displayName: Set Node.js version
inputs:
versionSpec: $(nodeVersion)
version: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1
@ -1089,10 +1089,10 @@ stages:
pool:
vmImage: $(imageName)
steps:
- task: NodeTool@0
- task: UseNode@1
displayName: Set Node.js version
inputs:
versionSpec: $(nodeVersion)
version: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@ -20,6 +21,7 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import BlocklistFilterModal from './BlocklistFilterModal';
import BlocklistRowConnector from './BlocklistRowConnector';
class Blocklist extends Component {
@ -114,9 +116,13 @@ class Blocklist extends Component {
error,
items,
columns,
selectedFilterKey,
filters,
customFilters,
totalRecords,
isRemoving,
isClearingBlocklistExecuting,
onFilterSelect,
...otherProps
} = this.props;
@ -161,6 +167,15 @@ class Blocklist extends Component {
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={BlocklistFilterModal}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
</PageToolbar>
@ -180,7 +195,11 @@ class Blocklist extends Component {
{
isPopulated && !error && !items.length &&
<Alert kind={kinds.INFO}>
{translate('NoHistoryBlocklist')}
{
selectedFilterKey === 'all' ?
translate('NoHistoryBlocklist') :
translate('BlocklistFilterHasNoItems')
}
</Alert>
}
@ -251,11 +270,15 @@ Blocklist.propTypes = {
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isRemoving: PropTypes.bool.isRequired,
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
onRemoveSelected: PropTypes.func.isRequired,
onClearBlocklistPress: PropTypes.func.isRequired
onClearBlocklistPress: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired
};
export default Blocklist;

View File

@ -6,6 +6,7 @@ import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage';
import * as blocklistActions from 'Store/Actions/blocklistActions';
import { executeCommand } from 'Store/Actions/commandActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import Blocklist from './Blocklist';
@ -13,10 +14,12 @@ import Blocklist from './Blocklist';
function createMapStateToProps() {
return createSelector(
(state) => state.blocklist,
createCustomFiltersSelector('blocklist'),
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
(blocklist, isClearingBlocklistExecuting) => {
(blocklist, customFilters, isClearingBlocklistExecuting) => {
return {
isClearingBlocklistExecuting,
customFilters,
...blocklist
};
}
@ -97,6 +100,14 @@ class BlocklistConnector extends Component {
this.props.setBlocklistSort({ sortKey });
};
onFilterSelect = (selectedFilterKey) => {
this.props.setBlocklistFilter({ selectedFilterKey });
};
onClearBlocklistPress = () => {
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
};
onTableOptionChange = (payload) => {
this.props.setBlocklistTableOption(payload);
@ -105,10 +116,6 @@ class BlocklistConnector extends Component {
}
};
onClearBlocklistPress = () => {
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
};
//
// Render
@ -122,6 +129,7 @@ class BlocklistConnector extends Component {
onPageSelect={this.onPageSelect}
onRemoveSelected={this.onRemoveSelected}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
onTableOptionChange={this.onTableOptionChange}
onClearBlocklistPress={this.onClearBlocklistPress}
{...this.props}
@ -142,6 +150,7 @@ BlocklistConnector.propTypes = {
gotoBlocklistPage: PropTypes.func.isRequired,
removeBlocklistItems: PropTypes.func.isRequired,
setBlocklistSort: PropTypes.func.isRequired,
setBlocklistFilter: PropTypes.func.isRequired,
setBlocklistTableOption: PropTypes.func.isRequired,
clearBlocklist: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired

View File

@ -0,0 +1,54 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setBlocklistFilter } from 'Store/Actions/blocklistActions';
function createBlocklistSelector() {
return createSelector(
(state: AppState) => state.blocklist.items,
(blocklistItems) => {
return blocklistItems;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.blocklist.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface BlocklistFilterModalProps {
isOpen: boolean;
}
export default function BlocklistFilterModal(props: BlocklistFilterModalProps) {
const sectionItems = useSelector(createBlocklistSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'blocklist';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setBlocklistFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@ -12,11 +12,10 @@ function App({ store, history }) {
<DocumentTitle title={window.Radarr.instanceName}>
<Provider store={store}>
<ConnectedRouter history={history}>
<ApplyTheme>
<PageConnector>
<AppRoutes app={App} />
</PageConnector>
</ApplyTheme>
<ApplyTheme />
<PageConnector>
<AppRoutes app={App} />
</PageConnector>
</ConnectedRouter>
</Provider>
</DocumentTitle>

View File

@ -33,6 +33,8 @@ import Status from 'System/Status/Status';
import Tasks from 'System/Tasks/Tasks';
import UpdatesConnector from 'System/Updates/UpdatesConnector';
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
import MissingConnector from 'Wanted/Missing/MissingConnector';
function AppRoutes(props) {
const {
@ -121,6 +123,20 @@ function AppRoutes(props) {
component={BlocklistConnector}
/>
{/*
Wanted
*/}
<Route
path="/wanted/missing"
component={MissingConnector}
/>
<Route
path="/wanted/cutoffunmet"
component={CutoffUnmetConnector}
/>
{/*
Settings
*/}

View File

@ -1,49 +0,0 @@
import PropTypes from 'prop-types';
import React, { Fragment, useCallback, useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import themes from 'Styles/Themes';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.ui.item.theme || window.Radarr.theme,
(
theme
) => {
return {
theme
};
}
);
}
function ApplyTheme({ theme, children }) {
// Update the CSS Variables
const updateCSSVariables = useCallback(() => {
const arrayOfVariableKeys = Object.keys(themes[theme]);
const arrayOfVariableValues = Object.values(themes[theme]);
// Loop through each array key and set the CSS Variables
arrayOfVariableKeys.forEach((cssVariableKey, index) => {
// Based on our snippet from MDN
document.documentElement.style.setProperty(
`--${cssVariableKey}`,
arrayOfVariableValues[index]
);
});
}, [theme]);
// On Component Mount and Component Update
useEffect(() => {
updateCSSVariables(theme);
}, [updateCSSVariables, theme]);
return <Fragment>{children}</Fragment>;
}
ApplyTheme.propTypes = {
theme: PropTypes.string.isRequired,
children: PropTypes.object.isRequired
};
export default connect(createMapStateToProps)(ApplyTheme);

View File

@ -0,0 +1,37 @@
import React, { Fragment, ReactNode, useCallback, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import themes from 'Styles/Themes';
import AppState from './State/AppState';
interface ApplyThemeProps {
children: ReactNode;
}
function createThemeSelector() {
return createSelector(
(state: AppState) => state.settings.ui.item.theme || window.Radarr.theme,
(theme) => {
return theme;
}
);
}
function ApplyTheme({ children }: ApplyThemeProps) {
const theme = useSelector(createThemeSelector());
const updateCSSVariables = useCallback(() => {
Object.entries(themes[theme]).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--${key}`, value);
});
}, [theme]);
// On Component Mount and Component Update
useEffect(() => {
updateCSSVariables();
}, [updateCSSVariables, theme]);
return <Fragment>{children}</Fragment>;
}
export default ApplyTheme;

View File

@ -1,4 +1,5 @@
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
import BlocklistAppState from './BlocklistAppState';
import CalendarAppState from './CalendarAppState';
import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState';
@ -54,6 +55,7 @@ export interface AppSectionState {
interface AppState {
app: AppSectionState;
blocklist: BlocklistAppState;
calendar: CalendarAppState;
commands: CommandAppState;
history: HistoryAppState;

View File

@ -0,0 +1,8 @@
import Blocklist from 'typings/Blocklist';
import AppSectionState, { AppSectionFilterState } from './AppSectionState';
interface BlocklistAppState
extends AppSectionState<Blocklist>,
AppSectionFilterState<Blocklist> {}
export default BlocklistAppState;

View File

@ -20,7 +20,7 @@ const monitoredOptions = [
get value() {
return translate('NoChange');
},
disabled: true
isDisabled: true
},
{
key: 'monitored',
@ -42,7 +42,7 @@ const searchOnAddOptions = [
get value() {
return translate('NoChange');
},
disabled: true
isDisabled: true
},
{
key: 'yes',

View File

@ -115,3 +115,16 @@ $hoverScale: 1.05;
color: var(--iconButtonHoverLightColor);
}
}
.excluded {
position: absolute;
top: 0;
right: 0;
z-index: 1;
width: 0;
height: 0;
border-width: 0 25px 25px 0;
border-style: solid;
border-color: transparent var(--dangerColor) transparent transparent;
color: var(--white);
}

View File

@ -6,6 +6,7 @@ interface CssExports {
'content': string;
'controls': string;
'editorSelect': string;
'excluded': string;
'externalLinks': string;
'link': string;
'monitorToggleButton': string;

View File

@ -5,6 +5,7 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
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';
@ -72,6 +73,7 @@ class CollectionMovie extends Component {
isAvailable,
movieFile,
isExistingMovie,
isExcluded,
posterWidth,
posterHeight,
detailedProgressBar,
@ -107,6 +109,15 @@ class CollectionMovie extends Component {
</div>
}
{
isExcluded ?
<div
className={styles.excluded}
title={translate('Excluded')}
/> :
null
}
<Link
className={styles.link}
style={elementStyle}
@ -189,6 +200,7 @@ CollectionMovie.propTypes = {
posterHeight: PropTypes.number.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
isExistingMovie: PropTypes.bool,
isExcluded: PropTypes.bool,
tmdbId: PropTypes.number.isRequired,
imdbId: PropTypes.string,
youTubeTrailerId: PropTypes.string,

View File

@ -13,6 +13,7 @@ export interface CommandBody {
trigger: string;
suppressMessages: boolean;
movieId?: number;
movieIds?: number[];
}
interface Command extends ModelBase {

View File

@ -1,3 +1,4 @@
import { maxBy } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormInputGroup from 'Components/Form/FormInputGroup';
@ -50,7 +51,7 @@ class FilterBuilderModalContent extends Component {
if (id) {
dispatchSetFilter({ selectedFilterKey: id });
} else {
const last = customFilters[customFilters.length -1];
const last = maxBy(customFilters, 'id');
dispatchSetFilter({ selectedFilterKey: last.id });
}
@ -108,7 +109,7 @@ class FilterBuilderModalContent extends Component {
this.setState({
labelErrors: [
{
message: 'Label is required'
message: translate('LabelIsRequired')
}
]
});
@ -146,13 +147,13 @@ class FilterBuilderModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Custom Filter
{translate('CustomFilter')}
</ModalHeader>
<ModalBody>
<div className={styles.labelContainer}>
<div className={styles.label}>
Label
{translate('Label')}
</div>
<div className={styles.labelInputContainer}>
@ -166,9 +167,7 @@ class FilterBuilderModalContent extends Component {
</div>
</div>
<div className={styles.label}>
{translate('Filters')}
</div>
<div className={styles.label}>{translate('Filters')}</div>
<div className={styles.rows}>
{

View File

@ -37,8 +37,8 @@ class CustomFilter extends Component {
dispatchSetFilter
} = this.props;
// Assume that delete and then unmounting means the delete was successful.
// Moving this check to a ancestor would be more accurate, but would have
// Assume that delete and then unmounting means the deletion was successful.
// Moving this check to an ancestor would be more accurate, but would have
// more boilerplate.
if (this.state.isDeleting && id === selectedFilterKey) {
dispatchSetFilter({ selectedFilterKey: 'all' });

View File

@ -36,7 +36,7 @@ function AvailabilitySelectInput(props) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: true
isDisabled: true
});
}
@ -44,7 +44,7 @@ function AvailabilitySelectInput(props) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}

View File

@ -19,7 +19,7 @@
.isDisabled {
opacity: 0.7;
cursor: not-allowed;
cursor: not-allowed !important;
}
.dropdownArrowContainer {

View File

@ -17,6 +17,7 @@ import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
import MovieTagInput from './MovieTagInput';
import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector';
import PasswordInput from './PasswordInput';
@ -89,6 +90,10 @@ function getComponent(type) {
case inputTypes.DYNAMIC_SELECT:
return EnhancedSelectInputConnector;
case inputTypes.MOVIE_TAG:
return MovieTagInput;
case inputTypes.TAG:
return TagInputConnector;
@ -282,6 +287,7 @@ FormInputGroup.propTypes = {
includeNoChange: PropTypes.bool,
includeNoChangeDisabled: PropTypes.bool,
selectedValueOptions: PropTypes.object,
indexerFlags: PropTypes.number,
pending: PropTypes.bool,
errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object),

View File

@ -4,22 +4,18 @@ import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import EnhancedSelectInput from './EnhancedSelectInput';
interface IndexerFlagsSelectInputProps {
name: string;
indexerFlags: number;
onChange(payload: object): void;
}
const selectIndexerFlagsValues = (selectedFlags: number) =>
createSelector(
(state: AppState) => state.settings.indexerFlags,
(indexerFlags) => {
const value = indexerFlags.items
.filter(
// eslint-disable-next-line no-bitwise
(item) => (selectedFlags & item.id) === item.id
)
.map(({ id }) => id);
const value = indexerFlags.items.reduce((acc: number[], { id }) => {
// eslint-disable-next-line no-bitwise
if ((selectedFlags & id) === id) {
acc.push(id);
}
return acc;
}, []);
const values = indexerFlags.items.map(({ id, name }) => ({
key: id,
@ -33,6 +29,12 @@ const selectIndexerFlagsValues = (selectedFlags: number) =>
}
);
interface IndexerFlagsSelectInputProps {
name: string;
indexerFlags: number;
onChange(payload: object): void;
}
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
const { indexerFlags, onChange } = props;

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import monitorOptions from 'Utilities/Movie/monitorOptions';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
import EnhancedSelectInput from './EnhancedSelectInput';
function MovieMonitoredSelectInput(props) {
const values = [...monitorOptions];
@ -16,7 +16,7 @@ function MovieMonitoredSelectInput(props) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: true
isDisabled: true
});
}
@ -24,12 +24,12 @@ function MovieMonitoredSelectInput(props) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}
return (
<SelectInput
<EnhancedSelectInput
{...props}
values={values}
/>

View File

@ -0,0 +1,53 @@
import React, { useCallback } from 'react';
import TagInputConnector from './TagInputConnector';
interface MovieTagInputProps {
name: string;
value: number | number[];
onChange: ({
name,
value,
}: {
name: string;
value: number | number[];
}) => void;
}
export default function MovieTagInput(props: MovieTagInputProps) {
const { value, onChange, ...otherProps } = props;
const isArray = Array.isArray(value);
const handleChange = useCallback(
({ name, value: newValue }: { name: string; value: number[] }) => {
if (isArray) {
onChange({ name, value: newValue });
} else {
onChange({
name,
value: newValue.length ? newValue[newValue.length - 1] : 0,
});
}
},
[isArray, onChange]
);
let finalValue: number[] = [];
if (isArray) {
finalValue = value;
} else if (value === 0) {
finalValue = [];
} else {
finalValue = [value];
}
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore 2786 'TagInputConnector' isn't typed yet
<TagInputConnector
{...otherProps}
value={finalValue}
onChange={handleChange}
/>
);
}

View File

@ -1,5 +0,0 @@
.input {
composes: input from '~Components/Form/TextInput.css';
font-family: $passwordFamily;
}

View File

@ -1,7 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
import TextInput from './TextInput';
import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input
function onCopy(e) {
@ -13,17 +11,14 @@ function PasswordInput(props) {
return (
<TextInput
{...props}
type="password"
onCopy={onCopy}
/>
);
}
PasswordInput.propTypes = {
className: PropTypes.string.isRequired
};
PasswordInput.defaultProps = {
className: styles.input
...TextInput.props
};
export default PasswordInput;

View File

@ -27,6 +27,8 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.DYNAMIC_SELECT;
}
return inputTypes.SELECT;
case 'movieTag':
return inputTypes.MOVIE_TAG;
case 'tag':
return inputTypes.TEXT_TAG;
case 'tagSelect':

View File

@ -26,7 +26,7 @@ function createMapStateToProps() {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: includeNoChangeDisabled
isDisabled: includeNoChangeDisabled
});
}
@ -34,7 +34,7 @@ function createMapStateToProps() {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}

View File

@ -91,6 +91,7 @@ class TextTagInputConnector extends Component {
render() {
return (
<TagInput
delimiters={['Tab', 'Enter', ',']}
tagList={[]}
onTagAdd={this.onTagAdd}
onTagDelete={this.onTagDelete}

View File

@ -45,6 +45,7 @@ const selectAppProps = createSelector(
);
const selectIsPopulated = createSelector(
(state) => state.movies.isPopulated,
(state) => state.customFilters.isPopulated,
(state) => state.tags.isPopulated,
(state) => state.settings.ui.isPopulated,
@ -56,6 +57,7 @@ const selectIsPopulated = createSelector(
(state) => state.movieCollections.isPopulated,
(state) => state.app.translations.isPopulated,
(
moviesIsPopulated,
customFiltersIsPopulated,
tagsIsPopulated,
uiSettingsIsPopulated,
@ -68,6 +70,7 @@ const selectIsPopulated = createSelector(
translationsIsPopulated
) => {
return (
moviesIsPopulated &&
customFiltersIsPopulated &&
tagsIsPopulated &&
uiSettingsIsPopulated &&
@ -83,6 +86,7 @@ const selectIsPopulated = createSelector(
);
const selectErrors = createSelector(
(state) => state.movies.error,
(state) => state.customFilters.error,
(state) => state.tags.error,
(state) => state.settings.ui.error,
@ -94,6 +98,7 @@ const selectErrors = createSelector(
(state) => state.movieCollections.error,
(state) => state.app.translations.error,
(
moviesError,
customFiltersError,
tagsError,
uiSettingsError,
@ -106,6 +111,7 @@ const selectErrors = createSelector(
translationsError
) => {
const hasError = !!(
moviesError ||
customFiltersError ||
tagsError ||
uiSettingsError ||

View File

@ -71,6 +71,22 @@ const links = [
]
},
{
iconName: icons.WARNING,
title: () => translate('Wanted'),
to: '/wanted/missing',
children: [
{
title: () => translate('Missing'),
to: '/wanted/missing'
},
{
title: () => translate('CutoffUnmet'),
to: '/wanted/cutoffunmet'
}
]
},
{
iconName: icons.SETTINGS,
title: () => translate('Settings'),

View File

@ -244,6 +244,26 @@ class SignalRConnector extends Component {
this.props.dispatchSetVersion({ version });
};
handleWantedCutoff = (body) => {
if (body.action === 'updated') {
this.props.dispatchUpdateItem({
section: 'wanted.cutoffUnmet',
updateOnly: true,
...body.resource
});
}
};
handleWantedMissing = (body) => {
if (body.action === 'updated') {
this.props.dispatchUpdateItem({
section: 'wanted.missing',
updateOnly: true,
...body.resource
});
}
};
handleSystemTask = () => {
this.props.dispatchFetchCommands();
};

View File

@ -25,14 +25,3 @@
font-family: 'Ubuntu Mono';
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
}
/*
* text-security-disc
*/
@font-face {
font-weight: normal;
font-style: normal;
font-family: 'text-security-disc';
src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype');
}

View File

@ -15,5 +15,5 @@
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "minimal-ui"
"display": "standalone"
}

View File

@ -75,9 +75,19 @@ class DiscoverMovie extends Component {
const {
items,
sortKey,
sortDirection
sortDirection,
includeRecommendations,
includeTrending,
includePopular
} = this.props;
if (includeRecommendations !== prevProps.includeRecommendations ||
includeTrending !== prevProps.includeTrending ||
includePopular !== prevProps.includePopular
) {
this.props.dispatchFetchListMovies();
}
if (sortKey !== prevProps.sortKey ||
sortDirection !== prevProps.sortDirection ||
hasDifferentItemsOrOrder(prevProps.items, items)
@ -443,6 +453,9 @@ DiscoverMovie.propTypes = {
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
view: PropTypes.string.isRequired,
includeRecommendations: PropTypes.bool.isRequired,
includeTrending: PropTypes.bool.isRequired,
includePopular: PropTypes.bool.isRequired,
isSyncingLists: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired,
@ -451,7 +464,8 @@ DiscoverMovie.propTypes = {
onScroll: PropTypes.func.isRequired,
onAddMoviesPress: PropTypes.func.isRequired,
onExcludeMoviesPress: PropTypes.func.isRequired,
onImportListSyncPress: PropTypes.func.isRequired
onImportListSyncPress: PropTypes.func.isRequired,
dispatchFetchListMovies: PropTypes.func.isRequired
};
export default DiscoverMovie;

View File

@ -17,15 +17,18 @@ import DiscoverMovie from './DiscoverMovie';
function createMapStateToProps() {
return createSelector(
(state) => state.discoverMovie,
createDiscoverMovieClientSideCollectionItemsSelector('discoverMovie'),
createCommandExecutingSelector(commandNames.IMPORT_LIST_SYNC),
createDimensionsSelector(),
(
discoverMovie,
movies,
isSyncingLists,
dimensionsState
) => {
return {
...discoverMovie.options,
...movies,
isSyncingLists,
isSmallScreen: dimensionsState.isSmallScreen

View File

@ -49,7 +49,9 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
showRatings: props.showRatings,
showYear: props.showYear,
showGenres: props.showGenres,
includeRecommendations: props.includeRecommendations
includeRecommendations: props.includeRecommendations,
includeTrending: props.includeTrending,
includePopular: props.includePopular
};
}
@ -61,7 +63,9 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
showRatings,
showCertification,
showGenres,
includeRecommendations
includeRecommendations,
includeTrending,
includePopular
} = this.props;
const state = {};
@ -94,6 +98,14 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
state.includeRecommendations = includeRecommendations;
}
if (includeTrending !== prevProps.includeTrending) {
state.includeTrending = includeTrending;
}
if (includePopular !== prevProps.includePopular) {
state.includePopular = includePopular;
}
if (!_.isEmpty(state)) {
this.setState(state);
}
@ -135,19 +147,22 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
showRatings,
showYear,
showGenres,
includeRecommendations
includeRecommendations,
includeTrending,
includePopular
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Overview Options
{translate('OverviewOptions')}
</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>{translate('IncludeRadarrRecommendations')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeRecommendations"
@ -157,6 +172,30 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IncludeTrending')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeTrending"
value={includeTrending}
helpText={translate('IncludeTrendingMoviesHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IncludePopular')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includePopular"
value={includePopular}
helpText={translate('IncludePopularMoviesHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PosterSize')}</FormLabel>
@ -246,6 +285,8 @@ DiscoverMovieOverviewOptionsModalContent.propTypes = {
showCertification: PropTypes.bool.isRequired,
showGenres: PropTypes.bool.isRequired,
includeRecommendations: PropTypes.bool.isRequired,
includeTrending: PropTypes.bool.isRequired,
includePopular: PropTypes.bool.isRequired,
onChangeOverviewOption: PropTypes.func.isRequired,
onChangeOption: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@ -3,7 +3,7 @@ import React from 'react';
import Icon from 'Components/Icon';
import TmdbRating from 'Components/TmdbRating';
import { icons } from 'Helpers/Props';
import { getMovieStatusDetails } from 'Movie/MovieStatus';
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
import formatRuntime from 'Utilities/Date/formatRuntime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import translate from 'Utilities/String/translate';

View File

@ -45,7 +45,9 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
this.state = {
size: props.size,
showTitle: props.showTitle,
includeRecommendations: props.includeRecommendations
includeRecommendations: props.includeRecommendations,
includeTrending: props.includeTrending,
includePopular: props.includePopular
};
}
@ -53,7 +55,9 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
const {
size,
showTitle,
includeRecommendations
includeRecommendations,
includeTrending,
includePopular
} = this.props;
const state = {};
@ -70,6 +74,14 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
state.includeRecommendations = includeRecommendations;
}
if (includeTrending !== prevProps.includeTrending) {
state.includeTrending = includeTrending;
}
if (includePopular !== prevProps.includePopular) {
state.includePopular = includePopular;
}
if (!_.isEmpty(state)) {
this.setState(state);
}
@ -107,13 +119,15 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
const {
size,
showTitle,
includeRecommendations
includeRecommendations,
includeTrending,
includePopular
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Poster Options
{translate('PosterOptions')}
</ModalHeader>
<ModalBody>
@ -130,6 +144,30 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IncludeTrending')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeTrending"
value={includeTrending}
helpText={translate('IncludeTrendingMoviesHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IncludePopular')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includePopular"
value={includePopular}
helpText={translate('IncludePopularMoviesHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PosterSize')}</FormLabel>
@ -172,6 +210,8 @@ DiscoverMoviePosterOptionsModalContent.propTypes = {
size: PropTypes.string.isRequired,
showTitle: PropTypes.bool.isRequired,
includeRecommendations: PropTypes.bool.isRequired,
includeTrending: PropTypes.bool.isRequired,
includePopular: PropTypes.bool.isRequired,
onChangePosterOption: PropTypes.func.isRequired,
onChangeOption: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@ -15,17 +15,29 @@ class DiscoverMovieTableOptions extends Component {
super(props, context);
this.state = {
includeRecommendations: props.includeRecommendations
includeRecommendations: props.includeRecommendations,
includeTrending: props.includeTrending,
includePopular: props.includePopular
};
}
componentDidUpdate(prevProps) {
const { includeRecommendations } = this.props;
const {
includeRecommendations,
includeTrending,
includePopular
} = this.props;
if (includeRecommendations !== prevProps.includeRecommendations) {
this.setState({
includeRecommendations
});
this.setState({ includeRecommendations });
}
if (includeTrending !== prevProps.includeTrending) {
this.setState({ includeTrending });
}
if (includePopular !== prevProps.includePopular) {
this.setState({ includePopular });
}
}
@ -47,27 +59,57 @@ class DiscoverMovieTableOptions extends Component {
render() {
const {
includeRecommendations
includeRecommendations,
includeTrending,
includePopular
} = this.state;
return (
<FormGroup>
<FormLabel>{translate('IncludeRadarrRecommendations')}</FormLabel>
<>
<FormGroup>
<FormLabel>{translate('IncludeRadarrRecommendations')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeRecommendations"
value={includeRecommendations}
helpText={translate('IncludeRecommendationsHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.CHECK}
name="includeRecommendations"
value={includeRecommendations}
helpText={translate('IncludeRecommendationsHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IncludeTrending')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeTrending"
value={includeTrending}
helpText={translate('IncludeTrendingMoviesHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('IncludePopular')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includePopular"
value={includePopular}
helpText={translate('IncludePopularMoviesHelpText')}
onChange={this.onChangeOption}
/>
</FormGroup>
</>
);
}
}
DiscoverMovieTableOptions.propTypes = {
includeRecommendations: PropTypes.bool.isRequired,
includeTrending: PropTypes.bool.isRequired,
includePopular: PropTypes.bool.isRequired,
onChangeOption: PropTypes.func.isRequired
};

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
import { getMovieStatusDetails } from 'Movie/MovieStatus';
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
import styles from './ListMovieStatusCell.css';
function ListMovieStatusCell(props) {

View File

@ -0,0 +1,17 @@
import { useCallback, useState } from 'react';
export default function useModalOpenState(
initialState: boolean
): [boolean, () => void, () => void] {
const [isOpen, setOpen] = useState(initialState);
const setModalOpen = useCallback(() => {
setOpen(true);
}, [setOpen]);
const setModalClosed = useCallback(() => {
setOpen(false);
}, [setOpen]);
return [isOpen, setModalOpen, setModalClosed];
}

View File

@ -17,6 +17,7 @@ export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const SELECT = 'select';
export const MOVIE_TAG = 'movieTag';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
export const TEXT = 'text';
@ -45,6 +46,7 @@ export const all = [
INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT,
SELECT,
MOVIE_TAG,
DYNAMIC_SELECT,
TAG,
TEXT,

View File

@ -0,0 +1,34 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent';
interface SelectIndexerFlagsModalProps {
isOpen: boolean;
indexerFlags: number;
modalTitle: string;
onIndexerFlagsSelect(indexerFlags: number): void;
onModalClose(): void;
}
function SelectIndexerFlagsModal(props: SelectIndexerFlagsModalProps) {
const {
isOpen,
indexerFlags,
modalTitle,
onIndexerFlagsSelect,
onModalClose,
} = props;
return (
<Modal isOpen={isOpen} onModalClose={onModalClose}>
<SelectIndexerFlagsModalContent
indexerFlags={indexerFlags}
modalTitle={modalTitle}
onIndexerFlagsSelect={onIndexerFlagsSelect}
onModalClose={onModalClose}
/>
</Modal>
);
}
export default SelectIndexerFlagsModal;

View File

@ -0,0 +1,7 @@
.modalBody {
composes: modalBody from '~Components/Modal/ModalBody.css';
display: flex;
flex: 1 1 auto;
flex-direction: column;
}

View File

@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'modalBody': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -0,0 +1,75 @@
import React, { useCallback, useState } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerFlagsModalContent.css';
interface SelectIndexerFlagsModalContentProps {
indexerFlags: number;
modalTitle: string;
onIndexerFlagsSelect(indexerFlags: number): void;
onModalClose(): void;
}
function SelectIndexerFlagsModalContent(
props: SelectIndexerFlagsModalContentProps
) {
const { modalTitle, onIndexerFlagsSelect, onModalClose } = props;
const [indexerFlags, setIndexerFlags] = useState(props.indexerFlags);
const onIndexerFlagsChange = useCallback(
({ value }: { value: number }) => {
setIndexerFlags(value);
},
[setIndexerFlags]
);
const onIndexerFlagsSelectWrapper = useCallback(() => {
onIndexerFlagsSelect(indexerFlags);
}, [indexerFlags, onIndexerFlagsSelect]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('SetIndexerFlagsModalTitle', { modalTitle })}
</ModalHeader>
<ModalBody
className={styles.modalBody}
scrollDirection={scrollDirections.NONE}
>
<Form>
<FormGroup>
<FormLabel>{translate('IndexerFlags')}</FormLabel>
<FormInputGroup
type={inputTypes.INDEXER_FLAGS_SELECT}
name="indexerFlags"
indexerFlags={indexerFlags}
autoFocus={true}
onChange={onIndexerFlagsChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.SUCCESS} onPress={onIndexerFlagsSelectWrapper}>
{translate('SetIndexerFlags')}
</Button>
</ModalFooter>
</ModalContent>
);
}
export default SelectIndexerFlagsModalContent;

View File

@ -26,6 +26,7 @@ import usePrevious from 'Helpers/Hooks/usePrevious';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import ImportMode from 'InteractiveImport/ImportMode';
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
import InteractiveImport, {
InteractiveImportCommandOptions,
} from 'InteractiveImport/InteractiveImport';
@ -59,7 +60,13 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import InteractiveImportRow from './InteractiveImportRow';
import styles from './InteractiveImportModalContent.css';
type SelectType = 'select' | 'movie' | 'releaseGroup' | 'quality' | 'language';
type SelectType =
| 'select'
| 'movie'
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
type FilterExistingFiles = 'all' | 'new';
@ -113,6 +120,15 @@ const COLUMNS = [
isSortable: true,
isVisible: true,
},
{
name: 'indexerFlags',
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags'),
}),
isSortable: true,
isVisible: true,
},
{
name: 'rejections',
label: React.createElement(Icon, {
@ -257,8 +273,18 @@ function InteractiveImportModalContent(
}
}
const showIndexerFlags = items.some((item) => item.indexerFlags);
if (!showIndexerFlags) {
const indexerFlagsColumn = result.find((c) => c.name === 'indexerFlags');
if (indexerFlagsColumn) {
indexerFlagsColumn.isVisible = false;
}
}
return result;
}, [showMovie]);
}, [showMovie, items]);
const selectedIds: number[] = useMemo(() => {
return getSelectedIds(selectedState);
@ -283,6 +309,10 @@ function InteractiveImportModalContent(
key: 'language',
value: translate('SelectLanguage'),
},
{
key: 'indexerFlags',
value: translate('SelectIndexerFlags'),
},
];
if (allowMovieChange) {
@ -416,7 +446,14 @@ function InteractiveImportModalContent(
const isSelected = selectedIds.indexOf(item.id) > -1;
if (isSelected) {
const { movie, releaseGroup, quality, languages, movieFileId } = item;
const {
movie,
releaseGroup,
quality,
languages,
indexerFlags,
movieFileId,
} = item;
if (!movie) {
setInteractiveImportErrorMessage(
@ -450,6 +487,7 @@ function InteractiveImportModalContent(
releaseGroup,
quality,
languages,
indexerFlags,
});
return;
@ -463,6 +501,7 @@ function InteractiveImportModalContent(
releaseGroup,
quality,
languages,
indexerFlags,
downloadId,
movieFileId,
});
@ -620,6 +659,22 @@ function InteractiveImportModalContent(
[selectedIds, dispatch]
);
const onIndexerFlagsSelect = useCallback(
(indexerFlags: number) => {
dispatch(
updateInteractiveImportItems({
ids: selectedIds,
indexerFlags,
})
);
dispatch(reprocessInteractiveImportItems({ ids: selectedIds }));
setSelectModalOpen(null);
},
[selectedIds, dispatch]
);
const errorMessage = getErrorMessage(
error,
translate('InteractiveImportLoadError')
@ -794,6 +849,14 @@ function InteractiveImportModalContent(
onModalClose={onSelectModalClose}
/>
<SelectIndexerFlagsModal
isOpen={selectModalOpen === 'indexerFlags'}
indexerFlags={0}
modalTitle={modalTitle}
onIndexerFlagsSelect={onIndexerFlagsSelect}
onModalClose={onSelectModalClose}
/>
<ConfirmModal
isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER}

View File

@ -8,11 +8,13 @@ import Column from 'Components/Table/Column';
import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import Language from 'Language/Language';
import IndexerFlags from 'Movie/IndexerFlags';
import Movie from 'Movie/Movie';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
@ -30,7 +32,12 @@ import translate from 'Utilities/String/translate';
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
import styles from './InteractiveImportRow.css';
type SelectType = 'movie' | 'releaseGroup' | 'quality' | 'language';
type SelectType =
| 'movie'
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
type SelectedChangeProps = SelectStateInputProps & {
hasMovieFileId: boolean;
@ -47,6 +54,7 @@ interface InteractiveImportRowProps {
size: number;
customFormats?: object[];
customFormatScore?: number;
indexerFlags: number;
rejections: Rejection[];
columns: Column[];
movieFileId?: number;
@ -69,6 +77,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
size,
customFormats,
customFormatScore,
indexerFlags,
rejections,
isSelected,
modalTitle,
@ -84,6 +93,10 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
() => columns.find((c) => c.name === 'movie')?.isVisible ?? false,
[columns]
);
const isIndexerFlagsColumnVisible = useMemo(
() => columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false,
[columns]
);
const [selectModalOpen, setSelectModalOpen] = useState<SelectType | null>(
null
@ -91,7 +104,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
useEffect(
() => {
if (allowMovieChange && movie && quality && languages) {
if (allowMovieChange && movie && quality && languages && size > 0) {
onSelectedChange({
id,
hasMovieFileId: !!movieFileId,
@ -223,12 +236,34 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
);
const onSelectIndexerFlagsPress = useCallback(() => {
setSelectModalOpen('indexerFlags');
}, [setSelectModalOpen]);
const onIndexerFlagsSelect = useCallback(
(indexerFlags: number) => {
dispatch(
updateInteractiveImportItem({
id,
indexerFlags,
})
);
dispatch(reprocessInteractiveImportItems({ ids: [id] }));
setSelectModalOpen(null);
selectRowAfterChange();
},
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
);
const movieTitle = movie ? movie.title : '';
const showMoviePlaceholder = isSelected && !movie;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
const showQualityPlaceholder = isSelected && !quality;
const showLanguagePlaceholder = isSelected && !languages;
const showIndexerFlagsPlaceholder = isSelected && !indexerFlags;
return (
<TableRow>
@ -311,6 +346,28 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
) : null}
</TableRowCell>
{isIndexerFlagsColumnVisible ? (
<TableRowCellButton
title={translate('ClickToChangeIndexerFlags')}
onPress={onSelectIndexerFlagsPress}
>
{showIndexerFlagsPlaceholder ? (
<InteractiveImportRowCellPlaceholder isOptional={true} />
) : (
<>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}
/>
) : null}
</>
)}
</TableRowCellButton>
) : null}
<TableRowCell>
{rejections.length ? (
<Popover
@ -361,6 +418,14 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
onLanguagesSelect={onLanguagesSelect}
onModalClose={onSelectModalClose}
/>
<SelectIndexerFlagsModal
isOpen={selectModalOpen === 'indexerFlags'}
indexerFlags={indexerFlags ?? 0}
modalTitle={modalTitle}
onIndexerFlagsSelect={onIndexerFlagsSelect}
onModalClose={onSelectModalClose}
/>
</TableRow>
);
}

View File

@ -11,6 +11,7 @@ export interface InteractiveImportCommandOptions {
releaseGroup?: string;
quality: QualityModel;
languages: Language[];
indexerFlags: number;
downloadId?: string;
movieFileId?: number;
}
@ -27,6 +28,7 @@ interface InteractiveImport extends ModelBase {
movie?: Movie;
qualityWeight: number;
customFormats: object[];
indexerFlags: number;
rejections: Rejection[];
movieFileId?: number;
}

View File

@ -39,8 +39,7 @@
}
.rejected,
.indexerFlags,
.download {
.indexerFlags {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 50px;

View File

@ -90,8 +90,8 @@ interface InteractiveSearchRowProps {
customFormats: CustomFormat[];
customFormatScore: number;
mappedMovieId?: number;
rejections: string[];
indexerFlags: string[];
rejections: string[];
downloadAllowed: boolean;
isGrabbing: boolean;
isGrabbed: boolean;
@ -125,8 +125,8 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
customFormatScore,
customFormats,
mappedMovieId,
rejections = [],
indexerFlags = [],
rejections = [],
downloadAllowed,
isGrabbing = false,
isGrabbed = false,
@ -276,7 +276,7 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
customFormats.length
)}
tooltip={<MovieFormats formats={customFormats} />}
position={tooltipPositions.TOP}
position={tooltipPositions.LEFT}
/>
</TableRowCell>

View File

@ -1,9 +1,10 @@
.links {
display: flex;
flex-wrap: wrap;
margin: 0;
}
.link {
display: inline-block;
white-space: nowrap;
}

View File

@ -8,18 +8,29 @@ import translate from 'Utilities/String/translate';
import styles from './MovieReleaseDates.css';
interface MovieReleaseDatesProps {
inCinemas: string;
physicalRelease: string;
digitalRelease: string;
inCinemas?: string;
digitalRelease?: string;
physicalRelease?: string;
}
function MovieReleaseDates(props: MovieReleaseDatesProps) {
const { inCinemas, physicalRelease, digitalRelease } = props;
const { inCinemas, digitalRelease, physicalRelease } = props;
const { showRelativeDates, shortDateFormat, timeFormat } = useSelector(
createUISettingsSelector()
);
if (!inCinemas && !physicalRelease && !digitalRelease) {
return (
<div>
<div className={styles.dateIcon}>
<Icon name={icons.MISSING} />
</div>
{translate('NoMovieReleaseDatesAvailable')}
</div>
);
}
return (
<div>
{inCinemas ? (

View File

@ -15,7 +15,7 @@ function MovieHistoryModal(props) {
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
size={sizes.EXTRA_LARGE}
size={sizes.EXTRA_EXTRA_LARGE}
>
<MovieHistoryModalContentConnector
{...otherProps}

View File

@ -4,13 +4,13 @@ import HistoryDetailsModal from 'Activity/History/Details/HistoryDetailsModal';
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import styles from './MovieHistoryRow.css';
@ -109,9 +109,9 @@ class MovieHistoryRow extends Component {
{formatCustomFormatScore(customFormatScore, customFormats.length)}
</TableRowCell>
<RelativeDateCellConnector
date={date}
/>
<TableRowCell>
{formatDateTime(date, shortDateFormat, timeFormat, { includeSeconds: true })}
</TableRowCell>
<TableRowCell className={styles.actions}>
<IconButton

View File

@ -44,6 +44,7 @@ import MovieIndexViewMenu from './Menus/MovieIndexViewMenu';
import MovieIndexFooter from './MovieIndexFooter';
import MovieIndexRefreshMovieButton from './MovieIndexRefreshMovieButton';
import MovieIndexSearchButton from './MovieIndexSearchButton';
import MovieIndexSearchMenuItem from './MovieIndexSearchMenuItem';
import MovieIndexOverviews from './Overview/MovieIndexOverviews';
import MovieIndexOverviewOptionsModal from './Overview/Options/MovieIndexOverviewOptionsModal';
import MovieIndexPosters from './Posters/MovieIndexPosters';
@ -247,6 +248,7 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => {
<MovieIndexSearchButton
isSelectMode={isSelectMode}
selectedFilterKey={selectedFilterKey}
overflowComponent={MovieIndexSearchMenuItem}
/>
<PageToolbarButton

View File

@ -16,6 +16,7 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
interface MovieIndexSearchButtonProps {
isSelectMode: boolean;
selectedFilterKey: string;
overflowComponent: React.FunctionComponent<never>;
}
function MovieIndexSearchButton(props: MovieIndexSearchButtonProps) {

View File

@ -0,0 +1,72 @@
import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import MoviesAppState, { MovieIndexAppState } from 'App/State/MoviesAppState';
import { MOVIE_SEARCH } from 'Commands/commandNames';
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
import { icons } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
interface MovieIndexSearchMenuItemProps {
isSelectMode: boolean;
selectedFilterKey: string;
}
function MovieIndexSearchMenuItem(props: MovieIndexSearchMenuItemProps) {
const isSearching = useSelector(createCommandExecutingSelector(MOVIE_SEARCH));
const {
items,
}: MoviesAppState & MovieIndexAppState & ClientSideCollectionAppState =
useSelector(createMovieClientSideCollectionItemsSelector('movieIndex'));
const dispatch = useDispatch();
const { isSelectMode, selectedFilterKey } = props;
const [selectState] = useSelect();
const { selectedState } = selectState;
const selectedMovieIds = useMemo(() => {
return getSelectedIds(selectedState);
}, [selectedState]);
const moviesToSearch =
isSelectMode && selectedMovieIds.length > 0
? selectedMovieIds
: items.map((m) => m.id);
const searchIndexLabel =
selectedFilterKey === 'all'
? translate('SearchAll')
: translate('SearchFiltered');
const searchSelectLabel =
selectedMovieIds.length > 0
? translate('SearchSelected')
: translate('SearchAll');
const onPress = useCallback(() => {
dispatch(
executeCommand({
name: MOVIE_SEARCH,
movieIds: moviesToSearch,
})
);
}, [dispatch, moviesToSearch]);
return (
<PageToolbarOverflowMenuItem
label={isSelectMode ? searchSelectLabel : searchIndexLabel}
isSpinning={isSearching}
isDisabled={!items.length}
iconName={icons.SEARCH}
onPress={onPress}
/>
);
}
export default MovieIndexSearchMenuItem;

View File

@ -34,7 +34,7 @@ const monitoredOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'monitored',

View File

@ -4,7 +4,7 @@ import Icon from 'Components/Icon';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import VirtualTableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons } from 'Helpers/Props';
import { getMovieStatusDetails } from 'Movie/MovieStatus';
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
import { toggleMovieMonitored } from 'Store/Actions/movieActions';
import translate from 'Utilities/String/translate';
import styles from './MovieStatusCell.css';

View File

@ -0,0 +1,26 @@
import React from 'react';
import { useSelector } from 'react-redux';
import createIndexerFlagsSelector from 'Store/Selectors/createIndexerFlagsSelector';
interface IndexerFlagsProps {
indexerFlags: number;
}
function IndexerFlags({ indexerFlags = 0 }: IndexerFlagsProps) {
const allIndexerFlags = useSelector(createIndexerFlagsSelector);
const flags = allIndexerFlags.items.filter(
// eslint-disable-next-line no-bitwise
(item) => (indexerFlags & item.id) === item.id
);
return flags.length ? (
<ul>
{flags.map((flag, index) => {
return <li key={index}>{flag.name}</li>;
})}
</ul>
) : null;
}
export default IndexerFlags;

View File

@ -0,0 +1,6 @@
.movieSearchCell {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 70px;
white-space: nowrap;
}

View File

@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'movieSearchCell': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -0,0 +1,81 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import MovieInteractiveSearchModalConnector from './Search/MovieInteractiveSearchModalConnector';
import styles from './MovieSearchCell.css';
class MovieSearchCell extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isInteractiveSearchModalOpen: false
};
}
//
// Listeners
onManualSearchPress = () => {
this.setState({ isInteractiveSearchModalOpen: true });
};
onInteractiveSearchModalClose = () => {
this.setState({ isInteractiveSearchModalOpen: false });
};
//
// Render
render() {
const {
movieId,
movieTitle,
isSearching,
onSearchPress,
...otherProps
} = this.props;
return (
<TableRowCell className={styles.movieSearchCell}>
<SpinnerIconButton
name={icons.SEARCH}
isSpinning={isSearching}
onPress={onSearchPress}
title={translate('AutomaticSearch')}
/>
<IconButton
name={icons.INTERACTIVE}
onPress={this.onManualSearchPress}
title={translate('InteractiveSearch')}
/>
<MovieInteractiveSearchModalConnector
isOpen={this.state.isInteractiveSearchModalOpen}
movieId={movieId}
movieTitle={movieTitle}
onModalClose={this.onInteractiveSearchModalClose}
{...otherProps}
/>
</TableRowCell>
);
}
}
MovieSearchCell.propTypes = {
movieId: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired,
isSearching: PropTypes.bool.isRequired,
onSearchPress: PropTypes.func.isRequired
};
export default MovieSearchCell;

View File

@ -0,0 +1,48 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import MovieSearchCell from 'Movie/MovieSearchCell';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import { isCommandExecuting } from 'Utilities/Command';
function createMapStateToProps() {
return createSelector(
(state, { movieId }) => movieId,
createMovieSelector(),
createCommandsSelector(),
(movieId, movie, commands) => {
const isSearching = commands.some((command) => {
const movieSearch = command.name === commandNames.MOVIE_SEARCH;
if (!movieSearch) {
return false;
}
return (
isCommandExecuting(command) &&
command.body.movieIds.indexOf(movieId) > -1
);
});
return {
movieMonitored: movie.monitored,
isSearching
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onSearchPress(name, path) {
dispatch(executeCommand({
name: commandNames.MOVIE_SEARCH,
movieIds: [props.movieId]
}));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieSearchCell);

View File

@ -0,0 +1,4 @@
.center {
display: flex;
justify-content: center;
}

View File

@ -1,7 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'input': string;
'center': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -1,33 +1,115 @@
import { icons } from 'Helpers/Props';
import PropTypes from 'prop-types';
import React from 'react';
import QueueDetails from 'Activity/Queue/QueueDetails';
import Icon from 'Components/Icon';
import ProgressBar from 'Components/ProgressBar';
import { icons, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import MovieQuality from './MovieQuality';
import styles from './MovieStatus.css';
export function getMovieStatusDetails(status) {
function MovieStatus(props) {
const {
isAvailable,
monitored,
grabbed,
queueItem,
movieFile
} = props;
let statusDetails = {
icon: icons.ANNOUNCED,
title: translate('Announced'),
message: translate('AnnouncedMsg')
};
const hasMovieFile = !!movieFile;
const isQueued = !!queueItem;
if (status === 'deleted') {
statusDetails = {
icon: icons.MOVIE_DELETED,
title: translate('Deleted'),
message: translate('DeletedMsg')
};
} else if (status === 'inCinemas') {
statusDetails = {
icon: icons.IN_CINEMAS,
title: translate('InCinemas'),
message: translate('InCinemasMsg')
};
} else if (status === 'released') {
statusDetails = {
icon: icons.MOVIE_FILE,
title: translate('Released'),
message: translate('ReleasedMsg')
};
if (isQueued) {
const {
sizeleft,
size
} = queueItem;
const progress = size ? (100 - sizeleft / size * 100) : 0;
return (
<div className={styles.center}>
<QueueDetails
{...queueItem}
progressBar={
<ProgressBar
progress={progress}
kind={kinds.PURPLE}
size={sizes.MEDIUM}
/>
}
/>
</div>
);
}
return statusDetails;
if (grabbed) {
return (
<div className={styles.center}>
<Icon
name={icons.DOWNLOADING}
title={translate('MovieIsDownloading')}
/>
</div>
);
}
if (hasMovieFile) {
const quality = movieFile.quality;
const isCutoffNotMet = movieFile.qualityCutoffNotMet;
return (
<div className={styles.center}>
<MovieQuality
quality={quality}
size={movieFile.size}
isCutoffNotMet={isCutoffNotMet}
title={translate('MovieDownloaded')}
/>
</div>
);
}
if (!monitored) {
return (
<div className={styles.center}>
<Icon
name={icons.UNMONITORED}
kind={kinds.DISABLED}
title={translate('MovieIsNotMonitored')}
/>
</div>
);
}
if (isAvailable) {
return (
<div className={styles.center}>
<Icon
name={icons.MISSING}
title={translate('MovieMissingFromDisk')}
/>
</div>
);
}
return (
<div className={styles.center}>
<Icon
name={icons.NOT_AIRED}
title={translate('MovieIsNotAvailable')}
/>
</div>
);
}
MovieStatus.propTypes = {
isAvailable: PropTypes.bool.isRequired,
monitored: PropTypes.bool.isRequired,
grabbed: PropTypes.bool,
queueItem: PropTypes.object,
movieFile: PropTypes.object
};
export default MovieStatus;

View File

@ -0,0 +1,50 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import MovieStatus from 'Movie/MovieStatus';
import createMovieFileSelector from 'Store/Selectors/createMovieFileSelector';
import { createMovieByEntitySelector } from 'Store/Selectors/createMovieSelector';
import createQueueItemSelector from 'Store/Selectors/createQueueItemSelector';
function createMapStateToProps() {
return createSelector(
createMovieByEntitySelector(),
createQueueItemSelector(),
createMovieFileSelector(),
(movie, queueItem, movieFile) => {
const result = _.pick(movie, [
'isAvailable',
'monitored',
'grabbed'
]);
result.queueItem = queueItem;
result.movieFile = movieFile;
return result;
}
);
}
class MovieStatusConnector extends Component {
//
// Render
render() {
return (
<MovieStatus
{...this.props}
/>
);
}
}
MovieStatusConnector.propTypes = {
movieId: PropTypes.number.isRequired,
movieFileId: PropTypes.number.isRequired
};
export default connect(createMapStateToProps, null)(MovieStatusConnector);

View File

@ -8,6 +8,7 @@ function MovieInteractiveSearchModal(props) {
const {
isOpen,
movieId,
movieTitle,
onModalClose
} = props;
@ -20,6 +21,7 @@ function MovieInteractiveSearchModal(props) {
>
<MovieInteractiveSearchModalContent
movieId={movieId}
movieTitle={movieTitle}
onModalClose={onModalClose}
/>
</Modal>
@ -29,6 +31,7 @@ function MovieInteractiveSearchModal(props) {
MovieInteractiveSearchModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
movieId: PropTypes.number.isRequired,
movieTitle: PropTypes.string,
onModalClose: PropTypes.func.isRequired
};

View File

@ -12,13 +12,17 @@ import translate from 'Utilities/String/translate';
function MovieInteractiveSearchModalContent(props) {
const {
movieId,
movieTitle,
onModalClose
} = props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('InteractiveSearchModalHeader')}
{movieTitle === undefined ?
translate('InteractiveSearchModalHeader') :
translate('InteractiveSearchModalHeaderTitle', { title: movieTitle })
}
</ModalHeader>
<ModalBody scrollDirection={scrollDirections.BOTH}>
@ -38,6 +42,7 @@ function MovieInteractiveSearchModalContent(props) {
MovieInteractiveSearchModalContent.propTypes = {
movieId: PropTypes.number.isRequired,
movieTitle: PropTypes.string,
onModalClose: PropTypes.func.isRequired
};

View File

@ -0,0 +1,32 @@
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
export default function getMovieStatusDetails(status) {
let statusDetails = {
icon: icons.ANNOUNCED,
title: translate('Announced'),
message: translate('AnnouncedMsg')
};
if (status === 'deleted') {
statusDetails = {
icon: icons.MOVIE_DELETED,
title: translate('Deleted'),
message: translate('DeletedMsg')
};
} else if (status === 'inCinemas') {
statusDetails = {
icon: icons.IN_CINEMAS,
title: translate('InCinemas'),
message: translate('InCinemasMsg')
};
} else if (status === 'released') {
statusDetails = {
icon: icons.MOVIE_FILE,
title: translate('Released'),
message: translate('ReleasedMsg')
};
}
return statusDetails;
}

View File

@ -1,9 +1,13 @@
export const CALENDAR = 'calendar';
export const MOVIES = 'movies';
export const INTERACTIVE_IMPORT = 'interactiveImport.movies';
export const WANTED_CUTOFF_UNMET = 'wanted.cutoffUnmet';
export const WANTED_MISSING = 'wanted.missing';
export default {
CALENDAR,
MOVIES,
INTERACTIVE_IMPORT
INTERACTIVE_IMPORT,
WANTED_CUTOFF_UNMET,
WANTED_MISSING
};

View File

@ -57,3 +57,9 @@
width: 55px;
}
.indexerFlags {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 50px;
}

View File

@ -9,6 +9,7 @@ interface CssExports {
'dateAdded': string;
'download': string;
'formats': string;
'indexerFlags': string;
'language': string;
'languages': string;
'quality': string;

View File

@ -1,12 +1,15 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import IndexerFlags from 'Movie/IndexerFlags';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
@ -82,6 +85,7 @@ class MovieFileEditorRow extends Component {
qualityCutoffNotMet,
customFormats,
customFormatScore,
indexerFlags,
languages,
dateAdded,
columns
@ -143,12 +147,30 @@ class MovieFileEditorRow extends Component {
customFormats.length
)}
tooltip={<MovieFormats formats={customFormats} />}
position={tooltipPositions.TOP}
position={tooltipPositions.LEFT}
/>
</TableRowCell>
);
}
if (name === 'indexerFlags') {
return (
<TableRowCell
key={name}
className={styles.indexerFlags}
>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}
/>
) : null}
</TableRowCell>
);
}
if (name === 'languages') {
return (
<TableRowCell
@ -363,6 +385,7 @@ MovieFileEditorRow.propTypes = {
releaseGroup: PropTypes.string,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
customFormatScore: PropTypes.number.isRequired,
indexerFlags: PropTypes.number.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
mediaInfo: PropTypes.object,
@ -372,7 +395,8 @@ MovieFileEditorRow.propTypes = {
};
MovieFileEditorRow.defaultProps = {
customFormats: []
customFormats: [],
indexerFlags: 0
};
export default MovieFileEditorRow;

View File

@ -15,6 +15,7 @@ export interface MovieFile extends ModelBase {
languages: Language[];
quality: QualityModel;
customFormats: CustomFormat[];
indexerFlags: number;
mediaInfo: MediaInfo;
qualityCutoffNotMet: boolean;
}

View File

@ -8,7 +8,7 @@ function createMapStateToProps() {
createMovieFileSelector(),
(movieFile) => {
return {
language: movieFile ? movieFile.language : undefined
languages: movieFile ? movieFile.languages : undefined
};
}
);

View File

@ -151,6 +151,11 @@ class EditCustomFormatModalContent extends Component {
</Form>
<FieldSet legend={translate('Conditions')}>
<Alert kind={kinds.INFO}>
<div>
{translate('CustomFormatsSettingsTriggerInfo')}
</div>
</Alert>
<div className={styles.customFormats}>
{
specifications.map((tag) => {

View File

@ -15,6 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditDownloadClientModalContent.css';
@ -37,6 +38,7 @@ class EditDownloadClientModalContent extends Component {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteDownloadClientPress,
...otherProps
} = this.props;
@ -199,6 +201,12 @@ class EditDownloadClientModalContent extends Component {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@ -239,6 +247,7 @@ EditDownloadClientModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteDownloadClientPress: PropTypes.func
};

View File

@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
import {
saveDownloadClient,
setDownloadClientFieldValue,
setDownloadClientValue,
testDownloadClient,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
@ -23,7 +29,8 @@ const mapDispatchToProps = {
setDownloadClientValue,
setDownloadClientFieldValue,
saveDownloadClient,
testDownloadClient
testDownloadClient,
toggleAdvancedSettings
};
class EditDownloadClientModalContentConnector extends Component {
@ -56,6 +63,10 @@ class EditDownloadClientModalContentConnector extends Component {
this.props.testDownloadClient({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@ -65,6 +76,7 @@ class EditDownloadClientModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@ -82,6 +94,7 @@ EditDownloadClientModalContentConnector.propTypes = {
setDownloadClientFieldValue: PropTypes.func.isRequired,
saveDownloadClient: PropTypes.func.isRequired,
testDownloadClient: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@ -32,7 +32,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@ -17,6 +17,8 @@
}
.name {
@add-mixin truncate;
text-align: center;
font-weight: lighter;
font-size: 24px;

View File

@ -14,6 +14,7 @@ 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 AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css';
@ -33,6 +34,7 @@ function EditImportListModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteImportListPress,
...otherProps
} = props;
@ -234,6 +236,12 @@ function EditImportListModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@ -274,6 +282,7 @@ EditImportListModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteImportListPress: PropTypes.func
};

View File

@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveImportList, setImportListFieldValue, setImportListValue, testImportList } from 'Store/Actions/settingsActions';
import {
saveImportList,
setImportListFieldValue,
setImportListValue,
testImportList,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditImportListModalContent from './EditImportListModalContent';
@ -33,7 +39,8 @@ const mapDispatchToProps = {
setImportListValue,
setImportListFieldValue,
saveImportList,
testImportList
testImportList,
toggleAdvancedSettings
};
class EditImportListModalContentConnector extends Component {
@ -66,6 +73,10 @@ class EditImportListModalContentConnector extends Component {
this.props.testImportList({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@ -75,6 +86,7 @@ class EditImportListModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@ -92,6 +104,7 @@ EditImportListModalContentConnector.propTypes = {
setImportListFieldValue: PropTypes.func.isRequired,
saveImportList: PropTypes.func.isRequired,
testImportList: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@ -33,7 +33,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@ -32,7 +32,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@ -72,15 +72,15 @@ const fileNameTokens = [
];
const movieTokens = [
{ token: '{Movie Title}', example: 'Movie\'s Title' },
{ token: '{Movie Title:DE}', example: 'Titel des Films' },
{ token: '{Movie CleanTitle}', example: 'Movies Title' },
{ token: '{Movie TitleThe}', example: 'Movie\'s Title, The' },
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας' },
{ token: '{Movie CleanOriginalTitle}', example: 'Τίτλος ταινίας' },
{ token: '{Movie Title}', example: 'Movie\'s Title', footNote: 1 },
{ token: '{Movie Title:DE}', example: 'Titel des Films', footNote: 1 },
{ token: '{Movie CleanTitle}', example: 'Movies Title', footNote: 1 },
{ token: '{Movie TitleThe}', example: 'Movie\'s Title, The', footNote: 1 },
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας', footNote: 1 },
{ token: '{Movie CleanOriginalTitle}', example: 'Τίτλος ταινίας', footNote: 1 },
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
{ token: '{Movie TitleFirstCharacter:DE}', example: 'T' },
{ token: '{Movie Collection}', example: 'The Movie Collection' },
{ token: '{Movie Collection}', example: 'The Movie Collection', footNote: 1 },
{ token: '{Movie Certification}', example: 'R' },
{ token: '{Release Year}', example: '2009' }
];
@ -112,15 +112,16 @@ const mediaInfoTokens = [
];
const releaseGroupTokens = [
{ token: '{Release Group}', example: 'Rls Grp' }
{ token: '{Release Group}', example: 'Rls Grp', footNote: 1 }
];
const editionTokens = [
{ token: '{Edition Tags}', example: 'IMAX' }
{ token: '{Edition Tags}', example: 'IMAX', footNote: 1 }
];
const customFormatTokens = [
{ token: '{Custom Formats}', example: 'Surround Sound x264' }
{ token: '{Custom Formats}', example: 'Surround Sound x264' },
{ token: '{Custom Format:FormatName}', example: 'AMZN' }
];
const originalTokens = [
@ -267,7 +268,7 @@ class NamingModal extends Component {
<FieldSet legend={translate('Movie')}>
<div className={styles.groups}>
{
movieTokens.map(({ token, example }) => {
movieTokens.map(({ token, example, footNote }) => {
return (
<NamingOption
key={token}
@ -275,6 +276,7 @@ class NamingModal extends Component {
value={value}
token={token}
example={example}
footNote={footNote}
tokenSeparator={tokenSeparator}
tokenCase={tokenCase}
onPress={this.onOptionPress}
@ -284,6 +286,11 @@ class NamingModal extends Component {
)
}
</div>
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<InlineMarkdown data={translate('MovieFootNote')} />
</div>
</FieldSet>
<FieldSet legend={translate('MovieID')}>
@ -364,7 +371,7 @@ class NamingModal extends Component {
<FieldSet legend={translate('ReleaseGroup')}>
<div className={styles.groups}>
{
releaseGroupTokens.map(({ token, example }) => {
releaseGroupTokens.map(({ token, example, footNote }) => {
return (
<NamingOption
key={token}
@ -372,6 +379,7 @@ class NamingModal extends Component {
value={value}
token={token}
example={example}
footNote={footNote}
tokenSeparator={tokenSeparator}
tokenCase={tokenCase}
onPress={this.onOptionPress}
@ -381,12 +389,17 @@ class NamingModal extends Component {
)
}
</div>
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<InlineMarkdown data={translate('ReleaseGroupFootNote')} />
</div>
</FieldSet>
<FieldSet legend={translate('Edition')}>
<div className={styles.groups}>
{
editionTokens.map(({ token, example }) => {
editionTokens.map(({ token, example, footNote }) => {
return (
<NamingOption
key={token}
@ -394,6 +407,7 @@ class NamingModal extends Component {
value={value}
token={token}
example={example}
footNote={footNote}
tokenSeparator={tokenSeparator}
tokenCase={tokenCase}
onPress={this.onOptionPress}
@ -403,6 +417,11 @@ class NamingModal extends Component {
)
}
</div>
<div className={styles.footNote}>
<Icon className={styles.icon} name={icons.FOOTNOTE} />
<InlineMarkdown data={translate('EditionFootNote')} />
</div>
</FieldSet>
<FieldSet legend={translate('CustomFormats')}>

View File

@ -17,7 +17,7 @@
}
.small {
width: 480px;
width: 490px;
}
.large {
@ -26,8 +26,8 @@
.token {
flex: 0 0 50%;
padding: 6px 16px;
background-color: var(--popoverTitleBorderColor);
padding: 6px;
background-color: var(--popoverTitleBackgroundColor);
font-family: $monoSpaceFontFamily;
}
@ -37,8 +37,8 @@
align-self: stretch;
justify-content: space-between;
flex: 0 0 50%;
padding: 6px 16px;
background-color: var(--popoverTitleBackgroundColor);
padding: 6px;
background-color: var(--popoverBodyBackgroundColor);
.footNote {
padding: 2px;

View File

@ -14,6 +14,7 @@ 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 AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import NotificationEventItems from './NotificationEventItems';
import styles from './EditNotificationModalContent.css';
@ -32,6 +33,7 @@ function EditNotificationModalContent(props) {
onModalClose,
onSavePress,
onTestPress,
onAdvancedSettingsPress,
onDeleteNotificationPress,
...otherProps
} = props;
@ -136,6 +138,12 @@ function EditNotificationModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@ -175,6 +183,7 @@ EditNotificationModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteNotificationPress: PropTypes.func
};

View File

@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveNotification, setNotificationFieldValue, setNotificationValue, testNotification } from 'Store/Actions/settingsActions';
import {
saveNotification,
setNotificationFieldValue,
setNotificationValue,
testNotification,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditNotificationModalContent from './EditNotificationModalContent';
@ -23,7 +29,8 @@ const mapDispatchToProps = {
setNotificationValue,
setNotificationFieldValue,
saveNotification,
testNotification
testNotification,
toggleAdvancedSettings
};
class EditNotificationModalContentConnector extends Component {
@ -56,6 +63,10 @@ class EditNotificationModalContentConnector extends Component {
this.props.testNotification({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@ -65,6 +76,7 @@ class EditNotificationModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@ -82,6 +94,7 @@ EditNotificationModalContentConnector.propTypes = {
setNotificationFieldValue: PropTypes.func.isRequired,
saveNotification: PropTypes.func.isRequired,
testNotification: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

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