Compare commits

...

94 Commits

Author SHA1 Message Date
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
272 changed files with 6304 additions and 3055 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
setup/Output/
*.~is
.mono
# VS outout folders
bin

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

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

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

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": "Run 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,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.2.1'
majorVersion: '2.4.0'
minorVersion: $[counter('minorVersion', 1076)]
lidarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(lidarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.417'
dotnetVersion: '6.0.421'
nodeVersion: '20.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
@ -166,10 +166,10 @@ stages:
pool:
vmImage: $(imageName)
steps:
- task: NodeTool@0
- task: UseNode@1
displayName: Set Node.js version
inputs:
versionSpec: $(nodeVersion)
version: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1
@ -1093,10 +1093,10 @@ stages:
pool:
vmImage: $(imageName)
steps:
- task: NodeTool@0
- task: UseNode@1
displayName: Set Node.js version
inputs:
versionSpec: $(nodeVersion)
version: $(nodeVersion)
- checkout: self
submodules: true
fetchDepth: 1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,8 @@ function createMapStateToProps() {
trackFilePath: trackFile ? trackFile.path : null,
trackFileSize: trackFile ? trackFile.size : null,
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,
artistName,
albumType,
statistics,
statistics = {},
item,
isSaving,
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 (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
size={sizes.EXTRA_EXTRA_LARGE}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
>

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -4,6 +4,7 @@ import Link from 'Components/Link/Link';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AlbumReleaseSelectInputConnector from './AlbumReleaseSelectInputConnector';
import ArtistTagInput from './ArtistTagInput';
import AutoCompleteInput from './AutoCompleteInput';
import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput';
@ -12,6 +13,7 @@ import DownloadClientSelectInputConnector from './DownloadClientSelectInputConne
import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
import IndexerFlagsSelectInput from './IndexerFlagsSelectInput';
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
@ -83,6 +85,9 @@ function getComponent(type) {
case inputTypes.INDEXER_SELECT:
return IndexerSelectInputConnector;
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInput;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector;
@ -95,6 +100,9 @@ function getComponent(type) {
case inputTypes.DYNAMIC_SELECT:
return EnhancedSelectInputConnector;
case inputTypes.ARTIST_TAG:
return ArtistTagInput;
case inputTypes.SERIES_TYPE_SELECT:
return SeriesTypeSelectInput;
@ -292,6 +300,7 @@ FormInputGroup.propTypes = {
includeNoChangeDisabled: PropTypes.bool,
includeNone: PropTypes.bool,
selectedValueOptions: PropTypes.object,
indexerFlags: PropTypes.number,
pending: PropTypes.bool,
errors: 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({
key: 'noChange',
value: translate('NoChange'),
disabled: includeNoChangeDisabled
isDisabled: includeNoChangeDisabled
});
}
@ -46,7 +46,7 @@ function createMapStateToProps() {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
isDisabled: true
});
}

View File

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

View File

@ -18,7 +18,7 @@ function MonitorNewItemsSelectInput(props) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: includeNoChangeDisabled
isDisabled: includeNoChangeDisabled
});
}
@ -26,7 +26,7 @@ function MonitorNewItemsSelectInput(props) {
values.unshift({
key: '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 TextInput from './TextInput';
import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input
function onCopy(e) {
@ -13,17 +11,14 @@ function PasswordInput(props) {
return (
<TextInput
{...props}
type="password"
onCopy={onCopy}
/>
);
}
PasswordInput.propTypes = {
className: PropTypes.string.isRequired
};
PasswordInput.defaultProps = {
className: styles.input
...TextInput.props
};
export default PasswordInput;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
faFileInvoice as farFileInvoice,
faFilter as fasFilter,
faFlag as fasFlag,
faFolderOpen as fasFolderOpen,
faForward as fasForward,
faHeart as fasHeart,
@ -158,6 +159,7 @@ export const FILE = farFile;
export const FILE_IMPORT = fasFileImport;
export const FILE_MISSING = fasFileCircleQuestion;
export const FILTER = fasFilter;
export const FLAG = fasFlag;
export const FOLDER = farFolder;
export const FOLDER_OPEN = fasFolderOpen;
export const GROUP = farObjectGroup;

View File

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

View File

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

View File

@ -1,10 +1,9 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
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 TableRow from 'Components/Table/TableRow';
import { inputTypes } from 'Helpers/Props';
import shortenList from 'Utilities/String/shortenList';
import titleCase from 'Utilities/String/titleCase';
@ -56,8 +55,7 @@ class SelectAlbumReleaseRow extends Component {
if (name === 'release') {
return (
<TableRowCell key={name}>
<FormInputGroup
type={inputTypes.SELECT}
<SelectInput
name={id.toString()}
values={_.map(releases, (r) => ({
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.
// Please do not change this file!
interface CssExports {
'input': string;
'modalBody': string;
}
export const cssExports: 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 SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
@ -30,7 +31,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected';
import InteractiveImportRow from './InteractiveImportRow';
import styles from './InteractiveImportModalContent.css';
const columns = [
const COLUMNS = [
{
name: 'path',
label: () => translate('Path'),
@ -79,11 +80,21 @@ const columns = [
isSortable: true,
isVisible: true
},
{
name: 'indexerFlags',
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags')
}),
isSortable: true,
isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, {
name: icons.DANGER,
kind: kinds.DANGER
kind: kinds.DANGER,
title: () => translate('Rejections')
}),
isSortable: true,
isVisible: true
@ -107,6 +118,7 @@ const ALBUM = 'album';
const ALBUM_RELEASE = 'albumRelease';
const RELEASE_GROUP = 'releaseGroup';
const QUALITY = 'quality';
const INDEXER_FLAGS = 'indexerFlags';
const replaceExistingFilesOptions = {
COMBINE: 'combine',
@ -301,6 +313,21 @@ class InteractiveImportModalContent extends Component {
inconsistentAlbumReleases
} = 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 selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
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_RELEASE, value: translate('SelectAlbumRelease') },
{ 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) {
@ -433,6 +461,7 @@ class InteractiveImportModalContent extends Component {
isSaving={isSaving}
{...item}
allowArtistChange={allowArtistChange}
columns={columns}
onSelectedChange={this.onSelectedChange}
onValidRowChange={this.onValidRowChange}
/>
@ -547,6 +576,13 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose}
/>
<SelectIndexerFlagsModal
isOpen={selectModalOpen === INDEXER_FLAGS}
ids={selectedIds}
indexerFlags={0}
onModalClose={this.onSelectModalClose}
/>
<ConfirmImportModal
isOpen={isConfirmImportModalOpen}
albums={albumsImported}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,11 @@
import $ from 'jquery';
import _ from 'lodash';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getProviderState from 'Utilities/State/getProviderState';
import { set } from '../baseActions';
const abortCurrentRequests = {};
let lastTestData = null;
export function createCancelTestProviderHandler(section) {
return function(getState, payload, dispatch) {
@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) {
return function(getState, payload, dispatch) {
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 = {
url: `${url}/test`,
url: `${url}/test?${$.param(params, true)}`,
method: 'POST',
contentType: 'application/json',
dataType: 'json',
@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) {
abortCurrentRequests[section] = abortRequest;
request.done((data) => {
lastTestData = null;
dispatch(set({
section,
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

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,19 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import createAllArtistSelector from './createAllArtistSelector';
function createArtistCountSelector() {
return createSelector(
createAllArtistSelector(),
(state) => state.artist.error,
(state) => state.artist.isFetching,
(state) => state.artist.isPopulated,
(state: AppState) => state.artist.error,
(state: AppState) => state.artist.isFetching,
(state: AppState) => state.artist.isPopulated,
(artists, error, isFetching, isPopulated) => {
return {
count: artists.length,
error,
isFetching,
isPopulated
isPopulated,
};
}
);

View File

@ -2,13 +2,10 @@ import { createSelector } from 'reselect';
import { isCommandExecuting } from 'Utilities/Command';
import createCommandSelector from './createCommandSelector';
function createCommandExecutingSelector(name, contraints = {}) {
return createSelector(
createCommandSelector(name, contraints),
(command) => {
return isCommandExecuting(command);
}
);
function createCommandExecutingSelector(name: string, contraints = {}) {
return createSelector(createCommandSelector(name, contraints), (command) => {
return isCommandExecuting(command);
});
}
export default createCommandExecutingSelector;

View File

@ -1,14 +0,0 @@
import { createSelector } from 'reselect';
import { findCommand } from 'Utilities/Command';
import createCommandsSelector from './createCommandsSelector';
function createCommandSelector(name, contraints = {}) {
return createSelector(
createCommandsSelector(),
(commands) => {
return findCommand(commands, { name, ...contraints });
}
);
}
export default createCommandSelector;

View File

@ -0,0 +1,11 @@
import { createSelector } from 'reselect';
import { findCommand } from 'Utilities/Command';
import createCommandsSelector from './createCommandsSelector';
function createCommandSelector(name: string, contraints = {}) {
return createSelector(createCommandsSelector(), (commands) => {
return findCommand(commands, { name, ...contraints });
});
}
export default createCommandSelector;

View File

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

View File

@ -1,9 +0,0 @@
import _ from 'lodash';
import { createSelectorCreator, defaultMemoize } from 'reselect';
const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
_.isEqual
);
export default createDeepEqualSelector;

View File

@ -0,0 +1,6 @@
import { isEqual } from 'lodash';
import { createSelectorCreator, defaultMemoize } from 'reselect';
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, isEqual);
export default createDeepEqualSelector;

View File

@ -1,9 +1,10 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import { isCommandExecuting } from 'Utilities/Command';
function createExecutingCommandsSelector() {
return createSelector(
(state) => state.commands.items,
(state: AppState) => state.commands.items,
(commands) => {
return commands.filter((command) => isCommandExecuting(command));
}

View File

@ -1,13 +1,15 @@
import _ from 'lodash';
import { some } from 'lodash';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import createAllArtistSelector from './createAllArtistSelector';
function createExistingArtistSelector() {
return createSelector(
(state, { foreignArtistId }) => foreignArtistId,
(_: AppState, { foreignArtistId }: { foreignArtistId: string }) =>
foreignArtistId,
createAllArtistSelector(),
(foreignArtistId, artist) => {
return _.some(artist, { foreignArtistId });
return some(artist, { foreignArtistId });
}
);
}

View File

@ -0,0 +1,9 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
const createIndexerFlagsSelector = createSelector(
(state: AppState) => state.settings.indexerFlags,
(indexerFlags) => indexerFlags
);
export default createIndexerFlagsSelector;

View File

@ -1,15 +0,0 @@
import { createSelector } from 'reselect';
function createMetadataProfileSelector() {
return createSelector(
(state, { metadataProfileId }) => metadataProfileId,
(state) => state.settings.metadataProfiles.items,
(metadataProfileId, metadataProfiles) => {
return metadataProfiles.find((profile) => {
return profile.id === metadataProfileId;
});
}
);
}
export default createMetadataProfileSelector;

View File

@ -0,0 +1,17 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
function createMetadataProfileSelector() {
return createSelector(
(_: AppState, { metadataProfileId }: { metadataProfileId: number }) =>
metadataProfileId,
(state: AppState) => state.settings.metadataProfiles.items,
(metadataProfileId, metadataProfiles) => {
return metadataProfiles.find(
(profile) => profile.id === metadataProfileId
);
}
);
}
export default createMetadataProfileSelector;

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