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

Compare commits

...

187 Commits

Author SHA1 Message Date
Bogdan
80ca1a6ac2 Fixed: Editing Quality Profiles 2024-07-18 08:10:43 -07:00
Weblate
f59c0b16ca Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ru/
Translation: Servarr/Sonarr
2024-07-16 21:42:50 -07:00
Mark McDowall
0e95ba2021 New: Allow major version updates to be installed 2024-07-16 21:39:49 -07:00
Mark McDowall
c023fc7008 New: Show update settings on all platforms 2024-07-16 21:39:49 -07:00
Mark McDowall
19466aa290 Fixed: Assume category path from qBittorent starting with '//' is a Windows UNC path
Radarr/Radarr#10162
2024-07-17 00:39:40 -04:00
Mark McDowall
4b5ef4907b Set default value for CustomColonReplacementFormat if not provided 2024-07-16 21:38:26 -07:00
Stevie Robinson
7b8d606a1b New: Wrap specifications in Custom Format and Auto Tagging modals 2024-07-17 00:38:15 -04:00
diamondpete
6a4824c029 Fixed: Remove apostrophe, backtick in contractions 2024-07-17 00:36:29 -04:00
Mark McDowall
1a1c8e6c08 New: Use natural sorting for lists of items in the UI
Closes #6955
2024-07-17 00:34:43 -04:00
Mark McDowall
e35b39b4b1 New: Add option to show tags on series Poster and Overview
Closes #6946
2024-07-16 21:34:25 -07:00
Mark McDowall
d3f14d5f5e Fixed: Parse Chinese anime formats with reverse title order 2024-07-16 21:34:11 -07:00
Mark McDowall
06936c4f22 Fixed: Parsing of Chinese anime with ordinal number in English title 2024-07-16 21:34:11 -07:00
Mark McDowall
0a28ff84e8 Fixed: Parsing of anime with 3 digit number in middle of title 2024-07-16 21:34:11 -07:00
Bogdan
703dee9383 New: Rating votes tooltip and series filter 2024-07-16 21:33:49 -07:00
Marc Carbonell
dca5239420 Remove extraneous indentation in RemoveFileExtension 2024-07-17 00:33:34 -04:00
Mark McDowall
1aaa9a14bc Bump version to 4.0.8 2024-07-15 21:36:26 -07:00
Weblate
c6c37a408a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ru/
Translation: Servarr/Sonarr
2024-07-15 21:36:15 -07:00
Weblate
ae4a97b4ae Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dream <seth.gecko.rr@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ru/
Translation: Servarr/Sonarr
2024-07-15 12:07:12 -07:00
Mark McDowall
3afae968eb Fixed: Import queue not processing after incomplete import 2024-07-15 12:03:53 -07:00
Mark McDowall
c01abbf3b5 Add 'On Import Complete' for Discord notifications 2024-07-15 12:03:31 -07:00
Mark McDowall
f5ccf98162 Rename 'On Upgrade' to 'On File Upgrade' 2024-07-15 12:03:31 -07:00
Mark McDowall
6afd3bd344 Bump version to 4.0.7 2024-07-14 16:44:07 -07:00
Weblate
acaf5cd353 Multiple Translations updated by Weblate
ignore-downstream

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: Rauniik <raunerjakub@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translation: Servarr/Sonarr
2024-07-14 16:43:56 -07:00
Sonarr
e97e5bfe8f Automated API Docs update
ignore-downstream
2024-07-09 22:22:48 -07:00
martylukyy
678872b879 Fixed: Parsing of some Web releases 2024-07-10 01:19:07 -04:00
Mark McDowall
10e9735c1c New: Update AutoTags on series update
Closes #6783
2024-07-10 01:02:23 -04:00
Mark McDowall
293a1bc618 New: Custom colon replacement option
Closes #6898
2024-07-10 01:02:04 -04:00
Bogdan
0c883f7886 Fixed: Removing pending release without blocklisting 2024-07-09 22:01:23 -07:00
Mark McDowall
46c7de379c New: Group updates for the same series for Kodi and Emby / Jellyfin 2024-07-09 22:00:57 -07:00
Mark McDowall
a83b521766 New: 'On Import Complete' notification when all episodes in a release are imported
Closes #363
2024-07-09 22:00:57 -07:00
Bogdan
1d06e40acb New: Queued episode count for seasons in series details 2024-07-09 21:59:41 -07:00
Weblate
bfcdc89f6a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Serhii Matrunchyk <serhii@digitalidea.studio>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/uk/
Translation: Servarr/Sonarr
2024-07-09 21:59:25 -07:00
Sonarr
67943edfbc Automated API Docs update
ignore-downstream
2024-07-05 16:35:21 -07:00
Mark McDowall
04f8595498 Custom Import List improvements
Fixed: Add placeholder title for Custom Import List title
New: Support 'title' property for Custom Import List
2024-07-05 16:35:08 -07:00
Bogdan
81ac73299a Fixed: Bulk series deletion for unmonitored series
Closes #6933
2024-07-05 19:34:56 -04:00
Mark McDowall
a779a5fad2 Fixed: Parsing of anime season releases with 3-digit number in title 2024-07-05 16:34:06 -07:00
Mark McDowall
bfe6a740fa Fixed: Parsing of anime releases using standard numbering
Closes #6925
2024-07-05 16:34:06 -07:00
Mark McDowall
c9ea40b874 New: Parse VFI as French
Closes #6927
2024-07-05 16:34:00 -07:00
Bogdan
4ee0ae1418 Fixed: History with unknown series 2024-07-05 16:33:50 -07:00
Bogdan
ac1da45ecd Fixed: Calculate Custom Formats after user specified options in Manual Import 2024-07-05 19:33:33 -04:00
Weblate
5c327d5be3 Multiple Translations updated by Weblate
ignore-downstream

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: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: quek76 <quek@libertysurf.fr>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translation: Servarr/Sonarr
2024-07-05 16:31:56 -07:00
Mark McDowall
55c1ce2e3d Bump version to 4.0.6 2024-06-30 15:42:47 -07:00
Weblate
fd7f0ea973 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Kshitij Burman <kburman6@gmail.com>
Co-authored-by: MattiaPell <mattiapellegrini16@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: damienmillet <contact@damien-millet.dev>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translation: Servarr/Sonarr
2024-06-30 15:42:41 -07:00
Bogdan
d5dff8e8d6 Fixed: Trimming disabled logs database
Closes #6918
2024-06-30 13:49:41 -04:00
Bogdan
8099ba10af Fixed: Already imported downloads appearing in Queue briefly 2024-06-30 13:47:00 -04:00
Mark McDowall
143ccb1e2a Remove seriesTitle from EpisodeResource
Closes #6841
2024-06-28 06:22:10 -07:00
Mark McDowall
29480d9544 Fixed: Don't use cleaned up release title for release title 2024-06-28 06:22:04 -07:00
Mark McDowall
6de536a7ad Fixed: Limit Queue maximum page size to 200
Closes #6899
2024-06-26 09:45:43 -07:00
Mark McDowall
bce848facf Fixed: Reprocessing items that were previously blocked during importing 2024-06-26 09:45:28 -07:00
Mark McDowall
ea4fe392a0 New: Remove websites in parentheses before parsing 2024-06-25 15:52:24 -07:00
Mark McDowall
45fe585944 Fixed: Prevent errors parsing releases in unexpected formats 2024-06-25 15:52:24 -07:00
Mark McDowall
a0d2933134 New: Ignore Deluge torrents without a title
Closes #6885
2024-06-25 18:52:12 -04:00
Mark McDowall
4c622fd412 New: Ability to select Plex Media Server from plex.tv
Closes #6887
2024-06-25 15:51:57 -07:00
Bogdan
fb060730c7 Fixed: Exclude invalid releases from Newznab and Torznab parsers 2024-06-25 15:51:41 -07:00
Mark McDowall
6d5ff9c4d6 New: Improve UI status when downloads cannot be imported automatically
Closes #6873
2024-06-25 18:51:20 -04:00
Mark McDowall
63bed3e670 New: Parse anime seasons with trailing number in title
Closes #6883
2024-06-25 15:51:03 -07:00
Weblate
e684c10432 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: MattiaPell <mattiapellegrini16@gmail.com>
Co-authored-by: Taylan Tatlı <taylantatli90@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translation: Servarr/Sonarr
2024-06-25 15:50:56 -07:00
Bogdan
d2509798e9 New: Display stats for delete multiple series modal 2024-06-17 20:40:04 -07:00
Bogdan
6c39855ebe Fix UpdatePackageProviderFixture for v4
ignore-downstream
2024-06-17 20:39:33 -07:00
Bogdan
a30e9da767 New: Ignore inaccessible folders when getting folders 2024-06-17 20:39:33 -07:00
Mark McDowall
f8e81396d4 Fixed: Importing from IMDb list 2024-06-17 20:39:22 -07:00
Mark McDowall
7fccf590a8 Fixed: Adding series with unknown items in queue 2024-06-17 23:39:13 -04:00
Stephan Sundermann
e1b937e8d5 New: Add TMDB ID support
Closes #6866
2024-06-17 23:38:41 -04:00
Bogdan
c331c8bd11 Ignore Grabbed from API docs
Run application in docs.sh specific to platform
2024-06-10 20:30:26 -07:00
Mark McDowall
52b72925f9 Fixed: Improve error messaging if config file isn't formatted correctly
Closes #6860
2024-06-10 20:30:13 -07:00
Mark McDowall
378fedcd9d Fixed: Skip invalid series paths during validation 2024-06-10 23:30:03 -04:00
Bogdan
a90ab1a8fd Fixed: Ignore case when resolving indexer by name in release push 2024-06-10 20:29:46 -07:00
Bogdan
0edc5ba99a Fixed: Ignore case for name validation in providers 2024-06-10 20:29:46 -07:00
Bogdan
ea54ade9bf New: Refresh cache for tracked queue on series add 2024-06-10 20:29:41 -07:00
Weblate
e07eb05e8b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: AlbertCoolGuy <Albert.rosenstand@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: xuzhihui <5894940@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-06-10 20:29:28 -07:00
Bogdan
d9b771ab0b Fixed: Error sending Manual Interaction Required when series is unknown 2024-05-31 20:11:31 -04:00
Mark McDowall
6b08e849b8 Search for raw and clean titles for Newznab/Torznab indexers that support raw title searching 2024-05-31 17:10:32 -07:00
Mark McDowall
9c1f48ebc9 Fixed: Include full series title in episode search 2024-05-31 17:10:32 -07:00
Mark McDowall
fd3dd1ab7d New: Genres and Images for Webhooks and Notifiarr
Closes #6822
2024-05-31 17:10:13 -07:00
yammes08
11e5c5a11b Fixed: SDR Files Being Parsed As HLG 2024-05-31 20:09:53 -04:00
Weblate
48f0291884 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: r0bertreh <Robert.reh@live.de>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translation: Servarr/Sonarr
2024-05-31 17:09:11 -07:00
Mark McDowall
af0e55aef4 Bump version to 4.0.5 2024-05-29 16:23:16 -07:00
Weblate
39a439eb4c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Bao Trinh <servarr@baodtrinh.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: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: mm519897405 <baiya@vip.qq.com>
Co-authored-by: thegamingcat13 <sandervanbeek2004@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-05-29 16:23:04 -07:00
Weblate
66940b283b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: mm519897405 <baiya@vip.qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-05-24 06:21:13 -07:00
Mark McDowall
2a662afaef Fixed: Time for episodes airing today being blank 2024-05-24 06:19:38 -07:00
Weblate
62a9c2519b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: mm519897405 <baiya@vip.qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-05-22 20:09:18 -07:00
Mark McDowall
ca372bee25 Fixed: Queue and Calendar not loading 2024-05-22 20:07:31 -07:00
Sonarr
0904a0737e Automated API Docs update
ignore-downstream
2024-05-21 17:09:20 -07:00
Bogdan
70bc26dc19 Disable workflows on forks
ignore-downstream
2024-05-21 17:06:44 -07:00
Bogdan
a2e0002a08 Replace multiple occurrences in branch env variable
ignore-downstream
2024-05-21 17:06:44 -07:00
Bogdan
d7ceb11a64 Fixed: Trimming slashes from UrlBase when using environment variable 2024-05-21 17:06:44 -07:00
Bogdan
cc5b5463f2 Ignore Grabbed with STJson 2024-05-21 17:06:36 -07:00
Bogdan
9b4ff657af Update the wanted section for missing and cutoff unmet 2024-05-21 17:06:36 -07:00
Bogdan
aea50fa47e Bump Npgsql to 7.0.7
ignore-downstream
2024-05-21 17:06:28 -07:00
Mark McDowall
05edd44ed6 New: Include time for episode/season/series history 2024-05-21 17:06:18 -07:00
Bogdan
4440aa3cac New: Root folder exists validation for import lists 2024-05-21 17:06:09 -07:00
Bogdan
084fcc2295 Implement equality checks for providers 2024-05-21 17:05:48 -07:00
Weblate
536ff142c3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Ransack6086 <servarr.jubilant150@slmail.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yi Cao <caoyi06@qq.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: topnew <sznetim@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-05-21 17:05:34 -07:00
Mark McDowall
627b2a4289 New: Parse 480i Bluray/Remux as Bluray 480p
Closes #6801
2024-05-09 22:04:18 -07:00
Bogdan
9734c2d144 Fixed: Notifications with only On Rename enabled
ignore-downstream
2024-05-09 22:04:12 -07:00
Bogdan
c7c1e3ac9e Refactor PasswordInput to use type password 2024-05-09 22:04:04 -07:00
Bogdan
429444d085 Fixed: Text color for inputs on login page 2024-05-09 22:03:56 -07:00
Mark McDowall
5cb649e9d8 Fixed: Attempt to parse and reject ambiguous dates
Closes #6799
2024-05-09 22:03:44 -07:00
Mark McDowall
cac7d239ea Fixed: Parsing of partial season pack 2024-05-09 22:03:44 -07:00
Sonarr
3940059ea3 Automated API Docs update
ignore-downstream
2024-05-09 22:03:31 -07:00
Weblate
20d00fe88c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/uk/
Translation: Servarr/Sonarr
2024-05-09 22:03:24 -07:00
Mark McDowall
b4d05214ae Fixed: Ignore invalid movie tags when writing XBMC metadata
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2024-05-08 18:45:19 -07:00
Mark McDowall
cc0a284660 New: Add series tags to Webhook and Notifiarr events 2024-05-08 18:45:19 -07:00
Mark McDowall
f50a263f4f New: Add Custom Format Score to file in Episode Details 2024-05-08 18:45:03 -07:00
Mark McDowall
29176c8367 New: Has Unmonitored Season filter for Series 2024-05-08 18:44:52 -07:00
Bogdan
1eddf3a152 Use number input for seed ratio 2024-05-08 18:44:36 -07:00
Bogdan
8360dd7a7b Fixed: Parsing long downloading/seeding values from Transmission 2024-05-08 18:44:27 -07:00
Bogdan
7e8d8500f2 Fixed: Next/previous/last air dates with Postgres DB
Closes #6790
2024-05-08 18:43:51 -07:00
Mark McDowall
cae134ec7b New: Dark theme for login screen
Closes #6751
2024-05-08 18:42:54 -07:00
Stevie Robinson
f81bb3ec19 New: Blocklist Custom Filters
Closes #6763
2024-05-08 18:42:41 -07:00
Bogdan
128309068d Fixed: Initialize databases after app folder migrations
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-05-08 18:42:15 -07:00
Mickaël Thomas
73a4bdea52 New: Support stoppedUP and stoppedDL states from qBittorrent 2024-05-08 18:41:59 -07:00
Bogdan
47ba002806 Fixed: Indexer flags for torrent release pushes 2024-05-04 18:56:52 -07:00
Mark McDowall
ba88185dea New: Treat batch releases with total episode count as full season release
Closes #6757
2024-05-04 18:56:15 -07:00
Mark McDowall
e24ce40eb8 Fixed: History with unknown episode
Closes #6782
2024-05-04 18:55:56 -07:00
Stevie Robinson
8be8c7f89c Add missing translation key 2024-05-04 18:55:44 -07:00
Bogdan
7166a6c019 Parameter binding for API requests 2024-05-04 18:55:12 -07:00
Mark McDowall
3fbe436138 Forward X-Forwarded-Host header
Closes #6764
2024-05-04 18:54:55 -07:00
Jared
92eab4b2e2 New: Config file setting to disable log database
Closes #6743
2024-05-04 18:54:42 -07:00
Mika
23c741fd00 Add file-count for Transmission RPC 2024-05-04 18:53:47 -07:00
Weblate
8ddf46113b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translation: Servarr/Sonarr
2024-05-04 18:53:15 -07:00
Bogdan
c81ae65461 Fixed: Limit titles in task name to 10 series 2024-04-27 18:09:08 -07:00
Bogdan
efb3fa93e4 Fixed: Retrying download for pushed releases
ignore-downstream
#6752
2024-04-27 21:08:40 -04:00
Stevie Robinson
04bd535cfc New: Don't initially select 0 byte files in Interactive Import
Closes #6686
2024-04-27 21:07:41 -04:00
Bogdan
9738101042 Treat CorruptDatabaseException as a startup failure
ignore-downstream
2024-04-27 18:06:49 -07:00
Bogdan
1df7cdc65e New: Add KRaLiMaRKo and BluDragon to release group parsing exceptions
ignore-downstream
2024-04-27 18:06:38 -07:00
Jared
d051dac12c New: Optionally use Environment Variables for settings in config.xml
Closes #6744
2024-04-27 21:06:26 -04:00
Bogdan
5d01ecd30e Bump frontend dependencies
ignore-downstream
2024-04-27 18:05:16 -07:00
Mark McDowall
316b5cbf75 New: Validate that folders in paths don't start or end with a space
Closes #6709
2024-04-27 21:04:50 -04:00
Bogdan
2440672179 Bump SixLabors.ImageSharp to 3.1.4
ignore-downstream
2024-04-27 18:04:35 -07:00
Mark McDowall
a97fbcc40a Fixed: Improve paths longer than 256 on Windows failing to hardlink 2024-04-27 18:04:26 -07:00
Christopher
d738035fed New: Remove qBitorrent torrents that reach inactive seeding time 2024-04-27 21:04:16 -04:00
Mark McDowall
dc3e932102 macOS tests now run on arm64 2024-04-27 18:03:12 -07:00
Weblate
aded9d95f7 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: maodun96 <435795439@qq.com>
Co-authored-by: toeiazarothis <patrickdealmeida89000@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-04-27 18:03:03 -07:00
Mark McDowall
b81c3ee4a8 Fix labeling config 2024-04-15 21:14:49 -07:00
Mark McDowall
cf6748a80c Fix merge conflict labeling 2024-04-15 21:14:49 -07:00
Sonarr
ef6cc7fa3a Automated API Docs update
ignore-downstream
2024-04-15 20:35:29 -07:00
Mark McDowall
f9b013a8bf New: Parse releases with multiple Ukranian audio tracks
Closes #6714
2024-04-15 20:25:41 -07:00
Bogdan
e966254462 Fixed: Re-testing edited providers will forcibly test them 2024-04-15 20:25:31 -07:00
Gauvino
016c4b353b Add merge conflict labeler 2024-04-15 23:25:13 -04:00
Uruk
d71c619f1a Update CI dependencies 2024-04-15 20:24:20 -07:00
Gauthier
6c232b062c New: Multi Language selection per indexer
Closes #2854
2024-04-15 23:24:05 -04:00
Josh McKinney
d6278fced4 Add dev container workspace
Allows the linting and style settings for the frontend to be applied even when you load the main repo as a workspace
2024-04-15 20:23:13 -07:00
Weblate
317ce39aa2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Fonkio <maxime.fabre10@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Jacopo Luca Maria Latrofa <jacopo.latrofa@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translation: Servarr/Sonarr
2024-04-15 20:23:04 -07:00
Mark McDowall
941985f65b Bump version to 4.0.4 2024-04-13 10:05:01 -07:00
Mark McDowall
10daf97d81 Improve build step dependencies 2024-04-12 16:00:46 -07:00
Mark McDowall
6b08117d7d Improve release notes for main releases 2024-04-12 16:00:46 -07:00
Weblate
9afe1c4b3f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: YSLG <1451164040@qq.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-04-12 16:00:37 -07:00
Mark McDowall
0fdbbd018c New: Parse absolute episode numbers within square brackets
Closes #6694
2024-04-12 16:00:16 -07:00
Sonarr
8a7b67c593 Automated API Docs update
ignore-downstream
2024-04-09 16:17:48 -07:00
Weblate
4b8afe3d33 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: myrad2267 <myrad2267@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translation: Servarr/Sonarr
2024-04-09 16:16:11 -07:00
Bogdan
476e7a7b94 Fixed: Changing Release Type in Manage Episodes
Closes #6706
2024-04-09 19:14:56 -04:00
Bogdan
1fcd2b492c Prevent multiple enumerations in Custom Formats token 2024-04-09 16:14:33 -07:00
Bogdan
1aef91041e New: Detect shfs mounts in disk space 2024-04-09 16:14:16 -07:00
Bogdan
fc06e51352 Fixed: Renaming episodes for a series
Closes #6640
2024-04-09 19:13:59 -04:00
Mark McDowall
f4c19a384b New: Auto tag series based on tags present/absent on series
Closes #6236
2024-04-09 16:13:30 -07:00
Josh McKinney
5061dc4b5e Add DevContainer, VSCode config and extensions.json 2024-04-09 19:12:58 -04:00
Mark McDowall
37863a8deb New: Option to prefix app name on Telegram notification titles 2024-04-09 19:12:20 -04:00
Mark McDowall
5c42935eb3 Fixed: Improve AniList testing with Media filters 2024-04-09 16:12:05 -07:00
Weblate
dac69445e4 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/uk/
Translation: Servarr/Sonarr
2024-04-05 23:13:02 -07:00
Qstick
aca10f6f4f Fixed: Skip move when source and destination are the same
ignore-downstream

