Compare commits

...

120 Commits

Author SHA1 Message Date
Bogdan 6ec298ed2a Fixed: Trimming slashes from UrlBase when using environment variable
(cherry picked from commit d7ceb11a64c3926f35aabf67c935680cf031bd0e)
2024-05-22 03:19:55 +03:00
Bogdan 563db9231e Update the wanted section for missing and cutoff unmet
(cherry picked from commit 9b4ff657af41e67aeb5866ee3056f1a8f2a901ea)
2024-05-22 03:19:44 +03:00
Bogdan d27b062d6a Bump version to 2.4.0 2024-05-13 17:01:53 +03:00
Bogdan febb3ef485 Bump Npgsql to 7.0.7 2024-05-13 15:19:22 +03:00
sillock1 30d9891bf0 New: Optionally use Environment Variables for settings in config.xml 2024-05-12 15:42:47 +03:00
Bogdan 2621acdae5 Minor translations to album interactive search 2024-05-12 01:01:41 +03:00
Bogdan 18772553f2 Fallback value for statistics 2024-05-10 19:42:50 +03:00
Weblate 0300bf2dd2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Michael5564445 <michaelvelosk@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translation: Servarr/Lidarr
2024-05-10 14:04:49 +03:00
Bogdan 77861e4303 Refactor PasswordInput to use type password
(cherry picked from commit c7c1e3ac9e5bffd4d92298fed70916e3808613fd)
2024-05-10 14:04:24 +03:00
Bogdan 3545a7451e Fixed: Text color for inputs on login page 2024-05-10 01:32:27 +03:00
Mark McDowall 9f8c4530ca New: Dark theme for login screen
(cherry picked from commit cae134ec7b331d1c906343716472f3d043614b2c)

Closes #4798
2024-05-10 01:29:30 +03:00
Mickaël Thomas 9da690f807 New: Support stoppedUP and stoppedDL states from qBittorrent
(cherry picked from commit 73a4bdea5247ee87e6bbae95f5325e1f03c88a7f)

Closes #4795
2024-05-10 00:56:42 +03:00
Bogdan 31f342b8ad Use number input for seed ratio
(cherry picked from commit 1eddf3a152fae04142263c02a3e3b317ff2feeb2)

Plus translations

Closes #4802
2024-05-10 00:53:56 +03:00
Bogdan 596a36d45f Fixed: Notifications with only On Rename enabled 2024-05-10 00:50:52 +03:00
Bogdan 94bb8a436b Fixed: Parsing long downloading/seeding values from Transmission
(cherry picked from commit 8360dd7a7bab1dfb49a40aae382b47e9253d9fd1)
2024-05-09 05:31:58 +03:00
Weblate 94d2a20b6a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
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/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translation: Servarr/Lidarr
2024-05-05 12:33:43 +03:00
Bogdan a25e5aae10 Fixed: Indexer flags for torrent release pushes
(cherry picked from commit 47ba002806fe2c2004a649aa193ae318343a84e4)
2024-05-05 12:32:44 +03:00
Mark McDowall f4a02ffc83 Forward X-Forwarded-Host header
(cherry picked from commit 3fbe4361386e9fb8dafdf82ad9f00f02bec746cc)
2024-05-05 12:32:32 +03:00
Bogdan 1bdcf91014 Bump version to 2.3.3 2024-05-05 12:32:12 +03:00
Bogdan 4d28d3f25a Fixed: Initialize databases after app folder migrations
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-05-05 01:03:15 +03:00
Bogdan 9660ec37cd Use newer Node.js task for in pipelines 2024-04-29 14:41:31 +03:00
Bogdan 66c7521f4b Fixed: Limit titles in task name to 10 artists
(cherry picked from commit c81ae6546118e954e481894d0b3fa6e9a20359c7)

Closes #4777
2024-04-28 13:51:01 +03:00
Stevie Robinson 8b57b33c99 New: Don't initially select 0 byte files in Interactive Import
(cherry picked from commit 04bd535cfca5e25c6a2d5417c6f18d5bf5180f67)

Closes #4776
2024-04-28 13:49:04 +03:00
Mark McDowall 580e4becbe Fixed: Improve paths longer than 256 on Windows failing to hardlink
(cherry picked from commit a97fbcc40a6247bf59678425cf460588fd4dbecd)
2024-04-28 13:42:37 +03:00
Christopher 5f248aa25e New: Remove qBitorrent torrents that reach inactive seeding time
(cherry picked from commit d738035fed859eb475051f3df494b9c975a42e82)
2024-04-28 13:42:22 +03:00
Bogdan a735eccb65 Bump version to 2.3.2 2024-04-28 12:57:55 +03:00
Weblate d11ed42830 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Ano10 <arnaudthommeray+github@ik.me>
Co-authored-by: Fara <faraindahhh@gmail.com>
Co-authored-by: Fonkio <maxime.fabre10@gmail.com>
Co-authored-by: Gandrushka <andrew.pyndyk@gmail.com>
Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jacopo Luca Maria Latrofa <jacopo.latrofa@gmail.com>
Co-authored-by: Mailme Dashite <mailmedashite@protonmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: YSLG <1451164040@qq.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: maodun96 <435795439@qq.com>
Co-authored-by: toeiazarothis <patrickdealmeida89000@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2024-04-27 21:14:04 +03:00
Bogdan b0038dd143 Fixed: Retrying download on not suppressed HTTP errors 2024-04-27 21:09:14 +03:00
Bogdan 2e242aeb7b Database corruption message linking to wiki 2024-04-25 11:30:05 +03:00
Bogdan 416d505316 Bump dotnet to 6.0.29 2024-04-22 07:58:44 +03:00
Bogdan 4816f35256 Bump typescript eslint plugin and parser 2024-04-21 12:45:16 +03:00
Josh McKinney e42e0a72eb Add dev container workspace
Allows the linting and style settings for the frontend to be applied even when you load the main repo as a workspace

(cherry picked from commit d6278fced49b26be975c3a6039b38a94f700864b)

Closes #4756
2024-04-21 12:05:07 +03:00
Bogdan db9e62f79d Convert store selectors to Typescript
Closes #3937
2024-04-21 12:04:13 +03:00
Bogdan bc69fa4842 Bump frontend dependencies 2024-04-21 11:11:55 +03:00
Bogdan 86dad72c49 Bump version to 2.3.1 2024-04-21 09:15:31 +03:00
Bogdan 4a8d6c367d Bump skipping spotify tests 2024-04-20 18:37:21 +03:00
Bogdan c1926f8758 Fixed: Skip move when source and destination are the same
Co-authored-by: Qstick <qstick@gmail.com>
2024-04-20 17:56:04 +03:00
Bogdan 7820bcf91f Bump SixLabors.ImageSharp to 3.1.4 2024-04-19 08:00:59 +03:00
Servarr 431ad0a028 Automated API Docs update 2024-04-18 15:33:13 +03:00
Bogdan 59cf7a95c3 Fixed: Re-testing edited providers will forcibly test them
(cherry picked from commit e9662544621b2d1fb133ff9d96d0eb20b8198725)
2024-04-16 10:54:12 +03:00
Bogdan e17e3633f8 Don't block task queue for queued update task for Rescan Folders
Towards #4551
2024-04-14 15:45:35 +03:00
Bogdan 46da2b49c6 Bump version to 2.3.0 2024-04-13 07:10:44 +03:00
Josh McKinney 3071977284 Add DevContainer, VSCode config and extensions.json
(cherry picked from commit 5061dc4b5e5ea9925740496a5939a1762788b793)

Closes #4740
2024-04-11 23:16:31 +03:00
Mark McDowall b14e2bb618 New: Auto tag artists based on tags present/absent on artists
(cherry picked from commit f4c19a384bd9bb4e35c9fa0ca5d9a448c04e409e)

Closes #4742
2024-04-11 23:02:04 +03:00
Mark McDowall 8c09c0cb5c New: Option to prefix app name on Telegram notification titles
(cherry picked from commit 37863a8deb339ef730b2dd5be61e1da1311fdd23)

Closes #4739
2024-04-11 22:43:54 +03:00
Weblate 8cebb21c2d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: myrad2267 <myrad2267@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2024-04-10 02:21:56 +03:00
Bogdan 74ac263b74 New: Detect shfs mounts in disk space
(cherry picked from commit 1aef91041e404f76f278f430e4e53140fb125792)
2024-04-10 02:21:35 +03:00
Bogdan adcec90ef8 Fixed: Sorting by Artist Name in Missing/Cutoff Unmet under Postgres
Fixes #4736
Fixes #3392
Closes #4374
2024-04-08 18:14:14 +03:00
Qstick daf8b94c8e Added table identifier to OrderBy to avoid column ambiguity on joins
Co-Authored-By: Richard <1252123+kharenis@users.noreply.github.com>
(cherry picked from commit c57ceac4debf7419be84096f997ba7b75c906586)

Closes #3993
2024-04-08 17:25:31 +03:00
Bogdan 7c4f0c597e Bump version to 2.2.5 2024-04-07 07:57:46 +03:00
Bogdan 1d2af2aab4 Fix translation for indexer priority help text 2024-04-07 02:48:33 +03:00
Stevie Robinson 5d537689fb New: Informational text on Custom Formats modal
(cherry picked from commit 238ba85f0a2639608d9890292dfe0b96c0212f10)

Closes #4729
2024-04-06 16:39:40 +03:00
Cuki ca6beea62b Fixed: Use widely supported display mode for PWA
(cherry picked from commit 1562d3bae3002947f9e428321d2b162ad69c3309)
2024-04-06 16:36:04 +03:00
Mark McDowall a82c919093 Fixed: Cleanse BHD RSS key in log files
(cherry picked from commit 60ee7cc716d344fc904fa6fb28f7be0386ae710d)
2024-04-06 16:35:39 +03:00
Mark McDowall 2941e0c4b7 Fixed: Sending ntfy.sh notifications with unicode characters
(cherry picked from commit a169ebff2adda5c8585c6aae6249b1c1f7c12264)
2024-04-06 16:35:21 +03:00
Bogdan ca0b900d92 Fixed: (Gazelle) Ignore ineligible releases with Use Freeleech Token 2024-04-06 06:33:35 +03:00
Weblate 72f1b2075b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
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/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2024-04-06 06:21:20 +03:00
Bogdan e847828191 Fixed: Album release selection in manual import 2024-03-31 21:08:44 +03:00
Bogdan 2a10505dff Bump skipping spotify tests 2024-03-28 13:02:04 +02:00
Mark McDowall 28f2eb974d Fixed: Task with removed artists causing error
(cherry picked from commit fc6494c569324c839debdb1d08dde23b8f1b8d76)

