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

Compare commits

...

136 Commits

Author SHA1 Message Date
Bogdan
9fe4793606 Fixed: Don't show today's relative time for release dates 2024-08-31 11:06:02 +03:00
Mark McDowall
fa80608394 Convert RelativeDateCell to TypeScript
(cherry picked from commit 824ed0a36931ce7aae9aa544a7baf0738dae568c)
2024-08-31 10:47:35 +03:00
Bogdan
6e81d5917e Refactor include time for movie history
(cherry picked from commit 05edd44ed6dfe73c25da021ef2600e609852f7e0)
(cherry picked from commit ca372bee258523339aff2b868f8f9a619d44dbca)
(cherry picked from commit 2a662afaef0bed9f8cd153d60eee5ecbe85508ba)

Closes #10039
Closes #10045
Closes #10046
2024-08-31 10:47:34 +03:00
Bogdan
8d189523c4 Fixed: Forbid empty spaces in Release Profile restrictions
(cherry picked from commit 9136ee4ad934c10eef743e646f8b1c05eff2a2ff)
2024-08-31 07:01:06 +03:00
Bogdan
4d589422e6 Fixed: Importing files without media info available 2024-08-30 01:52:45 +03:00
Bogdan
675612e7c6 Fixed: Existing and Already In Library icons for movie search results 2024-08-29 13:01:40 +03:00
Bogdan
be3916f67d Fixed: Released as default Minimum Availability for add movie modals 2024-08-29 12:50:08 +03:00
Bogdan
453f216e0d New: Add Malayalam and Kannada languages 2024-08-29 09:42:58 +03:00
Bogdan
2d4846e5be New: Add Macedonian and Slovenian languages 2024-08-29 09:42:58 +03:00
Bogdan
2700a6cf8a Fixed: Paths for renamed movie files in Custom Script and Webhook
Closes #10359
2024-08-27 02:11:31 +03:00
Bogdan
674e414111 Don't persist value for SslCertHash when checking for existence 2024-08-27 01:56:03 +03:00
Bogdan
21bd21b70c New: Digital and Physical release dates for Poster options 2024-08-27 01:45:13 +03:00
Bogdan
fde87a38f9 Convert MovieQuality to TypeScript 2024-08-26 08:18:53 +03:00
Bogdan
0d6ba200d3 Fixed: Hide reboot and shutdown UI buttons on docker
Closes #10358
2024-08-26 04:06:40 +03:00
Bogdan
93298645e3 Render with ternary in bulk manage
Closes #10360
2024-08-26 04:01:30 +03:00
Bogdan
58f544e9e0 New: Bypass IP addresses ranges in proxies
(cherry picked from commit 402db9128c214d4c5af6583643cb49d3aa7a28b5)
2024-08-26 03:52:08 +03:00
bakerboy448
cf952d5c0b Fixed: Trim spaces and empty values in Proxy Bypass List
(cherry picked from commit 846333ddf0d9da775c80d004fdb9b41e700ef359)
2024-08-26 03:51:51 +03:00
Treycos
f6d630bdd3 Updated code action fixall value for VSCode
(cherry picked from commit 8af4246ff9baee4c291550102769a1186f65dc29)
2024-08-26 03:51:37 +03:00
Bogdan
657ced4772 Fix disabled style for monitor toggle button
(cherry picked from commit dde28cbd7e16b85f78d38c8dde7cf6bbb6119bb3)
2024-08-26 03:51:23 +03:00
namakeingo
d3a0c83f98 Fixed: False positive HC for MultiSubs (#10024)
* Fixed: Multisubs wrongly detected as hardcoded

As flagged by multiple people before "Multisubs" is a commonly used Tag that indicates that the file has more than 3 subtitle languages. Multisubs never indicate a hardcoded sub as you cannot have a multisubs where you can select between different languages if the subtitles are hardcoded in the video. This minor change excludes "MULTISUBS" from the regex used.
2024-08-26 00:57:52 +03:00
Bogdan
5833d5d4c4 Bump version to 5.10.1 2024-08-25 10:12:39 +03:00
Bogdan
2ba4562f49 Fixed: Improve appearance and sorting by ratings for Discover
Fixes #7271
2024-08-25 09:22:23 +03:00
Bogdan
d79db69644 Hide ratings on movie table in absence of data 2024-08-25 09:10:35 +03:00
Bogdan
7532dfb03c Remove extraneous height from movie posters 2024-08-25 09:08:54 +03:00
Bogdan
a47528aa81 Convert rating component to TypeScript 2024-08-25 09:07:02 +03:00
Bogdan
a812d9f39f Bump core-js
Update UI libs
2024-08-24 05:45:33 +03:00
Mark McDowall
fc97f05850 New: Optionally include Custom Format Score for Discord On File Import notifications
(cherry picked from commit e16ace54a8120cd98007a09fe1e6136be3e699fc)

Closes #10313
2024-08-24 04:32:49 +03:00
Bogdan
644876123d Convert MovieTitleLink to TypeScript
Closes #10322
2024-08-24 04:29:45 +03:00
Treycos
540659a799 Convert First Run to TypeScript
(cherry picked from commit 8484a8bebac38def457d428257888b7824ffb72f)

Closes #10307
2024-08-24 04:24:31 +03:00
Weblate
288668f7e6 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Kerk en IT <info@kerkenit.nl>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-08-24 04:09:22 +03:00
Servarr
fcf3be42d5 Automated API Docs update 2024-08-24 04:08:39 +03:00
Mark McDowall
16e218501e Fixed: Limit redirects after login to local paths
(cherry picked from commit 14005d8d1054eafaba808337a109d5812f3e79e6)
2024-08-24 01:56:42 +03:00
Bogdan
caf2d33c11 Fixed: Keeping search results when Missing/Cutoff Unmet repopulates 2024-08-24 01:54:04 +03:00
kephasdev
bc918ed3b5 Fix: Use indexer's Multi Languages setting for pushed releases
(cherry picked from commit 35a2bc940328bf61b39dd0012867bdaa564ee489)

Fixed: Calculating Custom Formats with languages in queue

(cherry picked from commit 8af12cc4e7f71cf169392cd86ccf0eb81f6b375c)

Closes #10273
Closes #10321
2024-08-23 05:57:03 +03:00
Bogdan
df77474314 Fixed: Improve sorting movies by release dates 2024-08-23 03:50:54 +03:00
Servarr
bf84471509 Automated API Docs update 2024-08-22 06:51:51 +03:00
Bogdan
d346d969de Fixed: Validate root folder existence when adding movie 2024-08-22 06:26:55 +03:00
Servarr
14b125ccd9 Automated API Docs update 2024-08-21 15:04:21 -05:00
Bogdan
da5323a08f New: Bulk manage custom formats 2024-08-21 03:57:21 +03:00
Bogdan
672b351497 Bump version to 5.10.0 2024-08-20 21:11:31 +03:00
Servarr
fc4f4ab211 Automated API Docs update 2024-08-20 02:30:14 +03:00
Weblate
333e8281ea Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Gabriel Markowski <gmarkowski62@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translation: Servarr/Radarr
2024-08-19 11:21:43 -05:00
shangchenglumetro
c278ffd8a0 Fix typos and improve log messages 2024-08-19 06:40:43 +03:00
Bogdan
5898eea3d0 Bump babel packages
Closes #10320
2024-08-19 06:24:55 +03:00
Bogdan
5b78a1297a Use autoprefixer in UI build
(cherry picked from commit 47a05ecb36e5c960b4f6ca7d279df7c281114611)

Closes #10319
2024-08-19 06:22:54 +03:00
martylukyy
14e3e1fa35 New: Configure log file size limit in UI
(cherry picked from commit 35baebaf7280749d5dfe5440e28b425e45a22d21)

Closes #10318
2024-08-19 06:19:34 +03:00
Bogdan
c0e76544ef Bump version to 5.9.1 2024-08-18 15:04:17 +03:00
Weblate
8c16677875 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-08-18 05:00:07 +03:00
Mark McDowall
401e19547c Cache root folders and improve getting disk space for movie path roots
(cherry picked from commit 63fdf8ca8ff9b22ce4cf8764cc05aad5d1d0ae62)

Closes #10219
2024-08-18 04:30:31 +03:00
Bogdan
c9f28fdc4f Fixed: Validation for Movie Info language 2024-08-18 00:21:04 +03:00
Bogdan
0ad4d7ea9a Fixed: Removing invalid statuses on provider deletion 2024-08-17 18:09:29 +03:00
Bogdan
e8bb3df68e New: Runtime for auto tagging 2024-08-16 23:45:55 +03:00
Bogdan
9442f1fb04 Fixed: Excluded movies on search results 2024-08-16 01:19:19 +03:00
Bogdan
9ad6b3a611 New: Improve status label and progress bar style for deleted movies
Closes #7127
2024-08-16 00:19:36 +03:00
Bogdan
fa1d6ad109 Fixed disabled options for SelectInput 2024-08-15 22:10:48 +03:00
Bogdan
ccbc8f591b Improve status labels for deleted movies 2024-08-15 19:14:22 +03:00
Bogdan
a4301f8db0 Fixed: Parse NEWCAM as CAM
Co-authored-by: bokkoman <bokkoman@gmail.com>

Fixes #10255
2024-08-15 16:05:14 +03:00
Bogdan
fe00825f2b New: Parse DarQ release group
Co-authored-by: Mark McDowall <mark@mcdowall.ca>

Closes #10299
2024-08-15 15:47:40 +03:00
Bogdan
17a9b0f7b0 Exclude movies without year in the missing page
They should not have any release dates, so not quite missing in any case.
2024-08-15 15:41:49 +03:00
Bogdan
62bdb66d0f New: Increase max size limit for quality definitions
Fixes #9822
Closes #10295
2024-08-15 13:26:07 +03:00
Bogdan
7c1fedb8ce Sort quality profiles by name in custom filters 2024-08-15 01:14:59 +03:00
Bogdan
333351da45 Trim trailing slash from trailer link
Convert MovieDetailsLinks to Typescript
2024-08-15 00:50:23 +03:00
Mark McDowall
fbbe7f7b5d Fix typos and improve log messages
(cherry picked from commit 37c4647f242c37f22c7ac455d304055441acf362)

Closes #10277
2024-08-14 23:44:33 +03:00
Mark McDowall
edec201a6c Improve messaging for for Send Notifications setting in Emby / Jellyfin
(cherry picked from commit 4c0b8961741a7dd0cf2aba81cdbcb74c1208a1ff)

Closes #10251
2024-08-14 23:42:51 +03:00
Bogdan
1e783bfe07 Fixed: Dedupe titles to avoid similar search requests
Closes #10278
2024-08-14 23:39:03 +03:00
Bogdan
7d5236de21 Display movie title for interactive search modal 2024-08-14 23:39:03 +03:00
Bogdan
1efe7db5f3 Fixed: Sending Manual Interaction Required notifications for unknown movies 2024-08-14 23:02:04 +03:00
Bogdan
b37cc42805 Fixed: Duplicated changelog lines 2024-08-14 20:57:08 +03:00
Bogdan
fa19f45171 Fixed: Stale formats score after changing quality profile for movies 2024-08-14 20:32:34 +03:00
Bogdan
4ae382cea7 Fixed: Showing multi-languages for movies parsed without languages 2024-08-14 19:26:52 +03:00
Mark McDowall
37c09ba1f8 Fixed: Allow leading/trailing spaces on non-Windows
(cherry picked from commit 9127a91dfc460f442498a00faed98737047098cd)

Closes #10240
2024-08-14 19:21:50 +03:00
Mark McDowall
322df78f5a Fixed: Updating movie path from different OS paths
(cherry picked from commit e791f4b743d9660b0ad1decc4c5ed0e864f3b243)

Closes #10218
2024-08-14 19:21:50 +03:00
Mark McDowall
3a4446cc8e New: Validate that folders in paths don't start or end with a space
(cherry picked from commit 316b5cbf75b45ef9a25f96ce1f2fbed25ad94296)

Closes #9958
2024-08-14 19:21:50 +03:00
Bogdan
6c456e57d8 Convert formatBytes to TypeScript
Closes #10272
2024-08-14 18:48:49 +03:00
Mark McDowall
abc7efabea New: Configurable log file size limit
(cherry picked from commit 813965e6a20edef2772d68eaa7646af33028425a)

Closes #10267
2024-08-14 18:41:00 +03:00
Mark McDowall
ace692aca6 New: Add Compact Log Event Format option for console logging
(cherry picked from commit 0d914f4c53876540ed2df83ad3d71615c013856f)

Closes #10266
2024-08-14 18:36:10 +03:00
Bogdan
882bde713f Upgrade nlog to 5.3.3
Closes #10265
2024-08-14 17:11:53 +03:00
Bogdan
2575e3647f Include available version in update health check
(cherry picked from commit 15e3c3efb18242caf28b9bfc77a72a78296018bf)

Closes #10227
2024-08-14 16:24:23 +03:00
Mark McDowall
5cac5b6068 Update React Lint rules for TSX
(cherry picked from commit 1299a97579bec52ee3d16ab8d05c9e22edd80330)

Closes #10248
2024-08-14 16:20:32 +03:00
Mark McDowall
4628868dfa Fixed: Marking queued item as failed not blocking the correct Torrent Info Hash
(cherry picked from commit 4b186e894e4e229a435c077e00c65b67ca178333)

Closes #10274
2024-08-14 16:09:54 +03:00
Bogdan
25685314bc Fixed: Parsing alternative titles containing "A.K.A." 2024-08-14 15:45:45 +03:00
Qstick
41b1ea553e Log calls to deprecated endpoints
(cherry picked from commit aaaf18aec33dc0ae5075b53ab81812743608d6a6)
2024-08-14 15:45:26 +03:00
Weblate
5d17f8e84d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wolfy The Broccoly <theproviderofsolace@gmail.com>
Co-authored-by: iMohmmedSA <i.mohmmed.i+1@gmail.com>
Co-authored-by: marudosurdo <marudosurdo@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-08-14 15:42:53 +03:00
jmwallace
7490fc7040 Fix typo in "Import Extra Files" help text
The typo can be found at Settings -> Media Management -> Importing -> Import Extra Files.
2024-08-14 15:41:33 +03:00
Stevie Robinson
f4e1f51a9c Fixed: Interactive Import dropdown width on mobile
(cherry picked from commit f2f4a98eed5bc83224917897642a28381ca648b9)
2024-08-14 15:40:39 +03:00
Mark McDowall
8e1016572b New: Return downloading magnets from Transmission
(cherry picked from commit 11a9dcb3890eaf99602900f37e64007f2fbf9b8e)
2024-08-14 15:40:12 +03:00
Bogdan
caabb032f3 Fixed: Persist selected filter for interactive searches 2024-08-14 15:39:52 +03:00
RaZaSB
ce9c5d4d97 New: Remove all single quote characters from searches
(cherry picked from commit 0877a6718d3df8e217a72cc5b113b8398e495eb1)
2024-08-14 15:39:38 +03:00
Mark McDowall
967bed3161 Align queue action buttons on right
(cherry picked from commit f7a58aab339e2012b6bb91d0b3a38d733ec213c6)
2024-08-14 15:39:11 +03:00
Servarr
8d9f1697ee Automated API Docs update 2024-08-14 15:38:54 +03:00
Bogdan
3be2c6b0be Fixed: Validate uniqueness for import list exclusions 2024-08-13 00:01:24 -05:00
The Dark
b6d9c73a17 New: Import list exclusion pagination and bulk removal
(cherry picked from commit 428569106499b5e3a463f1990ae2996d1ae4ab49)

Persist page size for Import List Exclusions

(cherry picked from commit e81bb3b993adac705fd61dc9e281b040ca2338f5)

Clear pending changes for edit import list exclusions on modal close

(cherry picked from commit 7b87de2e93c2aa499cff224f84253ba944bb58d4)

Fixed actions column width for import list exclusions

(cherry picked from commit d691ad8e12ea4f2bc77f0b551c17d22d91c4ba22)
2024-08-13 00:01:24 -05:00
Bogdan
b1a7652753 Rename ImportExclusion to ImportListExclusion 2024-08-13 00:01:24 -05:00
Bogdan
f76c97c3ce Remove unused ImportExclusions property 2024-08-13 00:01:24 -05:00
Bogdan
1f5a84d202 Convert Import List Options to TypeScript
Co-authored-by: The Dark <12370876+CheAle14@users.noreply.github.com>
2024-08-13 00:01:24 -05:00
Bogdan
d25bcdb043 Rename ImportExclusions to ImportListExclusions 2024-08-13 00:01:24 -05:00
Bogdan
f75497f57d Fixed: Overwriting query params for remove item handler 2024-08-13 00:01:24 -05:00
Bogdan
2f413c68d9 Fixed: Total runtime hours without decimal point 2024-08-13 00:00:37 -05:00
Bogdan
68c20713e5 Validation for bulk movies editor 2024-08-11 19:58:20 -05:00
Bogdan
6eeed96d12 Fix tags height in tag inputs
And other relevant changes missing from #4715
2024-08-06 11:27:55 -05:00
Bogdan
6f306a22e5 Fixed: Persist indexer flags for automatic imports 2024-08-03 12:48:34 -05:00
Bogdan
29ef75960d Fixed: Moving files for torrents when Remove Completed is disabled
(cherry picked from commit 78a0def46a4c8628d9bcf6af2701aa35b3f959b9)

Fixed: Moving files on import for usenet clients

(cherry picked from commit 291d792810d071f28c389d100b9642854d7cd70e)
2024-08-02 22:40:49 -05:00
Wolfy The Broccoly
364a42424a Add translation for MovieIndexSelectAllButton 2024-08-02 22:38:06 -05:00
Mark McDowall
a5b315ba83 Fix height of tags in tag inputs
(cherry picked from commit 5ac6c0e651400aa4d2e7126b0ccf1bcd4c6224b2)
2024-08-02 22:27:45 -05:00
Mark McDowall
e80e96de0e Don't hash files in development builds
(cherry picked from commit bc7799139e52b92956eb595fb87f44d7dda9a320)
2024-08-02 22:27:15 -05:00
Bogdan
44c7c71226 Fixed: Calculate movie availability comparing UTC dates 2024-08-02 22:25:52 -05:00
Mark McDowall
04c5e6c2a6 New: Default file log level changed to debug
(cherry picked from commit 9b528eb82914a05cfc3b67d4d6146ce51e86f68d)
2024-08-02 22:24:13 -05:00
OnTheCliff
5533528b56 Fixed: Runtime value for movies longer than 24 hours
* Update MediaInfoResource.cs

Fixed runtime value for movies longer than 24 hours

* Update src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs

Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>

* Update src/Radarr.Api.V3/MovieFiles/MediaInfoResource.cs

Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>

---------

Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2024-08-02 22:23:16 -05:00
Bogdan
74246df881 Fixed: Pagination for TMDb lists containing a mix of media types 2024-07-30 18:59:32 -05:00
Bogdan
88127298ae Improve messaging for renamed movie files progress info 2024-07-26 00:07:35 +03:00
Bogdan
5559fa5fa5 Bump ImageSharp to 3.1.5
https://github.com/advisories/GHSA-63p8-c4ww-9cg7
2024-07-26 00:05:51 +03:00
diamondpete
d503e01747 Fixed: Remove apostrophe, backtick in contractions
(cherry picked from commit 6a4824c02932ee1bd57c1f4f0644f8bc693f6006)

Closes #10178
2024-07-25 21:40:44 +03:00
Mark McDowall
ae89ae175f Fixed: Don't treat SubFrench as French audio language
(cherry picked from commit 5ad3d2efcce7d983bd783b551f32666529086901)

Closes #10209
2024-07-25 21:36:50 +03:00
Bogdan
df35e78e1f New: Display original language on movie details and search results page
Closes #10206
2024-07-25 21:33:19 +03:00
ManiMatter
a3b3fee06b Treat forcedMetaDL from qBit as queued instead of downloading
(cherry picked from commit 9a613afa355fbc8cdf29c4d1b8eb1f1586405eb7)
2024-07-25 07:42:53 +03:00
Bogdan
ae377d97a5 New: Ignore Litestream tables in Database
(cherry picked from commit 2a26c6722afa5c657fde162cbddbe9e8731f3a0c)
2024-07-25 07:37:13 +03:00
Bogdan
270df9d1dd Fixed: Improve filtering performance in Select Movie Modal 2024-07-25 07:23:32 +03:00
Bogdan
6ed3045433 Original Language filter optional for TMDb Popular lists 2024-07-25 03:18:07 +03:00
justin vanderhooft
ddb7d5690b Fixed: Show root folder when path is not available yet on imports
(cherry picked from commit f6fbd3cfee4db891e68f9f15551ea62b02077b5e)
2024-07-25 03:06:26 +03:00
Jendrik Weise
a1104b8263 New: Update matching movie path in Jellyfin/Emby library
(cherry picked from commit ad0dc01cf7ed16ccfa8260717111ad8a44675221)

Closes #8898
2024-07-23 23:39:16 +03:00
Bogdan
358ff0c130 Fix table name for Alternative Titles migrations 2024-07-21 20:16:39 +03:00
Bogdan
ff0a04c331 Remove SQLite specific schema condition from migrations 2024-07-21 20:16:39 +03:00
Bogdan
c12f01f919 Bump version to 5.9.0 2024-07-21 17:35:30 +03:00
Weblate
93d661242a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2024-07-20 16:18:48 +03:00
Bogdan
324dac8db3 New: Bump dotnet to 6.0.32 2024-07-19 23:40:24 +03:00
Weblate
bba69d8b22 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: MattiaPell <mattiapellegrini16@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Rauniik <raunerjakub@gmail.com>
Co-authored-by: Serhii Matrunchyk <serhii@digitalidea.studio>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: damienmillet <contact@damien-millet.dev>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: quek76 <quek@libertysurf.fr>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/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/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2024-07-19 00:07:13 +03:00
Mark McDowall
1366f6e8b4 New: Show update settings on all platforms
(cherry picked from commit c023fc700896c7f0751c4ac63c4e1a89d6e1a9bb)

Closes #10184
2024-07-18 20:59:42 +03:00
servarr[bot]
f79712951b Fixed: Assume category path from qBittorent starting with '//' is a Windows UNC path
* Fixed: Assume category path from qBittorent starting with '//' is a Windows UNC path

Radarr/Radarr#10162

(cherry picked from commit 19466aa29050e1b13b1db8cc61662b10d76a82e4)

* fixup! Fixed: Assume category path from qBittorent starting with '//' is a Windows UNC path

---------

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2024-07-18 19:53:55 +03:00
Bogdan
101b046753 Fix custom formats sorting for quality profiles 2024-07-17 20:31:56 +03:00
Bogdan
cd713e7252 New: Sort by tags on movie index 2024-07-17 17:41:46 +03:00
Mark McDowall
a54f54eb6e New: Add option to show tags on movies Poster and Overview
(cherry picked from commit e35b39b4b1c88e13f9b1515c68b4d0942f84fa6d)

Closes #10176
2024-07-17 17:41:46 +03:00
Mark McDowall
f2af7a1b72 New: Use natural sorting for lists of items in the UI
(cherry picked from commit 1a1c8e6c08a6db5fcd2b5d17e65fa1f943d2e746)

Closes #10177
2024-07-17 17:41:46 +03:00
Marc Carbonell
a5b48153a6 New: Add a few spanish release groups to exceptions (#10120)
Co-authored-by: Marc Carbonell Belmonte <mcarbonell@sitel-sa.com>
2024-07-17 16:00:01 +03:00
Stevie Robinson
1804e486d6 New: Wrap specifications in Custom Format and Auto Tagging modals
(cherry picked from commit 7b8d606a1bed6257d7942de47576c1505fd9cb57)
2024-07-17 15:32:47 +03:00
Marc Carbonell
b490177a77 Remove extraneous indentation in RemoveFileExtension
(cherry picked from commit dca5239420e21f91c1d67bc8bbb14cdb13c8d5d9)
2024-07-17 15:21:25 +03:00
Bogdan
7a90b4a6b2 Bump version to 5.8.3 2024-07-14 12:34:19 +03:00
468 changed files with 9409 additions and 5739 deletions

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.8.2'
majorVersion: '5.10.1'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.421'
dotnetVersion: '6.0.424'
nodeVersion: '20.X'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'

View File

@@ -359,11 +359,16 @@ module.exports = {
],
rules: Object.assign(typescriptEslintRecommended.rules, {
'no-shadow': 'off',
// These should be enabled after cleaning things up
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true
}
],
'@typescript-eslint/explicit-function-return-type': 'off',
'react/prop-types': 'off',
'no-shadow': 'off',
'prettier/prettier': 'error',
'simple-import-sort/imports': [
'error',
@@ -376,7 +381,41 @@ module.exports = {
['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$']
]
}
]
],
// React Hooks
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
// React
'react/function-component-definition': 'error',
'react/hook-use-state': 'error',
'react/jsx-boolean-value': ['error', 'always'],
'react/jsx-curly-brace-presence': [
'error',
{ props: 'never', children: 'never' }
],
'react/jsx-fragments': 'error',
'react/jsx-handler-names': [
'error',
{
eventHandlerPrefix: 'on',
eventHandlerPropPrefix: 'on'
}
],
'react/jsx-no-bind': ['error', { ignoreRefs: true }],
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }],
'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
'react/jsx-sort-props': [
'error',
{
callbacksLast: true,
noSortAlphabetically: true,
reservedFirst: true
}
],
'react/prop-types': 'off',
'react/self-closing-comp': 'error'
})
},
{

View File

@@ -9,7 +9,7 @@
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
},
"typescript.preferences.quoteStyle": "single",