Co-Authored-By: Colin Hebert <makkhdyn@gmail.com>
(cherry picked from commit 7a5ae56a96700f401726ac80b3031a25207d8f75)
2024-04-05 23:11:37 -07:00
Mark McDowall
74cdf01e49 New: Set 'Release Type' during Manual Import
Closes #6681
2024-04-06 02:11:17 -04:00
Mark McDowall
a169ebff2a Fixed: Sending ntfy.sh notifications with unicode characters
Closes #6679
2024-04-06 02:11:03 -04:00
fireph
7fc3bebc91 New: Footnote to indicate some renaming tokens support truncation 2024-04-06 02:10:42 -04:00
Till Krüss
e672996dbb Improve text for file deleted through UI/API 2024-04-06 02:09:55 -04:00
Stevie Robinson
238ba85f0a New: Informational text on Custom Formats modal 2024-04-06 02:08:57 -04:00
Cuki
1562d3bae3 Fixed: Use widely supported display mode for PWA 2024-04-06 02:08:08 -04:00
Jendrik Weise
7776ec9955 Reimport files imported prematurely during script import 2024-04-05 23:07:38 -07:00
Jendrik Weise
af5a681ab7 Fix ignoring title based on pre-rename episodefile 2024-04-05 23:07:38 -07:00
Jendrik Weise
0a7f3a12c2 Do not remove all extras when script importing 2024-04-05 23:07:38 -07:00
Jendrik Weise
2ef46e5b90 Fix incorrect subtitle copy regex 2024-04-05 23:07:38 -07:00
Mark McDowall
6003ca1696 Fixed: Deleted episodes not being unmonitored when series folder has been deleted
Closes #6678
2024-04-05 23:07:07 -07:00
Mark McDowall
0937ee6fef Fixed: Path parsing incorrectly treating series title as episode number 2024-04-05 23:06:56 -07:00
Mark McDowall
60ee7cc716 Fixed: Cleanse BHD RSS key in log files
Closes #6666
2024-04-06 02:06:35 -04:00
Mark McDowall
4e83820511 Bump version to 4.0.3 2024-03-31 21:52:19 -07:00
Sonarr
5a66b949cf Automated API Docs update
ignore-downstream
2024-03-31 21:43:33 -07:00
Weblate
f010f56290 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: 王锋 <17611382361@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/
Translation: Servarr/Sonarr
2024-03-31 21:43:09 -07:00
Louis R
060b789bc6 Fixed: Exceptions when checking for routable IPv4 addresses 2024-03-28 01:31:28 -04:00
Bogdan
7353fe479d New: Allow HEAD requests to ping endpoint
Closes #6656
2024-03-28 01:30:45 -04:00
Alex Cortelyou
1ec1ce58e9 New: Add additional fields to Webhook Manual Interaction Required events 2024-03-28 01:30:21 -04:00
Stevie Robinson
35d0e6a6f8 Fixed: Handling torrents with relative path in rTorrent 2024-03-28 01:29:15 -04:00
Carlos Gustavo Sarmiento
588372fd95 Fixed: qBittorrent not correctly handling retention during testing 2024-03-28 01:28:41 -04:00
Bogdan
13c925b341 New: Advanced settings toggle in import list, notification and download client modals 2024-03-27 22:27:51 -07:00
iceypotato
1335efd487 New: My Anime List import list
Closes #5148
2024-03-27 22:27:34 -07:00
Mark McDowall
d338425951 Fixed: Use custom formats from import during rename 2024-03-27 22:27:25 -07:00
Mark McDowall
fc6494c569 Fixed: Task with removed series causing error 2024-03-27 22:27:14 -07:00
Weblate
c403b2cdd5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Stanislav <prekop3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/tr/
Translation: Servarr/Sonarr
2024-03-27 22:27:07 -07:00
503 changed files with 11664 additions and 4285 deletions