Closes #4696
2024-03-28 13:02:01 +02:00
Servarr 13ce040e4d Automated API Docs update 2024-03-28 13:01:34 +02:00
Weblate f477f9b287 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Altair <villagermd@outlook.com>
Co-authored-by: Casselluu <jack10193@163.com>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Stanislav <prekop3@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: shimmyx <shimmygodx@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2024-03-28 10:32:27 +02:00
Bogdan 0e84008669 New: Advanced settings toggle in import list, notification and download client modals
(cherry picked from commit 13c925b3418d1d48ec041e3d97ab51aaf2b8977a)
2024-03-28 10:31:57 +02:00
Louis R 52b5ff6fdd Fixed: Exceptions when checking for routable IPv4 addresses
(cherry picked from commit 060b789bc6f10f667795697eb536d4bd3851da49)
2024-03-28 10:31:32 +02:00
Carlos Gustavo Sarmiento 1d0de51917 Fixed: qBittorrent not correctly handling retention during testing
(cherry picked from commit 588372fd950fc85f5e9a4275fbcb423b247ed0ee)
2024-03-28 10:31:19 +02:00
Stevie Robinson a8648fdb71 Fixed: Handling torrents with relative path in rTorrent
(cherry picked from commit 35d0e6a6f806c68756450a7d199600d7fb49d6c5)
2024-03-28 10:31:06 +02:00
Bogdan f890a8c18f New: Allow HEAD requests to ping endpoint
(cherry picked from commit 7353fe479dbb8d0dab76993ebed92d48e1b05524)
2024-03-28 10:30:54 +02:00
Mark McDowall e730cf6307 Fixed: Task progress messages in the UI
(cherry picked from commit c6417337812f3578a27f9dc1e44fdad80f557271)

Closes #4689
2024-03-22 11:23:16 +02:00
Bogdan 9f4d821a2d Bump version to 2.2.4 2024-03-17 13:48:47 +02:00
Weblate ce6e4555ec Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dennis Langthjem <dennis@langthjem.dk>
Co-authored-by: Gianmarco Novelli <rinogaetano94@live.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ihor Mudryi <mudryy33@gmail.com>
Co-authored-by: MadaxDeLuXe <madaxdeluxe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: infoaitek24 <info@aitekph.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: vfaergestad <vgf@hotmail.no>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/uk/
Translation: Servarr/Lidarr
2024-03-16 18:15:32 +02:00
Mark McDowall 55eaecb3c8 Fixed: Disabled select option still selectable
(cherry picked from commit 063dba22a803295adee4fdcbe42718af3e85ca78)

Closes #4679
2024-03-14 15:46:28 +02:00
Bogdan 63e36f71d2 Ensure not allowed cursor is shown for disabled select inputs 2024-03-14 15:46:28 +02:00
Bogdan 89e184e768 Ensure artists are populated in PageConnector 2024-03-14 15:46:28 +02:00
Mark McDowall 873a225f0c New: Show artist names after task name when applicable
(cherry picked from commit 6d552f2a60f44052079b5e8944f5e1bbabac56e0)

Closes #4678
2024-03-14 15:46:19 +02:00
Stevie Robinson b81170d911 Fixed: Wrapping of naming tokens with alternate separators
(cherry picked from commit 80630bf97f5bb3b49d4824dc039d2edfc74e4797)

Closes #4561
Closes #4677
2024-03-14 15:16:13 +02:00
Servarr 5ffde40320 Automated API Docs update 2024-03-14 07:58:46 +02:00
Mark McDowall ebfa68087d Fixed: Release push with only Magnet URL
(cherry picked from commit 9f705e4161af3f4dd55b399d56b0b9c5a36e181b)
2024-03-14 07:12:50 +02:00
Bogdan 1db0eb1029 New: Indexer flags
(cherry picked from commit 7a768b5d0faf9aa57e78aee19cefee8fb19a42d5)
2024-03-10 17:30:09 +02:00
Bogdan 967b58017a Bump ImageSharp, Polly 2024-03-10 13:11:15 +02:00
Bogdan 3754d611c7 Bump version to 2.2.3 2024-03-10 09:05:47 +02:00
Weblate 8035d4202f Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/
Translation: Servarr/Lidarr
2024-03-09 11:10:55 +02:00
Bogdan 468f3acf85 Translations for InteractiveSearchRow
Closes #4077
2024-03-09 11:04:45 +02:00
Bogdan 29c77ec3a1 Fix version in namespace for AutoTagging 2024-03-09 10:57:24 +02:00
bpoxy d04bb5333a Fixed: Matching of custom formats during track files import
(cherry picked from commit 7fedfe7423a525f05008c2c7c15e3cb0c9c38fe5)

Closes #3484
2024-03-08 15:51:48 +02:00
Bogdan 0d76fbcf0d New: XXL modal size 2024-03-08 11:42:53 +02:00
Weblate 3df140b1f0 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Mark Martines <mark-martines@hotmail.com>
Co-authored-by: Maxence Winandy <maxence.winandy@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: linkin931 <931linkin@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translation: Servarr/Lidarr
2024-03-08 11:08:43 +02:00
Bogdan 340ae78f46 Prevent NullRef in naming when truncating a null Release Group
(cherry picked from commit 13e29bd257ccfccb09e66c940ffabeb6503c05b5)
2024-03-08 08:29:21 +02:00
Helvio Pedreschi 881fabad93 Fixed: WebApp functionality on Apple devices
(cherry picked from commit c7dd7abf892eead7796fcc482aa2f2aabaf88712)
2024-03-08 08:27:45 +02:00
Mark McDowall be8f7e5618 Fixed: Overly aggressive exception release group parsing
(cherry picked from commit 0183812cc58dad0e555125ddd8b33a85cbdecbf2)
2024-03-08 08:27:15 +02:00
Bogdan c9743448fd Configurable URL Base setting for Kodi connections 2024-03-04 03:10:45 +02:00
Bogdan 47e647ddb1 Fixed: URL Base setting for Kodi connections 2024-03-03 13:47:49 +02:00
Mark McDowall f6529d5ad3 New: URL Base setting for Media Server connections
Plus some translations

(cherry picked from commit 9fd193d2a82d5c2cdc0f36c1f984e4b6b68aaa8d)
2024-03-03 13:36:39 +02:00
Mark McDowall fb1b7274d0 Queue Manual Import commands at high priority
(cherry picked from commit 64c6a8879beb1b17122c8f6f74bf7b3cf4dd1570)
2024-03-03 13:20:24 +02:00
Louis R 33b12a532c Fixed: Don't disable IPv6 in IPv6-only Environment
(cherry picked from commit 13af6f57796e54c3949cf340e03f020e6f8575c4)
2024-03-03 13:19:46 +02:00
nopoz cea5ee503f New: Add download directory & move completed for Deluge
(cherry picked from commit 07bd159436935a7adb87ae1b6924a4d42d719b0f)
2024-03-03 13:19:29 +02:00
Bogdan 475590a21b Bump version to 2.2.2 2024-03-03 13:11:33 +02:00
Weblate 0ca0f68af1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: Nicolò Castagnola <nipica@outlook.it>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/tr/
Translation: Servarr/Lidarr
2024-03-03 02:32:43 +02:00
Bogdan 2c19b5aa61 Ignore spotify mapping test more temporarily 2024-03-02 09:19:32 +02:00
Weblate 7e0c5e0da5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Alexander <Funk2256@gmail.com>
Co-authored-by: EDUYO <eduardoestabiel@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Sadi A. Nogueira <contato@sadi.eti.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: wgwqd <wgwqd@163.com>
Co-authored-by: 闫锦彪 <yanjinbiaohere@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2024-03-02 03:59:19 +02:00
Mark McDowall adecb7f73c Increase migration timeout to 5 minutes
(cherry picked from commit 086d3b5afaa7680d22835ca66da2afcb6dd5865e)
2024-03-02 03:58:14 +02:00
Mark McDowall 98a90e2f8f New: Bypass archived history for failed downloads in SABnzbd
(cherry picked from commit c99d81e79ba5e6ecec01ddd942440d8a48a1c23b)
2024-03-02 03:57:42 +02:00
Bogdan ce2bb5be1f Update caniuse-lite
(cherry picked from commit 64f4365fe98b569efdf436710d5f56684f2aab66)
2024-03-02 03:56:51 +02:00
Bogdan e446c25a01 New: Options button for Missing/Cutoff Unmet
(cherry picked from commit 2773f77e1c4e3a8c8d01bcbea67333801c7840df)
2024-03-02 03:55:43 +02:00
servarr[bot] d38c101acd
Fixed: Multi-word genres in Auto Tags (#4601)
(cherry picked from commit 5c4f82999368edfedd038a0a27d323e04b81a400)

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-03-02 03:33:37 +02:00
Bogdan 022fbf864c Fixed: Selection of last added custom filter
(cherry picked from commit 1f97679868012b70beecc553557e96e6c8bc80e3)

Closes #4627
2024-02-29 06:50:59 +02:00
Bogdan 3ff9b8bd85 Bump version to 2.2.1 2024-02-29 06:43:28 +02:00
Bogdan 57926a61d2 Bump node to v20.x on builder 2024-02-23 20:18:49 +02:00
bakerboy448 87f88af7ee
Update name for errors with metadata API 2024-02-22 01:01:40 +02:00
Weblate 30fc3fc70a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Chaoshuai Lü <lcs@meta.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/lidarr/zh_CN/
Translation: Servarr/Lidarr
2024-02-16 16:55:10 +02:00
Bogdan 4abca0c896 Fixed: Don't die on album deleted notifications with the artist already removed 2024-02-16 16:52:23 +02:00
Bogdan b2f595436b Improve messaging on indexer specified download client is not available
(cherry picked from commit 84e657482d37eed35f09c6dab3c2b8b5ebd5bac4)
2024-02-14 03:10:26 +02:00
Bogdan e7ae0b9e22 Fixed: Refresh tags state to clear removed tags by housekeeping
(cherry picked from commit 2510f44c25bee6fede27d9fa2b9614176d12cb55)

(cherry picked from commit ed27bcf213bdbc5cede650f89eb65593dc9631b4)
2024-02-14 03:10:01 +02:00
Bogdan 0431b257e1 Show download client ID as hint in select options
(cherry picked from commit c0b17d9345367ab6500b7cca6bb70c1e3b930284)
2024-02-14 03:09:47 +02:00
abcasada 479e8cce20 Hints for week column and short dates in UI settings
(cherry picked from commit 4558f552820b52bb1f9cd97fdabe03654ce9924a)

(cherry picked from commit f1d343218cdbd5a63abeb2eb97bba1105dc8035d)
2024-02-14 03:09:33 +02:00
Bogdan 27723eb3ea Revert "New: Preserve replaygain tags"
This reverts commit 2a8c67badc.
2024-02-12 22:11:25 +02:00
Qstick 616b529c9a Fix CalendarPageConnector import sort 2024-02-11 21:31:38 -06:00
Qstick 8b85d4c941 Translate Frontend Utilities
Closes #4096

Co-Authored-By: Stevie Robinson <stevie.robinson@gmail.com>
2024-02-11 21:30:31 -06:00
Servarr f13b095040 Automated API Docs update 2024-02-11 21:05:15 -06:00
Qstick a4af75b60c New: Calendar filtering by tags
Closes #3658
Closes #4211

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2024-02-11 20:51:28 -06:00
Qstick c7faf7cc25
Bump version to 2.2.0 2024-02-11 19:29:42 -06:00
302 changed files with 7037 additions and 3349 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": "Lidarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"nvmVersion": "latest"
}
},
"forwardPorts": [8686],
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode"]
}
}
}

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

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

1
.gitignore vendored
View File

@ -126,6 +126,7 @@ coverage*.xml
coverage*.json coverage*.json
setup/Output/ setup/Output/
*.~is *.~is
.mono
# VS outout folders # VS outout folders
bin 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 Lidarr",
"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/Lidarr",
"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/Lidarr.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/Lidarr.sln",
"-property:GenerateFullPaths=true",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/Lidarr.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

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