View File

@@ -66,7 +66,7 @@ module.exports = (env) => {
output: {
path: distFolder,
publicPath: '/',
filename: '[name]-[contenthash].js',
filename: isProduction ? '[name]-[contenthash].js' : '[name].js',
sourceMapFilename: '[file].map'
},
@@ -91,7 +91,7 @@ module.exports = (env) => {
new MiniCssExtractPlugin({
filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css'
chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css'
}),
new HtmlWebpackPlugin({
@@ -201,7 +201,7 @@ module.exports = (env) => {
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]'
}
}
},

View File

@@ -16,6 +16,7 @@ const mixinsFiles = [
module.exports = {
plugins: [
'autoprefixer',
['postcss-mixins', {
mixinsFiles
}],

View File

@@ -1,13 +1,13 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieLanguages from 'Movie/MovieLanguages';
import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink';
import translate from 'Utilities/String/translate';
@@ -104,7 +104,7 @@ class BlocklistRow extends Component {
if (name === 'languages') {
return (
<TableRowCell key={name}>
<MovieLanguage
<MovieLanguages
languages={languages}
/>
</TableRowCell>
@@ -136,7 +136,7 @@ class BlocklistRow extends Component {
if (name === 'date') {
return (
<RelativeDateCellConnector
<RelativeDateCell
key={name}
date={date}
/>

View File

@@ -1,13 +1,13 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, tooltipPositions } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieLanguages from 'Movie/MovieLanguages';
import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
@@ -113,7 +113,7 @@ class HistoryRow extends Component {
if (name === 'languages') {
return (
<TableRowCell key={name}>
<MovieLanguage
<MovieLanguages
languages={languages}
/>
</TableRowCell>
@@ -143,7 +143,7 @@ class HistoryRow extends Component {
if (name === 'date') {
return (
<RelativeDateCellConnector
<RelativeDateCell
key={name}
date={date}
/>

View File

@@ -26,4 +26,5 @@
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 70px;
text-align: right;
}

View File

@@ -4,7 +4,7 @@ import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import ProgressBar from 'Components/ProgressBar';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
@@ -12,7 +12,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieLanguages from 'Movie/MovieLanguages';
import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink';
import formatBytes from 'Utilities/Number/formatBytes';
@@ -175,7 +175,7 @@ class QueueRow extends Component {
if (name === 'languages') {
return (
<TableRowCell key={name}>
<MovieLanguage
<MovieLanguages
languages={languages}
/>
</TableRowCell>
@@ -319,7 +319,7 @@ class QueueRow extends Component {
if (name === 'added') {
return (
<RelativeDateCellConnector
<RelativeDateCell
key={name}
date={added}
/>

View File

@@ -24,7 +24,11 @@ function TimeleftCell(props) {
} = props;
if (status === 'delay') {
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
const date = getRelativeDate({
date: estimatedCompletionTime,
shortDateFormat,
showRelativeDates
});
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
return (
@@ -40,7 +44,11 @@ function TimeleftCell(props) {
}
if (status === 'downloadClientUnavailable') {
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
const date = getRelativeDate({
date: estimatedCompletionTime,
shortDateFormat,
showRelativeDates
});
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
return (

View File

@@ -6,7 +6,6 @@ import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
@@ -36,7 +35,6 @@ const mapDispatchToProps = {
lookupMovie,
clearAddMovie,
fetchRootFolders,
fetchImportExclusions,
fetchQueueDetails,
clearQueueDetails,
fetchMovieFiles,
@@ -56,7 +54,6 @@ class AddNewMovieConnector extends Component {
componentDidMount() {
this.props.fetchRootFolders();
this.props.fetchImportExclusions();
this.props.fetchQueueDetails();
}
@@ -131,7 +128,6 @@ AddNewMovieConnector.propTypes = {
lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired,
fetchImportExclusions: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired,
fetchMovieFiles: PropTypes.func.isRequired,

View File

@@ -85,6 +85,7 @@
margin-top: 20px;
}
.originalLanguage,
.studio,
.genres {
margin-left: 5px;

View File

@@ -8,6 +8,7 @@ interface CssExports {
'genres': string;
'icons': string;
'links': string;
'originalLanguage': string;
'overlay': string;
'overview': string;
'poster': string;

View File

@@ -62,6 +62,7 @@ class AddNewMovieSearchResult extends Component {
titleSlug,
year,
studio,
originalLanguage,
genres,
status,
overview,
@@ -70,7 +71,7 @@ class AddNewMovieSearchResult extends Component {
images,
existingMovieId,
isExistingMovie,
isExclusionMovie,
isExcluded,
isSmallScreen,
colorImpairedMode,
id,
@@ -154,26 +155,27 @@ class AddNewMovieSearchResult extends Component {
</div>
<div className={styles.icons}>
<div>
{
isExistingMovie &&
<Icon
className={styles.alreadyExistsIcon}
name={icons.CHECK_CIRCLE}
size={36}
title={translate('AlreadyInYourLibrary')}
/>
}
{
isExistingMovie &&
<Icon
className={styles.alreadyExistsIcon}
name={icons.CHECK_CIRCLE}
size={36}
title={translate('AlreadyInYourLibrary')}
/>
}
{
isExclusionMovie &&
<Icon
className={styles.exclusionIcon}
name={icons.DANGER}
size={36}
title={translate('MovieIsOnImportExclusionList')}
/>
}
{
isExcluded &&
<Icon
className={styles.exclusionIcon}
name={icons.DANGER}
size={36}
title={translate('MovieIsOnImportExclusionList')}
/>
}
</div>
</div>
</div>
@@ -213,17 +215,31 @@ class AddNewMovieSearchResult extends Component {
}
{
!!studio &&
originalLanguage?.name ?
<Label size={sizes.LARGE}>
<Icon
name={icons.LANGUAGE}
size={13}
/>
<span className={styles.originalLanguage}>
{originalLanguage.name}
</span>
</Label> :
null
}
{
studio ?
<Label size={sizes.LARGE}>
<Icon
name={icons.STUDIO}
size={13}
/>
<span className={styles.studio}>
{studio}
</span>
</Label>
</Label> :
null
}
{
@@ -233,7 +249,6 @@ class AddNewMovieSearchResult extends Component {
name={icons.GENRE}
size={13}
/>
<span className={styles.genres}>
{genres.slice(0, 3).join(', ')}
</span>
@@ -271,6 +286,7 @@ class AddNewMovieSearchResult extends Component {
{
isExistingMovie && isSmallScreen &&
<MovieStatusLabel
status={status}
hasMovieFiles={hasMovieFile}
monitored={monitored}
isAvailable={isAvailable}
@@ -311,6 +327,7 @@ AddNewMovieSearchResult.propTypes = {
titleSlug: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
studio: PropTypes.string,
originalLanguage: PropTypes.object,
genres: PropTypes.arrayOf(PropTypes.string),
status: PropTypes.string.isRequired,
overview: PropTypes.string,
@@ -319,7 +336,7 @@ AddNewMovieSearchResult.propTypes = {
images: PropTypes.arrayOf(PropTypes.object).isRequired,
existingMovieId: PropTypes.number,
isExistingMovie: PropTypes.bool.isRequired,
isExclusionMovie: PropTypes.bool.isRequired,
isExcluded: PropTypes.bool,
isSmallScreen: PropTypes.bool.isRequired,
id: PropTypes.number,
monitored: PropTypes.bool.isRequired,
@@ -333,7 +350,8 @@ AddNewMovieSearchResult.propTypes = {
};
AddNewMovieSearchResult.defaultProps = {
genres: []
genres: [],
isExcluded: false
};
export default AddNewMovieSearchResult;

View File

@@ -1,27 +1,24 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector';
import AddNewMovieSearchResult from './AddNewMovieSearchResult';
function createMapStateToProps() {
return createSelector(
createExistingMovieSelector(),
createExclusionMovieSelector(),
createDimensionsSelector(),
(state) => state.queue.details.items,
(state) => state.movieFiles.items,
(state, { internalId }) => internalId,
(state) => state.settings.ui.item.movieRuntimeFormat,
(isExistingMovie, isExclusionMovie, dimensions, queueItems, movieFiles, internalId, movieRuntimeFormat) => {
(isExistingMovie, dimensions, queueItems, movieFiles, internalId, movieRuntimeFormat) => {
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
const movieFile = movieFiles.find((item) => internalId > 0 && item.movieId === internalId);
return {
existingMovieId: internalId,
isExistingMovie,
isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen,
queueItem,
movieFile,

View File

@@ -1,13 +1,9 @@
import React, { Fragment, ReactNode, useCallback, useEffect } from 'react';
import { 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,
@@ -17,7 +13,7 @@ function createThemeSelector() {
);
}
function ApplyTheme({ children }: ApplyThemeProps) {
function ApplyTheme() {
const theme = useSelector(createThemeSelector());
const updateCSSVariables = useCallback(() => {
@@ -31,7 +27,7 @@ function ApplyTheme({ children }: ApplyThemeProps) {
updateCSSVariables();
}, [updateCSSVariables, theme]);
return <Fragment>{children}</Fragment>;
return null;
}
export default ApplyTheme;

View File

@@ -18,7 +18,10 @@ export interface AppSectionSaveState {
}
export interface PagedAppSectionState {
page: number;
pageSize: number;
totalPages: number;
totalRecords?: number;
}
export interface AppSectionFilterState<T> {
@@ -38,6 +41,7 @@ export interface AppSectionItemState<T> {
isFetching: boolean;
isPopulated: boolean;
error: Error;
pendingChanges: Partial<T>;
item: T;
}

View File

@@ -20,11 +20,14 @@ export interface MovieIndexAppState {
showTitle: boolean;
showMonitored: boolean;
showQualityProfile: boolean;
showReleaseDate: boolean;
showCinemaRelease: boolean;
showDigitalRelease: boolean;
showPhysicalRelease: boolean;
showReleaseDate: boolean;
showTmdbRating: boolean;
showImdbRating: boolean;
showRottenTomatoesRating: boolean;
showTags: boolean;
showSearchAction: boolean;
};
@@ -37,6 +40,7 @@ export interface MovieIndexAppState {
showAdded: boolean;
showPath: boolean;
showSizeOnDisk: boolean;
showTags: boolean;
showSearchAction: boolean;
};

View File

@@ -3,21 +3,30 @@ import AppSectionState, {
AppSectionItemState,
AppSectionSaveState,
AppSectionSchemaState,
PagedAppSectionState,
} from 'App/State/AppSectionState';
import Language from 'Language/Language';
import CustomFormat from 'typings/CustomFormat';
import DownloadClient from 'typings/DownloadClient';
import ImportList from 'typings/ImportList';
import ImportListExclusion from 'typings/ImportListExclusion';
import ImportListOptionsSettings from 'typings/ImportListOptionsSettings';
import Indexer from 'typings/Indexer';
import IndexerFlag from 'typings/IndexerFlag';
import Notification from 'typings/Notification';
import QualityProfile from 'typings/QualityProfile';
import { UiSettings } from 'typings/UiSettings';
import General from 'typings/Settings/General';
import UiSettings from 'typings/Settings/UiSettings';
export interface DownloadClientAppState
extends AppSectionState<DownloadClient>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface GeneralAppState
extends AppSectionItemState<General>,
AppSectionSaveState {}
export interface ImportListAppState
extends AppSectionState<ImportList>,
AppSectionDeleteState,
@@ -36,12 +45,34 @@ export interface QualityProfilesAppState
extends AppSectionState<QualityProfile>,
AppSectionSchemaState<QualityProfile> {}
export interface CustomFormatAppState
extends AppSectionState<CustomFormat>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface ImportListOptionsSettingsAppState
extends AppSectionItemState<ImportListOptionsSettings>,
AppSectionSaveState {}
export interface ImportListExclusionsSettingsAppState
extends AppSectionState<ImportListExclusion>,
AppSectionSaveState,
PagedAppSectionState,
AppSectionDeleteState {
pendingChanges: Partial<ImportListExclusion>;
}
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
export type LanguageSettingsAppState = AppSectionState<Language>;
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
interface SettingsAppState {
advancedSettings: boolean;
customFormats: CustomFormatAppState;
downloadClients: DownloadClientAppState;
general: GeneralAppState;
importListExclusions: ImportListExclusionsSettingsAppState;
importListOptions: ImportListOptionsSettingsAppState;
importLists: ImportListAppState;
indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState;

View File

@@ -3,10 +3,10 @@ import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import CalendarEventQueueDetails from 'Calendar/Events/CalendarEventQueueDetails';
import getStatusStyle from 'Calendar/getStatusStyle';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons, kinds } from 'Helpers/Props';
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
import translate from 'Utilities/String/translate';
import styles from './AgendaEvent.css';
@@ -82,7 +82,7 @@ class AgendaEvent extends Component {
startTime = moment(startTime);
const downloading = !!(queueItem || grabbed);
const isMonitored = monitored;
const statusStyle = getStatusStyle(null, isMonitored, hasFile, isAvailable, 'style', downloading);
const statusStyle = getStatusStyle(hasFile, downloading, isMonitored, isAvailable);
const joinedGenres = genres.slice(0, 2).join(', ');
const link = `/movie/${titleSlug}`;

View File

@@ -28,7 +28,7 @@ class DayOfWeek extends Component {
if (view === calendarViews.WEEK) {
formatedDate = momentDate.format(calendarWeekColumnHeader);
} else if (view === calendarViews.FORECAST) {
formatedDate = getRelativeDate(date, shortDateFormat, showRelativeDates);
formatedDate = getRelativeDate({ date, shortDateFormat, showRelativeDates });
}
return (

View File

@@ -2,10 +2,10 @@ import classNames from 'classnames';
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import getStatusStyle from 'Calendar/getStatusStyle';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons, kinds } from 'Helpers/Props';
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
import translate from 'Utilities/String/translate';
import CalendarEventQueueDetails from './CalendarEventQueueDetails';
import styles from './CalendarEvent.css';
@@ -39,7 +39,7 @@ class CalendarEvent extends Component {
const isDownloading = !!(queueItem || grabbed);
const isMonitored = monitored;
const statusStyle = getStatusStyle(null, isMonitored, hasFile, isAvailable, 'style', isDownloading);
const statusStyle = getStatusStyle(hasFile, isDownloading, isMonitored, isAvailable);
const joinedGenres = genres.slice(0, 2).join(', ');
const link = `/movie/${titleSlug}`;
const eventType = [];

View File

@@ -0,0 +1,25 @@
function getStatusStyle(hasFile, downloading, isMonitored, isAvailable) {
if (downloading) {
return 'queue';
}
if (hasFile && isMonitored) {
return 'downloaded';
}
if (hasFile && !isMonitored) {
return 'unmonitored';
}
if (isAvailable && isMonitored) {
return 'missingMonitored';
}
if (!isMonitored) {
return 'missingUnmonitored';
}
return 'continuing';
}
export default getStatusStyle;

View File

@@ -2,7 +2,7 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
import getProgressBarKind from 'Utilities/Movie/getProgressBarKind';
import translate from 'Utilities/String/translate';
import styles from './CollectionMovieLabel.css';
@@ -45,7 +45,7 @@ class CollectionMovieLabel extends Component {
<div
className={classNames(
styles.movieStatus,
styles[getStatusStyle(status, monitored, hasFile, isAvailable, 'kinds')]
styles[getProgressBarKind(status, monitored, hasFile, isAvailable)]
)}
>
{

View File

@@ -63,7 +63,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
<div>{info.componentStack}</div>
)}
{<div className={styles.version}>Version: {window.Radarr.version}</div>}
<div className={styles.version}>Version: {window.Radarr.version}</div>
</details>
</div>
);

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import SelectInput from 'Components/Form/SelectInput';
import IconButton from 'Components/Link/IconButton';
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
import sortByProp from 'Utilities/Array/sortByProp';
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
@@ -14,7 +15,7 @@ import MinimumAvailabilityFilterBuilderRowValue from './MinimumAvailabilityFilte
import MovieFilterBuilderRowValue from './MovieFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
import QualityProfileFilterBuilderRowValue from './QualityProfileFilterBuilderRowValue';
import ReleaseStatusFilterBuilderRowValue from './ReleaseStatusFilterBuilderRowValue';
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
import styles from './FilterBuilderRow.css';
@@ -77,7 +78,7 @@ function getRowValueConnector(selectedFilterBuilderProp) {
return QualityFilterBuilderRowValueConnector;
case filterBuilderValueTypes.QUALITY_PROFILE:
return QualityProfileFilterBuilderRowValueConnector;
return QualityProfileFilterBuilderRowValue;
case filterBuilderValueTypes.MOVIE:
return MovieFilterBuilderRowValue;
@@ -228,7 +229,7 @@ class FilterBuilderRow extends Component {
key: name,
value: typeof label === 'function' ? label() : label
};
}).sort((a, b) => a.value.localeCompare(b.value));
}).sort(sortByProp('value'));
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);

View File

@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { filterBuilderTypes } from 'Helpers/Props';
import * as filterTypes from 'Helpers/Props/filterTypes';
import sortByName from 'Utilities/Array/sortByName';
import sortByProp from 'Utilities/Array/sortByProp';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createTagListSelector() {
@@ -38,7 +38,7 @@ function createTagListSelector() {
}
return acc;
}, []).sort(sortByName);
}, []).sort(sortByProp('name'));
}
return _.uniqBy(items, 'id');

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useSelector } from 'react-redux';
import Movie from 'Movie/Movie';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import sortByName from 'Utilities/Array/sortByName';
import sortByProp from 'Utilities/Array/sortByProp';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
@@ -11,7 +11,7 @@ function MovieFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const tagList = allMovies
.map((movie) => ({ id: movie.id, name: movie.title }))
.sort(sortByName);
.sort(sortByProp('name'));
return <FilterBuilderRowValue {...props} tagList={tagList} />;
}

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterBuilderRowValueProps from 'Components/Filter/Builder/FilterBuilderRowValueProps';
import sortByProp from 'Utilities/Array/sortByProp';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createQualityProfilesSelector() {
return createSelector(
(state: AppState) => state.settings.qualityProfiles.items,
(qualityProfiles) => {
return qualityProfiles;
}
);
}
function QualityProfileFilterBuilderRowValue(
props: FilterBuilderRowValueProps
) {
const qualityProfiles = useSelector(createQualityProfilesSelector());
const tagList = qualityProfiles
.map(({ id, name }) => ({ id, name }))
.sort(sortByProp('name'));
return <FilterBuilderRowValue {...props} tagList={tagList} />;
}
export default QualityProfileFilterBuilderRowValue;

View File

@@ -1,28 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.qualityProfiles,
(qualityProfiles) => {
const tagList = qualityProfiles.items.map((qualityProfile) => {
const {
id,
name
} = qualityProfile;
return {
id,
name
};
});
return {
tagList
};
}
);
}
export default connect(createMapStateToProps)(FilterBuilderRowValue);

View File

@@ -5,6 +5,7 @@ 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 sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import CustomFilter from './CustomFilter';
import styles from './CustomFiltersModalContent.css';
@@ -31,7 +32,7 @@ function CustomFiltersModalContent(props) {
<ModalBody>
{
customFilters
.sort((a, b) => a.label.localeCompare(b.label))
.sort((a, b) => sortByProp(a, b, 'label'))
.map((customFilter) => {
return (
<CustomFilter

View File

@@ -4,7 +4,8 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
import sortByName from 'Utilities/Array/sortByName';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
@@ -22,7 +23,7 @@ function createMapStateToProps() {
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
const values = _.map(filteredItems.sort(sortByProp('name')), (downloadClient) => {
return {
key: downloadClient.id,
value: downloadClient.name,
@@ -33,7 +34,7 @@ function createMapStateToProps() {
if (includeAny) {
values.unshift({
key: 0,
value: '(Any)'
value: `(${translate('Any')})`
});
}

View File

@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchIndexers } from 'Store/Actions/settingsActions';
import sortByName from 'Utilities/Array/sortByName';
import sortByProp from 'Utilities/Array/sortByProp';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
@@ -18,7 +18,7 @@ function createMapStateToProps() {
items
} = indexers;
const values = items.sort(sortByName).map((indexer) => ({
const values = items.sort(sortByProp('name')).map((indexer) => ({
key: indexer.id,
value: indexer.name
}));

View File

@@ -4,13 +4,13 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByName from 'Utilities/Array/sortByName';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.qualityProfiles', sortByName),
createSortedSectionSelector('settings.qualityProfiles', sortByProp('name')),
(state, { includeNoChange }) => includeNoChange,
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
(state, { includeMixed }) => includeMixed,

View File

@@ -52,6 +52,7 @@ class SelectInput extends Component {
const {
key,
value: optionValue,
isDisabled: optionIsDisabled = false,
...otherOptionProps
} = option;
@@ -59,6 +60,7 @@ class SelectInput extends Component {
<option
key={key}
value={key}
disabled={optionIsDisabled}
{...otherOptionProps}
>
{typeof optionValue === 'function' ? optionValue() : optionValue}

View File

@@ -12,6 +12,14 @@
}
}
.hasError {
composes: hasError from '~Components/Form/Input.css';
}
.hasWarning {
composes: hasWarning from '~Components/Form/Input.css';
}
.internalInput {
flex: 1 1 0%;
margin-left: 3px;

View File

@@ -1,6 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'hasError': string;
'hasWarning': string;
'input': string;
'internalInput': string;
'isFocused': string;

View File

@@ -225,6 +225,8 @@ class TagInput extends Component {
const {
className,
inputContainerClassName,
hasError,
hasWarning,
...otherProps
} = this.props;
@@ -241,7 +243,9 @@ class TagInput extends Component {
className={className}
inputContainerClassName={classNames(
inputContainerClassName,
isFocused && styles.isFocused
isFocused && styles.isFocused,
hasError && styles.hasError,
hasWarning && styles.hasWarning
)}
value={value}
suggestions={suggestions}

View File

@@ -7,17 +7,11 @@
}
.link {
composes: link from '~Components/Link/Link.css';
max-width: 100%;
line-height: 1px;
}
.linkWithEdit {
composes: link from '~Components/Link/Link.css';
max-width: calc(100% - 9px - 4px - 2px);
line-height: 1px;
}
.editContainer {
@@ -36,5 +30,6 @@
.label {
composes: label from '~Components/Label.css';
display: flex;
max-width: 100%;
}

View File

@@ -49,7 +49,11 @@ class TextTagInputConnector extends Component {
const newTags = tag.name.startsWith('/') ? [tag.name] : split(tag.name);
newTags.forEach((newTag) => {
newValue.push(newTag.trim());
const newTagValue = newTag.trim();
if (newTagValue) {
newValue.push(newTagValue);
}
});
onChange({ name, value: newValue });
@@ -80,7 +84,12 @@ class TextTagInputConnector extends Component {
const newValue = [...valueArray];
newValue.splice(tagToReplace.index, 1);
newValue.push(newTag.name.trim());
const newTagValue = newTag.name.trim();
if (newTagValue) {
newValue.push(newTagValue);
}
onChange({ name, value: newValue });
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@
cursor: default;
}
.title {
.name {
margin-bottom: 2px;
color: var(--helpTextColor);
font-size: 10px;

View File

@@ -4,9 +4,9 @@ interface CssExports {
'label': string;
'large': string;
'medium': string;
'name': string;
'outline': string;
'small': string;
'title': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -7,7 +7,7 @@ import styles from './InfoLabel.css';
function InfoLabel(props) {
const {
className,
title,
name,
kind,
size,
outline,
@@ -25,8 +25,8 @@ function InfoLabel(props) {
)}
{...otherProps}
>
<div className={styles.title}>
{title}
<div className={styles.name}>
{name}
</div>
<div>
{children}
@@ -37,7 +37,7 @@ function InfoLabel(props) {
InfoLabel.propTypes = {
className: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired,
size: PropTypes.oneOf(sizes.all).isRequired,
outline: PropTypes.bool.isRequired,

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import FilterMenuItem from './FilterMenuItem';
import MenuContent from './MenuContent';
@@ -47,7 +48,7 @@ class FilterMenuContent extends Component {
{
customFilters
.sort((a, b) => a.label.localeCompare(b.label))
.sort(sortByProp('label'))
.map((filter) => {
return (
<FilterMenuItem

View File

@@ -3,9 +3,9 @@
padding: 0;
font-size: inherit;
}
.isDisabled {
color: var(--disabledColor);
cursor: not-allowed;
&.isDisabled {
color: var(--disabledColor);
cursor: not-allowed;
}
}

View File

@@ -7,7 +7,7 @@ import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import KeyboardShortcutsModal from './KeyboardShortcutsModal';
import MovieSearchInputConnector from './MovieSearchInputConnector';
import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector';
import PageHeaderActionsMenu from './PageHeaderActionsMenu';
import styles from './PageHeader.css';
class PageHeader extends Component {
@@ -84,6 +84,7 @@ class PageHeader extends Component {
size={14}
title={translate('Donate')}
/>
<IconButton
className={styles.translate}
title={translate('SuggestTranslationChange')}
@@ -91,7 +92,8 @@ class PageHeader extends Component {
to="https://translate.servarr.com/projects/radarr/radarr/"
size={24}
/>
<PageHeaderActionsMenuConnector
<PageHeaderActionsMenu
onKeyboardShortcutsPress={this.onOpenKeyboardShortcutsModal}
/>
</div>

View File

@@ -1,90 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import Menu from 'Components/Menu/Menu';
import MenuButton from 'Components/Menu/MenuButton';
import MenuContent from 'Components/Menu/MenuContent';
import MenuItem from 'Components/Menu/MenuItem';
import MenuItemSeparator from 'Components/Menu/MenuItemSeparator';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './PageHeaderActionsMenu.css';
function PageHeaderActionsMenu(props) {
const {
formsAuth,
onKeyboardShortcutsPress,
onRestartPress,
onShutdownPress
} = props;
return (
<div>
<Menu alignMenu={align.RIGHT}>
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon
name={icons.INTERACTIVE}
title={translate('Menu')}
/>
</MenuButton>
<MenuContent>
<MenuItem onPress={onKeyboardShortcutsPress}>
<Icon
className={styles.itemIcon}
name={icons.KEYBOARD}
/>
{translate('KeyboardShortcuts')}
</MenuItem>
<MenuItemSeparator />
<MenuItem onPress={onRestartPress}>
<Icon
className={styles.itemIcon}
name={icons.RESTART}
/>
{translate('Restart')}
</MenuItem>
<MenuItem onPress={onShutdownPress}>
<Icon
className={styles.itemIcon}
name={icons.SHUTDOWN}
kind={kinds.DANGER}
/>
{translate('Shutdown')}
</MenuItem>
{
formsAuth &&
<div className={styles.separator} />
}
{
formsAuth &&
<MenuItem
to={`${window.Radarr.urlBase}/logout`}
noRouter={true}
>
<Icon
className={styles.itemIcon}
name={icons.LOGOUT}
/>
Logout
</MenuItem>
}
</MenuContent>
</Menu>
</div>
);
}
PageHeaderActionsMenu.propTypes = {
formsAuth: PropTypes.bool.isRequired,
onKeyboardShortcutsPress: PropTypes.func.isRequired,
onRestartPress: PropTypes.func.isRequired,
onShutdownPress: PropTypes.func.isRequired
};
export default PageHeaderActionsMenu;

View File

@@ -0,0 +1,87 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import Icon from 'Components/Icon';
import Menu from 'Components/Menu/Menu';
import MenuButton from 'Components/Menu/MenuButton';
import MenuContent from 'Components/Menu/MenuContent';
import MenuItem from 'Components/Menu/MenuItem';
import MenuItemSeparator from 'Components/Menu/MenuItemSeparator';
import { align, icons, kinds } from 'Helpers/Props';
import { restart, shutdown } from 'Store/Actions/systemActions';
import translate from 'Utilities/String/translate';
import styles from './PageHeaderActionsMenu.css';
interface PageHeaderActionsMenuProps {
onKeyboardShortcutsPress(): void;
}
function PageHeaderActionsMenu(props: PageHeaderActionsMenuProps) {
const { onKeyboardShortcutsPress } = props;
const dispatch = useDispatch();
const { authentication, isDocker } = useSelector(
(state: AppState) => state.system.status.item
);
const formsAuth = authentication === 'forms';
const handleRestartPress = useCallback(() => {
dispatch(restart());
}, [dispatch]);
const handleShutdownPress = useCallback(() => {
dispatch(shutdown());
}, [dispatch]);
return (
<div>
<Menu alignMenu={align.RIGHT}>
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon name={icons.INTERACTIVE} title={translate('Menu')} />
</MenuButton>
<MenuContent>
<MenuItem onPress={onKeyboardShortcutsPress}>
<Icon className={styles.itemIcon} name={icons.KEYBOARD} />
{translate('KeyboardShortcuts')}
</MenuItem>
{isDocker ? null : (
<>
<MenuItemSeparator />
<MenuItem onPress={handleRestartPress}>
<Icon className={styles.itemIcon} name={icons.RESTART} />
{translate('Restart')}
</MenuItem>
<MenuItem onPress={handleShutdownPress}>
<Icon
className={styles.itemIcon}
name={icons.SHUTDOWN}
kind={kinds.DANGER}
/>
{translate('Shutdown')}
</MenuItem>
</>
)}
{formsAuth ? (
<>
<MenuItemSeparator />
<MenuItem to={`${window.Radarr.urlBase}/logout`} noRouter={true}>
<Icon className={styles.itemIcon} name={icons.LOGOUT} />
{translate('Logout')}
</MenuItem>
</>
) : null}
</MenuContent>
</Menu>
</div>
);
}
export default PageHeaderActionsMenu;

View File

@@ -1,56 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { restart, shutdown } from 'Store/Actions/systemActions';
import PageHeaderActionsMenu from './PageHeaderActionsMenu';
function createMapStateToProps() {
return createSelector(
(state) => state.system.status,
(status) => {
return {
formsAuth: status.item.authentication === 'forms'
};
}
);
}
const mapDispatchToProps = {
restart,
shutdown
};
class PageHeaderActionsMenuConnector extends Component {
//
// Listeners
onRestartPress = () => {
this.props.restart();
};
onShutdownPress = () => {
this.props.shutdown();
};
//
// Render
render() {
return (
<PageHeaderActionsMenu
{...this.props}
onRestartPress={this.onRestartPress}
onShutdownPress={this.onShutdownPress}
/>
);
}
}
PageHeaderActionsMenuConnector.propTypes = {
restart: PropTypes.func.isRequired,
shutdown: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(PageHeaderActionsMenuConnector);

View File

@@ -21,6 +21,10 @@
background-color: var(--darkGray);
}
&.inverse {
background-color: var(--inverseLabelColor);
}
&.primary {
background-color: var(--primaryColor);
}
@@ -61,10 +65,18 @@
.frontTextContainer {
z-index: 1;
color: var(--progressBarFrontTextColor);
&.inverse {
color: var(--inverseLabelTextColor);
}
}
.backTextContainer {
color: var(--progressBarBackTextColor);
&.inverse {
color: var(--inverseLabelTextColor);
}
}
.backTextContainer,

View File

@@ -9,6 +9,7 @@ interface CssExports {
'frontText': string;
'frontTextContainer': string;
'info': string;
'inverse': string;
'large': string;
'medium': string;
'primary': string;

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
import { kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ProgressBar.css';
function ProgressBar(props) {
@@ -57,7 +58,7 @@ function ProgressBar(props) {
enableColorImpairedMode && 'colorImpaired'
)}
role="meter"
aria-label={`Progress Bar at ${progress.toFixed(0)}%`}
aria-label={translate('ProgressBarProgress', { progress: progress.toFixed(0) })}
aria-valuenow={progress.toFixed(0)}
aria-valuemin="0"
aria-valuemax="100"

View File

@@ -1,60 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './RottenTomatoRating.css';
class RottenTomatoRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const rtRotten = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNDQ1LjE4NSA0NDQuNjg0Yy03OS4zNjkgNC4xNjctOTUuNTg3LTg2LjY1Mi0xMjYuNzI2LTg2LjAwNi0xMy4yNjguMjc5LTIzLjcyNiAxNC4xNTEtMTkuMTMzIDMwLjMyIDIuNTI1IDguODg4IDkuNTMgMjEuOTIzIDEzLjk0NCAzMC4wMTEgMTUuNTcgMjguNTQ0LTcuNDQ3IDYwLjg0NS0zNC4zODMgNjMuNTc3LTQ0Ljc2IDQuNTQtNjMuNDMzLTIxLjQyNi02Mi4yNzgtNDguMDA3IDEuMy0yOS44NCAyNi42LTYwLjMzMS42NS03My4zMDUtMjcuMTk0LTEzLjU5Ny00OS4zMDEgMzkuNTcyLTc1LjMyNSA1MS40MzktMjMuNTUzIDEwLjc0MS01Ni4yNDggMi40MTMtNjcuODcyLTIzLjc0MS04LjE2NC0xOC4zNzktNi42OC01My43NjggMjkuNjctNjcuMjcgMjIuNzA2LTguNDMzIDczLjMwNSAxMS4wMjkgNzUuOS0xMy42MjMgMi45OTItMjguNDE2LTUzLjE1NS0zMC44MTItNzAuMDYtMzcuNjI2LTI5LjkxMi0xMi4wNTUtNDcuNTY3LTM3Ljg1LTMzLjczNC02NS41MjIgMTAuMzc4LTIwLjc1NyA0MC45MTUtMjkuMjAzIDY0LjIyMy0yMC4xMSAyNy45MjIgMTAuODkyIDMyLjQwNCAzOS44NTMgNDYuNzEgNTEuODk3IDEyLjMyNCAxMC4zOCAyOS4xOSAxMS42OCA0MC4yMiA0LjU0MyA4LjEzNS01LjI2NSAxMC44NDMtMTYuODI4IDcuNzc0LTI3LjM5LTQuMDctMTQuMDIzLTE0Ljg3NS0yMi43NzMtMjUuNDE1LTMxLjM0Ni0xOC43NTgtMTUuMjQ5LTQ1LjI0LTI4LjM2LTI5LjIyMi02OS45ODMgMTMuMTMtMzQuMTEgNTEuNjQyLTM1LjM0IDUxLjY0Mi0zNS4zNCAxNS4zLTEuNzIgMjkuMDAyIDIuOSA0MC4xNjcgMTIuODc1IDE0LjkyNyAxMy4zMzUgMTcuODM0IDMxLjE2IDE1LjMzNiA1MC4xNzYtMi4yODMgMTcuMzU4LTguNDI2IDMyLjU2LTExLjYzIDQ5Ljc1OS0zLjcxNyAxOS45NjYgNi45NTQgNDAuMDg2IDI3LjI0OSA0MC44NjkgMjYuNjk0IDEuMDMxIDM0LjY5OC0xOS40ODYgMzcuOTY0LTMyLjQ5MiA0Ljc4Mi0xOS4wMjggMTEuMDU4LTM2LjY5NCAyOC43MTgtNDcuODIgMjUuMzQ2LTE1Ljk3IDYwLjU1Mi0xMi40NyA3Ni44ODYgMTguMjIyIDEyLjkyIDI0LjI4NCA4Ljc3MiA1Ny43MTUtMTEuMDQ3IDc1Ljk3LTguODkyIDguMTg4LTE5LjU4NCAxMS4wNzUtMzEuMTQ4IDExLjE1Ni0xNi41ODUuMTE3LTMzLjE2Mi0uMjktNDguNTU2IDcuNDcxLTEwLjQ4IDUuMjgxLTE1LjA0NyAxMy44ODgtMTUuMDQ1IDI1LjQyMyAwIDExLjI0MiA1Ljg1MyAxOC41ODUgMTUuMzM2IDIzLjM2MyAxNy44NiA5LjAwMyAzNy41NzcgMTAuODQzIDU2Ljg3MSAxNC4yMjIgMjcuOTggNC45IDUyLjU4MSAxNC43NTUgNjguMzc1IDQwLjcyLjE0Mi4yMjguMjguNDU4LjQxNS42OSAxOC4xMzkgMzAuNzQxLS44MzEgNzUuMDA1LTM2LjQ3NiA3Ni44NzgiIGZpbGw9IiMwQUM4NTUiLz48L3N2Zz4=';
const rtFresh = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNDc4LjI5IDI5Ni45OGMtMy45OS02My45NjYtMzYuNTItMTExLjgyLTg1LjQ2OC0xMzguNTggMC4yNzggMS41Ni0xLjEwOSAzLjUwOC0yLjY4OCAyLjgxOC0zMi4wMTYtMTQuMDA2LTg2LjMyOCAzMS4zMi0xMjQuMjggNy41ODQgMC4yODUgOC41MTktMS4zNzggNTAuMDcyLTU5LjkxNCA1Mi40ODMtMS4zODIgMC4wNTYtMi4xNDItMS4zNTUtMS4yNjgtMi4zNTQgNy44MjgtOC45MjkgMTUuNzMyLTMxLjUzNSA0LjM2Ny00My41ODYtMjQuMzM4IDIxLjgxLTM4LjQ3MiAzMC4wMTctODUuMTM4IDE5LjE4Ni0yOS44NzggMzEuMjQxLTQ2LjgwOSA3NC00My40ODUgMTI3LjI2IDYuNzggMTA4Ljc0IDEwOC42MyAxNzAuODkgMjExLjE5IDE2NC40OSAxMDIuNTYtNi4zOTUgMTkzLjQ3LTgwLjU3MiAxODYuNjgtMTg5LjMxIiBmaWxsPSIjRkEzMjBBIi8+PHBhdGggZD0iTTI5MS4zNzUgMTMyLjI5M2MyMS4wNzUtNS4wMjMgODEuNjkzLS40OSAxMDEuMTE0IDI1LjI3NCAxLjE2NiAxLjU0NS0uNDc1IDQuNDY4LTIuMzU1IDMuNjQ4LTMyLjAxNi0xNC4wMDYtODYuMzI4IDMxLjMyLTEyNC4yODIgNy41ODQuMjg1IDguNTE5LTEuMzc4IDUwLjA3Mi01OS45MTQgNTIuNDgzLTEuMzgyLjA1Ni0yLjE0Mi0xLjM1NS0xLjI2OC0yLjM1NCA3LjgyOC04LjkyOSAxNS43My0zMS41MzUgNC4zNjctNDMuNTg2LTI2LjUxMiAyMy43NTgtNDAuODg0IDMxLjM5Mi05OC40MjYgMTUuODM4LTEuODgzLS41MDgtMS4yNDEtMy41MzUuNzYyLTQuMjk4IDEwLjg3Ni00LjE1NyAzNS41MTUtMjIuMzYxIDU4LjgyNC0zMC4zODUgNC40MzgtMS41MjYgOC44NjItMi43MSAxMy4xOC0zLjQtMjUuNjY1LTIuMjkzLTM3LjIzNS01Ljg2Mi01My41NTktMy40LTEuNzg5LjI3LTMuMDA0LTEuODEzLTEuODk1LTMuMjQxIDIxLjk5NS0yOC4zMzIgNjIuNTEzLTM2Ljg4OCA4Ny41MTItMjEuODM3LTE1LjQxLTE5LjA5NC0yNy40OC0zNC4zMjEtMjcuNDgtMzQuMzIxbDI4LjYwMS0xNi4yNDZzMTEuODE3IDI2LjQgMjAuNDE0IDQ1LjYxNGMyMS4yNzUtMzEuNDM1IDYwLjg2LTM0LjMzNiA3Ny41ODUtMTIuMDMzLjk5MiAxLjMyNi0uMDQ1IDMuMjEtMS43MDIgMy4xNzEtMTMuNjEyLS4zMzEtMjEuMTA3IDEyLjA1LTIxLjY3NSAyMS40NjZsLjE5Ny4wMjMiIGZpbGw9IiMwMDkxMkQiLz48L3N2Zz4=';
const rating = ratings.rottenTomatoes;
let ratingString = '0%';
let ratingImage = rtFresh;
if (rating) {
ratingString = `${rating.value}%`;
ratingImage = rating.value > 50 ? rtFresh : rtRotten;
}
return (
<span>
{
!hideIcon &&
<img
className={styles.image}
src={ratingImage}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
RottenTomatoRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
RottenTomatoRating.defaultProps = {
iconSize: 14
};
export default RottenTomatoRating;

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Ratings, RatingValues } from 'Movie/Movie';
import translate from 'Utilities/String/translate';
import styles from './RottenTomatoRating.css';
interface RottenTomatoRatingProps {
ratings: Ratings;
iconSize?: number;
hideIcon?: boolean;
}
function RottenTomatoRating(props: RottenTomatoRatingProps) {
const { ratings, iconSize = 14, hideIcon = false } = props;
const rtRotten =
'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNDQ1LjE4NSA0NDQuNjg0Yy03OS4zNjkgNC4xNjctOTUuNTg3LTg2LjY1Mi0xMjYuNzI2LTg2LjAwNi0xMy4yNjguMjc5LTIzLjcyNiAxNC4xNTEtMTkuMTMzIDMwLjMyIDIuNTI1IDguODg4IDkuNTMgMjEuOTIzIDEzLjk0NCAzMC4wMTEgMTUuNTcgMjguNTQ0LTcuNDQ3IDYwLjg0NS0zNC4zODMgNjMuNTc3LTQ0Ljc2IDQuNTQtNjMuNDMzLTIxLjQyNi02Mi4yNzgtNDguMDA3IDEuMy0yOS44NCAyNi42LTYwLjMzMS42NS03My4zMDUtMjcuMTk0LTEzLjU5Ny00OS4zMDEgMzkuNTcyLTc1LjMyNSA1MS40MzktMjMuNTUzIDEwLjc0MS01Ni4yNDggMi40MTMtNjcuODcyLTIzLjc0MS04LjE2NC0xOC4zNzktNi42OC01My43NjggMjkuNjctNjcuMjcgMjIuNzA2LTguNDMzIDczLjMwNSAxMS4wMjkgNzUuOS0xMy42MjMgMi45OTItMjguNDE2LTUzLjE1NS0zMC44MTItNzAuMDYtMzcuNjI2LTI5LjkxMi0xMi4wNTUtNDcuNTY3LTM3Ljg1LTMzLjczNC02NS41MjIgMTAuMzc4LTIwLjc1NyA0MC45MTUtMjkuMjAzIDY0LjIyMy0yMC4xMSAyNy45MjIgMTAuODkyIDMyLjQwNCAzOS44NTMgNDYuNzEgNTEuODk3IDEyLjMyNCAxMC4zOCAyOS4xOSAxMS42OCA0MC4yMiA0LjU0MyA4LjEzNS01LjI2NSAxMC44NDMtMTYuODI4IDcuNzc0LTI3LjM5LTQuMDctMTQuMDIzLTE0Ljg3NS0yMi43NzMtMjUuNDE1LTMxLjM0Ni0xOC43NTgtMTUuMjQ5LTQ1LjI0LTI4LjM2LTI5LjIyMi02OS45ODMgMTMuMTMtMzQuMTEgNTEuNjQyLTM1LjM0IDUxLjY0Mi0zNS4zNCAxNS4zLTEuNzIgMjkuMDAyIDIuOSA0MC4xNjcgMTIuODc1IDE0LjkyNyAxMy4zMzUgMTcuODM0IDMxLjE2IDE1LjMzNiA1MC4xNzYtMi4yODMgMTcuMzU4LTguNDI2IDMyLjU2LTExLjYzIDQ5Ljc1OS0zLjcxNyAxOS45NjYgNi45NTQgNDAuMDg2IDI3LjI0OSA0MC44NjkgMjYuNjk0IDEuMDMxIDM0LjY5OC0xOS40ODYgMzcuOTY0LTMyLjQ5MiA0Ljc4Mi0xOS4wMjggMTEuMDU4LTM2LjY5NCAyOC43MTgtNDcuODIgMjUuMzQ2LTE1Ljk3IDYwLjU1Mi0xMi40NyA3Ni44ODYgMTguMjIyIDEyLjkyIDI0LjI4NCA4Ljc3MiA1Ny43MTUtMTEuMDQ3IDc1Ljk3LTguODkyIDguMTg4LTE5LjU4NCAxMS4wNzUtMzEuMTQ4IDExLjE1Ni0xNi41ODUuMTE3LTMzLjE2Mi0uMjktNDguNTU2IDcuNDcxLTEwLjQ4IDUuMjgxLTE1LjA0NyAxMy44ODgtMTUuMDQ1IDI1LjQyMyAwIDExLjI0MiA1Ljg1MyAxOC41ODUgMTUuMzM2IDIzLjM2MyAxNy44NiA5LjAwMyAzNy41NzcgMTAuODQzIDU2Ljg3MSAxNC4yMjIgMjcuOTggNC45IDUyLjU4MSAxNC43NTUgNjguMzc1IDQwLjcyLjE0Mi4yMjguMjguNDU4LjQxNS42OSAxOC4xMzkgMzAuNzQxLS44MzEgNzUuMDA1LTM2LjQ3NiA3Ni44NzgiIGZpbGw9IiMwQUM4NTUiLz48L3N2Zz4=';
const rtFresh =
'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNDc4LjI5IDI5Ni45OGMtMy45OS02My45NjYtMzYuNTItMTExLjgyLTg1LjQ2OC0xMzguNTggMC4yNzggMS41Ni0xLjEwOSAzLjUwOC0yLjY4OCAyLjgxOC0zMi4wMTYtMTQuMDA2LTg2LjMyOCAzMS4zMi0xMjQuMjggNy41ODQgMC4yODUgOC41MTktMS4zNzggNTAuMDcyLTU5LjkxNCA1Mi40ODMtMS4zODIgMC4wNTYtMi4xNDItMS4zNTUtMS4yNjgtMi4zNTQgNy44MjgtOC45MjkgMTUuNzMyLTMxLjUzNSA0LjM2Ny00My41ODYtMjQuMzM4IDIxLjgxLTM4LjQ3MiAzMC4wMTctODUuMTM4IDE5LjE4Ni0yOS44NzggMzEuMjQxLTQ2LjgwOSA3NC00My40ODUgMTI3LjI2IDYuNzggMTA4Ljc0IDEwOC42MyAxNzAuODkgMjExLjE5IDE2NC40OSAxMDIuNTYtNi4zOTUgMTkzLjQ3LTgwLjU3MiAxODYuNjgtMTg5LjMxIiBmaWxsPSIjRkEzMjBBIi8+PHBhdGggZD0iTTI5MS4zNzUgMTMyLjI5M2MyMS4wNzUtNS4wMjMgODEuNjkzLS40OSAxMDEuMTE0IDI1LjI3NCAxLjE2NiAxLjU0NS0uNDc1IDQuNDY4LTIuMzU1IDMuNjQ4LTMyLjAxNi0xNC4wMDYtODYuMzI4IDMxLjMyLTEyNC4yODIgNy41ODQuMjg1IDguNTE5LTEuMzc4IDUwLjA3Mi01OS45MTQgNTIuNDgzLTEuMzgyLjA1Ni0yLjE0Mi0xLjM1NS0xLjI2OC0yLjM1NCA3LjgyOC04LjkyOSAxNS43My0zMS41MzUgNC4zNjctNDMuNTg2LTI2LjUxMiAyMy43NTgtNDAuODg0IDMxLjM5Mi05OC40MjYgMTUuODM4LTEuODgzLS41MDgtMS4yNDEtMy41MzUuNzYyLTQuMjk4IDEwLjg3Ni00LjE1NyAzNS41MTUtMjIuMzYxIDU4LjgyNC0zMC4zODUgNC40MzgtMS41MjYgOC44NjItMi43MSAxMy4xOC0zLjQtMjUuNjY1LTIuMjkzLTM3LjIzNS01Ljg2Mi01My41NTktMy40LTEuNzg5LjI3LTMuMDA0LTEuODEzLTEuODk1LTMuMjQxIDIxLjk5NS0yOC4zMzIgNjIuNTEzLTM2Ljg4OCA4Ny41MTItMjEuODM3LTE1LjQxLTE5LjA5NC0yNy40OC0zNC4zMjEtMjcuNDgtMzQuMzIxbDI4LjYwMS0xNi4yNDZzMTEuODE3IDI2LjQgMjAuNDE0IDQ1LjYxNGMyMS4yNzUtMzEuNDM1IDYwLjg2LTM0LjMzNiA3Ny41ODUtMTIuMDMzLjk5MiAxLjMyNi0uMDQ1IDMuMjEtMS43MDIgMy4xNzEtMTMuNjEyLS4zMzEtMjEuMTA3IDEyLjA1LTIxLjY3NSAyMS40NjZsLjE5Ny4wMjMiIGZpbGw9IiMwMDkxMkQiLz48L3N2Zz4=';
const { rottenTomatoes: rottenTomatoesRatings = {} as RatingValues } =
ratings;
const { value = 0 } = rottenTomatoesRatings;
const ratingImage = value > 50 ? rtFresh : rtRotten;
return (
<span>
{!hideIcon && (
<img
className={styles.image}
alt={translate('RottenTomatoesRating')}
src={ratingImage}
style={{
height: `${iconSize}px`,
}}
/>
)}
{value}%
</span>
);
}
export default RottenTomatoRating;

View File

@@ -202,6 +202,8 @@ class SignalRConnector extends Component {
if (action === 'updated') {
this.props.dispatchUpdateItem({ section, ...body.resource });
repopulatePage('movieUpdated');
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: body.resource.id });
}

View File

@@ -1,66 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import TableRowCell from './TableRowCell';
import styles from './RelativeDateCell.css';
class RelativeDateCell extends PureComponent {
//
// Render
render() {
const {
className,
date,
includeSeconds,
showRelativeDates,
shortDateFormat,
longDateFormat,
timeFormat,
component: Component,
dispatch,
...otherProps
} = this.props;
if (!date) {
return (
<Component
className={className}
{...otherProps}
/>
);
}
return (
<Component
className={className}
title={formatDateTime(date, longDateFormat, timeFormat, { includeSeconds, includeRelativeDay: !showRelativeDates })}
{...otherProps}
>
{getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds, timeForToday: true })}
</Component>
);
}
}
RelativeDateCell.propTypes = {
className: PropTypes.string.isRequired,
date: PropTypes.string,
includeSeconds: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
component: PropTypes.elementType,
dispatch: PropTypes.func
};
RelativeDateCell.defaultProps = {
className: styles.cell,
includeSeconds: false,
component: TableRowCell
};
export default RelativeDateCell;

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { useSelector } from 'react-redux';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import TableRowCell from './TableRowCell';
import styles from './RelativeDateCell.css';
interface RelativeDateCellProps {
className?: string;
date?: string;
includeSeconds?: boolean;
includeTime?: boolean;
timeForToday?: boolean;
component?: React.ElementType;
}
function RelativeDateCell(props: RelativeDateCellProps) {
const {
className = styles.cell,
date,
includeSeconds = false,
includeTime = false,
timeForToday = true,
component: Component = TableRowCell,
...otherProps
} = props;
const { showRelativeDates, shortDateFormat, longDateFormat, timeFormat } =
useSelector(createUISettingsSelector());
if (!date) {
return <Component className={className} {...otherProps} />;
}
return (
<Component
className={className}
title={formatDateTime(date, longDateFormat, timeFormat, {
includeSeconds,
includeRelativeDay: !showRelativeDates,
})}
{...otherProps}
>
{getRelativeDate({
date,
shortDateFormat,
showRelativeDates,
timeFormat,
includeSeconds,
includeTime,
timeForToday,
})}
</Component>
);
}
export default RelativeDateCell;

View File

@@ -1,20 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import RelativeDateCell from './RelativeDateCell';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
export default connect(createMapStateToProps, null)(RelativeDateCell);

View File

@@ -5,6 +5,7 @@ type PropertyFunction<T> = () => T;
interface Column {
name: string;
label: string | PropertyFunction<string> | React.ReactNode;
className?: string;
columnLabel?: string;
isSortable?: boolean;
isVisible: boolean;

View File

@@ -66,7 +66,9 @@ function Table(props) {
columns.map((column) => {
const {
name,
isVisible
isVisible,
isSortable,
...otherColumnProps
} = column;
if (!isVisible) {
@@ -84,6 +86,7 @@ function Table(props) {
name={name}
isSortable={false}
{...otherProps}
{...otherColumnProps}
>
<TableOptionsModalWrapper
columns={columns}

View File

@@ -0,0 +1,54 @@
import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
interface PagingOptions {
page: number;
totalPages: number;
gotoPage: ({ page }: { page: number }) => void;
}
function usePaging(options: PagingOptions) {
const { page, totalPages, gotoPage } = options;
const dispatch = useDispatch();
const handleFirstPagePress = useCallback(() => {
dispatch(gotoPage({ page: 1 }));
}, [dispatch, gotoPage]);
const handlePreviousPagePress = useCallback(() => {
dispatch(gotoPage({ page: Math.max(page - 1, 1) }));
}, [page, dispatch, gotoPage]);
const handleNextPagePress = useCallback(() => {
dispatch(gotoPage({ page: Math.min(page + 1, totalPages) }));
}, [page, totalPages, dispatch, gotoPage]);
const handleLastPagePress = useCallback(() => {
dispatch(gotoPage({ page: totalPages }));
}, [totalPages, dispatch, gotoPage]);
const handlePageSelect = useCallback(
(page: number) => {
dispatch(gotoPage({ page }));
},
[dispatch, gotoPage]
);
return useMemo(() => {
return {
handleFirstPagePress,
handlePreviousPagePress,
handleNextPagePress,
handleLastPagePress,
handlePageSelect,
};
}, [
handleFirstPagePress,
handlePreviousPagePress,
handleNextPagePress,
handleLastPagePress,
handlePageSelect,
]);
}
export default usePaging;

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import { kinds } from 'Helpers/Props';
import sortByProp from 'Utilities/Array/sortByProp';
import Label from './Label';
import styles from './TagList.css';
@@ -8,7 +9,7 @@ function TagList({ tags, tagList }) {
const sortedTags = tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
.filter((tag) => !!tag)
.sort((a, b) => a.label.localeCompare(b.label));
.sort(sortByProp('label'));
return (
<div className={styles.tags}>

View File

@@ -1,57 +0,0 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import styles from './TmdbRating.css';
class TmdbRating extends PureComponent {
//
// Render
render() {
const {
ratings,
hideIcon,
iconSize
} = this.props;
const tmdbImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTAuMjQgODEuNTIiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeTE9IjQwLjc2IiB4Mj0iMTkwLjI0IiB5Mj0iNDAuNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5MGNlYTEiLz48c3RvcCBvZmZzZXQ9Ii41NiIgc3RvcC1jb2xvcj0iIzNjYmVjOSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwYjNlNSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xMDUuNjcgMzYuMDZoNjYuOWExNy42NyAxNy42NyAwIDAwMTcuNjctMTcuNjZBMTcuNjcgMTcuNjcgMCAwMDE3Mi41Ny43M2gtNjYuOUExNy42NyAxNy42NyAwIDAwODggMTguNGExNy42NyAxNy42NyAwIDAwMTcuNjcgMTcuNjZ6bS04OCA0NWg3Ni45YTE3LjY3IDE3LjY3IDAgMDAxNy42Ny0xNy42NiAxNy42NyAxNy42NyAwIDAwLTE3LjY3LTE3LjY3aC03Ni45QTE3LjY3IDE3LjY3IDAgMDAwIDYzLjRhMTcuNjcgMTcuNjcgMCAwMDE3LjY3IDE3LjY2em0tNy4yNi00NS42NGg3LjhWNi45MmgxMC4xVjBoLTI4djYuOWgxMC4xem0yOC4xIDBoNy44VjguMjVoLjFsOSAyNy4xNWg2bDkuMy0yNy4xNWguMVYzNS40aDcuOFYwSDY2Ljc2bC04LjIgMjMuMWgtLjFMNTAuMzEgMGgtMTEuOHptMTEzLjkyIDIwLjI1YTE1LjA3IDE1LjA3IDAgMDAtNC41Mi01LjUyIDE4LjU3IDE4LjU3IDAgMDAtNi42OC0zLjA4IDMzLjU0IDMzLjU0IDAgMDAtOC4wNy0xaC0xMS43djM1LjRoMTIuNzVhMjQuNTggMjQuNTggMCAwMDcuNTUtMS4xNSAxOS4zNCAxOS4zNCAwIDAwNi4zNS0zLjMyIDE2LjI3IDE2LjI3IDAgMDA0LjM3LTUuNSAxNi45MSAxNi45MSAwIDAwMS42My03LjU4IDE4LjUgMTguNSAwIDAwLTEuNjgtOC4yNXpNMTQ1IDY4LjZhOC44IDguOCAwIDAxLTIuNjQgMy40IDEwLjcgMTAuNyAwIDAxLTQgMS44MiAyMS41NyAyMS41NyAwIDAxLTUgLjU1aC00LjA1di0yMWg0LjZhMTcgMTcgMCAwMTQuNjcuNjMgMTEuNjYgMTEuNjYgMCAwMTMuODggMS44N0E5LjE0IDkuMTQgMCAwMTE0NSA1OWE5Ljg3IDkuODcgMCAwMTEgNC41MiAxMS44OSAxMS44OSAwIDAxLTEgNS4wOHptNDQuNjMtLjEzYTggOCAwIDAwLTEuNTgtMi42MiA4LjM4IDguMzggMCAwMC0yLjQyLTEuODUgMTAuMzEgMTAuMzEgMCAwMC0zLjE3LTF2LS4xYTkuMjIgOS4yMiAwIDAwNC40Mi0yLjgyIDcuNDMgNy40MyAwIDAwMS42OC01IDguNDIgOC40MiAwIDAwLTEuMTUtNC42NSA4LjA5IDguMDkgMCAwMC0zLTIuNzIgMTIuNTYgMTIuNTYgMCAwMC00LjE4LTEuMyAzMi44NCAzMi44NCAwIDAwLTQuNjItLjMzaC0xMy4ydjM1LjRoMTQuNWEyMi40MSAyMi40MSAwIDAwNC43Mi0uNSAxMy41MyAxMy41MyAwIDAwNC4yOC0xLjY1IDkuNDIgOS40MiAwIDAwMy4xLTMgOC41MiA4LjUyIDAgMDAxLjItNC42OCA5LjM5IDkuMzkgMCAwMC0uNTUtMy4xOHptLTE5LjQyLTE1Ljc1aDUuM2ExMCAxMCAwIDAxMS44NS4xOCA2LjE4IDYuMTggMCAwMTEuNy41NyAzLjM5IDMuMzkgMCAwMTEuMjIgMS4xMyAzLjIyIDMuMjIgMCAwMS40OCAxLjgyIDMuNjMgMy42MyAwIDAxLS40MyAxLjggMy40IDMuNCAwIDAxLTEuMTIgMS4yIDQuOTIgNC45MiAwIDAxLTEuNTguNjUgNy41MSA3LjUxIDAgMDEtMS43Ny4yaC01LjY1em0xMS43MiAyMGEzLjkgMy45IDAgMDEtMS4yMiAxLjMgNC42NCA0LjY0IDAgMDEtMS42OC43IDguMTggOC4xOCAwIDAxLTEuODIuMmgtN3YtOGg1LjlhMTUuMzUgMTUuMzUgMCAwMTIgLjE1IDguNDcgOC40NyAwIDAxMi4wNS41NSA0IDQgMCAwMTEuNTcgMS4xOCAzLjExIDMuMTEgMCAwMS42MyAyIDMuNzEgMy43MSAwIDAxLS40MyAxLjkyeiIgZmlsbD0idXJsKCNhKSIvPjwvc3ZnPg==';
const rating = ratings.tmdb;
let ratingString = '0%';
if (rating) {
ratingString = `${(rating.value * 10).toFixed()}%`;
}
return (
<span title={`${rating.votes} votes`}>
{
!hideIcon &&
<img
className={styles.image}
src={tmdbImage}
style={{
height: `${iconSize}px`
}}
/>
}
{ratingString}
</span>
);
}
}
TmdbRating.propTypes = {
ratings: PropTypes.object.isRequired,
iconSize: PropTypes.number.isRequired,
hideIcon: PropTypes.bool
};
TmdbRating.defaultProps = {
iconSize: 14
};
export default TmdbRating;

View File

@@ -0,0 +1,46 @@
import React from 'react';
import Tooltip from 'Components/Tooltip/Tooltip';
import { kinds, tooltipPositions } from 'Helpers/Props';
import { Ratings } from 'Movie/Movie';
import translate from 'Utilities/String/translate';
import styles from './TmdbRating.css';
interface TmdbRatingProps {
ratings: Ratings;
iconSize?: number;
hideIcon?: boolean;
}
function TmdbRating(props: TmdbRatingProps) {
const { ratings, iconSize = 14, hideIcon = false } = props;
const tmdbImage =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTAuMjQgODEuNTIiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeTE9IjQwLjc2IiB4Mj0iMTkwLjI0IiB5Mj0iNDAuNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5MGNlYTEiLz48c3RvcCBvZmZzZXQ9Ii41NiIgc3RvcC1jb2xvcj0iIzNjYmVjOSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwYjNlNSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xMDUuNjcgMzYuMDZoNjYuOWExNy42NyAxNy42NyAwIDAwMTcuNjctMTcuNjZBMTcuNjcgMTcuNjcgMCAwMDE3Mi41Ny43M2gtNjYuOUExNy42NyAxNy42NyAwIDAwODggMTguNGExNy42NyAxNy42NyAwIDAwMTcuNjcgMTcuNjZ6bS04OCA0NWg3Ni45YTE3LjY3IDE3LjY3IDAgMDAxNy42Ny0xNy42NiAxNy42NyAxNy42NyAwIDAwLTE3LjY3LTE3LjY3aC03Ni45QTE3LjY3IDE3LjY3IDAgMDAwIDYzLjRhMTcuNjcgMTcuNjcgMCAwMDE3LjY3IDE3LjY2em0tNy4yNi00NS42NGg3LjhWNi45MmgxMC4xVjBoLTI4djYuOWgxMC4xem0yOC4xIDBoNy44VjguMjVoLjFsOSAyNy4xNWg2bDkuMy0yNy4xNWguMVYzNS40aDcuOFYwSDY2Ljc2bC04LjIgMjMuMWgtLjFMNTAuMzEgMGgtMTEuOHptMTEzLjkyIDIwLjI1YTE1LjA3IDE1LjA3IDAgMDAtNC41Mi01LjUyIDE4LjU3IDE4LjU3IDAgMDAtNi42OC0zLjA4IDMzLjU0IDMzLjU0IDAgMDAtOC4wNy0xaC0xMS43djM1LjRoMTIuNzVhMjQuNTggMjQuNTggMCAwMDcuNTUtMS4xNSAxOS4zNCAxOS4zNCAwIDAwNi4zNS0zLjMyIDE2LjI3IDE2LjI3IDAgMDA0LjM3LTUuNSAxNi45MSAxNi45MSAwIDAwMS42My03LjU4IDE4LjUgMTguNSAwIDAwLTEuNjgtOC4yNXpNMTQ1IDY4LjZhOC44IDguOCAwIDAxLTIuNjQgMy40IDEwLjcgMTAuNyAwIDAxLTQgMS44MiAyMS41NyAyMS41NyAwIDAxLTUgLjU1aC00LjA1di0yMWg0LjZhMTcgMTcgMCAwMTQuNjcuNjMgMTEuNjYgMTEuNjYgMCAwMTMuODggMS44N0E5LjE0IDkuMTQgMCAwMTE0NSA1OWE5Ljg3IDkuODcgMCAwMTEgNC41MiAxMS44OSAxMS44OSAwIDAxLTEgNS4wOHptNDQuNjMtLjEzYTggOCAwIDAwLTEuNTgtMi42MiA4LjM4IDguMzggMCAwMC0yLjQyLTEuODUgMTAuMzEgMTAuMzEgMCAwMC0zLjE3LTF2LS4xYTkuMjIgOS4yMiAwIDAwNC40Mi0yLjgyIDcuNDMgNy40MyAwIDAwMS42OC01IDguNDIgOC40MiAwIDAwLTEuMTUtNC42NSA4LjA5IDguMDkgMCAwMC0zLTIuNzIgMTIuNTYgMTIuNTYgMCAwMC00LjE4LTEuMyAzMi44NCAzMi44NCAwIDAwLTQuNjItLjMzaC0xMy4ydjM1LjRoMTQuNWEyMi40MSAyMi40MSAwIDAwNC43Mi0uNSAxMy41MyAxMy41MyAwIDAwNC4yOC0xLjY1IDkuNDIgOS40MiAwIDAwMy4xLTMgOC41MiA4LjUyIDAgMDAxLjItNC42OCA5LjM5IDkuMzkgMCAwMC0uNTUtMy4xOHptLTE5LjQyLTE1Ljc1aDUuM2ExMCAxMCAwIDAxMS44NS4xOCA2LjE4IDYuMTggMCAwMTEuNy41NyAzLjM5IDMuMzkgMCAwMTEuMjIgMS4xMyAzLjIyIDMuMjIgMCAwMS40OCAxLjgyIDMuNjMgMy42MyAwIDAxLS40MyAxLjggMy40IDMuNCAwIDAxLTEuMTIgMS4yIDQuOTIgNC45MiAwIDAxLTEuNTguNjUgNy41MSA3LjUxIDAgMDEtMS43Ny4yaC01LjY1em0xMS43MiAyMGEzLjkgMy45IDAgMDEtMS4yMiAxLjMgNC42NCA0LjY0IDAgMDEtMS42OC43IDguMTggOC4xOCAwIDAxLTEuODIuMmgtN3YtOGg1LjlhMTUuMzUgMTUuMzUgMCAwMTIgLjE1IDguNDcgOC40NyAwIDAxMi4wNS41NSA0IDQgMCAwMTEuNTcgMS4xOCAzLjExIDMuMTEgMCAwMS42MyAyIDMuNzEgMy43MSAwIDAxLS40MyAxLjkyeiIgZmlsbD0idXJsKCNhKSIvPjwvc3ZnPg==';
const { value = 0, votes = 0 } = ratings.tmdb;
return (
<Tooltip
anchor={
<span>
{!hideIcon && (
<img
className={styles.image}
alt={translate('TmdbRating')}
src={tmdbImage}
style={{
height: `${iconSize}px`,
}}
/>
)}
{(value * 10).toFixed()}%
</span>
}
tooltip={translate('CountVotes', { votes })}
kind={kinds.INVERSE}
position={tooltipPositions.TOP}
/>
);
}
export default TmdbRating;

View File

@@ -5,9 +5,8 @@ import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import withScrollPosition from 'Components/withScrollPosition';
import { executeCommand } from 'Store/Actions/commandActions';
import { addImportExclusions, addMovies, clearAddMovie, fetchDiscoverMovies, setListMovieFilter, setListMovieSort, setListMovieTableOption, setListMovieView } from 'Store/Actions/discoverMovieActions';
import { addImportListExclusions, addMovies, clearAddMovie, fetchDiscoverMovies, setListMovieFilter, setListMovieSort, setListMovieTableOption, setListMovieView } from 'Store/Actions/discoverMovieActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import scrollPositions from 'Store/scrollPositions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
@@ -43,10 +42,6 @@ function createMapDispatchToProps(dispatch, props) {
dispatch(fetchRootFolders());
},
dispatchFetchImportExclusions() {
dispatch(fetchImportExclusions());
},
dispatchClearListMovie() {
dispatch(clearAddMovie());
},
@@ -75,8 +70,8 @@ function createMapDispatchToProps(dispatch, props) {
dispatch(addMovies({ ids, addOptions }));
},
dispatchAddImportExclusions(exclusions) {
dispatch(addImportExclusions(exclusions));
dispatchAddImportListExclusions(exclusions) {
dispatch(addImportListExclusions(exclusions));
},
onImportListSyncPress() {
@@ -96,7 +91,6 @@ class DiscoverMovieConnector extends Component {
componentDidMount() {
registerPagePopulator(this.repopulate);
this.props.dispatchFetchRootFolders();
this.props.dispatchFetchImportExclusions();
this.props.dispatchFetchListMovies();
}
@@ -121,7 +115,7 @@ class DiscoverMovieConnector extends Component {
};
onExcludeMoviesPress =({ ids }) => {
this.props.dispatchAddImportExclusions({ ids });
this.props.dispatchAddImportListExclusions({ ids });
};
//
@@ -144,13 +138,12 @@ class DiscoverMovieConnector extends Component {
DiscoverMovieConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
view: PropTypes.string.isRequired,
dispatchFetchImportExclusions: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired,
dispatchFetchListMovies: PropTypes.func.isRequired,
dispatchClearListMovie: PropTypes.func.isRequired,
dispatchSetListMovieView: PropTypes.func.isRequired,
dispatchAddMovies: PropTypes.func.isRequired,
dispatchAddImportExclusions: PropTypes.func.isRequired
dispatchAddImportListExclusions: PropTypes.func.isRequired
};
export default withScrollPosition(

View File

@@ -8,9 +8,9 @@ import DiscoverMovieFooter from './DiscoverMovieFooter';
function createMapStateToProps() {
return createSelector(
(state) => state.discoverMovie,
(state) => state.settings.importExclusions,
(state) => state.settings.importListExclusions,
(state, { selectedIds }) => selectedIds,
(discoverMovie, importExclusions, selectedIds) => {
(discoverMovie, importListExclusions, selectedIds) => {
const {
monitor: defaultMonitor,
qualityProfileId: defaultQualityProfileId,
@@ -25,7 +25,7 @@ function createMapStateToProps() {
const {
isSaving
} = importExclusions;
} = importListExclusions;
return {
selectedCount: selectedIds.length,

View File

@@ -1,11 +1,11 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addImportExclusions } from 'Store/Actions/discoverMovieActions';
import { addImportListExclusions } from 'Store/Actions/discoverMovieActions';
import ExcludeMovieModalContent from './ExcludeMovieModalContent';
const mapDispatchToProps = {
addImportExclusions
addImportListExclusions
};
class ExcludeMovieModalContentConnector extends Component {
@@ -14,7 +14,7 @@ class ExcludeMovieModalContentConnector extends Component {
// Listeners
onExcludePress = () => {
this.props.addImportExclusions({ ids: [this.props.tmdbId] });
this.props.addImportListExclusions({ ids: [this.props.tmdbId] });
this.props.onModalClose(true);
};
@@ -37,7 +37,7 @@ ExcludeMovieModalContentConnector.propTypes = {
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
onModalClose: PropTypes.func.isRequired,
addImportExclusions: PropTypes.func.isRequired
addImportListExclusions: PropTypes.func.isRequired
};
export default connect(undefined, mapDispatchToProps)(ExcludeMovieModalContentConnector);

View File

@@ -56,15 +56,6 @@ function DiscoverMovieSortMenu(props) {
{translate('InCinemas')}
</SortMenuItem>
<SortMenuItem
name="physicalRelease"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('PhysicalRelease')}
</SortMenuItem>
<SortMenuItem
name="digitalRelease"
sortKey={sortKey}
@@ -74,6 +65,15 @@ function DiscoverMovieSortMenu(props) {
{translate('DigitalRelease')}
</SortMenuItem>
<SortMenuItem
name="physicalRelease"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('PhysicalRelease')}
</SortMenuItem>
<SortMenuItem
name="runtime"
sortKey={sortKey}
@@ -84,12 +84,30 @@ function DiscoverMovieSortMenu(props) {
</SortMenuItem>
<SortMenuItem
name="ratings"
name="tmdbRating"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Rating')}
{translate('TmdbRating')}
</SortMenuItem>
<SortMenuItem
name="imdbRating"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('ImdbRating')}
</SortMenuItem>
<SortMenuItem
name="rottenTomatoesRating"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('RottenTomatoesRating')}
</SortMenuItem>
<SortMenuItem

View File

@@ -1,3 +1,4 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import { icons } from 'Helpers/Props';
@@ -14,25 +15,30 @@ const rows = [
showProp: 'showYear',
valueProp: 'year'
},
{
name: 'studio',
showProp: 'showStudio',
valueProp: 'studio'
},
{
name: 'genres',
showProp: 'showGenres',
valueProp: 'genres'
},
{
name: 'ratings',
showProp: 'showRatings',
valueProp: 'ratings'
name: 'tmdbRating',
showProp: 'showTmdbRating',
valueProp: 'ratings.tmdb.value'
},
{
name: 'imdbRating',
showProp: 'showImdbRating',
valueProp: 'ratings.imdb.value'
},
{
name: 'certification',
showProp: 'showCertification',
valueProp: 'certification'
},
{
name: 'studio',
showProp: 'showStudio',
valueProp: 'studio'
}
];
@@ -43,11 +49,7 @@ function isVisible(row, props) {
valueProp
} = row;
if (props[valueProp] == null) {
return false;
}
return props[showProp] || props.sortKey === name;
return _.has(props, valueProp) && (_.get(props, showProp) || props.sortKey === name);
}
function getInfoRowProps(row, props) {
@@ -61,6 +63,14 @@ function getInfoRowProps(row, props) {
};
}
if (name === 'studio') {
return {
title: translate('Studio'),
iconName: icons.STUDIO,
label: props.studio
};
}
if (name === 'genres') {
return {
title: translate('Genres'),
@@ -69,14 +79,22 @@ function getInfoRowProps(row, props) {
};
}
if (name === 'ratings') {
if (name === 'tmdbRating' && !!props.ratings.tmdb) {
return {
title: translate('Ratings'),
title: translate('TmdbRating'),
iconName: icons.HEART,
label: `${(props.ratings.tmdb.value * 10).toFixed()}%`
};
}
if (name === 'imdbRating' && !!props.ratings.imdb) {
return {
title: translate('ImdbRating'),
iconName: icons.IMDB,
label: `${(props.ratings.imdb.value).toFixed(1)}`
};
}
if (name === 'certification') {
return {
title: translate('Certification'),
@@ -84,14 +102,6 @@ function getInfoRowProps(row, props) {
label: props.certification
};
}
if (name === 'studio') {
return {
title: translate('Studio'),
iconName: icons.STUDIO,
label: props.studio
};
}
}
function DiscoverMovieOverviewInfo(props) {
@@ -132,11 +142,12 @@ function DiscoverMovieOverviewInfo(props) {
DiscoverMovieOverviewInfo.propTypes = {
height: PropTypes.number.isRequired,
showStudio: PropTypes.bool.isRequired,
showYear: PropTypes.bool.isRequired,
showRatings: PropTypes.bool.isRequired,
showCertification: PropTypes.bool.isRequired,
showStudio: PropTypes.bool.isRequired,
showGenres: PropTypes.bool.isRequired,
showTmdbRating: PropTypes.bool.isRequired,
showImdbRating: PropTypes.bool.isRequired,
showCertification: PropTypes.bool.isRequired,
studio: PropTypes.string,
year: PropTypes.number,
certification: PropTypes.string,

View File

@@ -1,35 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import styles from './DiscoverMovieOverviewInfoRow.css';
function DiscoverMovieOverviewInfoRow(props) {
const {
title,
iconName,
label
} = props;
return (
<div
className={styles.infoRow}
title={title}
>
<Icon
className={styles.icon}
name={iconName}
size={14}
/>
{label}
</div>
);
}
DiscoverMovieOverviewInfoRow.propTypes = {
title: PropTypes.string,
iconName: PropTypes.object.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
};
export default DiscoverMovieOverviewInfoRow;

View File

@@ -0,0 +1,26 @@
import { IconDefinition } from '@fortawesome/free-regular-svg-icons';
import React from 'react';
import Icon from 'Components/Icon';
import styles from './DiscoverMovieOverviewInfoRow.css';
interface DiscoverMovieOverviewInfoRowProps {
title?: string;
iconName?: IconDefinition;
label: string | null;
}
function DiscoverMovieOverviewInfoRow(
props: DiscoverMovieOverviewInfoRowProps
) {
const { title, iconName, label } = props;
return (
<div className={styles.infoRow} title={title}>
<Icon className={styles.icon} name={iconName} size={14} />
{label}
</div>
);
}
export default DiscoverMovieOverviewInfoRow;

View File

@@ -65,7 +65,8 @@ class DiscoverMovieOverviews extends Component {
items,
sortKey,
overviewOptions,
jumpToCharacter
jumpToCharacter,
isSmallScreen
} = this.props;
const {
@@ -75,13 +76,17 @@ class DiscoverMovieOverviews extends Component {
if (prevProps.sortKey !== sortKey ||
prevProps.overviewOptions !== overviewOptions) {
this.calculateGrid();
this.calculateGrid(this.state.width, isSmallScreen);
}
if (this._grid &&
if (
this._grid &&
(prevState.width !== width ||
prevState.rowHeight !== rowHeight ||
hasDifferentItemsOrOrder(prevProps.items, items, 'tmdbId'))) {
hasDifferentItemsOrOrder(prevProps.items, items, 'tmdbId') ||
prevProps.overviewOptions !== overviewOptions
)
) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}

View File

@@ -44,11 +44,12 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
this.state = {
size: props.size,
showStudio: props.showStudio,
showCertification: props.showCertification,
showRatings: props.showRatings,
showYear: props.showYear,
showStudio: props.showStudio,
showGenres: props.showGenres,
showTmdbRating: props.showTmdbRating,
showImdbRating: props.showImdbRating,
showCertification: props.showCertification,
includeRecommendations: props.includeRecommendations,
includeTrending: props.includeTrending,
includePopular: props.includePopular
@@ -58,11 +59,12 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
componentDidUpdate(prevProps) {
const {
size,
showStudio,
showYear,
showRatings,
showCertification,
showStudio,
showGenres,
showTmdbRating,
showImdbRating,
showCertification,
includeRecommendations,
includeTrending,
includePopular
@@ -74,26 +76,30 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
state.size = size;
}
if (showStudio !== prevProps.showStudio) {
state.showStudio = showStudio;
}
if (showYear !== prevProps.showYear) {
state.showYear = showYear;
}
if (showRatings !== prevProps.showRatings) {
state.showRatings = showRatings;
}
if (showCertification !== prevProps.showCertification) {
state.showCertification = showCertification;
if (showStudio !== prevProps.showStudio) {
state.showStudio = showStudio;
}
if (showGenres !== prevProps.showGenres) {
state.showGenres = showGenres;
}
if (showTmdbRating !== prevProps.showTmdbRating) {
state.showTmdbRating = showTmdbRating;
}
if (showImdbRating !== prevProps.showImdbRating) {
state.showImdbRating = showImdbRating;
}
if (showCertification !== prevProps.showCertification) {
state.showCertification = showCertification;
}
if (includeRecommendations !== prevProps.includeRecommendations) {
state.includeRecommendations = includeRecommendations;
}
@@ -142,11 +148,12 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
const {
size,
showStudio,
showCertification,
showRatings,
showYear,
showStudio,
showGenres,
showTmdbRating,
showImdbRating,
showCertification,
includeRecommendations,
includeTrending,
includePopular
@@ -209,12 +216,12 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowGenres')}</FormLabel>
<FormLabel>{translate('ShowYear')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showGenres"
value={showGenres}
name="showYear"
value={showYear}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
@@ -231,23 +238,34 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowYear')}</FormLabel>
<FormLabel>{translate('ShowGenres')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showYear"
value={showYear}
name="showGenres"
value={showGenres}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowRatings')}</FormLabel>
<FormLabel>{translate('ShowTmdbRating')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showRatings"
value={showRatings}
name="showTmdbRating"
value={showTmdbRating}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowImdbRating')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showImdbRating"
value={showImdbRating}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
@@ -279,11 +297,12 @@ class DiscoverMovieOverviewOptionsModalContent extends Component {
DiscoverMovieOverviewOptionsModalContent.propTypes = {
size: PropTypes.string.isRequired,
showStudio: PropTypes.bool.isRequired,
showYear: PropTypes.bool.isRequired,
showRatings: PropTypes.bool.isRequired,
showCertification: PropTypes.bool.isRequired,
showStudio: PropTypes.bool.isRequired,
showGenres: PropTypes.bool.isRequired,
showTmdbRating: PropTypes.bool.isRequired,
showImdbRating: PropTypes.bool.isRequired,
showCertification: PropTypes.bool.isRequired,
includeRecommendations: PropTypes.bool.isRequired,
includeTrending: PropTypes.bool.isRequired,
includePopular: PropTypes.bool.isRequired,

View File

@@ -2,9 +2,12 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import CheckInput from 'Components/Form/CheckInput';
import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import TmdbRating from 'Components/TmdbRating';
import Popover from 'Components/Tooltip/Popover';
import AddNewDiscoverMovieModal from 'DiscoverMovie/AddNewDiscoverMovieModal';
import ExcludeMovieModal from 'DiscoverMovie/Exclusion/ExcludeMovieModal';
@@ -86,6 +89,10 @@ class DiscoverMoviePoster extends Component {
posterWidth,
posterHeight,
showTitle,
showTmdbRating,
showImdbRating,
showRottenTomatoesRating,
ratings,
isExisting,
isExcluded,
isSelected,
@@ -192,18 +199,39 @@ class DiscoverMoviePoster extends Component {
</Link>
</div>
{
showTitle &&
<div className={styles.title} title={title}>
{title}
</div>
}
{showTitle ?
<div className={styles.title} title={title}>
{title}
</div> :
null}
{showTmdbRating && !!ratings.tmdb ? (
<div className={styles.title}>
<TmdbRating ratings={ratings} iconSize={12} />
</div>
) : null}
{showImdbRating && !!ratings.imdb ? (
<div className={styles.title}>
<ImdbRating ratings={ratings} iconSize={12} />
</div>
) : null}
{showRottenTomatoesRating && !!ratings.rottenTomatoes ? (
<div className={styles.title}>
<RottenTomatoRating ratings={ratings} iconSize={12} />
</div>
) : null}
<DiscoverMoviePosterInfo
showRelativeDates={showRelativeDates}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
movieRuntimeFormat={movieRuntimeFormat}
ratings={ratings}
showTmdbRating={showTmdbRating}
showImdbRating={showImdbRating}
showRottenTomatoesRating={showRottenTomatoesRating}
{...otherProps}
/>
@@ -243,6 +271,10 @@ DiscoverMoviePoster.propTypes = {
posterWidth: PropTypes.number.isRequired,
posterHeight: PropTypes.number.isRequired,
showTitle: PropTypes.bool.isRequired,
showTmdbRating: PropTypes.bool.isRequired,
showImdbRating: PropTypes.bool.isRequired,
showRottenTomatoesRating: PropTypes.bool.isRequired,
ratings: PropTypes.object.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import TmdbRating from 'Components/TmdbRating';
import { icons } from 'Helpers/Props';
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
@@ -23,7 +25,10 @@ function DiscoverMoviePosterInfo(props) {
showRelativeDates,
shortDateFormat,
timeFormat,
movieRuntimeFormat
movieRuntimeFormat,
showTmdbRating,
showImdbRating,
showRottenTomatoesRating
} = props;
if (sortKey === 'status' && status) {
@@ -112,10 +117,26 @@ function DiscoverMoviePosterInfo(props) {
);
}
if (sortKey === 'ratings' && ratings) {
if (!showTmdbRating && sortKey === 'tmdbRating' && !!ratings.tmdb) {
return (
<div className={styles.info}>
<TmdbRating ratings={ratings} />
<TmdbRating ratings={ratings} iconSize={12} />
</div>
);
}
if (!showImdbRating && sortKey === 'imdbRating' && !!ratings.imdb) {
return (
<div className={styles.info}>
<ImdbRating ratings={ratings} iconSize={12} />
</div>
);
}
if (!showRottenTomatoesRating && sortKey === 'rottenTomatoesRating' && !!ratings.rottenTomatoes) {
return (
<div className={styles.info}>
<RottenTomatoRating ratings={ratings} iconSize={12} />
</div>
);
}
@@ -136,7 +157,10 @@ DiscoverMoviePosterInfo.propTypes = {
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired
movieRuntimeFormat: PropTypes.string.isRequired,
showTmdbRating: PropTypes.bool.isRequired,
showImdbRating: PropTypes.bool.isRequired,
showRottenTomatoesRating: PropTypes.bool.isRequired
};
export default DiscoverMoviePosterInfo;

View File

@@ -36,15 +36,15 @@ function calculateColumnWidth(width, posterSize, isSmallScreen) {
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions) {
const {
detailedProgressBar,
showTitle
showTitle,
showTmdbRating,
showImdbRating,
showRottenTomatoesRating
} = posterOptions;
const nextAiringHeight = 19;
const heights = [
posterHeight,
detailedProgressBar ? detailedProgressBarHeight : progressBarHeight,
nextAiringHeight,
isSmallScreen ? columnPaddingSmallScreen : columnPadding
];
@@ -52,8 +52,42 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
heights.push(19);
}
if (showTmdbRating) {
heights.push(19);
}
if (showImdbRating) {
heights.push(19);
}
if (showRottenTomatoesRating) {
heights.push(19);
}
switch (sortKey) {
case 'studio':
case 'inCinemas':
case 'digitalRelease':
case 'physicalRelease':
case 'runtime':
case 'certification':
heights.push(19);
break;
case 'tmdbRating':
if (!showTmdbRating) {
heights.push(19);
}
break;
case 'imdbRating':
if (!showImdbRating) {
heights.push(19);
}
break;
case 'rottenTomatoesRating':
if (!showRottenTomatoesRating) {
heights.push(19);
}
break;
default:
// No need to add a height of 0
}
@@ -182,7 +216,10 @@ class DiscoverMoviePosters extends Component {
} = this.state;
const {
showTitle
showTitle,
showTmdbRating,
showImdbRating,
showRottenTomatoesRating
} = posterOptions;
const movieIdx = rowIndex * columnCount + columnIndex;
@@ -208,6 +245,9 @@ class DiscoverMoviePosters extends Component {
posterWidth={posterWidth}
posterHeight={posterHeight}
showTitle={showTitle}
showTmdbRating={showTmdbRating}
showImdbRating={showImdbRating}
showRottenTomatoesRating={showRottenTomatoesRating}
showRelativeDates={showRelativeDates}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}

View File

@@ -45,6 +45,9 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
this.state = {
size: props.size,
showTitle: props.showTitle,
showTmdbRating: props.showTmdbRating,
showImdbRating: props.showImdbRating,
showRottenTomatoesRating: props.showRottenTomatoesRating,
includeRecommendations: props.includeRecommendations,
includeTrending: props.includeTrending,
includePopular: props.includePopular
@@ -55,6 +58,9 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
const {
size,
showTitle,
showTmdbRating,
showImdbRating,
showRottenTomatoesRating,
includeRecommendations,
includeTrending,
includePopular
@@ -70,6 +76,18 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
state.showTitle = showTitle;
}
if (showTmdbRating !== prevProps.showTmdbRating) {
state.showTmdbRating = showTmdbRating;
}
if (showImdbRating !== prevProps.showImdbRating) {
state.showImdbRating = showImdbRating;
}
if (showRottenTomatoesRating !== prevProps.showRottenTomatoesRating) {
state.showRottenTomatoesRating = showRottenTomatoesRating;
}
if (includeRecommendations !== prevProps.includeRecommendations) {
state.includeRecommendations = includeRecommendations;
}
@@ -119,6 +137,9 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
const {
size,
showTitle,
showTmdbRating,
showImdbRating,
showRottenTomatoesRating,
includeRecommendations,
includeTrending,
includePopular
@@ -191,6 +212,42 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowTmdbRating')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showTmdbRating"
value={showTmdbRating}
helpText={translate('ShowTmdbRatingHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowImdbRating')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showImdbRating"
value={showImdbRating}
helpText={translate('ShowImdbRatingHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowRottenTomatoesRating')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showRottenTomatoesRating"
value={showRottenTomatoesRating}
helpText={translate('ShowRottenTomatoesRatingHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
</Form>
</ModalBody>
@@ -209,6 +266,9 @@ class DiscoverMoviePosterOptionsModalContent extends Component {
DiscoverMoviePosterOptionsModalContent.propTypes = {
size: PropTypes.string.isRequired,
showTitle: PropTypes.bool.isRequired,
showTmdbRating: PropTypes.bool.isRequired,
showImdbRating: PropTypes.bool.isRequired,
showRottenTomatoesRating: PropTypes.bool.isRequired,
includeRecommendations: PropTypes.bool.isRequired,
includeTrending: PropTypes.bool.isRequired,
includePopular: PropTypes.bool.isRequired,

View File

@@ -32,7 +32,9 @@
flex: 1 0 125px;
}
.ratings,
.tmdbRating,
.imdbRating,
.rottenTomatoesRating,
.runtime {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';

View File

@@ -6,6 +6,7 @@ interface CssExports {
'collection': string;
'digitalRelease': string;
'genres': string;
'imdbRating': string;
'inCinemas': string;
'isPopular': string;
'isRecommendation': string;
@@ -14,11 +15,12 @@ interface CssExports {
'originalLanguage': string;
'physicalRelease': string;
'popularity': string;
'ratings': string;
'rottenTomatoesRating': string;
'runtime': string;
'sortTitle': string;
'status': string;
'studio': string;
'tmdbRating': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -57,7 +57,9 @@
flex: 1 0 125px;
}
.ratings,
.tmdbRating,
.imdbRating,
.rottenTomatoesRating,
.runtime {
composes: cell;

View File

@@ -11,6 +11,7 @@ interface CssExports {
'exclusionIcon': string;
'externalLinks': string;
'genres': string;
'imdbRating': string;
'inCinemas': string;
'isPopular': string;
'isRecommendation': string;
@@ -19,12 +20,13 @@ interface CssExports {
'originalLanguage': string;
'physicalRelease': string;
'popularity': string;
'ratings': string;
'rottenTomatoesRating': string;
'runtime': string;
'sortTitle': string;
'status': string;
'statusIcon': string;
'studio': string;
'tmdbRating': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,10 +1,12 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import ImportListListConnector from 'Components/ImportListListConnector';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import RottenTomatoRating from 'Components/RottenTomatoRating';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TmdbRating from 'Components/TmdbRating';
@@ -196,10 +198,11 @@ class DiscoverMovieRow extends Component {
if (name === 'inCinemas') {
return (
<RelativeDateCellConnector
<RelativeDateCell
key={name}
className={styles[name]}
date={inCinemas}
timeForToday={false}
component={VirtualTableRowCell}
/>
);
@@ -207,10 +210,11 @@ class DiscoverMovieRow extends Component {
if (name === 'physicalRelease') {
return (
<RelativeDateCellConnector
<RelativeDateCell
key={name}
className={styles[name]}
date={physicalRelease}
timeForToday={false}
component={VirtualTableRowCell}
/>
);
@@ -218,10 +222,11 @@ class DiscoverMovieRow extends Component {
if (name === 'digitalRelease') {
return (
<RelativeDateCellConnector
<RelativeDateCell
key={name}
className={styles[name]}
date={digitalRelease}
timeForToday={false}
component={VirtualTableRowCell}
/>
);
@@ -253,15 +258,35 @@ class DiscoverMovieRow extends Component {
);
}
if (name === 'ratings') {
if (name === 'tmdbRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
<TmdbRating
ratings={ratings}
/>
{ratings.tmdb ? <TmdbRating ratings={ratings} /> : null}
</VirtualTableRowCell>
);
}
if (name === 'imdbRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
{ratings.imdb ? <ImdbRating ratings={ratings} /> : null}
</VirtualTableRowCell>
);
}
if (name === 'rottenTomatoesRating') {
return (
<VirtualTableRowCell
key={name}
className={styles[name]}
>
{ratings.rottenTomatoes ? <RottenTomatoRating ratings={ratings} /> : null}
</VirtualTableRowCell>
);
}

View File

@@ -1,34 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AuthenticationRequiredModalContentConnector from './AuthenticationRequiredModalContentConnector';
function onModalClose() {
// No-op
}
function AuthenticationRequiredModal(props) {
const {
isOpen
} = props;
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>
<AuthenticationRequiredModalContentConnector
onModalClose={onModalClose}
/>
</Modal>
);
}
AuthenticationRequiredModal.propTypes = {
isOpen: PropTypes.bool.isRequired
};
export default AuthenticationRequiredModal;

View File

@@ -0,0 +1,27 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
function onModalClose() {
// No-op
}
interface AuthenticationRequiredModalProps {
isOpen: boolean;
}
export default function AuthenticationRequiredModal({
isOpen,
}: AuthenticationRequiredModalProps) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>
<AuthenticationRequiredModalContent />
</Modal>
);
}

View File

@@ -1,170 +0,0 @@
import PropTypes from 'prop-types';
import React, { useEffect, useRef } from 'react';
import Alert from 'Components/Alert';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import { authenticationMethodOptions, authenticationRequiredOptions } from 'Settings/General/SecuritySettings';
import translate from 'Utilities/String/translate';
import styles from './AuthenticationRequiredModalContent.css';
function onModalClose() {
// No-op
}
function AuthenticationRequiredModalContent(props) {
const {
isPopulated,
error,
isSaving,
settings,
onInputChange,
onSavePress,
dispatchFetchStatus
} = props;
const {
authenticationMethod,
authenticationRequired,
username,
password,
passwordConfirmation
} = settings;
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
const didMount = useRef(false);
useEffect(() => {
if (!isSaving && didMount.current) {
dispatchFetchStatus();
}
didMount.current = true;
}, [isSaving, dispatchFetchStatus]);
return (
<ModalContent
showCloseButton={false}
onModalClose={onModalClose}
>
<ModalHeader>
{translate('AuthenticationRequired')}
</ModalHeader>
<ModalBody>
<Alert
className={styles.authRequiredAlert}
kind={kinds.WARNING}
>
{translate('AuthenticationRequiredWarning')}
</Alert>
{
isPopulated && !error ?
<div>
<FormGroup>
<FormLabel>{translate('AuthenticationMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
helpLink="https://wiki.servarr.com/radarr/faq#forced-authentication"
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
onChange={onInputChange}
helpTextWarning={username?.value ? undefined : translate('AuthenticationRequiredUsernameHelpTextWarning')}
{...username}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
onChange={onInputChange}
helpTextWarning={password?.value ? undefined : translate('AuthenticationRequiredPasswordHelpTextWarning')}
{...password}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
{...passwordConfirmation}
/>
</FormGroup>
</div> :
null
}
{
!isPopulated && !error ? <LoadingIndicator /> : null
}
</ModalBody>
<ModalFooter>
<SpinnerButton
kind={kinds.PRIMARY}
isSpinning={isSaving}
isDisabled={!authenticationEnabled}
onPress={onSavePress}
>
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
AuthenticationRequiredModalContent.propTypes = {
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
settings: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired
};
export default AuthenticationRequiredModalContent;

View File

@@ -0,0 +1,194 @@
import React, { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Alert from 'Components/Alert';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { inputTypes, kinds } from 'Helpers/Props';
import {
authenticationMethodOptions,
authenticationRequiredOptions,
} from 'Settings/General/SecuritySettings';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import {
fetchGeneralSettings,
saveGeneralSettings,
setGeneralSettingsValue,
} from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import { InputChanged } from 'typings/inputs';
import translate from 'Utilities/String/translate';
import styles from './AuthenticationRequiredModalContent.css';
const SECTION = 'general';
const selector = createSettingsSectionSelector(SECTION);
function onModalClose() {
// No-op
}
export default function AuthenticationRequiredModalContent() {
const { isPopulated, error, isSaving, settings } = useSelector(selector);
const dispatch = useDispatch();
const {
authenticationMethod,
authenticationRequired,
username,
password,
passwordConfirmation,
} = settings;
const wasSaving = usePrevious(isSaving);
useEffect(() => {
dispatch(fetchGeneralSettings());
return () => {
dispatch(clearPendingChanges());
};
}, [dispatch]);
const onInputChange = useCallback(
(args: InputChanged) => {
// @ts-expect-error Actions aren't typed
dispatch(setGeneralSettingsValue(args));
},
[dispatch]
);
const authenticationEnabled =
authenticationMethod && authenticationMethod.value !== 'none';
useEffect(() => {
if (isSaving || !wasSaving) {
return;
}
dispatch(fetchStatus());
}, [isSaving, wasSaving, dispatch]);
const onPress = useCallback(() => {
dispatch(saveGeneralSettings());
}, [dispatch]);
return (
<ModalContent showCloseButton={false} onModalClose={onModalClose}>
<ModalHeader>{translate('AuthenticationRequired')}</ModalHeader>
<ModalBody>
<Alert className={styles.authRequiredAlert} kind={kinds.WARNING}>
{translate('AuthenticationRequiredWarning')}
</Alert>
{isPopulated && !error ? (
<div>
<FormGroup>
<FormLabel>{translate('AuthenticationMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={
authenticationMethod.value === 'none'
? translate('AuthenticationMethodHelpTextWarning')
: undefined
}
helpLink="https://wiki.servarr.com/radarr/faq#forced-authentication"
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
helpTextWarning={
username?.value
? undefined
: translate('AuthenticationRequiredUsernameHelpTextWarning')
}
onChange={onInputChange}
{...username}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
helpTextWarning={
password?.value
? undefined
: translate('AuthenticationRequiredPasswordHelpTextWarning')
}
onChange={onInputChange}
{...password}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
helpTextWarning={
passwordConfirmation?.value
? undefined
: translate(
'AuthenticationRequiredPasswordConfirmationHelpTextWarning'
)
}
onChange={onInputChange}
{...passwordConfirmation}
/>
</FormGroup>
</div>
) : null}
{!isPopulated && !error ? <LoadingIndicator /> : null}
</ModalBody>
<ModalFooter>
<SpinnerButton
kind={kinds.PRIMARY}
isSpinning={isSaving}
isDisabled={!authenticationEnabled}
onPress={onPress}
>
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}

View File

@@ -1,86 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
const SECTION = 'general';
function createMapStateToProps() {
return createSelector(
createSettingsSectionSelector(SECTION),
(sectionSettings) => {
return {
...sectionSettings
};
}
);
}
const mapDispatchToProps = {
dispatchClearPendingChanges: clearPendingChanges,
dispatchSetGeneralSettingsValue: setGeneralSettingsValue,
dispatchSaveGeneralSettings: saveGeneralSettings,
dispatchFetchGeneralSettings: fetchGeneralSettings,
dispatchFetchStatus: fetchStatus
};
class AuthenticationRequiredModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchGeneralSettings();
}
componentWillUnmount() {
this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.dispatchSetGeneralSettingsValue({ name, value });
};
onSavePress = () => {
this.props.dispatchSaveGeneralSettings();
};
//
// Render
render() {
const {
dispatchClearPendingChanges,
dispatchFetchGeneralSettings,
dispatchSetGeneralSettingsValue,
dispatchSaveGeneralSettings,
...otherProps
} = this.props;
return (
<AuthenticationRequiredModalContent
{...otherProps}
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
/>
);
}
}
AuthenticationRequiredModalContentConnector.propTypes = {
dispatchClearPendingChanges: PropTypes.func.isRequired,
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
dispatchSetGeneralSettingsValue: PropTypes.func.isRequired,
dispatchSaveGeneralSettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AuthenticationRequiredModalContentConnector);

View File

@@ -0,0 +1,9 @@
import { useHistory } from 'react-router-dom';
function useCurrentPage() {
const history = useHistory();
return history.action === 'POP';
}
export default useCurrentPage;

View File

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

View File

@@ -1,6 +1,10 @@
//
// Brands
import {
faImdb as fabImdb
} from '@fortawesome/free-brands-svg-icons';
//
// Regular
import {
faBookmark as farBookmark,
faCalendar as farCalendar,
@@ -175,10 +179,12 @@ export const HEART = fasHeart;
export const HISTORY = fasHistory;
export const HOUSEKEEPING = fasHome;
export const IGNORE = fasTimesCircle;
export const IMDB = fabImdb;
export const IN_CINEMAS = fasTicketAlt;
export const INFO = fasInfoCircle;
export const INTERACTIVE = fasUser;
export const KEYBOARD = farKeyboard;
export const LANGUAGE = fasLanguage;
export const LOGOUT = fasSignOutAlt;
export const MANAGE = fasListCheck;
export const MEDIA_INFO = farFileInvoice;

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowButton from 'Components/Table/TableRowButton';
import { icons } from 'Helpers/Props';
@@ -41,7 +41,7 @@ class RecentFolderRow extends Component {
<TableRowButton onPress={this.onPress}>
<TableRowCell>{folder}</TableRowCell>
<RelativeDateCellConnector date={lastUsed} />
<RelativeDateCell date={lastUsed} />
<TableRowCell className={styles.actions}>
<IconButton

View File

@@ -18,12 +18,17 @@
.leftButtons,
.rightButtons {
display: flex;
flex: 1 0 50%;
flex-wrap: wrap;
min-width: 0;
}
.leftButtons {
flex: 0 1 auto;
}
.rightButtons {
justify-content: flex-end;
flex: 1 1 50%;
}
.deleteButton {
@@ -37,6 +42,7 @@
composes: select from '~Components/Form/SelectInput.css';
margin-right: 10px;
max-width: 100%;
width: auto;
}
@@ -49,10 +55,12 @@
.leftButtons,
.rightButtons {
flex-direction: column;
gap: 3px;
}
.leftButtons {
align-items: flex-start;
max-width: fit-content;
}
.rightButtons {

View File

@@ -702,7 +702,7 @@ function InteractiveImportModalContent(
<MenuContent>
<SelectedMenuItem
name={'all'}
name="all"
isSelected={!filterExistingFiles}
onPress={onFilterExistingFilesChange}
>
@@ -710,7 +710,7 @@ function InteractiveImportModalContent(
</SelectedMenuItem>
<SelectedMenuItem
name={'new'}
name="new"
isSelected={filterExistingFiles}
onPress={onFilterExistingFilesChange}
>
@@ -790,7 +790,7 @@ function InteractiveImportModalContent(
<SelectInput
className={styles.bulkSelect}
name="select"
value={'select'}
value="select"
values={bulkSelectOptions}
isDisabled={!selectedIds.length}
onChange={onSelectModalSelect}

View File

@@ -17,7 +17,7 @@ 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';
import MovieLanguages from 'Movie/MovieLanguages';
import MovieQuality from 'Movie/MovieQuality';
import { QualityModel } from 'Quality/Quality';
import {
@@ -322,7 +322,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
{showLanguagePlaceholder && <InteractiveImportRowCellPlaceholder />}
{!showLanguagePlaceholder && !!languages && (
<MovieLanguage className={styles.label} languages={languages} />
<MovieLanguages className={styles.label} languages={languages} />
)}
</TableRowCellButton>

View File

@@ -17,7 +17,7 @@ function SelectLanguageModal(props: SelectLanguageModalProps) {
props;
return (
<Modal isOpen={isOpen} onModalClose={onModalClose} size={sizes.MEDIUM}>
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<SelectLanguageModalContent
languageIds={languageIds}
modalTitle={modalTitle}

View File

@@ -21,6 +21,7 @@ import { scrollDirections } from 'Helpers/Props';
import Movie from 'Movie/Movie';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import dimensions from 'Styles/Variables/dimensions';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import SelectMovieModalTableHeader from './SelectMovieModalTableHeader';
import SelectMovieRow from './SelectMovieRow';
@@ -63,19 +64,20 @@ interface RowItemData {
onMovieSelect(movieId: number): void;
}
const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
index,
style,
data,
}) => {
function Row({ index, style, data }: ListChildComponentProps<RowItemData>) {
const { items, columns, onMovieSelect } = data;
const movie = index >= items.length ? null : items[index];
if (index >= items.length) {
const handlePress = useCallback(() => {
if (movie?.id) {
onMovieSelect(movie.id);
}
}, [movie?.id, onMovieSelect]);
if (movie == null) {
return null;
}
const movie = items[index];
return (
<VirtualTableRowButton
style={{
@@ -83,7 +85,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
justifyContent: 'space-between',
...style,
}}
onPress={() => onMovieSelect(movie.id)}
onPress={handlePress}
>
<SelectMovieRow
id={movie.id}
@@ -96,7 +98,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
/>
</VirtualTableRowButton>
);
};
}
function SelectMovieModalContent(props: SelectMovieModalContentProps) {
const { modalTitle, onMovieSelect, onModalClose } = props;
@@ -161,18 +163,21 @@ function SelectMovieModalContent(props: SelectMovieModalContentProps) {
[allMovies, onMovieSelect]
);
const items = useMemo(() => {
const sorted = [...allMovies].sort((a, b) =>
a.sortTitle.localeCompare(b.sortTitle)
);
const sortedMovies = useMemo(
() => [...allMovies].sort(sortByProp('sortTitle')),
[allMovies]
);
return sorted.filter(
(item) =>
item.title.toLowerCase().includes(filter.toLowerCase()) ||
item.tmdbId.toString().includes(filter) ||
item.imdbId?.includes(filter)
);
}, [allMovies, filter]);
const items = useMemo(
() =>
sortedMovies.filter(
(item) =>
item.title.toLowerCase().includes(filter.toLowerCase()) ||
item.tmdbId.toString().includes(filter) ||
item.imdbId?.includes(filter)
),
[sortedMovies, filter]
);
return (
<ModalContent onModalClose={onModalClose}>
@@ -192,9 +197,9 @@ function SelectMovieModalContent(props: SelectMovieModalContentProps) {
/>
<Scroller
ref={scrollerRef}
className={styles.scroller}
autoFocus={false}
ref={scrollerRef}
>
<SelectMovieModalTableHeader columns={columns} />
<List<RowItemData>

View File

@@ -1,39 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageMenuButton from 'Components/Menu/PageMenuButton';
import { align } from 'Helpers/Props';
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
import styles from './InteractiveSearch.css';
function InteractiveSearchFilterMenu(props) {
const {
selectedFilterKey,
filters,
customFilters,
onFilterSelect
} = props;
return (
<div className={styles.filterMenuContainer}>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
buttonComponent={PageMenuButton}
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
onFilterSelect={onFilterSelect}
/>
</div>
);
}
InteractiveSearchFilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
onFilterSelect: PropTypes.func.isRequired
};
export default InteractiveSearchFilterMenu;

View File

@@ -1,46 +0,0 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setReleasesFilter } from 'Store/Actions/releaseActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import InteractiveSearchFilterMenu from './InteractiveSearchFilterMenu';
function createMapStateToProps(appState) {
return createSelector(
createClientSideCollectionSelector('releases'),
(releases) => {
return {
...releases
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onFilterSelect(selectedFilterKey) {
dispatch(setReleasesFilter({ selectedFilterKey }));
}
};
}
class InteractiveSearchFilterMenuConnector extends Component {
//
// Render
render() {
const {
...otherProps
} = this.props;
return (
<InteractiveSearchFilterMenu
{...otherProps}
/>
);
}
}
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchFilterMenuConnector);

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