View File

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

View File

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

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

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

18
.github/labeler.yml vendored
View File

@@ -1,17 +1,23 @@
'connection':
- src/NzbDrone.Core/Notifications/**/*
- changed-files:
- any-glob-to-any-file: src/NzbDrone.Core/Notifications/**/*
'db-migration':
- src/NzbDrone.Core/Datastore/Migration/*
- changed-files:
- any-glob-to-any-file: src/NzbDrone.Core/Datastore/Migration/*
'download-client':
- src/NzbDrone.Core/Download/Clients/**/*
- changed-files:
- any-glob-to-any-file: src/NzbDrone.Core/Download/Clients/**/*
'indexer':
- src/NzbDrone.Core/Indexers/**/*
- changed-files:
- any-glob-to-any-file: src/NzbDrone.Core/Indexers/**/*
'parsing':
- src/NzbDrone.Core/Parser/**/*
- changed-files:
- any-glob-to-any-file: src/NzbDrone.Core/Parser/**/*
'ui-only':
- all: ['frontend/**/*']
- changed-files:
- any-glob-to-all-files: frontend/**/*

View File

@@ -6,13 +6,13 @@ on:
- develop
- main
paths-ignore:
- 'src/Sonarr.Api.*/openapi.json'
- "src/Sonarr.Api.*/openapi.json"
pull_request:
branches:
- develop
paths-ignore:
- 'src/NzbDrone.Core/Localization/Core/**'
- 'src/Sonarr.Api.*/openapi.json'
- "src/NzbDrone.Core/Localization/Core/**"
- "src/Sonarr.Api.*/openapi.json"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -22,7 +22,7 @@ env:
FRAMEWORK: net6.0
RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
SONARR_MAJOR_VERSION: 4
VERSION: 4.0.2
VERSION: 4.0.8
jobs:
backend:
@@ -32,105 +32,105 @@ jobs:
major_version: ${{ steps.variables.outputs.major_version }}
version: ${{ steps.variables.outputs.version }}
steps:
- name: Check out
uses: actions/checkout@v4
- name: Check out
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
- name: Setup Environment Variables
id: variables
shell: bash
run: |
# Add 800 to the build number because GitHub won't let us pick an arbitrary starting point
SONARR_VERSION="${{ env.VERSION }}.$((${{ github.run_number }}+800))"
DOTNET_VERSION=$(jq -r '.sdk.version' global.json)
- name: Setup Environment Variables
id: variables
shell: bash
run: |
# Add 800 to the build number because GitHub won't let us pick an arbitrary starting point
SONARR_VERSION="${{ env.VERSION }}.$((${{ github.run_number }}+800))"
DOTNET_VERSION=$(jq -r '.sdk.version' global.json)
echo "SDK_PATH=${{ env.DOTNET_ROOT }}/sdk/${DOTNET_VERSION}" >> "$GITHUB_ENV"
echo "SONARR_VERSION=$SONARR_VERSION" >> "$GITHUB_ENV"
echo "BRANCH=${RAW_BRANCH_NAME/\//-}" >> "$GITHUB_ENV"
echo "SDK_PATH=${{ env.DOTNET_ROOT }}/sdk/${DOTNET_VERSION}" >> "$GITHUB_ENV"
echo "SONARR_VERSION=$SONARR_VERSION" >> "$GITHUB_ENV"
echo "BRANCH=${RAW_BRANCH_NAME//\//-}" >> "$GITHUB_ENV"
echo "framework=${{ env.FRAMEWORK }}" >> "$GITHUB_OUTPUT"
echo "major_version=${{ env.SONARR_MAJOR_VERSION }}" >> "$GITHUB_OUTPUT"
echo "version=$SONARR_VERSION" >> "$GITHUB_OUTPUT"
echo "framework=${{ env.FRAMEWORK }}" >> "$GITHUB_OUTPUT"
echo "major_version=${{ env.SONARR_MAJOR_VERSION }}" >> "$GITHUB_OUTPUT"
echo "version=$SONARR_VERSION" >> "$GITHUB_OUTPUT"
- name: Enable Extra Platforms In SDK
shell: bash
run: ./build.sh --enable-extra-platforms-in-sdk
- name: Enable Extra Platforms In SDK
shell: bash
run: ./build.sh --enable-extra-platforms-in-sdk
- name: Build Backend
shell: bash
run: ./build.sh --backend --enable-extra-platforms --packages
- name: Build Backend
shell: bash
run: ./build.sh --backend --enable-extra-platforms --packages
# Test Artifacts
# Test Artifacts
- name: Publish win-x64 Test Artifact
uses: ./.github/actions/publish-test-artifact
with:
framework: ${{ env.FRAMEWORK }}
runtime: win-x64
- name: Publish win-x64 Test Artifact
uses: ./.github/actions/publish-test-artifact
with:
framework: ${{ env.FRAMEWORK }}
runtime: win-x64
- name: Publish linux-x64 Test Artifact
uses: ./.github/actions/publish-test-artifact
with:
framework: ${{ env.FRAMEWORK }}
runtime: linux-x64
- name: Publish linux-x64 Test Artifact
uses: ./.github/actions/publish-test-artifact
with:
framework: ${{ env.FRAMEWORK }}
runtime: linux-x64
- name: Publish osx-x64 Test Artifact
uses: ./.github/actions/publish-test-artifact
with:
framework: ${{ env.FRAMEWORK }}
runtime: osx-x64
- name: Publish osx-arm64 Test Artifact
uses: ./.github/actions/publish-test-artifact
with:
framework: ${{ env.FRAMEWORK }}
runtime: osx-arm64
# Build Artifacts (grouped by OS)
- name: Publish FreeBSD Artifact
uses: actions/upload-artifact@v4
with:
name: build_freebsd
path: _artifacts/freebsd-*/**/*
- name: Publish Linux Artifact
uses: actions/upload-artifact@v4
with:
name: build_linux
path: _artifacts/linux-*/**/*
- name: Publish macOS Artifact
uses: actions/upload-artifact@v4
with:
name: build_macos
path: _artifacts/osx-*/**/*
- name: Publish Windows Artifact
uses: actions/upload-artifact@v4
with:
name: build_windows
path: _artifacts/win-*/**/*
# Build Artifacts (grouped by OS)
- name: Publish FreeBSD Artifact
uses: actions/upload-artifact@v4
with:
name: build_freebsd
path: _artifacts/freebsd-*/**/*
- name: Publish Linux Artifact
uses: actions/upload-artifact@v4
with:
name: build_linux
path: _artifacts/linux-*/**/*
- name: Publish macOS Artifact
uses: actions/upload-artifact@v4
with:
name: build_macos
path: _artifacts/osx-*/**/*
- name: Publish Windows Artifact
uses: actions/upload-artifact@v4
with:
name: build_windows
path: _artifacts/win-*/**/*
frontend:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Check out
uses: actions/checkout@v4
- name: Volta
uses: volta-cli/action@v4
- name: Volta
uses: volta-cli/action@v4
- name: Yarn Install
run: yarn install
- name: Yarn Install
run: yarn install
- name: Lint
run: yarn lint
- name: Lint
run: yarn lint
- name: Stylelint
run: yarn stylelint -f github
- name: Stylelint
run: yarn stylelint -f github
- name: Build
run: yarn build --env production
- name: Build
run: yarn build --env production
- name: Publish UI Artifact
uses: actions/upload-artifact@v4
with:
name: build_ui
path: _output/UI/**/*
- name: Publish UI Artifact
uses: actions/upload-artifact@v4
with:
name: build_ui
path: _output/UI/**/*
unit_test:
needs: backend
@@ -143,39 +143,39 @@ jobs:
artifact: tests-linux-x64
filter: TestCategory!=ManualTest&TestCategory!=WINDOWS&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
- os: macos-latest
artifact: tests-osx-x64
artifact: tests-osx-arm64
filter: TestCategory!=ManualTest&TestCategory!=WINDOWS&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
- os: windows-latest
artifact: tests-win-x64
filter: TestCategory!=ManualTest&TestCategory!=LINUX&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
runs-on: ${{ matrix.os }}
steps:
- name: Check out
uses: actions/checkout@v4
- name: Check out
uses: actions/checkout@v4
- name: Test
uses: ./.github/actions/test
with:
os: ${{ matrix.os }}
artifact: ${{ matrix.artifact }}
pattern: Sonarr.*.Test.dll
filter: ${{ matrix.filter }}
- name: Test
uses: ./.github/actions/test
with:
os: ${{ matrix.os }}
artifact: ${{ matrix.artifact }}
pattern: Sonarr.*.Test.dll
filter: ${{ matrix.filter }}
unit_test_postgres:
needs: backend
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Check out
uses: actions/checkout@v4
- name: Test
uses: ./.github/actions/test
with:
os: ubuntu-latest
artifact: tests-linux-x64
pattern: Sonarr.*.Test.dll
filter: TestCategory!=ManualTest&TestCategory!=WINDOWS&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
use_postgres: true
- name: Test
uses: ./.github/actions/test
with:
os: ubuntu-latest
artifact: tests-linux-x64
pattern: Sonarr.*.Test.dll
filter: TestCategory!=ManualTest&TestCategory!=WINDOWS&TestCategory!=IntegrationTest&TestCategory!=AutomationTest
use_postgres: true
integration_test:
needs: backend
@@ -190,10 +190,10 @@ jobs:
binary_artifact: build_linux
binary_path: linux-x64/${{ needs.backend.outputs.framework }}/Sonarr
- os: macos-latest
artifact: tests-osx-x64
artifact: tests-osx-arm64
filter: TestCategory!=ManualTest&TestCategory!=WINDOWS&TestCategory=IntegrationTest
binary_artifact: build_macos
binary_path: osx-x64/${{ needs.backend.outputs.framework }}/Sonarr
binary_path: osx-arm64/${{ needs.backend.outputs.framework }}/Sonarr
- os: windows-latest
artifact: tests-win-x64
filter: TestCategory!=ManualTest&TestCategory!=LINUX&TestCategory=IntegrationTest
@@ -201,23 +201,23 @@ jobs:
binary_path: win-x64/${{ needs.backend.outputs.framework }}/Sonarr
runs-on: ${{ matrix.os }}
steps:
- name: Check out
uses: actions/checkout@v4
- name: Check out
uses: actions/checkout@v4
- name: Test
uses: ./.github/actions/test
with:
os: ${{ matrix.os }}
artifact: ${{ matrix.artifact }}
pattern: Sonarr.*.Test.dll
filter: ${{ matrix.filter }}
integration_tests: true
binary_artifact: ${{ matrix.binary_artifact }}
binary_path: ${{ matrix.binary_path }}
- name: Test
uses: ./.github/actions/test
with:
os: ${{ matrix.os }}
artifact: ${{ matrix.artifact }}
pattern: Sonarr.*.Test.dll
filter: ${{ matrix.filter }}
integration_tests: true
binary_artifact: ${{ matrix.binary_artifact }}
binary_path: ${{ matrix.binary_path }}
deploy:
if: ${{ github.ref_name == 'develop' || github.ref_name == 'main' }}
needs: [backend, unit_test, unit_test_postgres, integration_test]
needs: [backend, frontend, unit_test, unit_test_postgres, integration_test]
secrets: inherit
uses: ./.github/workflows/deploy.yml
with:
@@ -228,7 +228,15 @@ jobs:
notify:
name: Discord Notification
needs: [backend, unit_test, unit_test_postgres, integration_test]
needs:
[
backend,
frontend,
unit_test,
unit_test_postgres,
integration_test,
deploy,
]
if: ${{ !cancelled() && (github.ref_name == 'develop' || github.ref_name == 'main') }}
env:
STATUS: ${{ contains(needs.*.result, 'failure') && 'failure' || 'success' }}
@@ -236,13 +244,13 @@ jobs:
steps:
- name: Notify
uses: tsickert/discord-webhook@v5.3.0
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
username: 'GitHub Actions'
avatar-url: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
username: "GitHub Actions"
avatar-url: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
embed-title: "${{ github.workflow }}: ${{ env.STATUS == 'success' && 'Success' || 'Failure' }}"
embed-url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-description: |
**Branch** ${{ github.ref }}
**Build** ${{ needs.backend.outputs.version }}