View File

@ -28,7 +28,8 @@ module.exports = {
globals: { globals: {
expect: false, expect: false,
chai: false, chai: false,
sinon: false sinon: false,
JSX: true
}, },
parserOptions: { parserOptions: {

View File

@ -53,7 +53,7 @@ class DeleteAlbumModalContent extends Component {
render() { render() {
const { const {
title, title,
statistics, statistics = {},
onModalClose onModalClose
} = this.props; } = this.props;

View File

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

View File

@ -4,6 +4,7 @@ interface CssExports {
'audio': string; 'audio': string;
'customFormatScore': string; 'customFormatScore': string;
'duration': string; 'duration': string;
'indexerFlags': string;
'monitored': string; 'monitored': string;
'size': string; 'size': string;
'status': string; 'status': string;

View File

@ -2,15 +2,19 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import AlbumFormats from 'Album/AlbumFormats'; import AlbumFormats from 'Album/AlbumFormats';
import EpisodeStatusConnector from 'Album/EpisodeStatusConnector'; import EpisodeStatusConnector from 'Album/EpisodeStatusConnector';
import IndexerFlags from 'Album/IndexerFlags';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import MediaInfoConnector from 'TrackFile/MediaInfoConnector'; import MediaInfoConnector from 'TrackFile/MediaInfoConnector';
import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes'; import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import TrackActionsCell from './TrackActionsCell'; import TrackActionsCell from './TrackActionsCell';
import styles from './TrackRow.css'; import styles from './TrackRow.css';
@ -32,6 +36,7 @@ class TrackRow extends Component {
trackFileSize, trackFileSize,
customFormats, customFormats,
customFormatScore, customFormatScore,
indexerFlags,
columns, columns,
deleteTrackFile deleteTrackFile
} = this.props; } = this.props;
@ -141,12 +146,30 @@ class TrackRow extends Component {
customFormats.length customFormats.length
)} )}
tooltip={<AlbumFormats formats={customFormats} />} tooltip={<AlbumFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM} position={tooltipPositions.LEFT}
/> />
</TableRowCell> </TableRowCell>
); );
} }
if (name === 'indexerFlags') {
return (
<TableRowCell
key={name}
className={styles.indexerFlags}
>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}
/>
) : null}
</TableRowCell>
);
}
if (name === 'size') { if (name === 'size') {
return ( return (
<TableRowCell <TableRowCell
@ -208,12 +231,14 @@ TrackRow.propTypes = {
trackFileSize: PropTypes.number, trackFileSize: PropTypes.number,
customFormats: PropTypes.arrayOf(PropTypes.object), customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired, customFormatScore: PropTypes.number.isRequired,
indexerFlags: PropTypes.number.isRequired,
mediaInfo: PropTypes.object, mediaInfo: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired columns: PropTypes.arrayOf(PropTypes.object).isRequired
}; };
TrackRow.defaultProps = { TrackRow.defaultProps = {
customFormats: [] customFormats: [],
indexerFlags: 0
}; };
export default TrackRow; export default TrackRow;

View File

@ -13,7 +13,8 @@ function createMapStateToProps() {
trackFilePath: trackFile ? trackFile.path : null, trackFilePath: trackFile ? trackFile.path : null,
trackFileSize: trackFile ? trackFile.size : null, trackFileSize: trackFile ? trackFile.size : null,
customFormats: trackFile ? trackFile.customFormats : [], customFormats: trackFile ? trackFile.customFormats : [],
customFormatScore: trackFile ? trackFile.customFormatScore : 0 customFormatScore: trackFile ? trackFile.customFormatScore : 0,
indexerFlags: trackFile ? trackFile.indexerFlags : 0
}; };
} }
); );

View File