26
.github/workflows/conflict_labeler.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Merge Conflict Labeler
on:
push:
branches:
- develop
pull_request_target:
branches:
- develop
types: [synchronize]
jobs:
label:
name: Labeling
runs-on: ubuntu-latest
if: ${{ github.repository == 'Sonarr/Sonarr' }}
permissions:
contents: read
pull-requests: write
steps:
- name: Apply label
uses: eps1lon/actions-label-merge-conflict@v3
with:
dirtyLabel: 'merge-conflict'
repoToken: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -69,12 +69,38 @@ jobs:
pattern: release_*
merge-multiple: true
- name: Get Previous Release
id: previous-release
uses: cardinalby/git-get-release-action@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
latest: true
prerelease: ${{ inputs.branch != 'main' }}
- name: Generate Release Notes
id: generate-release-notes
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
result-encoding: string
script: |
const { data } = await github.rest.repos.generateReleaseNotes({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: 'v${{ inputs.version }}',
target_commitish: '${{ github.sha }}',
previous_tag_name: '${{ steps.previous-release.outputs.tag_name }}',
})
return data.body
- name: Create release
uses: ncipollo/release-action@v1
with:
artifacts: _artifacts/Sonarr.*
commit: ${{ github.sha }}
generateReleaseNotes: true
generateReleaseNotes: false
body: ${{ steps.generate-release-notes.outputs.result }}
name: ${{ inputs.version }}
prerelease: ${{ inputs.branch != 'main' }}
skipIfReleaseExists: true

View File

@@ -8,5 +8,6 @@ jobs:
contents: read
pull-requests: write
runs-on: ubuntu-latest
if: github.repository == 'Sonarr/Sonarr'
steps:
- uses: actions/labeler@v4
- uses: actions/labeler@v5

View File

@@ -8,14 +8,15 @@ on:
jobs:
lock:
runs-on: ubuntu-latest
if: github.repository == 'Sonarr/Sonarr'
steps:
- uses: dessant/lock-threads@v2
- uses: dessant/lock-threads@v5
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '90'
issue-exclude-created-before: ''
issue-exclude-labels: 'one-day-maybe'
issue-lock-labels: ''
issue-lock-comment: ''
issue-inactive-days: '90'
exclude-issue-created-before: ''
exclude-any-issue-labels: 'one-day-maybe'
add-issue-labels: ''
issue-comment: ''
issue-lock-reason: 'resolved'
process-only: ''

1
.gitignore vendored
View File

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

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

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

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

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

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

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

12
docs.sh
View File

@@ -25,17 +25,23 @@ slnFile=src/Sonarr.sln
platform=Posix
if [ "$PLATFORM" = "Windows" ]; then
application=Sonarr.Console.dll
else
application=Sonarr.dll
fi
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest
dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Sonarr.Api.V3/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/Sonarr.dll" v3 &
dotnet tool run swagger tofile --output ./src/Sonarr.Api.V3/openapi.json "$outputFolder/$FRAMEWORK/$RUNTIME/$application" v3 &
sleep 30
sleep 45
kill %1

View File

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

View File

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

View File

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

View File