@ -35,7 +35,7 @@ class EditAlbumModalContent extends Component {
title, title,
artistName, artistName,
albumType, albumType,
statistics, statistics = {},
item, item,
isSaving, isSaving,
onInputChange, onInputChange,

View File

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

View File

@ -15,7 +15,7 @@ function AlbumInteractiveSearchModal(props) {
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
size={sizes.EXTRA_LARGE} size={sizes.EXTRA_EXTRA_LARGE}
closeOnBackgroundClick={false} closeOnBackgroundClick={false}
onModalClose={onModalClose} onModalClose={onModalClose}
> >

View File

@ -7,6 +7,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector'; import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
import translate from 'Utilities/String/translate';
function AlbumInteractiveSearchModalContent(props) { function AlbumInteractiveSearchModalContent(props) {
const { const {
@ -18,7 +19,10 @@ function AlbumInteractiveSearchModalContent(props) {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Interactive Search {albumId != null && `- ${albumTitle}`} {albumTitle === undefined ?
translate('InteractiveSearchModalHeader') :
translate('InteractiveSearchModalHeaderTitle', { title: albumTitle })
}
</ModalHeader> </ModalHeader>
<ModalBody scrollDirection={scrollDirections.BOTH}> <ModalBody scrollDirection={scrollDirections.BOTH}>
@ -32,7 +36,7 @@ function AlbumInteractiveSearchModalContent(props) {
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
Close {translate('Close')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

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

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.Lidarr.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.Lidarr.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,8 +1,11 @@
import AlbumAppState from './AlbumAppState'; import AlbumAppState from './AlbumAppState';
import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState'; import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState';
import CalendarAppState from './CalendarAppState';
import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState'; import HistoryAppState from './HistoryAppState';
import QueueAppState from './QueueAppState'; import QueueAppState from './QueueAppState';
import SettingsAppState from './SettingsAppState'; import SettingsAppState from './SettingsAppState';
import SystemAppState from './SystemAppState';
import TagsAppState from './TagsAppState'; import TagsAppState from './TagsAppState';
import TrackFilesAppState from './TrackFilesAppState'; import TrackFilesAppState from './TrackFilesAppState';
import TracksAppState from './TracksAppState'; import TracksAppState from './TracksAppState';
@ -52,12 +55,15 @@ interface AppState {
app: AppSectionState; app: AppSectionState;
artist: ArtistAppState; artist: ArtistAppState;
artistIndex: ArtistIndexAppState; artistIndex: ArtistIndexAppState;
calendar: CalendarAppState;
commands: CommandAppState;
history: HistoryAppState; history: HistoryAppState;
queue: QueueAppState; queue: QueueAppState;
settings: SettingsAppState; settings: SettingsAppState;
tags: TagsAppState; tags: TagsAppState;
trackFiles: TrackFilesAppState; trackFiles: TrackFilesAppState;
tracksSelection: TracksAppState; tracksSelection: TracksAppState;
system: SystemAppState;
} }
export default AppState; export default AppState;

View File

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

View File

@ -0,0 +1,6 @@
import AppSectionState from 'App/State/AppSectionState';
import Command from 'Commands/Command';
export type CommandAppState = AppSectionState<Command>;
export default CommandAppState;

View File

@ -1,11 +1,13 @@
import AppSectionState, { import AppSectionState, {
AppSectionDeleteState, AppSectionDeleteState,
AppSectionItemState,
AppSectionSaveState, AppSectionSaveState,
AppSectionSchemaState, AppSectionSchemaState,
} from 'App/State/AppSectionState'; } from 'App/State/AppSectionState';
import DownloadClient from 'typings/DownloadClient'; import DownloadClient from 'typings/DownloadClient';
import ImportList from 'typings/ImportList'; import ImportList from 'typings/ImportList';
import Indexer from 'typings/Indexer'; import Indexer from 'typings/Indexer';
import IndexerFlag from 'typings/IndexerFlag';
import MetadataProfile from 'typings/MetadataProfile'; import MetadataProfile from 'typings/MetadataProfile';
import Notification from 'typings/Notification'; import Notification from 'typings/Notification';
import QualityProfile from 'typings/QualityProfile'; import QualityProfile from 'typings/QualityProfile';
@ -44,17 +46,19 @@ export interface RootFolderAppState
AppSectionDeleteState, AppSectionDeleteState,
AppSectionSaveState {} AppSectionSaveState {}
export type UiSettingsAppState = AppSectionState<UiSettings>; export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
interface SettingsAppState { interface SettingsAppState {
downloadClients: DownloadClientAppState; downloadClients: DownloadClientAppState;
importLists: ImportListAppState; importLists: ImportListAppState;
indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState; indexers: IndexerAppState;
metadataProfiles: MetadataProfilesAppState; metadataProfiles: MetadataProfilesAppState;
notifications: NotificationAppState; notifications: NotificationAppState;
qualityProfiles: QualityProfilesAppState; qualityProfiles: QualityProfilesAppState;
rootFolders: RootFolderAppState; rootFolders: RootFolderAppState;
uiSettings: UiSettingsAppState; ui: UiSettingsAppState;
} }
export default SettingsAppState; export default SettingsAppState;

View File

@ -0,0 +1,10 @@
import SystemStatus from 'typings/SystemStatus';
import { AppSectionItemState } from './AppSectionState';
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
interface SystemAppState {
status: SystemStatusAppState;
}
export default SystemAppState;

View File

@ -1,12 +1,32 @@
import ModelBase from 'App/ModelBase'; import ModelBase from 'App/ModelBase';
import AppSectionState, { import AppSectionState, {
AppSectionDeleteState, AppSectionDeleteState,
AppSectionSaveState,
} from 'App/State/AppSectionState'; } from 'App/State/AppSectionState';
export interface Tag extends ModelBase { export interface Tag extends ModelBase {
label: string; label: string;
} }
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {} export interface TagDetail extends ModelBase {
label: string;
autoTagIds: number[];
delayProfileIds: number[];
downloadClientIds: [];
importListIds: number[];
indexerIds: number[];
notificationIds: number[];
restrictionIds: number[];
artistIds: number[];
}
export interface TagDetailAppState
extends AppSectionState<TagDetail>,
AppSectionDeleteState,
AppSectionSaveState {}
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {
details: TagDetailAppState;
}
export default TagsAppState; export default TagsAppState;

View File

@ -135,14 +135,14 @@ class DeleteArtistModalContent extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
Close {translate('Close')}
</Button> </Button>
<Button <Button
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={this.onDeleteArtistConfirmed} onPress={this.onDeleteArtistConfirmed}
> >
Delete {translate('Delete')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@ -161,9 +161,7 @@ DeleteArtistModalContent.propTypes = {
}; };
DeleteArtistModalContent.defaultProps = { DeleteArtistModalContent.defaultProps = {
statistics: { statistics: {}
trackFileCount: 0
}
}; };
export default DeleteArtistModalContent; export default DeleteArtistModalContent;

View File

@ -192,7 +192,7 @@ class ArtistDetails extends Component {
artistName, artistName,
ratings, ratings,
path, path,
statistics, statistics = {},
qualityProfileId, qualityProfileId,
monitored, monitored,
genres, genres,

View File

@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Statistics } from 'Album/Album';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
@ -56,7 +57,7 @@ function AlbumDetails(props: AlbumDetailsProps) {
disambiguation, disambiguation,
albumType, albumType,
monitored, monitored,
statistics, statistics = {} as Statistics,
isSaving = false, isSaving = false,
} = album; } = album;

View File

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

View File

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

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 { setCalendarFilter } from 'Store/Actions/calendarActions';
function createCalendarSelector() {
return createSelector(
(state: AppState) => state.calendar.items,
(calendar) => {
return calendar;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.calendar.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface CalendarFilterModalProps {
isOpen: boolean;
}
export default function CalendarFilterModal(props: CalendarFilterModalProps) {
const sectionItems = useSelector(createCalendarSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'calendar';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setCalendarFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@ -14,6 +14,7 @@ import { align, icons } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import CalendarConnector from './CalendarConnector'; import CalendarConnector from './CalendarConnector';
import CalendarFilterModal from './CalendarFilterModal';
import CalendarLinkModal from './iCal/CalendarLinkModal'; import CalendarLinkModal from './iCal/CalendarLinkModal';
import LegendConnector from './Legend/LegendConnector'; import LegendConnector from './Legend/LegendConnector';
import CalendarOptionsModal from './Options/CalendarOptionsModal'; import CalendarOptionsModal from './Options/CalendarOptionsModal';
@ -78,6 +79,7 @@ class CalendarPage extends Component {
const { const {
selectedFilterKey, selectedFilterKey,
filters, filters,
customFilters,
hasArtist, hasArtist,
artistError, artistError,
artistIsFetching, artistIsFetching,
@ -137,7 +139,8 @@ class CalendarPage extends Component {
isDisabled={!hasArtist} isDisabled={!hasArtist}
selectedFilterKey={selectedFilterKey} selectedFilterKey={selectedFilterKey}
filters={filters} filters={filters}
customFilters={[]} customFilters={customFilters}
filterModalConnectorComponent={CalendarFilterModal}
onFilterSelect={onFilterSelect} onFilterSelect={onFilterSelect}
/> />
</PageToolbarSection> </PageToolbarSection>
@ -204,6 +207,7 @@ class CalendarPage extends Component {
CalendarPage.propTypes = { CalendarPage.propTypes = {
selectedFilterKey: PropTypes.string.isRequired, selectedFilterKey: PropTypes.string.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
hasArtist: PropTypes.bool.isRequired, hasArtist: PropTypes.bool.isRequired,
artistError: PropTypes.object, artistError: PropTypes.object,
artistIsFetching: PropTypes.bool.isRequired, artistIsFetching: PropTypes.bool.isRequired,

View File

@ -6,6 +6,7 @@ import withCurrentPage from 'Components/withCurrentPage';
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions'; import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import createArtistCountSelector from 'Store/Selectors/createArtistCountSelector'; import createArtistCountSelector from 'Store/Selectors/createArtistCountSelector';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector'; import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
@ -59,6 +60,7 @@ function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.calendar.selectedFilterKey, (state) => state.calendar.selectedFilterKey,
(state) => state.calendar.filters, (state) => state.calendar.filters,
createCustomFiltersSelector('calendar'),
createArtistCountSelector(), createArtistCountSelector(),
createUISettingsSelector(), createUISettingsSelector(),
createMissingAlbumIdsSelector(), createMissingAlbumIdsSelector(),
@ -67,6 +69,7 @@ function createMapStateToProps() {
( (
selectedFilterKey, selectedFilterKey,
filters, filters,
customFilters,
artistCount, artistCount,
uiSettings, uiSettings,
missingAlbumIds, missingAlbumIds,
@ -76,6 +79,7 @@ function createMapStateToProps() {
return { return {
selectedFilterKey, selectedFilterKey,
filters, filters,
customFilters,
colorImpairedMode: uiSettings.enableColorImpairedMode, colorImpairedMode: uiSettings.enableColorImpairedMode,
hasArtist: !!artistCount.count, hasArtist: !!artistCount.count,
artistError: artistCount.error, artistError: artistCount.error,

View File

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

View File

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

View File

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

View File

@ -0,0 +1,53 @@
import React, { useCallback } from 'react';
import TagInputConnector from './TagInputConnector';
interface ArtistTagInputProps {
name: string;
value: number | number[];
onChange: ({
name,
value,
}: {
name: string;
value: number | number[];
}) => void;
}
export default function ArtistTagInput(props: ArtistTagInputProps) {
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

@ -25,7 +25,8 @@ function createMapStateToProps() {
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => { const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return { return {
key: downloadClient.id, key: downloadClient.id,
value: downloadClient.name value: downloadClient.name,
hint: `(${downloadClient.id})`
}; };
}); });

View File

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

View File

@ -4,6 +4,7 @@ import Link from 'Components/Link/Link';
import { inputTypes, kinds } from 'Helpers/Props'; import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AlbumReleaseSelectInputConnector from './AlbumReleaseSelectInputConnector'; import AlbumReleaseSelectInputConnector from './AlbumReleaseSelectInputConnector';
import ArtistTagInput from './ArtistTagInput';
import AutoCompleteInput from './AutoCompleteInput'; import AutoCompleteInput from './AutoCompleteInput';
import CaptchaInputConnector from './CaptchaInputConnector'; import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput'; import CheckInput from './CheckInput';
@ -12,6 +13,7 @@ import DownloadClientSelectInputConnector from './DownloadClientSelectInputConne
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector'; import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText'; import FormInputHelpText from './FormInputHelpText';
import IndexerFlagsSelectInput from './IndexerFlagsSelectInput';
import IndexerSelectInputConnector from './IndexerSelectInputConnector'; import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import KeyValueListInput from './KeyValueListInput'; import KeyValueListInput from './KeyValueListInput';
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector'; import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
@ -83,6 +85,9 @@ function getComponent(type) {
case inputTypes.INDEXER_SELECT: case inputTypes.INDEXER_SELECT:
return IndexerSelectInputConnector; return IndexerSelectInputConnector;
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInput;
case inputTypes.DOWNLOAD_CLIENT_SELECT: case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector; return DownloadClientSelectInputConnector;
@ -95,6 +100,9 @@ function getComponent(type) {
case inputTypes.DYNAMIC_SELECT: case inputTypes.DYNAMIC_SELECT:
return EnhancedSelectInputConnector; return EnhancedSelectInputConnector;
case inputTypes.ARTIST_TAG:
return ArtistTagInput;
case inputTypes.SERIES_TYPE_SELECT: case inputTypes.SERIES_TYPE_SELECT:
return SeriesTypeSelectInput; return SeriesTypeSelectInput;
@ -292,6 +300,7 @@ FormInputGroup.propTypes = {
includeNoChangeDisabled: PropTypes.bool, includeNoChangeDisabled: PropTypes.bool,
includeNone: PropTypes.bool, includeNone: PropTypes.bool,
selectedValueOptions: PropTypes.object, selectedValueOptions: PropTypes.object,
indexerFlags: PropTypes.number,
pending: PropTypes.bool, pending: PropTypes.bool,
errors: PropTypes.arrayOf(PropTypes.object), errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object), warnings: PropTypes.arrayOf(PropTypes.object),

View File

@ -0,0 +1,62 @@
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import EnhancedSelectInput from './EnhancedSelectInput';
const selectIndexerFlagsValues = (selectedFlags: number) =>
createSelector(
(state: AppState) => state.settings.indexerFlags,
(indexerFlags) => {
const value = indexerFlags.items.reduce((acc: number[], { id }) => {
// eslint-disable-next-line no-bitwise
if ((selectedFlags & id) === id) {
acc.push(id);
}
return acc;
}, []);
const values = indexerFlags.items.map(({ id, name }) => ({
key: id,
value: name,
}));
return {
value,
values,
};
}
);
interface IndexerFlagsSelectInputProps {
name: string;
indexerFlags: number;
onChange(payload: object): void;
}
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
const { indexerFlags, onChange } = props;
const { value, values } = useSelector(selectIndexerFlagsValues(indexerFlags));
const onChangeWrapper = useCallback(
({ name, value }: { name: string; value: number[] }) => {
const indexerFlags = value.reduce((acc, flagId) => acc + flagId, 0);
onChange({ name, value: indexerFlags });
},
[onChange]
);
return (
<EnhancedSelectInput
{...props}
value={value}
values={values}
onChange={onChangeWrapper}
/>
);
}
export default IndexerFlagsSelectInput;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ function SeriesTypeSelectInput(props) {
values.unshift({ values.unshift({
key: 'noChange', key: 'noChange',
value: translate('NoChange'), value: translate('NoChange'),
disabled: includeNoChangeDisabled isDisabled: includeNoChangeDisabled
}); });
} }
@ -30,7 +30,7 @@ function SeriesTypeSelectInput(props) {
values.unshift({ values.unshift({
key: 'mixed', key: 'mixed',
value: '(Mixed)', value: '(Mixed)',
disabled: true isDisabled: true
}); });
} }

View File

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

View File

@ -63,6 +63,13 @@
width: 1280px; width: 1280px;
} }
.extraExtraLarge {
composes: modal;
width: 1600px;
}
@media only screen and (max-width: $breakpointExtraLarge) { @media only screen and (max-width: $breakpointExtraLarge) {
.modal.extraLarge { .modal.extraLarge {
width: 90%; width: 90%;
@ -90,7 +97,8 @@
.modal.small, .modal.small,
.modal.medium, .modal.medium,
.modal.large, .modal.large,
.modal.extraLarge { .modal.extraLarge,
.modal.extraExtraLarge {
max-height: 100%; max-height: 100%;
width: 100%; width: 100%;
height: 100% !important; height: 100% !important;

View File

@ -1,6 +1,7 @@
// This file is automatically generated. // This file is automatically generated.
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'extraExtraLarge': string;
'extraLarge': string; 'extraLarge': string;
'large': string; 'large': string;
'medium': string; 'medium': string;

View File

@ -6,7 +6,14 @@ import { createSelector } from 'reselect';
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchArtist } from 'Store/Actions/artistActions'; import { fetchArtist } from 'Store/Actions/artistActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions'; import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import { fetchImportLists, fetchLanguages, fetchMetadataProfiles, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions'; import {
fetchImportLists,
fetchIndexerFlags,
fetchLanguages,
fetchMetadataProfiles,
fetchQualityProfiles,
fetchUISettings
} from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchStatus } from 'Store/Actions/systemActions';
import { fetchTags } from 'Store/Actions/tagActions'; import { fetchTags } from 'Store/Actions/tagActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
@ -44,6 +51,7 @@ const selectAppProps = createSelector(
); );
const selectIsPopulated = createSelector( const selectIsPopulated = createSelector(
(state) => state.artist.isPopulated,
(state) => state.customFilters.isPopulated, (state) => state.customFilters.isPopulated,
(state) => state.tags.isPopulated, (state) => state.tags.isPopulated,
(state) => state.settings.ui.isPopulated, (state) => state.settings.ui.isPopulated,
@ -51,9 +59,11 @@ const selectIsPopulated = createSelector(
(state) => state.settings.qualityProfiles.isPopulated, (state) => state.settings.qualityProfiles.isPopulated,
(state) => state.settings.metadataProfiles.isPopulated, (state) => state.settings.metadataProfiles.isPopulated,
(state) => state.settings.importLists.isPopulated, (state) => state.settings.importLists.isPopulated,
(state) => state.settings.indexerFlags.isPopulated,
(state) => state.system.status.isPopulated, (state) => state.system.status.isPopulated,
(state) => state.app.translations.isPopulated, (state) => state.app.translations.isPopulated,
( (
artistsIsPopulated,
customFiltersIsPopulated, customFiltersIsPopulated,
tagsIsPopulated, tagsIsPopulated,
uiSettingsIsPopulated, uiSettingsIsPopulated,
@ -61,10 +71,12 @@ const selectIsPopulated = createSelector(
qualityProfilesIsPopulated, qualityProfilesIsPopulated,
metadataProfilesIsPopulated, metadataProfilesIsPopulated,
importListsIsPopulated, importListsIsPopulated,
indexerFlagsIsPopulated,
systemStatusIsPopulated, systemStatusIsPopulated,
translationsIsPopulated translationsIsPopulated
) => { ) => {
return ( return (
artistsIsPopulated &&
customFiltersIsPopulated && customFiltersIsPopulated &&
tagsIsPopulated && tagsIsPopulated &&
uiSettingsIsPopulated && uiSettingsIsPopulated &&
@ -72,6 +84,7 @@ const selectIsPopulated = createSelector(
qualityProfilesIsPopulated && qualityProfilesIsPopulated &&
metadataProfilesIsPopulated && metadataProfilesIsPopulated &&
importListsIsPopulated && importListsIsPopulated &&
indexerFlagsIsPopulated &&
systemStatusIsPopulated && systemStatusIsPopulated &&
translationsIsPopulated translationsIsPopulated
); );
@ -79,6 +92,7 @@ const selectIsPopulated = createSelector(
); );
const selectErrors = createSelector( const selectErrors = createSelector(
(state) => state.artist.error,
(state) => state.customFilters.error, (state) => state.customFilters.error,
(state) => state.tags.error, (state) => state.tags.error,
(state) => state.settings.ui.error, (state) => state.settings.ui.error,
@ -86,9 +100,11 @@ const selectErrors = createSelector(
(state) => state.settings.qualityProfiles.error, (state) => state.settings.qualityProfiles.error,
(state) => state.settings.metadataProfiles.error, (state) => state.settings.metadataProfiles.error,
(state) => state.settings.importLists.error, (state) => state.settings.importLists.error,
(state) => state.settings.indexerFlags.error,
(state) => state.system.status.error, (state) => state.system.status.error,
(state) => state.app.translations.error, (state) => state.app.translations.error,
( (
artistsError,
customFiltersError, customFiltersError,
tagsError, tagsError,
uiSettingsError, uiSettingsError,
@ -96,10 +112,12 @@ const selectErrors = createSelector(
qualityProfilesError, qualityProfilesError,
metadataProfilesError, metadataProfilesError,
importListsError, importListsError,
indexerFlagsError,
systemStatusError, systemStatusError,
translationsError translationsError
) => { ) => {
const hasError = !!( const hasError = !!(
artistsError ||
customFiltersError || customFiltersError ||
tagsError || tagsError ||
uiSettingsError || uiSettingsError ||
@ -107,6 +125,7 @@ const selectErrors = createSelector(
qualityProfilesError || qualityProfilesError ||
metadataProfilesError || metadataProfilesError ||
importListsError || importListsError ||
indexerFlagsError ||
systemStatusError || systemStatusError ||
translationsError translationsError
); );
@ -120,6 +139,7 @@ const selectErrors = createSelector(
qualityProfilesError, qualityProfilesError,
metadataProfilesError, metadataProfilesError,
importListsError, importListsError,
indexerFlagsError,
systemStatusError, systemStatusError,
translationsError translationsError
}; };
@ -177,6 +197,9 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchImportLists() { dispatchFetchImportLists() {
dispatch(fetchImportLists()); dispatch(fetchImportLists());
}, },
dispatchFetchIndexerFlags() {
dispatch(fetchIndexerFlags());
},
dispatchFetchUISettings() { dispatchFetchUISettings() {
dispatch(fetchUISettings()); dispatch(fetchUISettings());
}, },
@ -217,6 +240,7 @@ class PageConnector extends Component {
this.props.dispatchFetchQualityProfiles(); this.props.dispatchFetchQualityProfiles();
this.props.dispatchFetchMetadataProfiles(); this.props.dispatchFetchMetadataProfiles();
this.props.dispatchFetchImportLists(); this.props.dispatchFetchImportLists();
this.props.dispatchFetchIndexerFlags();
this.props.dispatchFetchUISettings(); this.props.dispatchFetchUISettings();
this.props.dispatchFetchStatus(); this.props.dispatchFetchStatus();
this.props.dispatchFetchTranslations(); this.props.dispatchFetchTranslations();
@ -243,6 +267,7 @@ class PageConnector extends Component {
dispatchFetchQualityProfiles, dispatchFetchQualityProfiles,
dispatchFetchMetadataProfiles, dispatchFetchMetadataProfiles,
dispatchFetchImportLists, dispatchFetchImportLists,
dispatchFetchIndexerFlags,
dispatchFetchUISettings, dispatchFetchUISettings,
dispatchFetchStatus, dispatchFetchStatus,
dispatchFetchTranslations, dispatchFetchTranslations,
@ -284,6 +309,7 @@ PageConnector.propTypes = {
dispatchFetchQualityProfiles: PropTypes.func.isRequired, dispatchFetchQualityProfiles: PropTypes.func.isRequired,
dispatchFetchMetadataProfiles: PropTypes.func.isRequired, dispatchFetchMetadataProfiles: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchImportLists: PropTypes.func.isRequired,
dispatchFetchIndexerFlags: PropTypes.func.isRequired,
dispatchFetchUISettings: PropTypes.func.isRequired, dispatchFetchUISettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired, dispatchFetchStatus: PropTypes.func.isRequired,
dispatchFetchTranslations: PropTypes.func.isRequired, dispatchFetchTranslations: PropTypes.func.isRequired,

View File

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

View File

@ -25,14 +25,3 @@
font-family: 'Ubuntu Mono'; 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'); 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": "../../../../", "start_url": "../../../../",
"theme_color": "#3a3f51", "theme_color": "#3a3f51",
"background_color": "#3a3f51", "background_color": "#3a3f51",
"display": "minimal-ui" "display": "standalone"
} }

View File

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

View File

@ -60,6 +60,7 @@ import {
faFileImport as fasFileImport, faFileImport as fasFileImport,
faFileInvoice as farFileInvoice, faFileInvoice as farFileInvoice,
faFilter as fasFilter, faFilter as fasFilter,
faFlag as fasFlag,
faFolderOpen as fasFolderOpen, faFolderOpen as fasFolderOpen,
faForward as fasForward, faForward as fasForward,
faHeart as fasHeart, faHeart as fasHeart,
@ -158,6 +159,7 @@ export const FILE = farFile;
export const FILE_IMPORT = fasFileImport; export const FILE_IMPORT = fasFileImport;
export const FILE_MISSING = fasFileCircleQuestion; export const FILE_MISSING = fasFileCircleQuestion;
export const FILTER = fasFilter; export const FILTER = fasFilter;
export const FLAG = fasFlag;
export const FOLDER = farFolder; export const FOLDER = farFolder;
export const FOLDER_OPEN = fasFolderOpen; export const FOLDER_OPEN = fasFolderOpen;
export const GROUP = farObjectGroup; export const GROUP = farObjectGroup;

View File

@ -15,10 +15,12 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect'; export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
export const ALBUM_RELEASE_SELECT = 'albumReleaseSelect'; export const ALBUM_RELEASE_SELECT = 'albumReleaseSelect';
export const INDEXER_SELECT = 'indexerSelect'; export const INDEXER_SELECT = 'indexerSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect'; export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect'; export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const SELECT = 'select'; export const SELECT = 'select';
export const SERIES_TYPE_SELECT = 'artistTypeSelect'; export const SERIES_TYPE_SELECT = 'artistTypeSelect';
export const ARTIST_TAG = 'artistTag';
export const DYNAMIC_SELECT = 'dynamicSelect'; export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag'; export const TAG = 'tag';
export const TAG_SELECT = 'tagSelect'; export const TAG_SELECT = 'tagSelect';
@ -48,6 +50,7 @@ export const all = [
DOWNLOAD_CLIENT_SELECT, DOWNLOAD_CLIENT_SELECT,
ROOT_FOLDER_SELECT, ROOT_FOLDER_SELECT,
SELECT, SELECT,
ARTIST_TAG,
DYNAMIC_SELECT, DYNAMIC_SELECT,
SERIES_TYPE_SELECT, SERIES_TYPE_SELECT,
TAG, TAG,

View File

@ -3,5 +3,5 @@ export const SMALL = 'small';
export const MEDIUM = 'medium'; export const MEDIUM = 'medium';
export const LARGE = 'large'; export const LARGE = 'large';
export const EXTRA_LARGE = 'extraLarge'; export const EXTRA_LARGE = 'extraLarge';
export const EXTRA_EXTRA_LARGE = 'extraExtraLarge';
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE]; export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE, EXTRA_EXTRA_LARGE];

View File

@ -1,10 +1,9 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import FormInputGroup from 'Components/Form/FormInputGroup'; import SelectInput from 'Components/Form/SelectInput';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import { inputTypes } from 'Helpers/Props';
import shortenList from 'Utilities/String/shortenList'; import shortenList from 'Utilities/String/shortenList';
import titleCase from 'Utilities/String/titleCase'; import titleCase from 'Utilities/String/titleCase';
@ -56,8 +55,7 @@ class SelectAlbumReleaseRow extends Component {
if (name === 'release') { if (name === 'release') {
return ( return (
<TableRowCell key={name}> <TableRowCell key={name}>
<FormInputGroup <SelectInput
type={inputTypes.SELECT}
name={id.toString()} name={id.toString()}
values={_.map(releases, (r) => ({ values={_.map(releases, (r) => ({
key: r.id, key: r.id,

View File

@ -0,0 +1,37 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal';
import SelectIndexerFlagsModalContentConnector from './SelectIndexerFlagsModalContentConnector';
class SelectIndexerFlagsModal extends Component {
//
// Render
render() {
const {
isOpen,
onModalClose,
...otherProps
} = this.props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<SelectIndexerFlagsModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
}
SelectIndexerFlagsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectIndexerFlagsModal;

View File

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

View File

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

View File

@ -0,0 +1,106 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerFlagsModalContent.css';
class SelectIndexerFlagsModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
const {
indexerFlags
} = props;
this.state = {
indexerFlags
};
}
//
// Listeners
onIndexerFlagsChange = ({ value }) => {
this.setState({ indexerFlags: value });
};
onIndexerFlagsSelect = () => {
this.props.onIndexerFlagsSelect(this.state);
};
//
// Render
render() {
const {
onModalClose
} = this.props;
const {
indexerFlags
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Manual Import - Set indexer Flags
</ModalHeader>
<ModalBody
className={styles.modalBody}
scrollDirection={scrollDirections.NONE}
>
<Form>
<FormGroup>
<FormLabel>
{translate('IndexerFlags')}
</FormLabel>
<FormInputGroup
type={inputTypes.INDEXER_FLAGS_SELECT}
name="indexerFlags"
indexerFlags={indexerFlags}
autoFocus={true}
onChange={this.onIndexerFlagsChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Cancel')}
</Button>
<Button
kind={kinds.SUCCESS}
onPress={this.onIndexerFlagsSelect}
>
{translate('SetIndexerFlags')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
SelectIndexerFlagsModalContent.propTypes = {
indexerFlags: PropTypes.number.isRequired,
onIndexerFlagsSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default SelectIndexerFlagsModalContent;

View File

@ -0,0 +1,54 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { saveInteractiveImportItem, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent';
const mapDispatchToProps = {
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
dispatchSaveInteractiveImportItems: saveInteractiveImportItem
};
class SelectIndexerFlagsModalContentConnector extends Component {
//
// Listeners
onIndexerFlagsSelect = ({ indexerFlags }) => {
const {
ids,
dispatchUpdateInteractiveImportItems,
dispatchSaveInteractiveImportItems
} = this.props;
dispatchUpdateInteractiveImportItems({
ids,
indexerFlags
});
dispatchSaveInteractiveImportItems({ ids });
this.props.onModalClose(true);
};
//
// Render
render() {
return (
<SelectIndexerFlagsModalContent
{...this.props}
onIndexerFlagsSelect={this.onIndexerFlagsSelect}
/>
);
}
}
SelectIndexerFlagsModalContentConnector.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
dispatchSaveInteractiveImportItems: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(null, mapDispatchToProps)(SelectIndexerFlagsModalContentConnector);

View File

@ -20,6 +20,7 @@ import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumReleaseModal'; import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumReleaseModal';
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal'; import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal'; import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
@ -30,7 +31,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected';
import InteractiveImportRow from './InteractiveImportRow'; import InteractiveImportRow from './InteractiveImportRow';
import styles from './InteractiveImportModalContent.css'; import styles from './InteractiveImportModalContent.css';
const columns = [ const COLUMNS = [
{ {
name: 'path', name: 'path',
label: () => translate('Path'), label: () => translate('Path'),
@ -79,11 +80,21 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'indexerFlags',
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags')
}),
isSortable: true,
isVisible: true
},
{ {
name: 'rejections', name: 'rejections',
label: React.createElement(Icon, { label: React.createElement(Icon, {
name: icons.DANGER, name: icons.DANGER,
kind: kinds.DANGER kind: kinds.DANGER,
title: () => translate('Rejections')
}), }),
isSortable: true, isSortable: true,
isVisible: true isVisible: true
@ -107,6 +118,7 @@ const ALBUM = 'album';
const ALBUM_RELEASE = 'albumRelease'; const ALBUM_RELEASE = 'albumRelease';
const RELEASE_GROUP = 'releaseGroup'; const RELEASE_GROUP = 'releaseGroup';
const QUALITY = 'quality'; const QUALITY = 'quality';
const INDEXER_FLAGS = 'indexerFlags';
const replaceExistingFilesOptions = { const replaceExistingFilesOptions = {
COMBINE: 'combine', COMBINE: 'combine',
@ -301,6 +313,21 @@ class InteractiveImportModalContent extends Component {
inconsistentAlbumReleases inconsistentAlbumReleases
} = this.state; } = this.state;
const allColumns = _.cloneDeep(COLUMNS);
const columns = allColumns.map((column) => {
const showIndexerFlags = items.some((item) => item.indexerFlags);
if (!showIndexerFlags) {
const indexerFlagsColumn = allColumns.find((c) => c.name === 'indexerFlags');
if (indexerFlagsColumn) {
indexerFlagsColumn.isVisible = false;
}
}
return column;
});
const selectedIds = this.getSelectedIds(); const selectedIds = this.getSelectedIds();
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null; const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
const errorMessage = getErrorMessage(error, 'Unable to load manual import items'); const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
@ -310,7 +337,8 @@ class InteractiveImportModalContent extends Component {
{ key: ALBUM, value: translate('SelectAlbum') }, { key: ALBUM, value: translate('SelectAlbum') },
{ key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') }, { key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') },
{ key: QUALITY, value: translate('SelectQuality') }, { key: QUALITY, value: translate('SelectQuality') },
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') } { key: RELEASE_GROUP, value: translate('SelectReleaseGroup') },
{ key: INDEXER_FLAGS, value: translate('SelectIndexerFlags') }
]; ];
if (allowArtistChange) { if (allowArtistChange) {
@ -433,6 +461,7 @@ class InteractiveImportModalContent extends Component {
isSaving={isSaving} isSaving={isSaving}
{...item} {...item}
allowArtistChange={allowArtistChange} allowArtistChange={allowArtistChange}
columns={columns}
onSelectedChange={this.onSelectedChange} onSelectedChange={this.onSelectedChange}
onValidRowChange={this.onValidRowChange} onValidRowChange={this.onValidRowChange}
/> />
@ -547,6 +576,13 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose} onModalClose={this.onSelectModalClose}
/> />
<SelectIndexerFlagsModal
isOpen={selectModalOpen === INDEXER_FLAGS}
ids={selectedIds}
indexerFlags={0}
onModalClose={this.onSelectModalClose}
/>
<ConfirmImportModal <ConfirmImportModal
isOpen={isConfirmImportModalOpen} isOpen={isConfirmImportModalOpen}
albums={albumsImported} albums={albumsImported}

View File

@ -135,6 +135,7 @@ class InteractiveImportModalContentConnector extends Component {
albumReleaseId, albumReleaseId,
tracks, tracks,
quality, quality,
indexerFlags,
disableReleaseSwitching disableReleaseSwitching
} = item; } = item;
@ -165,6 +166,7 @@ class InteractiveImportModalContentConnector extends Component {
albumReleaseId, albumReleaseId,
trackIds: _.map(tracks, 'id'), trackIds: _.map(tracks, 'id'),
quality, quality,
indexerFlags,
downloadId: this.props.downloadId, downloadId: this.props.downloadId,
disableReleaseSwitching disableReleaseSwitching
}); });

View File

@ -1,6 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import AlbumFormats from 'Album/AlbumFormats'; import AlbumFormats from 'Album/AlbumFormats';
import IndexerFlags from 'Album/IndexerFlags';
import TrackQuality from 'Album/TrackQuality'; import TrackQuality from 'Album/TrackQuality';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@ -13,6 +14,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sortDirections, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, sortDirections, tooltipPositions } from 'Helpers/Props';
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal'; import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal'; import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal'; import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
@ -35,7 +37,8 @@ class InteractiveImportRow extends Component {
isSelectAlbumModalOpen: false, isSelectAlbumModalOpen: false,
isSelectTrackModalOpen: false, isSelectTrackModalOpen: false,
isSelectReleaseGroupModalOpen: false, isSelectReleaseGroupModalOpen: false,
isSelectQualityModalOpen: false isSelectQualityModalOpen: false,
isSelectIndexerFlagsModalOpen: false
}; };
} }
@ -45,14 +48,16 @@ class InteractiveImportRow extends Component {
artist, artist,
album, album,
tracks, tracks,
quality quality,
size
} = this.props; } = this.props;
if ( if (
artist && artist &&
album != null && album != null &&
tracks.length && tracks.length &&
quality quality &&
size > 0
) { ) {
this.props.onSelectedChange({ id, value: true }); this.props.onSelectedChange({ id, value: true });
} }
@ -130,6 +135,10 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectQualityModalOpen: true }); this.setState({ isSelectQualityModalOpen: true });
}; };
onSelectIndexerFlagsPress = () => {
this.setState({ isSelectIndexerFlagsModalOpen: true });
};
onSelectArtistModalClose = (changed) => { onSelectArtistModalClose = (changed) => {
this.setState({ isSelectArtistModalOpen: false }); this.setState({ isSelectArtistModalOpen: false });
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
@ -155,6 +164,11 @@ class InteractiveImportRow extends Component {
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
}; };
onSelectIndexerFlagsModalClose = (changed) => {
this.setState({ isSelectIndexerFlagsModalOpen: false });
this.selectRowAfterChange(changed);
};
// //
// Render // Render
@ -171,7 +185,9 @@ class InteractiveImportRow extends Component {
releaseGroup, releaseGroup,
size, size,
customFormats, customFormats,
indexerFlags,
rejections, rejections,
columns,
isReprocessing, isReprocessing,
audioTags, audioTags,
additionalFile, additionalFile,
@ -184,7 +200,8 @@ class InteractiveImportRow extends Component {
isSelectAlbumModalOpen, isSelectAlbumModalOpen,
isSelectTrackModalOpen, isSelectTrackModalOpen,
isSelectReleaseGroupModalOpen, isSelectReleaseGroupModalOpen,
isSelectQualityModalOpen isSelectQualityModalOpen,
isSelectIndexerFlagsModalOpen
} = this.state; } = this.state;
const artistName = artist ? artist.artistName : ''; const artistName = artist ? artist.artistName : '';
@ -204,6 +221,7 @@ class InteractiveImportRow extends Component {
const showTrackNumbersLoading = isReprocessing && !tracks.length; const showTrackNumbersLoading = isReprocessing && !tracks.length;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup; const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
const showQualityPlaceholder = isSelected && !quality; const showQualityPlaceholder = isSelected && !quality;
const showIndexerFlagsPlaceholder = isSelected && !indexerFlags;
const pathCellContents = ( const pathCellContents = (
<div> <div>
@ -219,6 +237,8 @@ class InteractiveImportRow extends Component {
/> />
) : pathCellContents; ) : pathCellContents;
const isIndexerFlagsColumnVisible = columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false;
return ( return (
<TableRow <TableRow
className={additionalFile ? styles.additionalFile : undefined} className={additionalFile ? styles.additionalFile : undefined}
@ -322,6 +342,28 @@ class InteractiveImportRow extends Component {
} }
</TableRowCell> </TableRowCell>
{isIndexerFlagsColumnVisible ? (
<TableRowCellButton
title={translate('ClickToChangeIndexerFlags')}
onPress={this.onSelectIndexerFlagsPress}
>
{showIndexerFlagsPlaceholder ? (
<InteractiveImportRowCellPlaceholder isOptional={true} />
) : (
<>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}
/>
) : null}
</>
)}
</TableRowCellButton>
) : null}
<TableRowCell> <TableRowCell>
{ {
rejections.length ? rejections.length ?
@ -395,6 +437,13 @@ class InteractiveImportRow extends Component {
real={quality ? quality.revision.real > 0 : false} real={quality ? quality.revision.real > 0 : false}
onModalClose={this.onSelectQualityModalClose} onModalClose={this.onSelectQualityModalClose}
/> />
<SelectIndexerFlagsModal
isOpen={isSelectIndexerFlagsModalOpen}
ids={[id]}
indexerFlags={indexerFlags ?? 0}
onModalClose={this.onSelectIndexerFlagsModalClose}
/>
</TableRow> </TableRow>
); );
} }
@ -413,7 +462,9 @@ InteractiveImportRow.propTypes = {
quality: PropTypes.object, quality: PropTypes.object,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object), customFormats: PropTypes.arrayOf(PropTypes.object),
indexerFlags: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.object).isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
audioTags: PropTypes.object.isRequired, audioTags: PropTypes.object.isRequired,
additionalFile: PropTypes.bool.isRequired, additionalFile: PropTypes.bool.isRequired,
isReprocessing: PropTypes.bool, isReprocessing: PropTypes.bool,

View File

@ -48,7 +48,7 @@ class InteractiveImportModal extends Component {
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
size={sizes.EXTRA_LARGE} size={sizes.EXTRA_EXTRA_LARGE}
closeOnBackgroundClick={false} closeOnBackgroundClick={false}
onModalClose={onModalClose} onModalClose={onModalClose}
> >

View File

@ -65,6 +65,15 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'indexerFlags',
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags')
}),
isSortable: true,
isVisible: true
},
{ {
name: 'rejections', name: 'rejections',
label: React.createElement(Icon, { label: React.createElement(Icon, {

View File

@ -35,6 +35,7 @@
} }
.rejected, .rejected,
.indexerFlags,
.download { .download {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';

View File

@ -5,6 +5,7 @@ interface CssExports {
'customFormatScore': string; 'customFormatScore': string;
'download': string; 'download': string;
'indexer': string; 'indexer': string;
'indexerFlags': string;
'peers': string; 'peers': string;
'protocol': string; 'protocol': string;
'quality': string; 'quality': string;

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ProtocolLabel from 'Activity/Queue/ProtocolLabel'; import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import AlbumFormats from 'Album/AlbumFormats'; import AlbumFormats from 'Album/AlbumFormats';
import IndexerFlags from 'Album/IndexerFlags';
import TrackQuality from 'Album/TrackQuality'; import TrackQuality from 'Album/TrackQuality';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
@ -48,12 +49,12 @@ function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
if (isGrabbing) { if (isGrabbing) {
return ''; return '';
} else if (isGrabbed) { } else if (isGrabbed) {
return 'Added to downloaded queue'; return translate('AddedToDownloadQueue');
} else if (grabError) { } else if (grabError) {
return grabError; return grabError;
} }
return 'Add to downloaded queue'; return translate('AddToDownloadQueue');
} }
class InteractiveSearchRow extends Component { class InteractiveSearchRow extends Component {
@ -129,6 +130,7 @@ class InteractiveSearchRow extends Component {
quality, quality,
customFormatScore, customFormatScore,
customFormats, customFormats,
indexerFlags = 0,
rejections, rejections,
downloadAllowed, downloadAllowed,
isGrabbing, isGrabbing,
@ -187,10 +189,21 @@ class InteractiveSearchRow extends Component {
formatCustomFormatScore(customFormatScore, customFormats.length) formatCustomFormatScore(customFormatScore, customFormats.length)
} }
tooltip={<AlbumFormats formats={customFormats} />} tooltip={<AlbumFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM} position={tooltipPositions.LEFT}
/> />
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.indexerFlags}>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}
/>
) : null}
</TableRowCell>
<TableRowCell className={styles.rejected}> <TableRowCell className={styles.rejected}>
{ {
!!rejections.length && !!rejections.length &&
@ -236,7 +249,9 @@ class InteractiveSearchRow extends Component {
isOpen={this.state.isConfirmGrabModalOpen} isOpen={this.state.isConfirmGrabModalOpen}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('GrabRelease')} title={translate('GrabRelease')}
message={translate('GrabReleaseMessageText', [title])} message={translate('GrabReleaseUnknownArtistOrAlbumMessageText', {
title
})}
confirmLabel={translate('Grab')} confirmLabel={translate('Grab')}
onConfirm={this.onGrabConfirm} onConfirm={this.onGrabConfirm}
onCancel={this.onGrabCancel} onCancel={this.onGrabCancel}
@ -263,6 +278,7 @@ InteractiveSearchRow.propTypes = {
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object), customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired, customFormatScore: PropTypes.number.isRequired,
indexerFlags: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.string).isRequired, rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
downloadAllowed: PropTypes.bool.isRequired, downloadAllowed: PropTypes.bool.isRequired,
isGrabbing: PropTypes.bool.isRequired, isGrabbing: PropTypes.bool.isRequired,
@ -275,6 +291,7 @@ InteractiveSearchRow.propTypes = {
}; };
InteractiveSearchRow.defaultProps = { InteractiveSearchRow.defaultProps = {
indexerFlags: 0,
rejections: [], rejections: [],
isGrabbing: false, isGrabbing: false,
isGrabbed: false isGrabbed: false

View File

@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Card from 'Components/Card'; import Card from 'Components/Card';
import FieldSet from 'Components/FieldSet'; import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form'; import Form from 'Components/Form/Form';
@ -150,6 +151,11 @@ class EditCustomFormatModalContent extends Component {
</Form> </Form>
<FieldSet legend={translate('Conditions')}> <FieldSet legend={translate('Conditions')}>
<Alert kind={kinds.INFO}>
<div>
{translate('CustomFormatsSettingsTriggerInfo')}
</div>
</Alert>
<div className={styles.customFormats}> <div className={styles.customFormats}>
{ {
specifications.map((tag) => { specifications.map((tag) => {

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css'; import styles from './EditImportListModalContent.css';
@ -66,6 +67,7 @@ function EditImportListModalContent(props) {
onModalClose, onModalClose,
onSavePress, onSavePress,
onTestPress, onTestPress,
onAdvancedSettingsPress,
onDeleteImportListPress, onDeleteImportListPress,
showMetadataProfile, showMetadataProfile,
...otherProps ...otherProps
@ -333,6 +335,12 @@ function EditImportListModalContent(props) {
</Button> </Button>
} }
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton <SpinnerErrorButton
isSpinning={isTesting} isSpinning={isTesting}
error={saveError} error={saveError}
@ -373,6 +381,7 @@ EditImportListModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired, onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteImportListPress: PropTypes.func onDeleteImportListPress: PropTypes.func
}; };

View File

@ -2,7 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { saveImportList, setImportListFieldValue, setImportListValue, testImportList } from 'Store/Actions/settingsActions'; import {
saveImportList,
setImportListFieldValue,
setImportListValue,
testImportList,
toggleAdvancedSettings
} from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditImportListModalContent from './EditImportListModalContent'; import EditImportListModalContent from './EditImportListModalContent';
@ -25,7 +31,8 @@ const mapDispatchToProps = {
setImportListValue, setImportListValue,
setImportListFieldValue, setImportListFieldValue,
saveImportList, saveImportList,
testImportList testImportList,
toggleAdvancedSettings
}; };
class EditImportListModalContentConnector extends Component { class EditImportListModalContentConnector extends Component {
@ -58,6 +65,10 @@ class EditImportListModalContentConnector extends Component {
this.props.testImportList({ id: this.props.id }); this.props.testImportList({ id: this.props.id });
}; };
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
// //
// Render // Render
@ -67,6 +78,7 @@ class EditImportListModalContentConnector extends Component {
{...this.props} {...this.props}
onSavePress={this.onSavePress} onSavePress={this.onSavePress}
onTestPress={this.onTestPress} onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange} onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange} onFieldChange={this.onFieldChange}
/> />
@ -84,6 +96,7 @@ EditImportListModalContentConnector.propTypes = {
setImportListFieldValue: PropTypes.func.isRequired, setImportListFieldValue: PropTypes.func.isRequired,
saveImportList: PropTypes.func.isRequired, saveImportList: PropTypes.func.isRequired,
testImportList: PropTypes.func.isRequired, testImportList: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };

View File

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

View File

@ -160,7 +160,7 @@ function EditIndexerModalContent(props) {
<FormInputGroup <FormInputGroup
type={inputTypes.NUMBER} type={inputTypes.NUMBER}
name="priority" name="priority"
helpText={translate('PriorityHelpText')} helpText={translate('IndexerPriorityHelpText')}
min={1} min={1}
max={50} max={50}
{...priority} {...priority}

View File

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

View File

@ -1,6 +1,6 @@
.option { .option {
display: flex; display: flex;
align-items: center; align-items: stretch;
flex-wrap: wrap; flex-wrap: wrap;
margin: 3px; margin: 3px;
border: 1px solid var(--borderColor); border: 1px solid var(--borderColor);
@ -17,7 +17,7 @@
} }
.small { .small {
width: 460px; width: 490px;
} }
.large { .large {
@ -26,7 +26,7 @@
.token { .token {
flex: 0 0 50%; flex: 0 0 50%;
padding: 6px 16px; padding: 6px;
background-color: var(--popoverTitleBackgroundColor); background-color: var(--popoverTitleBackgroundColor);
font-family: $monoSpaceFontFamily; font-family: $monoSpaceFontFamily;
} }
@ -34,9 +34,9 @@
.example { .example {
display: flex; display: flex;
align-items: center; align-items: center;
align-self: stretch; justify-content: space-between;
flex: 0 0 50%; flex: 0 0 50%;
padding: 6px 16px; padding: 6px;
background-color: var(--popoverBodyBackgroundColor); background-color: var(--popoverBodyBackgroundColor);
.footNote { .footNote {

View File

@ -14,6 +14,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props'; import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import NotificationEventItems from './NotificationEventItems'; import NotificationEventItems from './NotificationEventItems';
import styles from './EditNotificationModalContent.css'; import styles from './EditNotificationModalContent.css';
@ -32,6 +33,7 @@ function EditNotificationModalContent(props) {
onModalClose, onModalClose,
onSavePress, onSavePress,
onTestPress, onTestPress,
onAdvancedSettingsPress,
onDeleteNotificationPress, onDeleteNotificationPress,
...otherProps ...otherProps
} = props; } = props;
@ -140,6 +142,12 @@ function EditNotificationModalContent(props) {
</Button> </Button>
} }
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton <SpinnerErrorButton
isSpinning={isTesting} isSpinning={isTesting}
error={saveError} error={saveError}
@ -179,6 +187,7 @@ EditNotificationModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired, onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteNotificationPress: PropTypes.func onDeleteNotificationPress: PropTypes.func
}; };

View File

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

View File

@ -12,7 +12,7 @@ export default function TagInUse(props) {
return null; return null;
} }
if (count > 1 && labelPlural ) { if (count > 1 && labelPlural) {
return ( return (
<div> <div>
{count} {labelPlural.toLowerCase()} {count} {labelPlural.toLowerCase()}

View File

@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions'; import { fetchDelayProfiles, fetchDownloadClients, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions'; import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions';
import Tags from './Tags'; import Tags from './Tags';
function createMapStateToProps() { function createMapStateToProps() {
@ -25,6 +25,7 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchFetchTags: fetchTags,
dispatchFetchTagDetails: fetchTagDetails, dispatchFetchTagDetails: fetchTagDetails,
dispatchFetchDelayProfiles: fetchDelayProfiles, dispatchFetchDelayProfiles: fetchDelayProfiles,
dispatchFetchImportLists: fetchImportLists, dispatchFetchImportLists: fetchImportLists,
@ -41,6 +42,7 @@ class MetadatasConnector extends Component {
componentDidMount() { componentDidMount() {
const { const {
dispatchFetchTags,
dispatchFetchTagDetails, dispatchFetchTagDetails,
dispatchFetchDelayProfiles, dispatchFetchDelayProfiles,
dispatchFetchImportLists, dispatchFetchImportLists,
@ -50,6 +52,7 @@ class MetadatasConnector extends Component {
dispatchFetchDownloadClients dispatchFetchDownloadClients
} = this.props; } = this.props;
dispatchFetchTags();
dispatchFetchTagDetails(); dispatchFetchTagDetails();
dispatchFetchDelayProfiles(); dispatchFetchDelayProfiles();
dispatchFetchImportLists(); dispatchFetchImportLists();
@ -72,6 +75,7 @@ class MetadatasConnector extends Component {
} }
MetadatasConnector.propTypes = { MetadatasConnector.propTypes = {
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchTagDetails: PropTypes.func.isRequired, dispatchFetchTagDetails: PropTypes.func.isRequired,
dispatchFetchDelayProfiles: PropTypes.func.isRequired, dispatchFetchDelayProfiles: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchImportLists: PropTypes.func.isRequired,

View File

@ -22,19 +22,19 @@ export const firstDayOfWeekOptions = [
]; ];
export const weekColumnOptions = [ export const weekColumnOptions = [
{ key: 'ddd M/D', value: 'Tue 3/25' }, { key: 'ddd M/D', value: 'Tue 3/25', hint: 'ddd M/D' },
{ key: 'ddd MM/DD', value: 'Tue 03/25' }, { key: 'ddd MM/DD', value: 'Tue 03/25', hint: 'ddd MM/DD' },
{ key: 'ddd D/M', value: 'Tue 25/3' }, { key: 'ddd D/M', value: 'Tue 25/3', hint: 'ddd D/M' },
{ key: 'ddd DD/MM', value: 'Tue 25/03' } { key: 'ddd DD/MM', value: 'Tue 25/03', hint: 'ddd DD/MM' }
]; ];
const shortDateFormatOptions = [ const shortDateFormatOptions = [
{ key: 'MMM D YYYY', value: 'Mar 25 2014' }, { key: 'MMM D YYYY', value: 'Mar 25 2014', hint: 'MMM D YYYY' },
{ key: 'DD MMM YYYY', value: '25 Mar 2014' }, { key: 'DD MMM YYYY', value: '25 Mar 2014', hint: 'DD MMM YYYY' },
{ key: 'MM/D/YYYY', value: '03/25/2014' }, { key: 'MM/D/YYYY', value: '03/25/2014', hint: 'MM/D/YYYY' },
{ key: 'MM/DD/YYYY', value: '03/25/2014' }, { key: 'MM/DD/YYYY', value: '03/25/2014', hint: 'MM/DD/YYYY' },
{ key: 'DD/MM/YYYY', value: '25/03/2014' }, { key: 'DD/MM/YYYY', value: '25/03/2014', hint: 'DD/MM/YYYY' },
{ key: 'YYYY-MM-DD', value: '2014-03-25' } { key: 'YYYY-MM-DD', value: '2014-03-25', hint: 'YYYY-MM-DD' }
]; ];
const longDateFormatOptions = [ const longDateFormatOptions = [

View File

@ -1,8 +1,11 @@
import $ from 'jquery';
import _ from 'lodash';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import getProviderState from 'Utilities/State/getProviderState'; import getProviderState from 'Utilities/State/getProviderState';
import { set } from '../baseActions'; import { set } from '../baseActions';
const abortCurrentRequests = {}; const abortCurrentRequests = {};
let lastTestData = null;
export function createCancelTestProviderHandler(section) { export function createCancelTestProviderHandler(section) {
return function(getState, payload, dispatch) { return function(getState, payload, dispatch) {
@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) {
return function(getState, payload, dispatch) { return function(getState, payload, dispatch) {
dispatch(set({ section, isTesting: true })); dispatch(set({ section, isTesting: true }));
const testData = getProviderState(payload, getState, section); const {
queryParams = {},
...otherPayload
} = payload;
const testData = getProviderState({ ...otherPayload }, getState, section);
const params = { ...queryParams };
// If the user is re-testing the same provider without changes
// force it to be tested.
if (_.isEqual(testData, lastTestData)) {
params.forceTest = true;
}
lastTestData = testData;
const ajaxOptions = { const ajaxOptions = {
url: `${url}/test`, url: `${url}/test?${$.param(params, true)}`,
method: 'POST', method: 'POST',
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) {
abortCurrentRequests[section] = abortRequest; abortCurrentRequests[section] = abortRequest;
request.done((data) => { request.done((data) => {
lastTestData = null;
dispatch(set({ dispatch(set({
section, section,
isTesting: false, isTesting: false,

View File

@ -0,0 +1,48 @@
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import { createThunk } from 'Store/thunks';
//
// Variables
const section = 'settings.indexerFlags';
//
// Actions Types
export const FETCH_INDEXER_FLAGS = 'settings/indexerFlags/fetchIndexerFlags';
//
// Action Creators
export const fetchIndexerFlags = createThunk(FETCH_INDEXER_FLAGS);
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
//
// Action Handlers
actionHandlers: {
[FETCH_INDEXER_FLAGS]: createFetchHandler(section, '/indexerFlag')
},
//
// Reducers
reducers: {
}
};

View File

@ -4,9 +4,10 @@ import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions'; import { batchActions } from 'redux-batched-actions';
import * as calendarViews from 'Calendar/calendarViews'; import * as calendarViews from 'Calendar/calendarViews';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import { filterTypes } from 'Helpers/Props'; import { filterBuilderTypes, filterBuilderValueTypes, filterTypes } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import { set, update } from './baseActions'; import { set, update } from './baseActions';
import { executeCommandHelper } from './commandActions'; import { executeCommandHelper } from './commandActions';
@ -54,8 +55,8 @@ export const defaultState = {
label: () => translate('All'), label: () => translate('All'),
filters: [ filters: [
{ {
key: 'monitored', key: 'unmonitored',
value: false, value: [true],
type: filterTypes.EQUAL type: filterTypes.EQUAL
} }
] ]
@ -65,19 +66,35 @@ export const defaultState = {
label: () => translate('MonitoredOnly'), label: () => translate('MonitoredOnly'),
filters: [ filters: [
{ {
key: 'monitored', key: 'unmonitored',
value: true, value: [false],
type: filterTypes.EQUAL type: filterTypes.EQUAL
} }
] ]
} }
],
filterBuilderProps: [
{
name: 'unmonitored',
label: () => translate('IncludeUnmonitored'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.BOOL
},
{
name: 'tags',
label: () => translate('Tags'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.TAG
}
] ]
}; };
export const persistState = [ export const persistState = [
'calendar.view', 'calendar.view',
'calendar.selectedFilterKey', 'calendar.selectedFilterKey',
'calendar.options' 'calendar.options',
'calendar.customFilters'
]; ];
// //
@ -189,6 +206,10 @@ function isRangePopulated(start, end, state) {
return false; return false;
} }
function getCustomFilters(state, type) {
return state.customFilters.items.filter((customFilter) => customFilter.type === type);
}
// //
// Action Creators // Action Creators
@ -210,7 +231,8 @@ export const actionHandlers = handleThunks({
[FETCH_CALENDAR]: function(getState, payload, dispatch) { [FETCH_CALENDAR]: function(getState, payload, dispatch) {
const state = getState(); const state = getState();
const calendar = state.calendar; const calendar = state.calendar;
const unmonitored = calendar.selectedFilterKey === 'all'; const customFilters = getCustomFilters(state, section);
const selectedFilters = findSelectedFilters(calendar.selectedFilterKey, calendar.filters, customFilters);
const { const {
time = calendar.time, time = calendar.time,
@ -237,13 +259,26 @@ export const actionHandlers = handleThunks({
dispatch(set(attrs)); dispatch(set(attrs));
const requestParams = {
start,
end
};
selectedFilters.forEach((selectedFilter) => {
if (selectedFilter.key === 'unmonitored') {
requestParams.unmonitored = selectedFilter.value.includes(true);
}
if (selectedFilter.key === 'tags') {
requestParams.tags = selectedFilter.value.join(',');
}
});
requestParams.unmonitored = requestParams.unmonitored ?? false;
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: '/calendar', url: '/calendar',
data: { data: requestParams
unmonitored,
start,
end
}
}).request; }).request;
promise.done((data) => { promise.done((data) => {

View File

@ -208,6 +208,7 @@ export const actionHandlers = handleThunks({
trackIds: (item.tracks || []).map((e) => e.id), trackIds: (item.tracks || []).map((e) => e.id),
quality: item.quality, quality: item.quality,
releaseGroup: item.releaseGroup, releaseGroup: item.releaseGroup,
indexerFlags: item.indexerFlags,
downloadId: item.downloadId, downloadId: item.downloadId,
additionalFile: item.additionalFile, additionalFile: item.additionalFile,
replaceExistingFiles: item.replaceExistingFiles, replaceExistingFiles: item.replaceExistingFiles,

View File

@ -11,6 +11,7 @@ import downloadClients from './Settings/downloadClients';
import general from './Settings/general'; import general from './Settings/general';
import importListExclusions from './Settings/importListExclusions'; import importListExclusions from './Settings/importListExclusions';
import importLists from './Settings/importLists'; import importLists from './Settings/importLists';
import indexerFlags from './Settings/indexerFlags';
import indexerOptions from './Settings/indexerOptions'; import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers'; import indexers from './Settings/indexers';
import languages from './Settings/languages'; import languages from './Settings/languages';
@ -38,6 +39,7 @@ export * from './Settings/downloadClientOptions';
export * from './Settings/general'; export * from './Settings/general';
export * from './Settings/importLists'; export * from './Settings/importLists';
export * from './Settings/importListExclusions'; export * from './Settings/importListExclusions';
export * from './Settings/indexerFlags';
export * from './Settings/indexerOptions'; export * from './Settings/indexerOptions';
export * from './Settings/indexers'; export * from './Settings/indexers';
export * from './Settings/languages'; export * from './Settings/languages';
@ -73,6 +75,7 @@ export const defaultState = {
downloadClients: downloadClients.defaultState, downloadClients: downloadClients.defaultState,
downloadClientOptions: downloadClientOptions.defaultState, downloadClientOptions: downloadClientOptions.defaultState,
general: general.defaultState, general: general.defaultState,
indexerFlags: indexerFlags.defaultState,
indexerOptions: indexerOptions.defaultState, indexerOptions: indexerOptions.defaultState,
indexers: indexers.defaultState, indexers: indexers.defaultState,
importLists: importLists.defaultState, importLists: importLists.defaultState,
@ -119,6 +122,7 @@ export const actionHandlers = handleThunks({
...downloadClients.actionHandlers, ...downloadClients.actionHandlers,
...downloadClientOptions.actionHandlers, ...downloadClientOptions.actionHandlers,
...general.actionHandlers, ...general.actionHandlers,
...indexerFlags.actionHandlers,
...indexerOptions.actionHandlers, ...indexerOptions.actionHandlers,
...indexers.actionHandlers, ...indexers.actionHandlers,
...importLists.actionHandlers, ...importLists.actionHandlers,
@ -156,6 +160,7 @@ export const reducers = createHandleActions({
...downloadClients.reducers, ...downloadClients.reducers,
...downloadClientOptions.reducers, ...downloadClientOptions.reducers,
...general.reducers, ...general.reducers,
...indexerFlags.reducers,
...indexerOptions.reducers, ...indexerOptions.reducers,
...indexers.reducers, ...indexers.reducers,
...importLists.reducers, ...importLists.reducers,

View File

@ -77,6 +77,15 @@ export const defaultState = {
}), }),
isVisible: false isVisible: false
}, },
{
name: 'indexerFlags',
columnLabel: () => translate('IndexerFlags'),
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags')
}),
isVisible: false
},
{ {
name: 'status', name: 'status',
label: () => translate('Status'), label: () => translate('Status'),

View File

@ -1,8 +1,9 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
function createAllArtistSelector() { function createAllArtistSelector() {
return createSelector( return createSelector(
(state) => state.artist, (state: AppState) => state.artist,
(artist) => { (artist) => {
return artist.items; return artist.items;
} }

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