@@ -77,7 +77,7 @@ class HistoryRow extends Component {
onMarkAsFailedPress
} = this.props;
if (!episode) {
if (!series || !episode) {
return null;
}

View File

@@ -217,6 +217,7 @@ class Queue extends Component {
>
<TableOptionsModalWrapper
columns={columns}
maxPageSize={200}
{...otherProps}
optionsComponent={QueueOptionsConnector}
>

View File

@@ -70,6 +70,11 @@ function QueueStatus(props) {
iconName = icons.DOWNLOADED;
title = translate('Downloaded');
if (trackedDownloadState === 'importBlocked') {
title += ` - ${translate('UnableToImportAutomatically')}`;
iconKind = kinds.WARNING;
}
if (trackedDownloadState === 'importPending') {
title += ` - ${translate('WaitingToImport')}`;
iconKind = kinds.PURPLE;

View File

@@ -118,6 +118,7 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
{
key: 'blocklistAndSearch',
value: translate('BlocklistAndSearch'),
isDisabled: isPending,
hint: multipleSelected
? translate('BlocklistAndSearchMultipleHint')
: translate('BlocklistAndSearchHint'),
@@ -130,7 +131,7 @@ function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
: translate('BlocklistOnlyHint'),
},
];
}, [multipleSelected]);
}, [isPending, multipleSelected]);
const handleRemovalMethodChange = useCallback(
({ value }: { value: RemovalMethod }) => {

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

@@ -145,6 +145,7 @@ class AddNewSeriesSearchResult extends Component {
<Label size={sizes.LARGE}>
<HeartRating
rating={ratings.value}
votes={ratings.votes}
iconSize={13}
/>
</Label>

View File

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

View File

@@ -30,7 +30,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector';
import Logs from 'System/Logs/Logs';
import Status from 'System/Status/Status';
import Tasks from 'System/Tasks/Tasks';
import UpdatesConnector from 'System/Updates/UpdatesConnector';
import Updates from 'System/Updates/Updates';
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
import MissingConnector from 'Wanted/Missing/MissingConnector';
@@ -248,7 +248,7 @@ function AppRoutes(props) {
<Route
path="/system/updates"
component={UpdatesConnector}
component={Updates}
/>
<Route

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
import BlocklistAppState from './BlocklistAppState';
import CalendarAppState from './CalendarAppState';
import CommandAppState from './CommandAppState';
import EpisodeFilesAppState from './EpisodeFilesAppState';
@@ -45,6 +46,7 @@ export interface CustomFilter {
}
export interface AppSectionState {
version: string;
dimensions: {
isSmallScreen: boolean;
width: number;
@@ -54,6 +56,7 @@ export interface AppSectionState {
interface AppState {
app: AppSectionState;
blocklist: BlocklistAppState;
calendar: CalendarAppState;
commands: CommandAppState;
episodeFiles: EpisodeFilesAppState;

View File

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

View File

@@ -20,6 +20,7 @@ export interface SeriesIndexAppState {
showTitle: boolean;
showMonitored: boolean;
showQualityProfile: boolean;
showTags: boolean;
showSearchAction: boolean;
};
@@ -34,6 +35,7 @@ export interface SeriesIndexAppState {
showSeasonCount: boolean;
showPath: boolean;
showSizeOnDisk: boolean;
showTags: boolean;
showSearchAction: boolean;
};

View File

@@ -14,13 +14,16 @@ 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 type GeneralAppState = AppSectionItemState<General>;
export interface ImportListAppState
extends AppSectionState<ImportList>,
AppSectionDeleteState,
@@ -58,6 +61,7 @@ export type UiSettingsAppState = AppSectionItemState<UiSettings>;
interface SettingsAppState {
advancedSettings: boolean;
downloadClients: DownloadClientAppState;
general: GeneralAppState;
importListExclusions: ImportListExclusionsSettingsAppState;
importListOptions: ImportListOptionsSettingsAppState;
importLists: ImportListAppState;

View File

@@ -1,9 +1,12 @@
import SystemStatus from 'typings/SystemStatus';
import { AppSectionItemState } from './AppSectionState';
import Update from 'typings/Update';
import AppSectionState, { AppSectionItemState } from './AppSectionState';
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
export type UpdateAppState = AppSectionState<Update>;
interface SystemAppState {
updates: UpdateAppState;
status: SystemStatusAppState;
}

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

@@ -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';
@@ -224,7 +225,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 Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
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 SeriesFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const tagList = allSeries
.map((series) => ({ id: series.id, name: series.title }))
.sort(sortByName);
.sort(sortByProp('name'));
return <FilterBuilderRowValue {...props} tagList={tagList} />;
}

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,7 @@ 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';
@@ -23,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,

View File

@@ -271,26 +271,32 @@ class EnhancedSelectInput extends Component {
this.setState({ isOpen: !this.state.isOpen });
};
onSelect = (value) => {
if (Array.isArray(this.props.value)) {
let newValue = null;
const index = this.props.value.indexOf(value);
onSelect = (newValue) => {
const { name, value, values, onChange } = this.props;
const additionalProperties = values.find((v) => v.key === newValue)?.additionalProperties;
if (Array.isArray(value)) {
let arrayValue = null;
const index = value.indexOf(newValue);
if (index === -1) {
newValue = this.props.values.map((v) => v.key).filter((v) => (v === value) || this.props.value.includes(v));
arrayValue = values.map((v) => v.key).filter((v) => (v === newValue) || value.includes(v));
} else {
newValue = [...this.props.value];
newValue.splice(index, 1);
arrayValue = [...value];
arrayValue.splice(index, 1);
}
this.props.onChange({
name: this.props.name,
value: newValue
onChange({
name,
value: arrayValue,
additionalProperties
});
} else {
this.setState({ isOpen: false });
this.props.onChange({
name: this.props.name,
value
onChange({
name,
value: newValue,
additionalProperties
});
}
};
@@ -485,7 +491,7 @@ class EnhancedSelectInput extends Component {
values.map((v, index) => {
const hasParent = v.parentKey !== undefined;
const depth = hasParent ? 1 : 0;
const parentSelected = hasParent && value.includes(v.parentKey);
const parentSelected = hasParent && Array.isArray(value) && value.includes(v.parentKey);
return (
<OptionComponent
key={v.key}

View File

@@ -9,7 +9,8 @@ import EnhancedSelectInput from './EnhancedSelectInput';
const importantFieldNames = [
'baseUrl',
'apiPath',
'apiKey'
'apiKey',
'authToken'
];
function getProviderDataKey(providerData) {
@@ -34,7 +35,9 @@ function getSelectOptions(items) {
key: option.value,
value: option.name,
hint: option.hint,
parentKey: option.parentValue
parentKey: option.parentValue,
isDisabled: option.isDisabled,
additionalProperties: option.additionalProperties
};
});
}
@@ -147,7 +150,7 @@ EnhancedSelectInputConnector.propTypes = {
provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
selectOptionsProviderAction: PropTypes.string,
onChange: PropTypes.func.isRequired,

View File

@@ -22,6 +22,7 @@ import PasswordInput from './PasswordInput';
import PathInputConnector from './PathInputConnector';
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
import SeriesTagInput from './SeriesTagInput';
import SeriesTypeSelectInput from './SeriesTypeSelectInput';
import TagInputConnector from './TagInputConnector';
import TagSelectInputConnector from './TagSelectInputConnector';
@@ -87,6 +88,9 @@ function getComponent(type) {
case inputTypes.DYNAMIC_SELECT:
return EnhancedSelectInputConnector;
case inputTypes.SERIES_TAG:
return SeriesTagInput;
case inputTypes.SERIES_TYPE_SELECT:
return SeriesTypeSelectInput;

View File

@@ -4,7 +4,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 translate from 'Utilities/String/translate';
import EnhancedSelectInput from './EnhancedSelectInput';
@@ -20,7 +20,7 @@ function createMapStateToProps() {
items
} = indexers;
const values = _.map(items.sort(sortByName), (indexer) => {
const values = _.map(items.sort(sortByProp('name')), (indexer) => {
return {
key: indexer.id,
value: indexer.name

View File

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

View File

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

View File

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

View File

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

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

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

View File

@@ -1,29 +1,40 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './HeartRating.css';
function HeartRating({ rating, iconSize }) {
function HeartRating({ rating, votes, iconSize }) {
return (
<span className={styles.rating}>
<Icon
className={styles.heart}
name={icons.HEART}
size={iconSize}
/>
<Tooltip
anchor={
<span className={styles.rating}>
<Icon
className={styles.heart}
name={icons.HEART}
size={iconSize}
/>
{rating * 10}%
</span>
{rating * 10}%
</span>
}
tooltip={translate('CountVotes', { votes })}
kind={kinds.INVERSE}
position={tooltipPositions.TOP}
/>
);
}
HeartRating.propTypes = {
rating: PropTypes.number.isRequired,
votes: PropTypes.number.isRequired,
iconSize: PropTypes.number.isRequired
};
HeartRating.defaultProps = {
votes: 0,
iconSize: 14
};

View File

@@ -88,6 +88,15 @@
}
}
.purple {
border-color: var(--purple);
background-color: var(--purple);
&.outline {
color: var(--purple);
}
}
/** Sizes **/
.small {

View File

@@ -11,6 +11,7 @@ interface CssExports {
'medium': string;
'outline': string;
'primary': string;
'purple': string;
'small': string;
'success': string;
'warning': string;

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

@@ -21,6 +21,7 @@ function createCleanSeriesSelector() {
tvdbId,
tvMazeId,
imdbId,
tmdbId,
tags = []
} = series;
@@ -33,6 +34,7 @@ function createCleanSeriesSelector() {
tvdbId,
tvMazeId,
imdbId,
tmdbId,
firstCharacter: title.charAt(0).toLowerCase(),
tags: tags.reduce((acc, id) => {
const matchingTag = allTags.find((tag) => tag.id === id);

View File

@@ -14,6 +14,7 @@ function SeriesSearchResult(props) {
tvdbId,
tvMazeId,
imdbId,
tmdbId,
tags
} = props;
@@ -73,6 +74,14 @@ function SeriesSearchResult(props) {
null
}
{
match.key === 'tmdbId' && tmdbId ?
<div className={styles.alternateTitle}>
TmdbId: {tmdbId}
</div> :
null
}
{
tag ?
<div className={styles.tagContainer}>
@@ -97,6 +106,7 @@ SeriesSearchResult.propTypes = {
tvdbId: PropTypes.number,
tvMazeId: PropTypes.number,
imdbId: PropTypes.string,
tmdbId: PropTypes.number,
tags: PropTypes.arrayOf(PropTypes.object).isRequired,
match: PropTypes.object.isRequired
};

View File

@@ -13,6 +13,7 @@ const fuseOptions = {
'tvdbId',
'tvMazeId',
'imdbId',
'tmdbId',
'tags.label'
]
};

View File

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

View File

@@ -15,6 +15,7 @@ class RelativeDateCell extends PureComponent {
className,
date,
includeSeconds,
includeTime,
showRelativeDates,
shortDateFormat,
longDateFormat,
@@ -39,7 +40,7 @@ class RelativeDateCell extends PureComponent {
title={formatDateTime(date, longDateFormat, timeFormat, { includeSeconds, includeRelativeDay: !showRelativeDates })}
{...otherProps}
>
{getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds, timeForToday: true })}
{getRelativeDate({ date, shortDateFormat, showRelativeDates, timeFormat, includeSeconds, includeTime, timeForToday: true })}
</Component>
);
}
@@ -49,6 +50,7 @@ RelativeDateCell.propTypes = {
className: PropTypes.string.isRequired,
date: PropTypes.string,
includeSeconds: PropTypes.bool.isRequired,
includeTime: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
@@ -60,6 +62,7 @@ RelativeDateCell.propTypes = {
RelativeDateCell.defaultProps = {
className: styles.cell,
includeSeconds: false,
includeTime: false,
component: TableRowCell
};

View File

@@ -49,11 +49,12 @@ class TableOptionsModal extends Component {
onPageSizeChange = ({ value }) => {
let pageSizeError = null;
const maxPageSize = this.props.maxPageSize ?? 250;
if (value < 5) {
pageSizeError = translate('TablePageSizeMinimum', { minimumValue: '5' });
} else if (value > 250) {
pageSizeError = translate('TablePageSizeMaximum', { maximumValue: '250' });
} else if (value > maxPageSize) {
pageSizeError = translate('TablePageSizeMaximum', { maximumValue: `${maxPageSize}` });
} else {
this.props.onTableOptionChange({ pageSize: value });
}
@@ -248,6 +249,7 @@ TableOptionsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
pageSize: PropTypes.number,
maxPageSize: PropTypes.number,
canModifyColumns: PropTypes.bool.isRequired,
optionsComponent: PropTypes.elementType,
onTableOptionChange: PropTypes.func.isRequired,

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

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

View File

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

View File

@@ -111,6 +111,8 @@ class EpisodeHistoryRow extends Component {
<RelativeDateCellConnector
date={date}
includeSeconds={true}
includeTime={true}
/>
<TableRowCell className={styles.actions}>

View File

@@ -17,6 +17,12 @@
width: 175px;
}
.customFormatScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 65px;
}
.actions {
composes: cell from '~Components/Table/Cells/TableRowCell.css';

View File

@@ -2,6 +2,7 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'customFormatScore': string;
'customFormats': string;
'languages': string;
'quality': string;

View File

@@ -11,6 +11,7 @@ import EpisodeLanguages from 'Episode/EpisodeLanguages';
import EpisodeQuality from 'Episode/EpisodeQuality';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import MediaInfo from './MediaInfo';
import styles from './EpisodeFileRow.css';
@@ -55,6 +56,7 @@ class EpisodeFileRow extends Component {
languages,
quality,
customFormats,
customFormatScore,
qualityCutoffNotMet,
mediaInfo,
columns
@@ -127,6 +129,17 @@ class EpisodeFileRow extends Component {
);
}
if (name === 'customFormatScore') {
return (
<TableRowCell
key={name}
className={styles.customFormatScore}
>
{formatCustomFormatScore(customFormatScore, customFormats.length)}
</TableRowCell>
);
}
if (name === 'actions') {
return (
<TableRowCell
@@ -183,6 +196,7 @@ EpisodeFileRow.propTypes = {
quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
mediaInfo: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onDeleteEpisodeFile: PropTypes.func.isRequired

View File

@@ -1,10 +1,11 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { kinds, sizes } from 'Helpers/Props';
import { icons, kinds, sizes } from 'Helpers/Props';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import translate from 'Utilities/String/translate';
import EpisodeAiringConnector from './EpisodeAiringConnector';
@@ -42,6 +43,15 @@ const columns = [
isSortable: false,
isVisible: true
},
{
name: 'customFormatScore',
label: React.createElement(Icon, {
name: icons.SCORE,
title: () => translate('CustomFormatScore')
}),
isSortable: true,
isVisible: true
},
{
name: 'actions',
label: '',
@@ -94,6 +104,7 @@ class EpisodeSummary extends Component {
languages,
quality,
customFormats,
customFormatScore,
qualityCutoffNotMet,
onDeleteEpisodeFile
} = this.props;
@@ -143,6 +154,7 @@ class EpisodeSummary extends Component {
quality={quality}
qualityCutoffNotMet={qualityCutoffNotMet}
customFormats={customFormats}
customFormatScore={customFormatScore}
mediaInfo={mediaInfo}
columns={columns}
onDeleteEpisodeFile={onDeleteEpisodeFile}
@@ -179,6 +191,7 @@ EpisodeSummary.propTypes = {
quality: PropTypes.object,
qualityCutoffNotMet: PropTypes.bool,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
onDeleteEpisodeFile: PropTypes.func.isRequired
};

View File

@@ -31,7 +31,8 @@ function createMapStateToProps() {
languages,
quality,
qualityCutoffNotMet,
customFormats
customFormats,
customFormatScore
} = episodeFile;
return {
@@ -45,7 +46,8 @@ function createMapStateToProps() {
languages,
quality,
qualityCutoffNotMet,
customFormats
customFormats,
customFormatScore
};
}
);

View File

@@ -0,0 +1,17 @@
import ReleaseType from 'InteractiveImport/ReleaseType';
import translate from 'Utilities/String/translate';
export default function getReleaseTypeName(
releaseType?: ReleaseType
): string | null {
switch (releaseType) {
case 'singleEpisode':
return translate('SingleEpisode');
case 'multiEpisode':
return translate('MultiEpisode');
case 'seasonPack':
return translate('SeasonPack');
default:
return translate('Unknown');
}
}

View File

@@ -1,4 +1,5 @@
import ModelBase from 'App/ModelBase';
import ReleaseType from 'InteractiveImport/ReleaseType';
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import CustomFormat from 'typings/CustomFormat';
@@ -17,6 +18,7 @@ export interface EpisodeFile extends ModelBase {
quality: QualityModel;
customFormats: CustomFormat[];
indexerFlags: number;
releaseType: ReleaseType;
mediaInfo: MediaInfo;
qualityCutoffNotMet: boolean;
}

View File

@@ -17,6 +17,7 @@ export const LANGUAGE_SELECT = 'languageSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const SELECT = 'select';
export const SERIES_TAG = 'seriesTag';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const SERIES_TYPE_SELECT = 'seriesTypeSelect';
export const TAG = 'tag';
@@ -45,6 +46,7 @@ export const all = [
ROOT_FOLDER_SELECT,
LANGUAGE_SELECT,
SELECT,
SERIES_TAG,
DYNAMIC_SELECT,
SERIES_TYPE_SELECT,
TAG,

View File

@@ -36,6 +36,7 @@ import InteractiveImport, {
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import SelectReleaseTypeModal from 'InteractiveImport/ReleaseType/SelectReleaseTypeModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
import Language from 'Language/Language';
@@ -73,7 +74,8 @@ type SelectType =
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
| 'indexerFlags'
| 'releaseType';
type FilterExistingFiles = 'all' | 'new';
@@ -128,6 +130,12 @@ const COLUMNS = [
isSortable: true,
isVisible: true,
},
{
name: 'releaseType',
label: () => translate('ReleaseType'),
isSortable: true,
isVisible: true,
},
{
name: 'customFormats',
label: React.createElement(Icon, {
@@ -369,6 +377,10 @@ function InteractiveImportModalContent(
key: 'indexerFlags',
value: translate('SelectIndexerFlags'),
},
{
key: 'releaseType',
value: translate('SelectReleaseType'),
},
];
if (allowSeriesChange) {
@@ -511,6 +523,7 @@ function InteractiveImportModalContent(
languages,
indexerFlags,
episodeFileId,
releaseType,
} = item;
if (!series) {
@@ -560,6 +573,7 @@ function InteractiveImportModalContent(
quality,
languages,
indexerFlags,
releaseType,
});
return;
@@ -575,6 +589,7 @@ function InteractiveImportModalContent(
quality,
languages,
indexerFlags,
releaseType,
downloadId,
episodeFileId,
});
@@ -787,6 +802,22 @@ function InteractiveImportModalContent(
[selectedIds, dispatch]
);
const onReleaseTypeSelect = useCallback(
(releaseType: string) => {
dispatch(
updateInteractiveImportItems({
ids: selectedIds,
releaseType,
})
);
dispatch(reprocessInteractiveImportItems({ ids: selectedIds }));
setSelectModalOpen(null);
},
[selectedIds, dispatch]
);
const orderedSelectedIds = items.reduce((acc: number[], file) => {
if (selectedIds.includes(file.id)) {
acc.push(file.id);
@@ -1000,6 +1031,14 @@ function InteractiveImportModalContent(
onModalClose={onSelectModalClose}
/>
<SelectReleaseTypeModal
isOpen={selectModalOpen === 'releaseType'}
releaseType="unknown"
modalTitle={modalTitle}
onReleaseTypeSelect={onReleaseTypeSelect}
onModalClose={onSelectModalClose}
/>
<ConfirmModal
isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER}

View File

@@ -12,6 +12,7 @@ import Episode from 'Episode/Episode';
import EpisodeFormats from 'Episode/EpisodeFormats';
import EpisodeLanguages from 'Episode/EpisodeLanguages';
import EpisodeQuality from 'Episode/EpisodeQuality';
import getReleaseTypeName from 'Episode/getReleaseTypeName';
import IndexerFlags from 'Episode/IndexerFlags';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
@@ -20,6 +21,8 @@ import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexe
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import ReleaseType from 'InteractiveImport/ReleaseType';
import SelectReleaseTypeModal from 'InteractiveImport/ReleaseType/SelectReleaseTypeModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
import Language from 'Language/Language';
@@ -44,7 +47,8 @@ type SelectType =
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
| 'indexerFlags'
| 'releaseType';
type SelectedChangeProps = SelectStateInputProps & {
hasEpisodeFileId: boolean;
@@ -61,6 +65,7 @@ interface InteractiveImportRowProps {
quality?: QualityModel;
languages?: Language[];
size: number;
releaseType: ReleaseType;
customFormats?: object[];
customFormatScore?: number;
indexerFlags: number;
@@ -86,6 +91,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
languages,
releaseGroup,
size,
releaseType,
customFormats,
customFormatScore,
indexerFlags,
@@ -122,7 +128,8 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
seasonNumber != null &&
episodes.length &&
quality &&
languages
languages &&
size > 0
) {
onSelectedChange({
id,
@@ -315,6 +322,27 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
);
const onSelectReleaseTypePress = useCallback(() => {
setSelectModalOpen('releaseType');
}, [setSelectModalOpen]);
const onReleaseTypeSelect = useCallback(
(releaseType: ReleaseType) => {
dispatch(
updateInteractiveImportItem({
id,
releaseType,
})
);
dispatch(reprocessInteractiveImportItems({ ids: [id] }));
setSelectModalOpen(null);
selectRowAfterChange();
},
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
);
const onSelectIndexerFlagsPress = useCallback(() => {
setSelectModalOpen('indexerFlags');
}, [setSelectModalOpen]);
@@ -461,6 +489,13 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
<TableRowCell>{formatBytes(size)}</TableRowCell>
<TableRowCellButton
title={translate('ClickToChangeReleaseType')}
onPress={onSelectReleaseTypePress}
>
{getReleaseTypeName(releaseType)}
</TableRowCellButton>
<TableRowCell>
{customFormats?.length ? (
<Popover
@@ -572,6 +607,14 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
onModalClose={onSelectModalClose}
/>
<SelectReleaseTypeModal
isOpen={selectModalOpen === 'releaseType'}
releaseType={releaseType ?? 'unknown'}
modalTitle={modalTitle}
onReleaseTypeSelect={onReleaseTypeSelect}
onModalClose={onSelectModalClose}
/>
<SelectIndexerFlagsModal
isOpen={selectModalOpen === 'indexerFlags'}
indexerFlags={indexerFlags ?? 0}

View File

@@ -15,6 +15,7 @@ export interface InteractiveImportCommandOptions {
quality: QualityModel;
languages: Language[];
indexerFlags: number;
releaseType: ReleaseType;
downloadId?: string;
episodeFileId?: number;
}

View File

@@ -0,0 +1,30 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import ReleaseType from 'InteractiveImport/ReleaseType';
import SelectReleaseTypeModalContent from './SelectReleaseTypeModalContent';
interface SelectQualityModalProps {
isOpen: boolean;
releaseType: ReleaseType;
modalTitle: string;
onReleaseTypeSelect(releaseType: ReleaseType): void;
onModalClose(): void;
}
function SelectReleaseTypeModal(props: SelectQualityModalProps) {
const { isOpen, releaseType, modalTitle, onReleaseTypeSelect, onModalClose } =
props;
return (
<Modal isOpen={isOpen} onModalClose={onModalClose}>
<SelectReleaseTypeModalContent
releaseType={releaseType}
modalTitle={modalTitle}
onReleaseTypeSelect={onReleaseTypeSelect}
onModalClose={onModalClose}
/>
</Modal>
);
}
export default SelectReleaseTypeModal;

View File

@@ -0,0 +1,99 @@
import React, { useCallback, useState } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import ReleaseType from 'InteractiveImport/ReleaseType';
import translate from 'Utilities/String/translate';
const options = [
{
key: 'unknown',
get value() {
return translate('Unknown');
},
},
{
key: 'singleEpisode',
get value() {
return translate('SingleEpisode');
},
},
{
key: 'multiEpisode',
get value() {
return translate('MultiEpisode');
},
},
{
key: 'seasonPack',
get value() {
return translate('SeasonPack');
},
},
];
interface SelectReleaseTypeModalContentProps {
releaseType: ReleaseType;
modalTitle: string;
onReleaseTypeSelect(releaseType: ReleaseType): void;
onModalClose(): void;
}
function SelectReleaseTypeModalContent(
props: SelectReleaseTypeModalContentProps
) {
const { modalTitle, onReleaseTypeSelect, onModalClose } = props;
const [releaseType, setReleaseType] = useState(props.releaseType);
const handleReleaseTypeChange = useCallback(
({ value }: { value: string }) => {
setReleaseType(value as ReleaseType);
},
[setReleaseType]
);
const handleReleaseTypeSelect = useCallback(() => {
onReleaseTypeSelect(releaseType);
}, [releaseType, onReleaseTypeSelect]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{modalTitle} - {translate('SelectReleaseType')}
</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>{translate('ReleaseType')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="releaseType"
value={releaseType}
values={options}
onChange={handleReleaseTypeChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.SUCCESS} onPress={handleReleaseTypeSelect}>
{translate('SelectReleaseType')}
</Button>
</ModalFooter>
</ModalContent>
);
}
export default SelectReleaseTypeModalContent;

View File

@@ -21,6 +21,7 @@ import { scrollDirections } from 'Helpers/Props';
import Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import dimensions from 'Styles/Variables/dimensions';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import SelectSeriesModalTableHeader from './SelectSeriesModalTableHeader';
import SelectSeriesRow from './SelectSeriesRow';
@@ -163,9 +164,7 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
);
const items = useMemo(() => {
const sorted = [...allSeries].sort((a, b) =>
a.sortTitle.localeCompare(b.sortTitle)
);
const sorted = [...allSeries].sort(sortByProp('sortTitle'));
return sorted.filter(
(item) =>

View File

@@ -14,4 +14,9 @@
.deleteFilesMessage {
margin-top: 20px;
color: var(--dangerColor);
.deleteCount {
margin-top: 20px;
color: var(--warningColor);
}
}

View File

@@ -1,6 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'deleteCount': string;
'deleteFilesMessage': string;
'folderPath': string;
'pathContainer': string;

View File

@@ -50,15 +50,15 @@ class DeleteSeriesModalContent extends Component {
const {
title,
path,
statistics,
statistics = {},
deleteOptions,
onModalClose,
onDeleteOptionChange
} = this.props;
const {
episodeFileCount,
sizeOnDisk
episodeFileCount = 0,
sizeOnDisk = 0
} = statistics;
const deleteFiles = this.state.deleteFiles;
@@ -108,16 +108,20 @@ class DeleteSeriesModalContent extends Component {
</FormGroup>
{
deleteFiles &&
deleteFiles ?
<div className={styles.deleteFilesMessage}>
<div><InlineMarkdown data={translate('DeleteSeriesFolderConfirmation', { path })} blockClassName={styles.folderPath} /></div>
{
!!episodeFileCount &&
<div>{translate('DeleteSeriesFolderEpisodeCount', { episodeFileCount, size: formatBytes(sizeOnDisk) })}</div>
}
</div>
}
{
episodeFileCount ?
<div className={styles.deleteCount}>
{translate('DeleteSeriesFolderEpisodeCount', { episodeFileCount, size: formatBytes(sizeOnDisk) })}
</div> :
null
}
</div> :
null
}
</ModalBody>
<ModalFooter>

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { useSelector } from 'react-redux';
import Label from 'Components/Label';
import { kinds, sizes } from 'Helpers/Props';
import createSeriesQueueItemsDetailsSelector, {
SeriesQueueDetails,
} from 'Series/Index/createSeriesQueueDetailsSelector';
function getEpisodeCountKind(
monitored: boolean,
episodeFileCount: number,
episodeCount: number,
isDownloading: boolean
) {
if (isDownloading) {
return kinds.PURPLE;
}
if (episodeFileCount === episodeCount && episodeCount > 0) {
return kinds.SUCCESS;
}
if (!monitored) {
return kinds.WARNING;
}
return kinds.DANGER;
}
interface SeasonProgressLabelProps {
seriesId: number;
seasonNumber: number;
monitored: boolean;
episodeCount: number;
episodeFileCount: number;
}
function SeasonProgressLabel({
seriesId,
seasonNumber,
monitored,
episodeCount,
episodeFileCount,
}: SeasonProgressLabelProps) {
const queueDetails: SeriesQueueDetails = useSelector(
createSeriesQueueItemsDetailsSelector(seriesId, seasonNumber)
);
const newDownloads = queueDetails.count - queueDetails.episodesWithFiles;
const text = newDownloads
? `${episodeFileCount} + ${newDownloads} / ${episodeCount}`
: `${episodeFileCount} / ${episodeCount}`;
return (
<Label
kind={getEpisodeCountKind(
monitored,
episodeFileCount,
episodeCount,
queueDetails.count > 0
)}
size={sizes.LARGE}
>
<span>{text}</span>
</Label>
);
}
export default SeasonProgressLabel;

View File

@@ -175,6 +175,7 @@ class SeriesDetails extends Component {
tvdbId,
tvMazeId,
imdbId,
tmdbId,
title,
runtime,
ratings,
@@ -411,10 +412,12 @@ class SeriesDetails extends Component {
ratings.value ?
<HeartRating
rating={ratings.value}
votes={ratings.votes}
iconSize={20}
/> :
null
}
<SeriesGenres genres={genres} />
<span>
@@ -566,6 +569,7 @@ class SeriesDetails extends Component {
tvdbId={tvdbId}
tvMazeId={tvMazeId}
imdbId={imdbId}
tmdbId={tmdbId}
/>
}
kind={kinds.INVERSE}
@@ -719,6 +723,7 @@ SeriesDetails.propTypes = {
tvdbId: PropTypes.number.isRequired,
tvMazeId: PropTypes.number,
imdbId: PropTypes.string,
tmdbId: PropTypes.number,
title: PropTypes.string.isRequired,
runtime: PropTypes.number.isRequired,
ratings: PropTypes.object.isRequired,

View File

@@ -9,7 +9,8 @@ function SeriesDetailsLinks(props) {
const {
tvdbId,
tvMazeId,
imdbId
imdbId,
tmdbId
} = props;
return (
@@ -71,6 +72,22 @@ function SeriesDetailsLinks(props) {
</Label>
</Link>
}
{
!!tmdbId &&
<Link
className={styles.link}
to={`https://www.themoviedb.org/tv/${tmdbId}`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
TMDB
</Label>
</Link>
}
</div>
);
}
@@ -78,7 +95,8 @@ function SeriesDetailsLinks(props) {
SeriesDetailsLinks.propTypes = {
tvdbId: PropTypes.number.isRequired,
tvMazeId: PropTypes.number,
imdbId: PropTypes.string
imdbId: PropTypes.string,
tmdbId: PropTypes.number
};
export default SeriesDetailsLinks;

View File

@@ -2,7 +2,6 @@ import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
@@ -15,7 +14,7 @@ import SpinnerIcon from 'Components/SpinnerIcon';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import Popover from 'Components/Tooltip/Popover';
import { align, icons, kinds, sizes, sortDirections, tooltipPositions } from 'Helpers/Props';
import { align, icons, sortDirections, tooltipPositions } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
import SeriesHistoryModal from 'Series/History/SeriesHistoryModal';
@@ -27,6 +26,7 @@ import translate from 'Utilities/String/translate';
import getToggledRange from 'Utilities/Table/getToggledRange';
import EpisodeRowConnector from './EpisodeRowConnector';
import SeasonInfo from './SeasonInfo';
import SeasonProgressLabel from './SeasonProgressLabel';
import styles from './SeriesDetailsSeason.css';
function getSeasonStatistics(episodes) {
@@ -64,18 +64,6 @@ function getSeasonStatistics(episodes) {
};
}
function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) {
if (episodeFileCount === episodeCount && episodeCount > 0) {
return kinds.SUCCESS;
}
if (!monitored) {
return kinds.WARNING;
}
return kinds.DANGER;
}
class SeriesDetailsSeason extends Component {
//
@@ -265,12 +253,13 @@ class SeriesDetailsSeason extends Component {
className={styles.episodeCountTooltip}
canFlip={true}
anchor={
<Label
kind={getEpisodeCountKind(monitored, episodeFileCount, episodeCount)}
size={sizes.LARGE}
>
<span>{episodeFileCount} / {episodeCount}</span>
</Label>
<SeasonProgressLabel
seriesId={seriesId}
seasonNumber={seasonNumber}
monitored={monitored}
episodeCount={episodeCount}
episodeFileCount={episodeFileCount}
/>
}
title={translate('SeasonInformation')}
body={

View File

@@ -2,6 +2,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByProp from 'Utilities/Array/sortByProp';
import SeriesTags from './SeriesTags';
function createMapStateToProps() {
@@ -12,8 +13,8 @@ function createMapStateToProps() {
const tags = series.tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
.filter((tag) => !!tag)
.map((tag) => tag.label)
.sort((a, b) => a.localeCompare(b));
.sort(sortByProp('label'))
.map((tag) => tag.label);
return {
tags

View File

@@ -14,7 +14,7 @@ function SeriesHistoryModal(props) {
return (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
size={sizes.EXTRA_EXTRA_LARGE}
onModalClose={onModalClose}
>
<SeriesHistoryModalContentConnector

View File

@@ -86,6 +86,10 @@ class SeriesHistoryRow extends Component {
const EpisodeComponent = fullSeries ? SeasonEpisodeNumber : EpisodeNumber;
if (!series || !episode) {
return null;
}
return (
<TableRow>
<HistoryEventTypeCell
@@ -131,6 +135,8 @@ class SeriesHistoryRow extends Component {
<RelativeDateCellConnector
date={date}
includeSeconds={true}
includeTime={true}
/>
<TableRowCell className={styles.actions}>

View File

@@ -55,6 +55,7 @@ function SeriesIndexOverviewOptionsModalContent(
showSeasonCount,
showPath,
showSizeOnDisk,
showTags,
showSearchAction,
} = useSelector(selectOverviewOptions);
@@ -185,6 +186,17 @@ function SeriesIndexOverviewOptionsModalContent(
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowTags')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showTags"
value={showTags}
onChange={onOverviewOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowSearch')}</FormLabel>

View File

@@ -73,14 +73,26 @@ $hoverScale: 1.05;
flex: 1 0 auto;
}
.overviewContainer {
display: flex;
justify-content: space-between;
flex: 0 1 1000px;
flex-direction: column;
}
.overview {
composes: link;
flex: 0 1 1000px;
overflow: hidden;
min-height: 0;
}
.tags {
display: flex;
justify-content: space-around;
overflow: hidden;
}
@media only screen and (max-width: $breakpointSmall) {
.overview {
display: none;

View File

@@ -8,8 +8,10 @@ interface CssExports {
'info': string;
'link': string;
'overview': string;
'overviewContainer': string;
'poster': string;
'posterContainer': string;
'tags': string;
'title': string;
'titleRow': string;
}

View File

@@ -5,6 +5,7 @@ import { REFRESH_SERIES, SERIES_SEARCH } from 'Commands/commandNames';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import TagListConnector from 'Components/TagListConnector';
import { icons } from 'Helpers/Props';
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
@@ -70,6 +71,7 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
overview,
statistics = {} as Statistics,
images,
tags,
network,
} = series;
@@ -205,15 +207,22 @@ function SeriesIndexOverview(props: SeriesIndexOverviewProps) {
</div>
<div className={styles.details}>
<Link className={styles.overview} to={link}>
<TextTruncate
line={Math.floor(
overviewHeight / (defaultFontSize * lineHeight)
)}
text={overview}
/>
</Link>
<div className={styles.overviewContainer}>
<Link className={styles.overview} to={link}>
<TextTruncate
line={Math.floor(
overviewHeight / (defaultFontSize * lineHeight)
)}
text={overview}
/>
</Link>
{overviewOptions.showTags ? (
<div className={styles.tags}>
<TagListConnector tags={tags} />
</div>
) : null}
</div>
<SeriesIndexOverviewInfo
height={overviewHeight}
monitored={monitored}

View File

@@ -5,7 +5,7 @@ import { icons } from 'Helpers/Props';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import dimensions from 'Styles/Variables/dimensions';
import QualityProfile from 'typings/QualityProfile';
import { UiSettings } from 'typings/UiSettings';
import UiSettings from 'typings/Settings/UiSettings';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
@@ -138,7 +138,10 @@ function getInfoRowProps(
}),
iconName: icons.CALENDAR,
label:
getRelativeDate(previousAiring, shortDateFormat, showRelativeDates, {
getRelativeDate({
date: previousAiring,
shortDateFormat,
showRelativeDates,
timeFormat,
timeForToday: true,
}) ?? '',
@@ -156,7 +159,10 @@ function getInfoRowProps(
}),
iconName: icons.ADD,
label:
getRelativeDate(added, shortDateFormat, showRelativeDates, {
getRelativeDate({
date: added,
shortDateFormat,
showRelativeDates,
timeFormat,
timeForToday: true,
}) ?? '',
@@ -232,15 +238,13 @@ function SeriesIndexOverviewInfo(props: SeriesIndexOverviewInfoProps) {
<SeriesIndexOverviewInfoRow
title={formatDateTime(nextAiring, longDateFormat, timeFormat)}
iconName={icons.SCHEDULED}
label={getRelativeDate(
nextAiring,
label={getRelativeDate({
date: nextAiring,
shortDateFormat,
showRelativeDates,
{
timeFormat,
timeForToday: true,
}
)}
timeFormat,
timeForToday: true,
})}
/>
)}

View File

@@ -52,6 +52,7 @@ function SeriesIndexPosterOptionsModalContent(
showTitle,
showMonitored,
showQualityProfile,
showTags,
showSearchAction,
} = posterOptions;
@@ -130,6 +131,18 @@ function SeriesIndexPosterOptionsModalContent(
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowTags')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showTags"
value={showTags}
helpText={translate('ShowTagsHelpText')}
onChange={onPosterOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowSearch')}</FormLabel>

View File

@@ -57,6 +57,20 @@ $hoverScale: 1.05;
font-size: $smallFontSize;
}
.tags {
display: flex;
align-items: center;
justify-content: space-around;
padding: 0 3px;
height: 21px;
background-color: var(--seriesBackgroundColor);
}
.tagsList {
display: flex;
overflow: hidden;
}
.ended {
position: absolute;
top: 0;

View File

@@ -10,6 +10,8 @@ interface CssExports {
'nextAiring': string;
'overlayTitle': string;
'posterContainer': string;
'tags': string;
'tagsList': string;
'title': string;
}
export const cssExports: CssExports;

View File

@@ -5,6 +5,7 @@ import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import TagListConnector from 'Components/TagListConnector';
import { icons } from 'Helpers/Props';
import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal';
import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector';
@@ -41,6 +42,7 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
showTitle,
showMonitored,
showQualityProfile,
showTags,
showSearchAction,
} = useSelector(selectPosterOptions);
@@ -60,6 +62,7 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
added,
statistics = {} as Statistics,
images,
tags,
} = series;
const {
@@ -208,6 +211,14 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
</div>
) : null}
{showTags && tags.length ? (
<div className={styles.tags}>
<div className={styles.tagsList}>
<TagListConnector tags={tags} />
</div>
</div>
) : null}
{nextAiring ? (
<div
className={styles.nextAiring}
@@ -217,7 +228,10 @@ function SeriesIndexPoster(props: SeriesIndexPosterProps) {
timeFormat
)}`}
>
{getRelativeDate(nextAiring, shortDateFormat, showRelativeDates, {
{getRelativeDate({
date: nextAiring,
shortDateFormat,
showRelativeDates,
timeFormat,
timeForToday: true,
})}

View File

@@ -80,7 +80,10 @@ function SeriesIndexPosterInfo(props: SeriesIndexPosterInfoProps) {
timeFormat
)}`}
>
{getRelativeDate(previousAiring, shortDateFormat, showRelativeDates, {
{getRelativeDate({
date: previousAiring,
shortDateFormat,
showRelativeDates,
timeFormat,
timeForToday: true,
})}
@@ -89,15 +92,13 @@ function SeriesIndexPosterInfo(props: SeriesIndexPosterInfoProps) {
}
if (sortKey === 'added' && added) {
const addedDate = getRelativeDate(
added,
const addedDate = getRelativeDate({
date: added,
shortDateFormat,
showRelativeDates,
{
timeFormat,
timeForToday: false,
}
);
timeFormat,
timeForToday: false,
});
return (
<div

View File

@@ -141,6 +141,7 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
showTitle,
showMonitored,
showQualityProfile,
showTags,
} = posterOptions;
const nextAiringHeight = 19;
@@ -164,6 +165,10 @@ export default function SeriesIndexPosters(props: SeriesIndexPostersProps) {
heights.push(19);
}
if (showTags) {
heights.push(21);
}
switch (sortKey) {
case 'network':
case 'seasons':

View File

@@ -10,4 +10,15 @@
.path {
margin-left: 5px;
color: var(--dangerColor);
font-weight: bold;
}
.statistics {
margin-left: 5px;
color: var(--warningColor);
}
.deleteFilesMessage {
margin-top: 20px;
color: var(--warningColor);
}

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