1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-05 13:20:20 -05:00

Compare commits

...

459 Commits

Author SHA1 Message Date
Taloth Saldono
0d99c87d87 Fixed: Bumped mono workaround version from 6.10 to 7.x for btls trust chain coz they still haven't fixed it after over a year 2021-09-30 21:12:02 +02:00
Mark McDowall
9111799f46 Not a real series title 2021-09-30 11:22:35 -07:00
Stevie Robinson
943a3d80c4 New: Disable autocomplete of port number 2021-09-30 11:11:52 -07:00
Kevin Lau
d9e9b72a89 New: Change Today color in calendar for better visibility 2021-09-30 11:11:14 -07:00
bakerboy448
5bf7228658 Update CONTRIBUTING.md
- wiki links
- irc link
- GitHub link 
- branches
2021-09-30 11:10:43 -07:00
Mark McDowall
8ad5e5dd13 Fixed: Parsing of quality when release group contains Remux
Closes #4594
2021-09-30 10:59:45 -07:00
Taloth Saldono
c0a961bb94 New: Updated TheXEM server url on xem admin request 2021-09-28 22:21:36 +02:00
Mark McDowall
6994ca720a Fixed: Parsing multi-episode file name with number in episode title
Closes #4613
2021-09-05 21:45:05 -07:00
Mark McDowall
1d8b711eda Aria2 fixes
Fixed: Removing completed downloads from Aria2
Fixed: Return correct path for Aria2 downloads in a job folder
Fixed: Seeding torrents in Aria2 are treated as finished downloading
Closes #4648
2021-09-03 21:41:52 -07:00
Mark McDowall
77fdebc366 Do not unmonitor episodes after using Manual File Import 2021-08-30 17:29:13 -07:00
Mark McDowall
dd3899806b Fixed: Parsing of some anime with standard numbers
Closes #4573
2021-08-29 20:40:46 -07:00
Mark McDowall
574f05e296 Fixed: Parsing of some multi-episode titles
Closes #4612
2021-08-29 20:15:27 -07:00
Mark McDowall
5c4687e0d9 Fixed: Unmonitor episodes after using Manual File Import
Closes #4638
2021-08-29 19:54:05 -07:00
Mark McDowall
e19d4cf85b Fixed: Log active indexers instead of implying all indexers are searched
Closes #4642
2021-08-29 19:25:41 -07:00
Mark McDowall
6b84da614b Fixed: Queue conflicts with the same download in multiple clients 2021-08-29 19:23:48 -07:00
LoV432
31833253dd Fixed: Erai-Raws Batches Parsing 2021-08-29 19:13:49 -07:00
Qstick
dbd140d4ec Update QualityDefinitionsConnector.js 2021-08-20 21:02:09 -07:00
Qstick
e9a49941c9 Fixed: Help message when adding download clients 2021-08-20 21:01:43 -07:00
Qstick
0fe436b952 Aria2 formatting cleanup 2021-08-20 21:01:23 -07:00
Qstick
22f044844c Fix Phantom branch reference in Join proxy 2021-08-15 20:14:51 +02:00
Taloth Saldono
20306a38e1 Fixed: Release Push api broken when no indexer id is specified 2021-08-11 13:48:12 +02:00
Winter
0d03dba6ea Fix Feature Request issue template 2021-08-07 09:22:19 +02:00
Winter
1c6863dd27 Fix Bug Report issue template
The `title` key is optional, but must not be empty if specified.
2021-08-07 09:22:19 +02:00
Mark McDowall
3f60e28c42 Fixed: Blocklisting pending releases
Closes #4598
2021-08-05 17:17:40 -07:00
Robin Dadswell
ead1371846 New: Renamed Blacklist to Blocklist 2021-08-05 16:55:23 -07:00
Taloth Saldono
2f6409226a Fixed: Cleanup of unused tags for Import lists.
Fixes #4610
2021-08-06 01:54:12 +02:00
bakerboy448
3fb5f65f08 Use YML Github templates 2021-08-05 16:16:57 -07:00
Mark McDowall
38feeefea3 Fixed: Improve More Info links for Indexers, download clients, etc 2021-08-03 19:13:08 -07:00
Mark McDowall
a7a3c546e5 Fix broken test 2021-08-03 18:55:45 -07:00
Taloth Saldono
f107ea5678 Simplified regex a bit. 2021-08-03 21:59:30 +02:00
Taloth Saldono
59409a7e72 Fixed: Parse endpoint response when title failed to parse
Closes #4591
2021-08-03 20:55:27 +02:00
Mark McDowall
dc7f46027a Fixed: Prevent conflicts with reserved device names
Closes #4595
2021-08-01 16:45:23 -07:00
Mark McDowall
2031da05f6 Updated more wiki links 2021-08-01 16:12:30 -07:00
Mark McDowall
92b9f46399 Fixed HealthCheckFixture test 2021-08-01 16:05:36 -07:00
Mark McDowall
0a30735f34 Fixed: Updated supported wiki links 2021-08-01 16:00:52 -07:00
Mark McDowall
021fd4afa7 Fixed: Updated wiki links 2021-08-01 15:34:44 -07:00
siankatabg
57e3bd8b4d New: Bulgarian Language 2021-08-01 13:32:49 -07:00
bakerboy448
3bbec2ff5d Fixed: Incorrectly Parsing [PublicHD] as Release Group 2021-08-01 13:30:33 -07:00
Zippy79
155dbd4dd5 New: Add TVDB URL in Kodi metadata 2021-08-01 13:29:25 -07:00
Mark McDowall
4bf3ab1511 Improve default path for Synology Download Station
Fixes: Missing default path for Download Station
Fixes: Error when getting destination path for Synology Download Station in health check
Closes #4562
2021-08-01 13:25:50 -07:00
LLeny
6fd31613c2 New: Aria2 Torrent Client
Closes #1374
2021-08-01 12:49:36 -07:00
Alanoll
d4cd4a9549 New: Named Release Profile preferred word renaming tokens 2021-08-01 12:45:26 -07:00
Mark McDowall
6596d0b4da Fixed: Show error if adding root folder fails
Closes #4570
2021-08-01 12:09:02 -07:00
Mark McDowall
dca2cfcecd Fixed: Peers filtering in Interactive Search results
Closes #4583
2021-08-01 12:09:01 -07:00
Mark McDowall
076c293942 Fixed: Monitoring pilot episode will not monitor first season
Closes #4597
2021-08-01 12:09:01 -07:00
Alex Thomson
94417402d8 Remove duplicate call to DeleteTorrent 2021-07-27 16:17:04 +02:00
bakerboy448
4659a8366d Update bug report template 2021-07-13 10:23:31 -07:00
6cUbi57z
c3d54b312e New: Add tag support to indexers
Closes #487
2021-07-04 11:17:57 -07:00
Yukine
14b551b027 Fixed: Parsing of some anime releases with year in title 2021-07-04 10:19:55 -07:00
Nathaniel Peiffer
43cd103248 New: Add Size column to Activity: Queue
Closes #4527
2021-07-04 10:14:42 -07:00
bakerboy448
bd4624c0ab Add wiki link for logs in bug report template 2021-07-04 10:13:20 -07:00
bakerboy448
b9539cc1f7 Update Contributing.md 2021-06-22 07:46:47 -07:00
Minamiya Natsuki
fc8bbf29d1 Fixed: Parsing of some Chinese anime releases without brackets 2021-06-21 20:42:32 -07:00
Robin Dadswell
98e5442f24 New: Added Running Years into the shows details 2021-06-21 20:39:10 -07:00
bakerboy448
c30ce3580a Fixed: Incorrectly parsing RePACKPOST as Group 2021-06-21 20:38:50 -07:00
Nathaniel Peiffer
2ddf131e1a New: Activity Queue: Rename Timeleft column to Time Left 2021-06-21 20:37:00 -07:00
Jake Soenneker
2f366bc3b7 New: Manual Import rejection column is sortable 2021-06-21 20:35:23 -07:00
TwentyNine78
49e90463e5 Fixed: Compatibility with the new Download Station API
Fixes #4388
2021-06-21 20:29:50 -07:00
Mark McDowall
4402f475d4 Update IRC links to Libera.chat 2021-06-17 11:26:49 -07:00
Qstick
6db2401359 Fixed: Use normal URL for Trakt Oauth per new docs
Closes #4521
2021-06-08 07:33:17 -07:00
Taloth Saldono
690b851836 Reverted test code 2021-05-27 10:15:52 +02:00
Mark McDowall
2804a961cb Fix webpack memory leak when copying HTML files 2021-05-26 21:34:03 -07:00
Taloth Saldono
32f2d417d3 Added EpisodesChanged to signalR series event to notify that episode monitored flag may have changed 2021-05-26 21:28:30 +02:00
Evan J
e8f58eb9be Update login.html 2021-05-24 17:21:29 -07:00
Robin Dadswell
a22a38016d Fixed: Root Folder Downloads check giving errors when RuTorrent is used 2021-05-24 17:19:17 -07:00
Taloth Saldono
6c5cc430b7 Clarify delay profile bypass only applies to preferred protocol 2021-05-22 10:44:57 +02:00
Taloth Saldono
9e81d41f26 Send signalr message for episode monitored flag changes 2021-05-22 10:40:10 +02:00
Taloth Saldono
dab1834960 Fixed: Inversion of defaults in CDH migration. 2021-05-20 01:44:11 +02:00
Mark McDowall
c3837c9f7b Fix ESLint 2021-05-19 15:06:06 -07:00
Qstick
2ad4e21aad Fixed: NZBGet Settings hint mentions Sabnzbd
closes #4494
2021-05-18 09:45:33 +02:00
Jesse Chan
707c2c7978 Flood: explicitly cast DateFinished long? to long 2021-05-18 09:44:39 +02:00
Jesse Chan
c8ad01e38e New: Removing Flood downloads when seeding criteria have been met
closes #4492
2021-05-17 22:26:58 +02:00
Taloth Saldono
fe8f319f7b Show User Agent in System->Tasks for externally triggered commands 2021-05-16 12:43:17 +02:00
Taloth Saldono
91fe47ef31 Removed extraneous enum hints in dropdown 2021-05-16 00:48:14 +02:00
Taloth Saldono
2dba5ef4b4 New: Per download client setting to Remove Completed/Failed downloads instead of global setting 2021-05-16 00:48:14 +02:00
Taloth Saldono
8e46362ff9 Fixed SeedConfigProvider cache refresh after indexer settings change 2021-05-16 00:48:14 +02:00
leaty
411be4d011 New: Removing rtorrent downloads when seeding criteria have been met 2021-05-16 00:48:14 +02:00
Robin Dadswell
cad8b37df8 New: Health Check for Downloads to Root Folder
already implemented within Radarr
(cherry picked from commit 88780f33a4c5032aaa151aaae7090371beb42f33)
2021-05-15 15:00:04 -07:00
Michael Higgins
82e0a4bd0e New: Setting SceneName and ReleaseGroup for EpisodeFiles via API 2021-05-15 14:58:53 -07:00
Taloth Saldono
ee227e3b1d Fixed timing issues
fixes #4487
2021-05-12 23:56:14 +02:00
Mark McDowall
ff048ffcb8 Spaces... 2021-05-10 20:43:38 -07:00
Mark McDowall
93443f1db2 Fixed: Adding new series via API with seasons array missing 2021-05-10 20:35:30 -07:00
Mark McDowall
a2427bd9df Fixed: Parsing of 540p releases 2021-05-10 20:35:30 -07:00
Mark McDowall
c722e91124 New: Add rel="noreferrer" to all external links 2021-05-10 20:35:30 -07:00
Mark McDowall
05bee7c37a Fixed: Remove preview from site name 2021-05-10 20:35:30 -07:00
Taloth Saldono
5dff21e6d5 Fixed logger parameter 2021-05-09 01:54:22 +02:00
Taloth Saldono
b3dbff1642 Log when season,ep query parameters aren't supported. 2021-05-09 01:10:09 +02:00
Taloth Saldono
8fc68420c9 Fixed: Scene Info displaying mappings for wrong season
Fixed: Extraneous searches based on scene mappings
2021-05-08 01:03:47 +02:00
Taloth Saldono
87897d56ea Fixed: MediaInfo tag in renaming format for certain OS language cultures
Based-On: a0d2af54e8
2021-05-07 22:09:23 +02:00
Taloth Saldono
bcdbadbede Simplify release titles before applying scene naming exception regex filter 2021-05-07 20:17:05 +02:00
Taloth Saldono
87f10bf7bb Fixed: TheXEM mappings not used properly when parsing season packs 2021-05-07 20:16:09 +02:00
Mark McDowall
a8a9399469 Fixed: Parsing of some anime releases with numbers in title 2021-04-25 02:22:10 -07:00
Mark McDowall
5d316ad7dc Fixed scrolling via jump list on series index 2021-04-24 21:57:54 -07:00
Mark McDowall
56a33e3b4c Use webpack environment variables in build.sh 2021-04-24 19:25:02 -07:00
Mark McDowall
684626ef73 Updated react-dnd and added touch support
Fixed: Drag and drop on mobile devices
Closes ##4429
2021-04-24 19:25:02 -07:00
Mark McDowall
47f3886d7a Updated react-virtualized 2021-04-24 19:25:02 -07:00
Mark McDowall
8acc0f77b6 Updated create-react-class package 2021-04-24 19:25:01 -07:00
Mark McDowall
23a7c728f4 Updated redux and react-redux packages 2021-04-24 19:25:01 -07:00
Mark McDowall
2964d0bb6d Update react-autosuggest, react-focus-lock, react-lazyload react-slider and react-tabs packages 2021-04-24 19:25:01 -07:00
Mark McDowall
c92d25694f Updated react-addons-shallow-compare package 2021-04-24 19:25:01 -07:00
Mark McDowall
a831ece57d Updated react and react-dom packages 2021-04-24 19:25:01 -07:00
Mark McDowall
b4973fd99d Updated jquery, filesize and qs packages 2021-04-24 19:25:01 -07:00
Mark McDowall
a0e3afd614 Updated clipboard, fuse.js, mobile-detect, moment and signalr packages 2021-04-24 19:25:01 -07:00
Mark McDowall
91c895219f Update classnames 2021-04-24 19:25:01 -07:00
Mark McDowall
38a312895a Update sentry 2021-04-24 19:25:01 -07:00
Mark McDowall
44cb493847 Update fontawesome 2021-04-24 19:25:01 -07:00
Mark McDowall
30ed105afc Update postcss packages 2021-04-24 19:25:00 -07:00
Mark McDowall
0374f05743 Upgrade core-js package 2021-04-24 19:25:00 -07:00
Mark McDowall
023498fdb9 Update linter packages 2021-04-24 19:25:00 -07:00
Mark McDowall
36d4c2c9a0 Upgrade babel packages 2021-04-24 19:25:00 -07:00
Mark McDowall
dde53b12a8 Removed gulp and organized package.json 2021-04-24 19:25:00 -07:00
Mark McDowall
a3bb2f1c32 Fixed files that were using incorrect imports 2021-04-24 19:25:00 -07:00
Mark McDowall
bd83a2af88 Forward slashes are actually forward slashes 2021-04-24 19:20:35 -07:00
bakerboy448
44f9d45938 New: Date format in log files 2021-04-24 19:17:33 -07:00
Robin Dadswell
a824fa44d2 Add missing On Delete Notifications to Mailgun notifications 2021-04-23 07:54:15 -07:00
cicomalieran
8175f19442 Fixed: Parsing RSS with null values 2021-04-22 16:38:26 -07:00
Skyler Mäntysaari
55752a6c62 New: Mailgun connection 2021-04-22 16:37:28 -07:00
bakerboy448
8d2d9078ff Update indexer category help text 2021-04-22 16:36:39 -07:00
Zippy79
199b126a46 New: Adds SSL option to Kodi connections 2021-04-22 16:36:05 -07:00
Mark McDowall
16156192c5 Fixed broken tests 2021-04-21 16:26:11 -07:00
Mark McDowall
0a2b109a3f Fixed: Refresh queue count when navigating Activity: Queue
Closes #4446
2021-04-20 00:21:20 -07:00
Mark McDowall
fde28c63d9 New: Removed EpisodeGuide tag from Kodi Metadata
Closes #4448
2021-04-20 00:11:09 -07:00
Mark McDowall
4ffa81c783 Fixed: Parsing WEB quality from some anime releases 2021-04-20 00:05:54 -07:00
Mark McDowall
6934eafd44 Fixed: Round durationseconds in Kodi metadata 2021-04-20 00:05:54 -07:00
Mark McDowall
c669be317f Fixed: Removing completed download from SABnzbd
Closes #4445
2021-04-20 00:05:54 -07:00
Mark McDowall
6079f1ef11 Fixed: CRC32 being parsed as release group
Closes #4449
2021-04-20 00:05:54 -07:00
Mark McDowall
3f8bb24b75 Fixed: Don't ignore number only aliases 2021-04-20 00:05:54 -07:00
Mark McDowall
dd8fbb438f Fixed: Parsing of some anime releases 2021-04-20 00:05:54 -07:00
Mark McDowall
d29e254dcb New: File info scrolls on mobile
Closes #4436
2021-04-12 22:15:59 -07:00
Mark McDowall
5938c38bc3 Fixed: Custom Script Health Issue Level 2021-04-12 22:15:27 -07:00
Mark McDowall
a47cb2390e Fixed: Links to Sonarr now uses auth cookie
Closes #4440
2021-04-12 22:14:41 -07:00
Mark McDowall
5aae777a18 Obfuscate anime title 2021-04-10 22:49:38 -07:00
Mark McDowall
5114c75cbb Fixed: Incorrectly grabbing revision downgrades
Closes #4431
2021-04-10 14:59:28 -07:00
Mark McDowall
d7e9ccde8e Fixed: Parsing of anime titles with numbers in the middle of their names
Closes #4433
2021-04-10 14:38:33 -07:00
Mark McDowall
100b87193d Fixed: Background click not closing episode modal when first opened 2021-04-10 01:36:43 -07:00
Mark McDowall
5449389ca5 Set timeout for sending email
Closes #4348
2021-04-09 10:38:16 -07:00
Mark McDowall
7da695bd62 Revert "Handle events asynchronously for notifications"
This reverts commit 46da657740.
2021-04-09 10:36:56 -07:00
Mark McDowall
46da657740 Handle events asynchronously for notifications
Fixed: Slow notification sending slowing down imports
Closes #4348
2021-04-08 17:36:24 -07:00
Mark McDowall
5301620ecf Refactor Email and add more logging 2021-04-08 17:34:56 -07:00
Mark McDowall
77ac0bb44c Fixed: Importing a language upgrade with a worse preferred word score 2021-04-08 17:27:01 -07:00
Mark McDowall
75e9d33fea Confirm scene name is not folder name for batch releases 2021-04-07 18:03:22 -07:00
Mark McDowall
ac8283d733 New: Remove completed downloads from disk when removing from SABnzbd
Closes #4423
2021-04-07 18:03:21 -07:00
Mark McDowall
f2422f814d Fixed: Parsing of some 360p multi-episode files 2021-04-07 18:03:21 -07:00
Mark McDowall
cb8a29ec00 Fixed: Some connection settings being invisibly enabled on creation 2021-04-07 18:03:21 -07:00
Mark McDowall
a4dea0aa62 Fix spelling of separated 2021-04-07 18:03:21 -07:00
Daniel Martin Gonzalez
d6e5d9c424 New: Add option to import from any user's personal list 2021-04-05 15:45:23 -07:00
Mark McDowall
1e99856ffc Fixed: Exception when searching some anime
Closes #4414
2021-03-29 13:27:52 -07:00
Mark McDowall
2da2420415 New: Series Year Renaming token
Closes #4407
2021-03-29 13:27:52 -07:00
Mark McDowall
0210b5c5c1 New: Calendar option for full color events 2021-03-29 13:27:52 -07:00
Mark McDowall
66c805feaf New: Support "Series" instead of "Season" in season packs
Closes #4399
2021-03-29 13:27:52 -07:00
Taloth Saldono
a200dd5f6d Bump package version 2021-03-27 22:14:03 +01:00
Taloth Saldono
f57efd30b8 Fixed data dir ownership in case of dpkg-reconfigure 2021-03-27 21:30:30 +01:00
bakerboy448
f2f1039c5e Fixed: Debatable typos in Naming Modal 2021-03-27 13:30:22 -07:00
bakerboy448
12ba4f73ed Updating the bug template yet again 2021-03-26 19:14:08 -07:00
Mark McDowall
370280b4bf Another wiki URL update
Close #4411
2021-03-26 19:08:04 -07:00
Mark McDowall
6c505937da Fixed: Interactive import modal horizontal scrolling on Firefox mobile
Closes #4401
2021-03-23 20:37:13 -07:00
Taloth Saldono
7272c5b7fc Added IsTorrentLoaded to tests 2021-03-20 00:33:51 +01:00
Mark McDowall
1d06c3fc15 Revert vswhere command 2021-03-19 15:21:04 -07:00
Mark McDowall
ec9f62285a Updated vswhere.exe 2021-03-19 14:40:17 -07:00
Mark McDowall
aae0d1c4ba Updated nuget.exe 2021-03-19 14:25:59 -07:00
Taloth Saldono
652d44722b Fixed: Qbittorrent api errors when only one of two seed criteria was configured
closes #4393
2021-03-19 21:32:42 +01:00
Taloth Saldono
5a69801877 Fixed: Unnecessary idle cpu usage
ref #4386
2021-03-19 02:48:09 +01:00
Taloth Saldono
fa8b2f48e7 Don't ignore original wal/journal during v3 migration 2021-03-18 23:43:16 +01:00
Taloth Saldono
34faa417c1 Fixed: Database migration failure when database was manually repaired in a certain way
fixes #4390
2021-03-18 01:25:21 +01:00
Mark McDowall
d6c0635a26 New: Improve message if Sonarr can't bind to IP/port during startup
Closes #4352
2021-03-17 17:21:30 -07:00
Mark McDowall
d4167d7169 New: Support for using parsed season number for some anime releases without aliases
Closes #4377
2021-03-17 17:14:54 -07:00
Taloth Saldono
eea6be459d Fixed post-install update check not running 2021-03-14 20:24:08 +01:00
Taloth Saldono
67e97f7aee Fixed: Setting seed criteria while torrent is still being loaded by qbittorrent
closes #4360
2021-03-14 00:46:28 +01:00
Mark McDowall
37e1c4f2eb New: Don't close interactive search with background click 2021-03-11 08:23:06 -08:00
Taloth Saldono
a9b8ec3505 Fixed failing tests 2021-03-10 23:38:59 +01:00
Taloth Saldono
f57cf1561b Log Skyhook connection failures with more info. 2021-03-10 23:05:19 +01:00
Taloth Saldono
6672650b6b Log Skyhook connection failures with more info. 2021-03-10 21:44:32 +01:00
Taloth Saldono
e4a064a1c0 Added comment to sonarr.service 2021-03-10 21:44:32 +01:00
Taloth Saldono
a848e575cd Make it clearer that Maximum size is the global limit. 2021-03-10 21:44:32 +01:00
Taloth Saldono
01995e686d New: Multiple Recipients on Email Notifications (Also CC, BCC)
Based on Qstick's Radarr commit of the same name
closes #4369

Signed-off-by: Taloth Saldono <Taloth@users.noreply.github.com>
2021-03-10 21:44:31 +01:00
Taloth Saldono
32058f1705 Fixed systemd unit search&replace issue and added umask to debconf 2021-03-10 21:44:31 +01:00
Mark McDowall
af3696af08 On Download -> On Import (again) 2021-03-10 11:25:29 -08:00
Mark McDowall
1477356cfc Update Discord link 2021-03-08 19:29:11 -08:00
Mark McDowall
aa19ddfbfd Fixed: Parsing of absolute episode numbers over 1000
Closes #4367
2021-03-08 19:22:41 -08:00
Mark McDowall
a697a69e88 Fixed: Some health check wiki links 2021-03-08 19:22:34 -08:00
Mark McDowall
3abb7e156a Fixed: Parsing of absolute episode number inside square brackets
Closes #4331
2021-03-07 17:31:43 -08:00
Mark McDowall
240791a7cd Fixed: Parsing of anime batch releases using a tilde instead of a dash
Closes #4330
2021-03-07 17:10:00 -08:00
Mark McDowall
0fe2453962 Fixed: Parsing similar series titles with common words at end 2021-03-07 16:53:56 -08:00
Robin Dadswell
85f4cbe94c Fix: Consistent SSL option for Download Clients
Closes #4323
2021-03-07 16:32:19 -08:00
Mark McDowall
e1f7bce14b New: Simplify Connection trigger settings
Closes #4351
2021-03-07 16:24:20 -08:00
Mark McDowall
675c72f02e Fixed: Set SameSite=Strict for SonarrAuth cookie
Closes #4365
2021-03-07 16:24:20 -08:00
Mark McDowall
6619350f87 Fixed: Don't set cookies for static resources
Closes #4356
2021-03-07 16:24:20 -08:00
Mark McDowall
efd9fe9ad0 Fixed: Cache headers for static resources
Towards #4356
2021-03-07 16:24:20 -08:00
Mark McDowall
ab502ffda4 Just one Application Version header 2021-03-07 16:24:20 -08:00
Taloth Saldono
90697d77a5 Bumped package version for main 2021-03-07 23:27:22 +01:00
Qstick
fa7aa05d60 Cleanup formatting in PackageGlobalMessageCheck.cs 2021-03-07 13:26:19 -08:00
Mark McDowall
4ed5fefcc6 Fixed: Remove selected in queue
Closes #4364
2021-03-07 12:24:06 -08:00
Taloth Saldono
4c324fbbbf Fixed failing test 2021-03-07 00:00:41 +01:00
Taloth Saldono
7da02c236a Added mechanism for package maintainers to produce a health check error. 2021-03-06 22:47:15 +01:00
Taloth Saldono
79cfa3a5f6 Removed bad file from commit 2021-03-06 22:47:15 +01:00
Qstick
2746556ae2 Cleanup trailing space in HttpResponse 2021-03-06 17:49:14 +01:00
Taloth Saldono
d668e923af New: Allow user to choose whether delay profile should apply to release of the highest enabled quality 2021-03-06 13:57:11 +01:00
Taloth Saldono
24ca47356e Sentry logging exceptions and some trace logging 2021-03-06 13:57:11 +01:00
Taloth Saldono
ab4f57f2fa Debug logging for email notifications
ref #4348
2021-03-06 13:57:10 +01:00
Mark McDowall
13ff2d4c70 Fixed: Restoring a backup with a different API didn't reload properly 2021-03-05 18:29:00 -08:00
Mark McDowall
2728bf79ca New: Improve messaging if release is in queue because all episodes in release were not imported 2021-03-05 18:29:00 -08:00
Mark McDowall
cd28af98ee Fixed: Removal of previous service 2021-03-05 18:28:59 -08:00
Mark McDowall
e9818b9982 Fixed: Queue refresh closing manual import from queue if items change 2021-03-05 18:28:59 -08:00
Qstick
d6cf370bcd Handle 303 and 307 redirects in Http Requests 2021-03-03 20:43:44 -08:00
Michael Casey
cb8ed74fe9 New: Add Recommended to the List types for Trakt
Closes #4167
2021-02-27 12:18:07 -08:00
bakerboy448
4e81b33006 Update contributing.md Github docs URL 2021-02-27 12:16:38 -08:00
bakerboy448
e67864fecb Fixed: Cleanse Tracker Announce Keys from logs
Closes #4341
2021-02-25 00:22:58 +01:00
Taloth Saldono
e289c428c6 Fixed: Refresh scene naming exceptions on series add to help first-use scenario 2021-02-20 20:04:34 +01:00
Taloth Saldono
23047623ee Cleanse more /home/username scenarios 2021-02-20 20:04:34 +01:00
Mark McDowall
062e47e27e Fixed: History details incorrect when preferred word score was 0
Closes #4328
2021-02-16 22:35:11 -08:00
Taloth Saldono
28ba037630 Fixed: Searching specials with NNTMux-based usenet indexers 2021-02-16 21:57:53 +01:00
Taloth Saldono
82da38941e Fixed: Debian package dependencies
closes #4332
2021-02-16 21:57:53 +01:00
Mark McDowall
10c770b116 Fixed: Use original file path when calculating preferred word score for existing file
Closes #3488
Closes #3913
2021-02-13 17:13:09 -08:00
Mark McDowall
3c45349404 New: Include renamed file information for Webhook and Custom Scripts
Closes #3927
2021-02-13 17:13:09 -08:00
Mark McDowall
b815d27a10 New: Include episode file with episode file deleted events
Closes #4282
2021-02-12 17:01:31 -08:00
Mark McDowall
ec698c2cf7 Fixed: Parsing of release names with trailing colon in the title
Closes #4238
2021-02-11 17:00:11 -08:00
Mark McDowall
e7d57a95f2 Series editor column fixes
Fixed: Series Editor sorting by size on disk
Fixed: Series Editor column order/enabled lost on refresh
2021-02-10 16:52:21 -08:00
Mark McDowall
1250d71e80 Appeasing the lint gods 2021-02-09 20:17:53 -08:00
Mark McDowall
e42d1af5ff Fixed: Unable to close indexer category select input on mobile
Closes #4296
2021-02-09 17:35:32 -08:00
Mark McDowall
88ad6f9544 Fixed: Error checking if files should be deleted after import won't leave import in limbo
Closes #4318
2021-02-09 17:35:32 -08:00
Michael Casey
54c386dd22 Use SVG for loading page icon
closes #4311
2021-02-09 19:20:59 +01:00
Mark McDowall
694360457d Fixed: Error logged when notification fails to send after episode file is deleted
Closes #4289
2021-02-09 07:55:25 -08:00
Mark McDowall
ae196af2ad New: Health check for import lists with missing root folders
New: Show missing root folder path in edit for Import List

Closes #4315
2021-02-08 23:12:23 -08:00
Mark McDowall
12fafb2457 Fixed: Mark as Failed errors 2021-02-08 19:26:06 -08:00
Mark McDowall
795bc91d25 Fixed: Error logged when notification fails to send after episode file is deleted
Closes #4289
2021-02-08 16:39:47 -08:00
Mark McDowall
044342f677 Fixed: Scene name not being set during import 2021-02-08 11:10:42 -08:00
Mark McDowall
5960035d5d Fixed: Restoring backup from zip file on disk 2021-02-08 08:21:14 -08:00
Mark McDowall
6c324c8a1c Fixed: Errors loading queue after episodes in series are removed
Closes #3565
2021-02-07 20:25:44 -08:00
Mark McDowall
54a267d860 Fixed: Don't automatically import if absolutely numbered file if it doesn't match expected season
Closes #377
2021-02-07 17:28:16 -08:00
Mark McDowall
b5f08a8f06 Alternate titles prop validation 2021-02-07 16:52:57 -08:00
Mark McDowall
653db8290e Update column properties when restoring persisted state 2021-02-07 16:52:57 -08:00
Mark McDowall
cbc4295f28 Fixed: Use file name when importing batch release when renaming is disabled
Closes #3056
2021-02-07 16:52:57 -08:00
Mark McDowall
8876c9194d New: Show preferred word score in history 2021-02-07 16:52:56 -08:00
Taloth Saldono
d898f55660 Generalized RateLimit logic to all indexers based on indexer id 2021-02-08 00:09:59 +01:00
Taloth Saldono
a85979c2f6 New: Added Hindi language
closes #4275
2021-02-07 21:35:30 +01:00
bakerboy448
ae63373b2b Update parser tests to be generic 2021-02-07 21:06:41 +01:00
Mark McDowall
044cb563a6 Fixed: Table column order resetting after refresh
#4297
2021-02-07 11:52:44 -08:00
Michael Casey
5302ee05bc New: Add logo to loading page
Closes #4304
2021-02-07 11:51:53 -08:00
Taloth Saldono
29bc660cfb Fixed: Jackett indexer search performance 2021-02-07 19:50:04 +01:00
Taloth Saldono
f8b8afdaa2 New: Added Arabic language 2021-02-07 19:50:04 +01:00
Ricardo Loureiro
33b708927c Fixed: Authentication on DSM 7
closes #3943
ref #4313
2021-02-07 14:05:31 +01:00
Mark McDowall
42d9e37e7d Fixed: Settings fields being altered during save
Closes #4309
2021-02-06 19:28:41 -08:00
Michael Casey
fc3bea370f New: Persist search settings in add new series
Closes #4245
2021-02-06 19:18:47 -08:00
Robin Dadswell
a1ddcf2b7b New: Show number of files as tooltip over size on disk
Closes #4203
2021-02-06 19:15:31 -08:00
bakerboy448
5b98a17873 Update feature request template 2021-02-06 19:06:53 -08:00
Mark McDowall
8fd4adbdb6 New: use-credentials for maniftest requests
Closes #4305
2021-02-04 20:28:24 -08:00
Mark McDowall
952a7248c9 New: Add FileId to History data for import events 2021-02-04 20:28:24 -08:00
bakerboy448
577604fccc Fixed: Series Removed From TVDB wiki link 2021-02-03 20:50:43 -08:00
Matt Evans
3d3cd8cf5c Detect Dolby Vision as HDR and MediaInfo Update
Fixed: Detect Dolby Vision as HDR
New: Updated MediaInfo on Windows and MacOS
Closes #4276
2021-02-02 17:19:22 -08:00
Robert Dailey
5e4c9dfe60 New: Add name field to release profiles 2021-02-02 16:51:06 -08:00
Taloth Saldono
f3f2648ce5 Fixed: Global scene mapping aliases disappeared from UI 2021-02-01 16:52:34 +01:00
Taloth Saldono
d1c3ae1749 Fixed: Validation of new qbittorrent max-ratio action config 2021-02-01 16:52:32 +01:00
Taloth Saldono
1cbcad6960 Added searchEngine support in Newznab/Torznab caps 2021-02-01 16:52:31 +01:00
Taloth Saldono
ab45910e56 Fixed: FLAC audio channels in media info 2021-02-01 16:52:30 +01:00
Mark McDowall
53f5694535 New: Disable season search if series is unmonitored 2021-01-31 11:04:55 -08:00
bakerboy448
7a3f4e8033 Fixed: Handle more obfuscated names
Closes #4198
2021-01-31 10:53:26 -08:00
Robin Dadswell
474f4bcc6d New: On Episode File Delete For Upgrade notification option 2021-01-30 14:54:26 -08:00
Robin Dadswell
ec058436d3 New: Unify series custom filter options
Closes #3548
2021-01-30 14:34:20 -08:00
Mark McDowall
39ca348666 Fixed: Label for 'On Episode File Delete' 2021-01-24 13:21:16 -08:00
Mark McDowall
02a46349a2 Consistent types for on delete custom script events 2021-01-24 12:19:15 -08:00
Mark McDowall
98dc20d919 Fixed: Webhook events not sent for series deletions 2021-01-24 12:18:39 -08:00
Mark McDowall
a510c9e86d Separate event types for series and episode deletions 2021-01-24 12:17:01 -08:00
Mark McDowall
f5d690aa7b Fixed: Queue refresh closing manual import from queue if items change
Closes #4221
2021-01-24 10:14:45 -08:00
Robin Dadswell
c91fabcf2d New: On Delete Notifications
Closes #2410
2021-01-24 01:21:29 -08:00
Mark McDowall
21fafb895f Fixed: Files with lower preferred word scores are imported
Closes #4212
2021-01-24 01:18:08 -08:00
Mark McDowall
8047e5aa67 Fixed: Series Type Filter
Closes #4274
2021-01-23 19:15:36 -08:00
Mark McDowall
546d65b663 Manual Import episode improvements
New: Show absolute episode number (for anime series) and title in manual import
New: Show absolute episode number (for anime series) in manual import episode selection
2021-01-23 19:02:22 -08:00
Mark McDowall
328cfa12f6 Fixed: Improve multi-episode title squashing 2021-01-23 18:16:32 -08:00
bakerboy448
d475fccb55 Update bug report template 2021-01-20 23:48:00 -08:00
bakerboy448
50a2e52c19 Lock closed issues after 90 days without activity 2021-01-20 23:47:19 -08:00
Jesse Chan
28e0ad38b0 New: Flood Download Client 2021-01-20 23:43:52 -08:00
Taloth Saldono
b66bf542c1 Typo for linux 2021-01-20 21:42:48 +01:00
Robin Dadswell
c05fccb90d Fixed: Error handling when cannot create folder in Recycling Bin
Closes #4163
2021-01-17 10:27:58 -08:00
Mark McDowall
ab478fd64b New: Treat Manual Bad in history as failed 2021-01-16 16:24:30 -08:00
bakerboy448
d016079f6b make HashedReleaseFixture entries generic 2021-01-16 15:48:47 -08:00
bakerboy448
4a7e5ac06e Fixed: Handle more obfuscated names
Closes #4198
2021-01-16 15:48:47 -08:00
Taloth Saldono
e10cff5414 Fixed parsing (duplicate) releases for series with multiple season number mappings 2021-01-16 21:13:32 +01:00
Taloth Saldono
6c17b4bb86 Fixed accounting for zero terminator in long path limitation
closes #4259
2021-01-16 02:49:48 +01:00
Mark McDowall
e704ee617f New: Require Encryption option for email 2021-01-15 17:29:43 -08:00
Mark McDowall
c2fcdb4457 Fixed: Managing display profiles on mobile 2021-01-15 17:29:43 -08:00
Mark McDowall
a6637b2911 Fixed: Sorting in Interactive search duplicates results
Closes #3964
2021-01-15 17:29:43 -08:00
Taloth Saldono
efb18223fe Fixed duplicate id searches due to missing Equals on SceneSeasonMapping 2021-01-16 01:32:08 +01:00
Taloth Saldono
c3e2f22adb Show separate message for unknown episode/series 2021-01-16 01:31:17 +01:00
Taloth Saldono
9de5181f01 Fixed: Regular Anime being caught in Chinese parser rules
closes #4257
2021-01-15 19:55:55 +01:00
Taloth Saldono
63607ad541 Fixed Agenda Time wrapping 2021-01-15 19:06:09 +01:00
Taloth Saldono
620359dcc6 Fixed: Updated BTN url to https
closes #4249
2021-01-14 23:30:47 +01:00
Taloth Saldono
881bbb654b Linting as usual 2021-01-14 23:20:09 +01:00
Taloth Saldono
c28cafba0a Fixed: Unnecessary certificate validation errors on localhost/loopback
closes #4215
2021-01-14 22:07:16 +01:00
Taloth Saldono
5668152d6f New: Added Scene Info to Interactive Search results to show more about the applied scene/TheXEM mappings 2021-01-14 22:07:16 +01:00
Taloth Saldono
dcda03da4a Fixed searching the wrong season. 2021-01-14 22:07:15 +01:00
Mark McDowall
ba2ca7ee29 New: Parsing of '[WEB]' as WebDL 2021-01-13 17:17:11 -08:00
bakerboy448
8077434a38 Update contributing.md 2021-01-13 17:00:09 -08:00
Qstick
a225b34806 Fix name of max NumberInput in QualityDefinition.js 2021-01-13 16:59:32 -08:00
bakerboy448
b181f8ae9f Readme updates 2021-01-13 16:59:08 -08:00
Qstick
579c443349 New: Replace SmtpClient with Mailkit
Closes #4213
2021-01-13 16:58:22 -08:00
bakerboy448
8a511a0e20 Fixed: Parse standalone UHD as 2160p if no other resolution info is present 2021-01-12 21:36:18 +01:00
Taloth Saldono
8bc0ab981c Fixed: Dailiezearch. 2021-01-11 21:04:16 +01:00
bakerboy448
741fa57f05 Update wiki link hints for health checks
Closes #4190
2021-01-09 12:39:30 -08:00
Mark McDowall
8a3027fa7c New: Allow quality size limits to be closer together 2021-01-06 22:01:50 -08:00
Mark McDowall
fddf2d1fc8 Better task interval fetching 2021-01-06 22:01:50 -08:00
Mark McDowall
6d44d6c1b7 Fixed: Only delete update folder if it exists 2021-01-06 22:01:50 -08:00
Taloth Saldono
7e045f3e3c Fixed tests 2021-01-04 22:18:08 +01:00
Taloth Saldono
f4d14301f1 No longer need the special tvdb season number handling since it's integrated into the search. 2021-01-04 21:41:05 +01:00
Taloth Saldono
17985f7a33 Fixed: Regression in searching anime by primary title 2021-01-04 20:55:13 +01:00
Taloth Saldono
772448b41b New: Support in services for multiple scene naming/numbering exceptions 2021-01-04 17:35:35 +01:00
Mark McDowall
ed2bb0d73a Fixed: Backups interval being used as minutes instead of days 2021-01-04 08:22:01 -08:00
Taloth Saldono
ae45089c62 Fixed tests 2021-01-03 21:44:46 +01:00
Taloth Saldono
f3695a41d7 Linting 2021-01-03 21:19:09 +01:00
bakerboy448
471f0016b4 Fixed: Additional handling for obfuscated releases
closes #4198
2021-01-03 21:19:03 +01:00
bakerboy448
056a699daf Fixed: Parsing of 4Kto1080p as 1080p
closes #4199
2021-01-03 21:18:48 +01:00
Mark McDowall
99be6a7e40 Use createHandleActions for adding/removing commands so itemMap is synced properly 2021-01-02 15:30:24 -08:00
Mark McDowall
c1d060ff58 New: Removing update folder from temp folder during housekeeping
Closes #4178
2021-01-02 15:13:01 -08:00
Mark McDowall
101b1ec743 New: Renamed Quick Import to Move Automatically
Close #4210
2021-01-02 12:14:16 -08:00
Mark McDowall
a4ffb256a6 Fixed UpdatePackageProviderFixture tests 2021-01-02 11:53:11 -08:00
Mark McDowall
ca52eb76ca Fixed: Don't convert series selection filter to lower case in state 2021-01-02 10:30:11 -08:00
Mark McDowall
37501094d7 Fixed: Restored robots.txt 2020-12-31 13:11:22 -08:00
Qstick
cfbb4a3235 Fixed: Timespan over 1 month shown incorrectly
Closes #4208
2020-12-31 11:51:22 -08:00
Mark McDowall
a2050803a2 Fixed: Missing leading 0 in minutes/seconds for media info duration
Closes #4208
2020-12-31 11:50:51 -08:00
Mark McDowall
e5e86680c8 Fixed: Backup interval is updated on change
Closes #4207
2020-12-31 11:42:00 -08:00
Mark McDowall
ca34f64eb0 Fixed: Update path before importing to ensure it hasn't changed
Closes #4206
2020-12-30 14:01:01 -08:00
tenshiak
c7b950f213 Fixed: Parsing Polish language 2020-12-30 12:11:47 -08:00
bakerboy448
36f231ea24 New: Rename Import to Library Import 2020-12-30 11:04:53 -08:00
Taloth Saldono
b60d4f46d2 eslint 2020-12-25 16:14:56 +01:00
Taloth Saldono
090cdc364e Small helper in UI to access Sonarr API more easily 2020-12-24 23:55:39 +01:00
Taloth Saldono
182ad17b77 Fixed: Series year wrong when airing January 1st.
closes #4191
2020-12-24 12:43:46 +01:00
Taloth Saldono
026af22229 Fixed: OSX version detection
ref #4113 (not a fix, just partially)
2020-12-24 12:43:31 +01:00
Qstick
078898af91 Fixed: Format Errors from AudioChannel formatter 2020-12-18 16:34:50 -08:00
Mark McDowall
8b8deb5646 Fixed Migration 148 test 2020-12-18 16:28:45 -08:00
Qstick
314a12ffb5 Fixed: Handle 3 digit audio channels 2020-12-18 16:28:33 -08:00
Nathaniel Peiffer
7b04e11c54 Fixed: Language parsing with space-delimited releases
Closes #4056
2020-12-17 16:55:07 -08:00
Qstick
9180e7d6fd Fixed: Don't workaround DTS if audioChannels invalid 2020-12-17 16:54:05 -08:00
Qstick
b8b09cd0ce Fixed: Migrate Mediainfo properties that changed names 2020-12-17 16:54:05 -08:00
Qstick
39cb0934bc Fixed: Use audioChannels_Original if it exists in MI 2020-12-17 16:54:05 -08:00
Mark McDowall
b84f84ad0a Fixed health check wiki link unit tests 2020-12-16 14:52:29 -08:00
Robin Dadswell
55a7253dc2 New: Sorting Series List/Mass Editor by Language Profile and Tags
Closes #3854
2020-12-14 20:07:00 -08:00
bakerboy448
e733529dc3 All Wiki links now use the consolidated Servarr wiki 2020-12-13 11:07:12 -08:00
Robin Dadswell
cc39d4ee23 Fixed: '/series' URL Base breaking UI navigation
Closes #4148
2020-12-13 10:57:06 -08:00
Robin Dadswell
0ff889c3be New: Added Series Monitoring Toggle to Series Details
Closes #3991
2020-12-13 09:58:56 -08:00
Mark McDowall
39589b8c02 Move config.yml for github 2020-12-06 11:37:03 -08:00
Mark McDowall
c5c0462258 Fixed: Using folder as scene name for season packs 2020-12-06 11:34:01 -08:00
bakerboy448
dafcba7336 Update GitHub templates
- remove other issue template
- update support and add wiki
- prevent blank issues
- add support links
2020-12-01 09:38:53 -08:00
bakerboy448
19ff7bdc30 Fixed: List Import no longer fails due to duplicates
Closes #4100
2020-11-25 16:51:22 -08:00
Taloth Saldono
a9384e26d8 Removed unnecessary importlists warning. 2020-11-23 11:04:23 +01:00
Taloth Saldono
0bc190e97e Fixed binary execute permissions for osx and Radarr 2020-11-22 17:10:29 +01:00
Taloth Saldono
2c76afb839 Fixed disk permission tests 2020-11-21 22:30:33 +01:00
Taloth Saldono
0edb7b77a1 Reverted temporary dev debug code change 2020-11-21 15:27:10 +01:00
Mark McDowall
59bffa66ad Fixed: Monitor 'None' won't monitor latest season 2020-11-20 14:43:54 -08:00
Mark McDowall
145c644c9d New: Validate that naming formats don't contain illegal characters 2020-11-20 14:43:54 -08:00
Taloth Saldono
d88bb7f855 New: Displaying folder-based permissions in UI rather than file-based permissions and with selectable sane presets
Fixed: Preserve setgid when applying unix permissions
2020-11-20 15:42:23 +01:00
Antoine Martin
850552bf17 Update indexer category parameters for the other nyaa 2020-11-17 22:20:56 -08:00
Mark McDowall
66a19424af Added tests for using folder name as scene name 2020-11-16 23:25:42 -08:00
Taloth Saldono
a234293146 Credit where credit is due 2020-11-16 21:31:13 +01:00
Taloth Saldono
5fced70948 Give systemd a bit more time to restart sonarr after update 2020-11-16 21:24:55 +01:00
ta264
7a0e1818c0 Fixed: Import single file torrents with a folder from QBittorrent
Signed-off-by: Taloth Saldono <Taloth@users.noreply.github.com>
2020-11-16 21:06:11 +01:00
Mark McDowall
bd0e5e16b8 Fixed unit tests 2020-11-15 23:49:02 -08:00
Mark McDowall
487c664e43 Fixed: Scene Name not being stored properly during import if not linked to a download client item and filename is obfuscated 2020-11-15 18:18:23 -08:00
Mark McDowall
fed2a429c7 New: Don't process files during Manual Import if there are more than 100 items
Closes #1142
2020-11-15 18:18:23 -08:00
Taloth Saldono
ad9e709d96 Fixed duplicate UpdateHistory items 2020-11-15 22:19:48 +01:00
Mark McDowall
4c58ea63d6 Fixed: Tags in tag editor in SafarIE
Closes #4071
2020-11-15 11:00:21 -08:00
Taloth Saldono
e5ec4e706a Readded Movies cat to the end of the Newznab list 2020-11-14 22:43:37 +01:00
Taloth Saldono
158e31d54a Fixed: Truncating too long filenames with unicode characters
closes #4085
2020-11-14 22:33:23 +01:00
Taloth Saldono
05820ac272 Protect against Qbittorrent edgecase if users add torrents manually with Keep top-level folder disabled 2020-11-14 22:33:22 +01:00
Mark McDowall
fe0d8bb7da Return max tooltip width 2020-11-13 17:16:05 -08:00
Mark McDowall
6f74a9e3eb Fixed: Reprocessing Manual Import items resetting season number if no episodes were selected
Closes #4089
2020-11-13 17:12:10 -08:00
Mark McDowall
d90f50d589 Fixed: Language chosen in manual import overridden during import
Closes #4082
2020-11-13 17:12:10 -08:00
Mark McDowall
517fc2bd75 Fixed: Example file names for Daily Series
Closes #4078
2020-11-13 17:12:10 -08:00
Mark McDowall
b6316c9fcd Spelling confidence 2020-11-13 17:12:10 -08:00
Mark McDowall
675d948a1f Fixed: Manual Import breaking if quality is selected before series 2020-11-13 17:12:10 -08:00
Taloth Saldono
49bf3f4512 And forgot test of course 2020-11-14 02:02:12 +01:00
Taloth Saldono
3ff848b019 Fix for QBittorrent directory for specific torrents
fixes #4091
2020-11-14 01:52:28 +01:00
Taloth Saldono
8b2550cef0 Bumped Sabnzbd default history request size from 30 to 60 2020-11-14 00:00:29 +01:00
ta264
813f886920 Fixed: QBittorrent imports when torrent name and folder name differ
closes #3346 
fixes #3968
closes #4068
2020-11-13 23:15:58 +01:00
Mark McDowall
c75c546888 Fixed: Manual import when quality was selected before episodes
Closes #4065
2020-11-08 21:33:43 -08:00
Mark McDowall
baa41b2c13 Fixed: Correctly storing v0 version during import, allowing them to be upgraded to v1 later
Closes #4066
2020-11-07 11:55:28 -08:00
Mark McDowall
aeaaa4a77a Fixed: wiki link for removed series health check
Closes #4053
2020-11-01 16:01:40 -08:00
Mark McDowall
1025bdc76e Show .net version in UI
Closes #4057
2020-11-01 16:01:40 -08:00
Mark McDowall
0b7aa19ac0 Fixed: Telegram silent notifications
Closes #4041
2020-11-01 16:01:40 -08:00
Robin Dadswell
cfdaddd81a New: Discord notification upgrade colour
Closes #4044
2020-11-01 16:01:31 -08:00
Timothy Pillow
fd4c2c11ad Change 'Ignore Deleted Episodes' to 'Unmonitor Deleted Episodes' for consistency 2020-11-01 16:00:02 -08:00
Robin Dadswell
42d93f6fdb New: Changed colour of Discord On Download notifications
Closes #4036
2020-10-26 07:45:50 -07:00
Mark McDowall
0de3f10701 Fixed: 1080i HDTV H264 incorrectly being detected as Raw HD 2020-10-25 17:17:57 -07:00
Mark McDowall
7b1876d0d8 Fixed: Cleanse account and passwd from Download Station URLs 2020-10-25 16:12:45 -07:00
Mark McDowall
91c395d0c6 Fixed: Show TLS errors in UI when testing download clients
Closes #4021
2020-10-25 15:59:40 -07:00
Mark McDowall
3fc3aef268 Improved Trakt list validation
New: Able to add empty Trakt lists (shows warning during test)
Fixed: Show error in UI if Trakt list hasn't been authenticated

Closes #4022
2020-10-25 12:01:29 -07:00
Mark McDowall
fa2e70d571 Fixed: Show feed URL if incorrect mime type is found 2020-10-25 12:01:29 -07:00
Mark McDowall
c823654041 Fixed: Discord notifications failing if episode overview is missing 2020-10-25 12:01:29 -07:00
Mark McDowall
cfa0c6d0d7 Fixed phrasing when release match found by ID 2020-10-25 12:01:29 -07:00
Taloth Saldono
4d4319797b Updated debian install script to handle longer user names 2020-10-24 10:41:58 +02:00
Mark McDowall
e90e144ebc Fixed: Grab/Import fields for Discord notifications being duplicated 2020-10-18 19:24:21 -07:00
Qstick
f701adaef5 Pass no parameter instead of null parameter on Kodi Update 2020-10-18 17:50:07 -07:00
Mark McDowall
427ce17e6e Kodi GetMovies fails due to Parameter Type 2020-10-18 17:50:02 -07:00
Qstick
3a8522e92f Resource missing from Gotify call 2020-10-18 17:44:09 -07:00
Qstick
886e5581d8 Gotify token as query parameter 2020-10-18 17:43:57 -07:00
Qstick
470c9101ae New: Customizable Discord Notifications
Closes #3985
2020-10-18 17:09:26 -07:00
Qstick
09347f79c5 TagSelect field type 2020-10-18 16:27:00 -07:00
ta264
1d02208316 New: Make Twitter NetStandard compatible 2020-10-18 16:26:59 -07:00
Mark McDowall
0878f514aa New: Remove Growl notifications 2020-10-18 16:26:58 -07:00
Mark McDowall
1b32949443 Rename migration 144 2020-10-18 15:32:44 -07:00
Qstick
665b80f15c Convert Notifications from RestSharp to HttpClient 2020-10-18 15:22:24 -07:00
Mark McDowall
51528f63e9 Fixed: Parsing of some daily episodes 2020-10-18 15:07:26 -07:00
Mark McDowall
a7e9eebfed Fix namespace for BlacklistBulkResource 2020-10-18 15:07:26 -07:00
Qstick
897673b459 Improve use of All() and speedup Unmapped Folder matching
Co-authored-by: ta264 <ta264@users.noreply.github.com>
2020-10-18 14:59:26 -07:00
Mark McDowall
19f724dcd9 Fixed: Limit Raw HD detection by MPEG-2 to HDTV sources 2020-10-13 00:06:12 -07:00
Mark McDowall
4ad137f1eb Fixed: Size on disk sorting and display
Closes #4014
2020-10-12 16:27:21 -07:00
Mark McDowall
dab6242ff2 New: Ability to edit restriction terms in Release Profiles 2020-10-12 15:30:32 -07:00
Mark McDowall
d47ad37791 Fix installer branch/build from testing 2020-10-12 12:19:34 -07:00
Mark McDowall
2adedb97da New: Differentiate between short term and long term (more than 6 hours) indexer failures
Closes #3279
2020-10-12 11:24:19 -07:00
Mark McDowall
7dd64d843a Fixed: (Windows) clean up extraneous files in build folder during installation 2020-10-12 11:03:47 -07:00
Mark McDowall
f30ae69c10 New: Reprocess items after selection in Manual Import
Closes #3818
2020-10-12 10:49:35 -07:00
Mark McDowall
c871b3f948 Fixed: Copying passwords
Closes #4011
2020-10-11 16:42:07 -07:00
Mark McDowall
67f5628340 New: Bulk remove from Blacklist
Closes #3500
2020-10-11 15:28:41 -07:00
Mark McDowall
fae38a107f New: Warning when combining preferred words with a specific indexer 2020-10-11 15:28:41 -07:00
Mark McDowall
f35b8174aa Re-saving edited providers will forcibly save them 2020-10-11 15:28:40 -07:00
Taloth Saldono
465de11c90 Fixed: Regression causing updater to fail (manual update required if on 3.0.3.971, see forums) 2020-10-11 20:52:12 +02:00
Taloth Saldono
a7ca139e13 Fixed test file casing 2020-10-11 16:20:03 +02:00
Taloth Saldono
94a78eabe5 Fixed: Dataloss when moving series folder to root folder with only different casing 2020-10-11 16:20:03 +02:00
Mark McDowall
0c7743e786 Fixed: Parsing of URLs with double slashes in the path
Closes #4005
2020-10-10 13:43:08 -07:00
Mark McDowall
d0f0fc787e Fixed: Use standard naming format for daily specials
Closes #3503
2020-10-10 10:40:03 -07:00
Mark McDowall
ce3c151b8c New: Search for specials by season/episode number in addition to name
Closes #415
2020-10-10 10:31:06 -07:00
Mark McDowall
f49d2338fd New: Search for anime specials without absolute episode numbers by name
Closes #1970
2020-10-10 10:24:08 -07:00
Mark McDowall
164f46e4c0 Fixed: Webhooks using lower case event types (in the future this could change)
Closes #4010
2020-10-10 10:15:48 -07:00
Mark McDowall
9f527718f2 Ignore HttpClientFixture integration tests 2020-10-09 20:19:48 -07:00
Mark McDowall
ee32829cdb New: Health events for Webhooks 2020-10-09 07:38:18 -07:00
Mark McDowall
43cb44dd38 Windows installer improvements
Fixed: Windows installer removing nzbdrone service
New: Add more information about Windows service to installer

Closes #3762
Closes #4006
2020-10-09 07:38:18 -07:00
Taloth Saldono
e8854a2675 Fixed: Opening dropdowns 2020-10-09 09:54:44 +02:00
Taloth
b4c27f5d34 New: Newznab/Torznab categories dropdown with indexer provided category names 2020-10-08 23:33:13 +02:00
Mark McDowall
9dab2ba6e4 Fixed: Quality sliders
Broken after 9c635781bd
2020-10-06 08:26:20 -07:00
Mark McDowall
d105dd47e0 Fixed: Fixed size on disk not showing for series in overview view
Closes #4000
2020-10-05 21:15:49 -07:00
Mark McDowall
f4f2a6f5fc Fix tooltip max width on larger screens 2020-10-04 20:39:24 -07:00
Mark McDowall
3542ab86e9 Update supporters and use jetbrains images 2020-10-04 15:56:29 -07:00
Mark McDowall
f76babc699 Add Open Collective Link 2020-10-04 15:43:53 -07:00
Mark McDowall
da7fec4d35 Regenerate yarn.lock after package updates 2020-10-04 12:22:05 -07:00
Mark McDowall
488e7b1a26 Appeasing the lint gods 2020-10-04 12:16:42 -07:00
Mark McDowall
f45b27f507 Import lists in settings overview
Closes #3996
2020-10-04 12:08:07 -07:00
Mark McDowall
796c5e8b6b Fixed: Episode history details tooltip jumping around
Closes #3965
2020-10-04 12:08:07 -07:00
Mark McDowall
89a4249072 New: Add size to episode files in Webhook payload 2020-10-04 12:08:07 -07:00
Mark McDowall
05ffdd07a2 Upgrade react-autosuggest 2020-10-04 12:08:07 -07:00
Mark McDowall
14fee1c086 Upgrade react-google-recaptcha 2020-10-04 12:08:07 -07:00
Mark McDowall
3a7b0cacb8 Upgrade del 2020-10-04 12:08:07 -07:00
Mark McDowall
ddd70fd198 Upgrade webpack loaders 2020-10-04 12:08:07 -07:00
Mark McDowall
fc231c5ef8 Upgrade stylelint 2020-10-04 12:08:07 -07:00
Mark McDowall
933832fe2c Upgrade react-lazyload 2020-10-04 12:08:07 -07:00
Mark McDowall
4ed1f6b814 Upgrade redux-batched-actions 2020-10-04 12:08:07 -07:00
Mark McDowall
9ed1d27f86 Upgrade esformatter 2020-10-04 12:08:07 -07:00
Mark McDowall
4057a3112d Upgrade filesize 2020-10-04 12:08:07 -07:00
Mark McDowall
0b01b75cac Upgrade eslint, esprint 2020-10-04 12:08:07 -07:00
Mark McDowall
9c635781bd Upgrade react-slider and react-text-truncate 2020-10-04 12:08:05 -07:00
Mark McDowall
b6bfeaaba3 Upgrade react-dnd 2020-10-04 12:07:54 -07:00
Mark McDowall
0318a4a5e1 Upgrade webpack and core-js 2020-10-04 12:07:54 -07:00
Mark McDowall
2d985c0c6a Upgrade a bunch of react/redux packages 2020-10-04 12:07:51 -07:00
Mark McDowall
3ef47b0ce3 Upgrade jquery, moment and qs 2020-10-03 12:36:58 -07:00
Mark McDowall
213db3b107 Upgrade fuse.js 2020-10-03 12:25:20 -07:00
Mark McDowall
d475ee37c3 Upgrade gulp 2020-10-03 12:14:52 -07:00
Mark McDowall
1d9ed1b56d Upgrade clipboard, lodash, mobile-detect and mousetrap 2020-10-03 12:13:08 -07:00
Mark McDowall
e34b6a36d5 Upgrade sentry 2020-10-03 12:12:10 -07:00
Mark McDowall
8468a74ade Upgrade Font Awesome 2020-10-03 12:11:12 -07:00
Mark McDowall
34cbdee510 Upgrade babel 2020-10-03 12:08:34 -07:00
Mark McDowall
924f6ca715 New: Pilot Episode monitoring option
Closes #3768
2020-10-03 11:53:56 -07:00
Taloth Saldono
9eb24cedd6 Fixed stylelint too 2020-10-03 20:34:35 +02:00
776 changed files with 23388 additions and 11985 deletions

View File

@@ -1,41 +0,0 @@
<!--
Before opening a new issue, please ensure:
- You use the forums for support/questions
- You search for existing bugs/feature requests
- Remove extraneous template details
- Do not prefix title with type of issue (Feature Request, Bug, etc.) The appropriate labels will be added during triage.
-->
## Support / Questions
Please use https://forums.sonarr.tv/ for support. Support requests or questions will be redirected to the forums and the issue will be closed.
<!--
Remove if not opening a bug report
-->
## Bug Report
### System Information/Logs
**Sonarr Version:**
**Operating System:**
**.net Framework (Windows) or mono (macOS/Linux) Version:**
**Link to Log Files (debug or trace):**
**Browser (for UI bugs):**
### Additional Information
<!--
Remove if not opening a feature request
-->
## Feature Request
### What problem are you looking to solve?
### Other Information

View File

@@ -1,28 +0,0 @@
---
name: Bug report
about: Create a report to help us improve Sonarr
---
**Describe the bug**
A clear and concise description of what the bug is.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Link to debug or trace log files.
**System Information**
- Sonarr Version: [e.g. 2.0.0.1]
- Operating System: [e.g. Windows 10]
- .net Framework (Windows) or mono (macOS/Linux) Version: [e.g. 4.5 or 5.12]
**UI Bugs:**
- OS: [e.g. Windows]
- Browser: [e.g. chrome, firefox]
- Version: [e.g. 22]
**Additional context**
Add any other context about the problem here.

72
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Bug Report
description: 'Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit, Discord, Forums, or IRC first. Exceptions do not mean you found a bug!'
labels: ['needs-triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
validations:
required: false
- type: textarea
attributes:
label: Environment
description: |
examples:
- **OS**: Ubuntu 20.04
- **Sonarr**: Sonarr 3.0.6.1265
- **Docker Install**: Yes
- **Using Reverse Proxy**: No
- **Browser**: Firefox 90 (If UI related)
value: |
- OS:
- Sonarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
render: markdown
validations:
required: true
- type: dropdown
attributes:
label: What branch are you running?
options:
- Main
- Develop
- Other (This issue will be closed)
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Trace Logs (https://wiki.servarr.com/sonarr/troubleshooting#logging-and-log-files)
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true

14
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Support via Discord
url: https://discord.gg/M6BvZn5
about: Chat with users and devs on support and setup related topics.
- name: Support via Reddit
url: https://reddit.com/r/Sonarr
about: Discuss and search through support topics.
- name: Support via Forums
url: https://forums.sonarr.tv/
about: Discuss and search through support topics.
- name: Support via IRC
url: https://web.libera.chat/?channels=#sonarr
about: Chat with users and devs on support and setup related topics.

View File

@@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest an idea for Sonarr
---
**Describe the problem**
A clear and concise description of the problem you're looking to solve.
**Describe any solutions you think might work**
A clear and concise description of any solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,38 @@
name: Feature Request
description: 'Suggest an idea for Sonarr'
labels: ['needs-triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you are requesting.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe
description: A clear and concise description of what the problem is.
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Mockups? Anything that will give us more context about the feature you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: true

View File

@@ -1,7 +0,0 @@
---
name: Other issues
about: How to get support or ask questions
---
Please use https://forums.sonarr.tv/ for support. Support requests or questions will be redirected to the forums and the issue will be closed.

View File

@@ -6,7 +6,7 @@ A few sentences describing the overall goals of the pull request's commits.
#### Todos
- [ ] Tests
- [ ] Documentation
- [ ] Wiki Updates
#### Issues Fixed or Closed by this PR

6
.github/SUPPORT.md vendored
View File

@@ -1,7 +1,7 @@
## Support
There are a number of frequently asked questions that have been answered in our [FAQ](https://github.com/Sonarr/Sonarr/wiki/FAQ)
There are a number of frequently asked questions that have been answered in our [FAQ](https://wiki.servarr.com/sonarr/faq)
The [wiki](https://github.com/Sonarr/Sonarr/wiki) contains other information and guides
The [wiki](https://wiki.servarr.com/sonarr) contains other information and guides
If you have a support question, please use the [support forums](https://forums.sonarr.tv/).
Please use one of the support channels: [forums](https://forums.sonarr.tv/), [subreddit](https://www.reddit.com/r/sonarr/), [discord ](https://discord.gg/M6BvZn5), or [IRC](https://web.libera.chat/?channels=#sonarr)for support/questions.

21
.github/workflows/lock.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: 'Lock threads'
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '90'
issue-exclude-created-before: ''
issue-exclude-labels: 'one-day-maybe'
issue-lock-labels: ''
issue-lock-comment: ''
issue-lock-reason: 'resolved'
process-only: ''

1
.gitignore vendored
View File

@@ -141,3 +141,4 @@ output/*
_start
src/.idea/
/distribution/windows/setup/output/*

View File

@@ -3,25 +3,40 @@
We're always looking for people to help make Sonarr even better, there are a number of ways to contribute.
## Documentation ##
Setup guides, FAQ, the more information we have on the wiki the better.
Setup guides, [FAQ](https://wiki.servarr.com/sonarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/sonarr) the better.
## Development ##
See the readme for information on setting up your development environment.
### Tools required ###
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
- [Git](https://git-scm.com/downloads)
- [NodeJS](https://nodejs.org/en/download/) (Node 10.X.X or higher)
- [Yarn](https://yarnpkg.com/)
### Getting started ###
1. Fork Sonarr
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
3. Install the required Node Packages `yarn install`
4. Start webpack to monitor your dev environment for any frontend changes that need post processing using `yarn start` command.
5. Build the project in Visual Studio, Setting startup project to `Sonarr.Console` and framework to `x86`
6. Debug the project in Visual Studio
7. Open http://localhost:8989
### Contributing Code ###
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Sonarr/Sonarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
- Rebase from Sonarr's develop branch, don't merge
- Rebase from Sonarr's `develop` branch, don't merge
- Make meaningful commits, or squash them
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
- Reach out to us on the forums or on IRC if you have any questions
- Reach out to us on our [forums](https://forums.sonarr.tv/), [subreddit](https://www.reddit.com/r/sonarr/), [discord](https://discord.gg/Ex7FmFK), or [IRC](https://web.libera.chat/?channels=#sonarr) if you have any questions
- Add tests (unit/integration)
- Commit with *nix line endings for consistency (We checkout Windows and commit *nix)
- One feature/bug fix per pull request to keep things clean and easy to understand
- Use 4 spaces instead of tabs, this is the default for VS 2012 and WebStorm (to my knowledge)
- Use 4 spaces instead of tabs, this should be the default for VS 2019 and WebStorm
### Pull Requesting ###
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
- Only make pull requests to develop (currently `develop`), never `main`, if you make a PR to master we'll comment on it and close it
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)

12
FUNDING.yml Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: sonarr
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-1.3318" y1="43.7371" x2="67.0419" y2="26.0967">
<stop offset="0.1237" style="stop-color:#7866FF"/>
<stop offset="0.5376" style="stop-color:#FE2EB6"/>
<stop offset="0.8548" style="stop-color:#FD0486"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="67.3,16 43.7,0 0,31.1 11.1,70 58.9,60.3 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="45.9148" y1="38.9098" x2="67.6577" y2="9.0989">
<stop offset="0.1237" style="stop-color:#FF0080"/>
<stop offset="0.2587" style="stop-color:#FE0385"/>
<stop offset="0.4109" style="stop-color:#FA0C92"/>
<stop offset="0.5713" style="stop-color:#F41BA9"/>
<stop offset="0.7363" style="stop-color:#EB2FC8"/>
<stop offset="0.8656" style="stop-color:#E343E6"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="67.3,16 43.7,0 38,15.7 38,47.8 70,47.8 "/>
</g>
<g>
<rect x="13.4" y="13.4" style="fill:#000000;" width="43.2" height="43.2"/>
<rect x="17.4" y="48.5" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
<g>
<path style="fill:#FFFFFF;" d="M17.4,19.1h6.9c5.6,0,9.5,3.8,9.5,8.9V28c0,5-3.9,8.9-9.5,8.9h-6.9V19.1z M21.4,22.7v10.7h3
c3.2,0,5.4-2.2,5.4-5.3V28c0-3.2-2.2-5.4-5.4-5.4H21.4z"/>
<polygon style="fill:#FFFFFF;" points="40.3,22.7 34.9,22.7 34.9,19.1 49.6,19.1 49.6,22.7 44.2,22.7 44.2,37 40.3,37 "/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="120.1px" height="130.2px" viewBox="0 0 120.1 130.2" style="enable-background:new 0 0 120.1 130.2;" xml:space="preserve"
>
<g>
<linearGradient id="XMLID_2_" gradientUnits="userSpaceOnUse" x1="31.8412" y1="120.5578" x2="110.2402" y2="73.24">
<stop offset="0" style="stop-color:#FCEE39"/>
<stop offset="1" style="stop-color:#F37B3D"/>
</linearGradient>
<path id="XMLID_3041_" style="fill:url(#XMLID_2_);" d="M118.6,71.8c0.9-0.8,1.4-1.9,1.5-3.2c0.1-2.6-1.8-4.7-4.4-4.9
c-1.2-0.1-2.4,0.4-3.3,1.1l0,0l-83.8,45.9c-1.9,0.8-3.6,2.2-4.7,4.1c-2.9,4.8-1.3,11,3.6,13.9c3.4,2,7.5,1.8,10.7-0.2l0,0l0,0
c0.2-0.2,0.5-0.3,0.7-0.5l78-54.8C117.3,72.9,118.4,72.1,118.6,71.8L118.6,71.8L118.6,71.8z"/>
<linearGradient id="XMLID_3_" gradientUnits="userSpaceOnUse" x1="48.3607" y1="6.9083" x2="119.9179" y2="69.5546">
<stop offset="0" style="stop-color:#EF5A6B"/>
<stop offset="0.57" style="stop-color:#F26F4E"/>
<stop offset="1" style="stop-color:#F37B3D"/>
</linearGradient>
<path id="XMLID_3049_" style="fill:url(#XMLID_3_);" d="M118.8,65.1L118.8,65.1L55,2.5C53.6,1,51.6,0,49.3,0
c-4.3,0-7.7,3.5-7.7,7.7v0c0,2.1,0.8,3.9,2.1,5.3l0,0l0,0c0.4,0.4,0.8,0.7,1.2,1l67.4,57.7l0,0c0.8,0.7,1.8,1.2,3,1.3
c2.6,0.1,4.7-1.8,4.9-4.4C120.2,67.3,119.7,66,118.8,65.1z"/>
<linearGradient id="XMLID_4_" gradientUnits="userSpaceOnUse" x1="52.9467" y1="63.6407" x2="10.5379" y2="37.1562">
<stop offset="0" style="stop-color:#7C59A4"/>
<stop offset="0.3852" style="stop-color:#AF4C92"/>
<stop offset="0.7654" style="stop-color:#DC4183"/>
<stop offset="0.957" style="stop-color:#ED3D7D"/>
</linearGradient>
<path id="XMLID_3042_" style="fill:url(#XMLID_4_);" d="M57.1,59.5C57,59.5,17.7,28.5,16.9,28l0,0l0,0c-0.6-0.3-1.2-0.6-1.8-0.9
c-5.8-2.2-12.2,0.8-14.4,6.6c-1.9,5.1,0.2,10.7,4.6,13.4l0,0l0,0C6,47.5,6.6,47.8,7.3,48c0.4,0.2,45.4,18.8,45.4,18.8l0,0
c1.8,0.8,3.9,0.3,5.1-1.2C59.3,63.7,59,61,57.1,59.5z"/>
<linearGradient id="XMLID_5_" gradientUnits="userSpaceOnUse" x1="52.1736" y1="3.7019" x2="10.7706" y2="37.8971">
<stop offset="0" style="stop-color:#EF5A6B"/>
<stop offset="0.364" style="stop-color:#EE4E72"/>
<stop offset="1" style="stop-color:#ED3D7D"/>
</linearGradient>
<path id="XMLID_3057_" style="fill:url(#XMLID_5_);" d="M49.3,0c-1.7,0-3.3,0.6-4.6,1.5L4.9,28.3c-0.1,0.1-0.2,0.1-0.2,0.2l-0.1,0
l0,0c-1.7,1.2-3.1,3-3.9,5.1C-1.5,39.4,1.5,45.9,7.3,48c3.6,1.4,7.5,0.7,10.4-1.4l0,0l0,0c0.7-0.5,1.3-1,1.8-1.6l34.6-31.2l0,0
c1.8-1.4,3-3.6,3-6.1v0C57.1,3.5,53.6,0,49.3,0z"/>
<g id="XMLID_3008_">
<rect id="XMLID_3033_" x="34.6" y="37.4" style="fill:#000000;" width="51" height="51"/>
<rect id="XMLID_3032_" x="39" y="78.8" style="fill:#FFFFFF;" width="19.1" height="3.2"/>
<g id="XMLID_3009_">
<path id="XMLID_3030_" style="fill:#FFFFFF;" d="M38.8,50.8l1.5-1.4c0.4,0.5,0.8,0.8,1.3,0.8c0.6,0,0.9-0.4,0.9-1.2l0-5.3l2.3,0
l0,5.3c0,1-0.3,1.8-0.8,2.3c-0.5,0.5-1.3,0.8-2.3,0.8C40.2,52.2,39.4,51.6,38.8,50.8z"/>
<path id="XMLID_3028_" style="fill:#FFFFFF;" d="M45.3,43.8l6.7,0v1.9l-4.4,0V47l4,0l0,1.8l-4,0l0,1.3l4.5,0l0,2l-6.7,0
L45.3,43.8z"/>
<path id="XMLID_3026_" style="fill:#FFFFFF;" d="M55,45.8l-2.5,0l0-2l7.3,0l0,2l-2.5,0l0,6.3l-2.3,0L55,45.8z"/>
<path id="XMLID_3022_" style="fill:#FFFFFF;" d="M39,54l4.3,0c1,0,1.8,0.3,2.3,0.7c0.3,0.3,0.5,0.8,0.5,1.4v0
c0,1-0.5,1.5-1.3,1.9c1,0.3,1.6,0.9,1.6,2v0c0,1.4-1.2,2.3-3.1,2.3l-4.3,0L39,54z M43.8,56.6c0-0.5-0.4-0.7-1-0.7l-1.5,0l0,1.5
l1.4,0C43.4,57.3,43.8,57.1,43.8,56.6L43.8,56.6z M43,59l-1.8,0l0,1.5H43c0.7,0,1.1-0.3,1.1-0.8v0C44.1,59.2,43.7,59,43,59z"/>
<path id="XMLID_3019_" style="fill:#FFFFFF;" d="M46.8,54l3.9,0c1.3,0,2.1,0.3,2.7,0.9c0.5,0.5,0.7,1.1,0.7,1.9v0
c0,1.3-0.7,2.1-1.7,2.6l2,2.9l-2.6,0l-1.7-2.5h-1l0,2.5l-2.3,0L46.8,54z M50.6,58c0.8,0,1.2-0.4,1.2-1v0c0-0.7-0.5-1-1.2-1
l-1.5,0v2H50.6z"/>
<path id="XMLID_3016_" style="fill:#FFFFFF;" d="M56.8,54l2.2,0l3.5,8.4l-2.5,0l-0.6-1.5l-3.2,0l-0.6,1.5l-2.4,0L56.8,54z
M58.8,59l-0.9-2.3L57,59L58.8,59z"/>
<path id="XMLID_3014_" style="fill:#FFFFFF;" d="M62.8,54l2.3,0l0,8.3l-2.3,0L62.8,54z"/>
<path id="XMLID_3012_" style="fill:#FFFFFF;" d="M65.7,54l2.1,0l3.4,4.4l0-4.4l2.3,0l0,8.3l-2,0L68,57.8l0,4.6l-2.3,0L65.7,54z"
/>
<path id="XMLID_3010_" style="fill:#FFFFFF;" d="M73.7,61.1l1.3-1.5c0.8,0.7,1.7,1,2.7,1c0.6,0,1-0.2,1-0.6v0
c0-0.4-0.3-0.5-1.4-0.8c-1.8-0.4-3.1-0.9-3.1-2.6v0c0-1.5,1.2-2.7,3.2-2.7c1.4,0,2.5,0.4,3.4,1.1l-1.2,1.6
c-0.8-0.5-1.6-0.8-2.3-0.8c-0.6,0-0.8,0.2-0.8,0.5v0c0,0.4,0.3,0.5,1.4,0.8c1.9,0.4,3.1,1,3.1,2.6v0c0,1.7-1.3,2.7-3.4,2.7
C76.1,62.5,74.7,62,73.7,61.1z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="70px" height="70px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
<g>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="22.9451" y1="75.7869" x2="74.7868" y2="20.6415">
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
<stop offset="0.4044" style="stop-color:#C41E57"/>
<stop offset="0.4677" style="stop-color:#C41E57"/>
<stop offset="0.6505" style="stop-color:#EB8523"/>
<stop offset="0.9516" style="stop-color:#FEBD11"/>
</linearGradient>
<polygon style="fill:url(#SVGID_1_);" points="49.8,15.2 36,36.7 58.4,70 70,23.1 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="17.7187" y1="73.2922" x2="69.5556" y2="18.1519">
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
<stop offset="0.4044" style="stop-color:#C41E57"/>
<stop offset="0.4677" style="stop-color:#C41E57"/>
<stop offset="0.7043" style="stop-color:#EB8523"/>
</linearGradient>
<polygon style="fill:url(#SVGID_2_);" points="51.1,15.7 49,0 18.8,33.6 27.6,42.3 20.8,70 58.4,70 "/>
</g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="1.8281" y1="53.4275" x2="48.8245" y2="9.2255">
<stop offset="1.612903e-002" style="stop-color:#B35BA3"/>
<stop offset="0.6613" style="stop-color:#C41E57"/>
</linearGradient>
<polygon style="fill:url(#SVGID_3_);" points="49,0 11.6,0 0,47.1 55.6,47.1 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="49.8935" y1="-11.5569" x2="48.8588" y2="24.0352">
<stop offset="0.5" style="stop-color:#C41E57"/>
<stop offset="0.6668" style="stop-color:#D13F48"/>
<stop offset="0.7952" style="stop-color:#D94F39"/>
<stop offset="0.8656" style="stop-color:#DD5433"/>
</linearGradient>
<polygon style="fill:url(#SVGID_4_);" points="55.3,47.1 51.1,15.7 49,0 41.7,23 "/>
</g>
<g>
<rect x="13.4" y="13.5" transform="matrix(-1 2.577289e-003 -2.577289e-003 -1 70.0288 70.081)" style="fill:#000000;" width="43.2" height="43.2"/>
<rect x="17.6" y="48.6" transform="matrix(1 -2.577289e-003 2.577289e-003 1 -0.1287 6.634109e-002)" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
<path style="fill:#FFFFFF;" d="M17.4,19.1l8.2,0c2.3,0,4,0.6,5.2,1.8c1,1,1.5,2.4,1.5,4.1l0,0.1c0,1.5-0.3,2.6-1.1,3.5
c-0.7,0.9-1.6,1.6-2.8,2l4.4,6.4l-4.6,0l-3.7-5.5l-3.3,0l0,5.5l-3.9,0L17.4,19.1z M25.3,27.8c1,0,1.7-0.2,2.2-0.7
c0.5-0.5,0.8-1.1,0.8-1.8l0-0.1c0-0.9-0.3-1.5-0.8-1.9c-0.5-0.4-1.3-0.6-2.3-0.6l-3.9,0l0,5.1L25.3,27.8z"/>
<path style="fill:#FFFFFF;" d="M36,33.2l-1.9,0l0-3.3l2.5,0l0.6-3.8l-2.3,0l0-3.3l2.8,0l0.6-3.7l3.4,0l-0.6,3.7l3.7,0l0.6-3.7
l3.4,0l-0.6,3.7l1.9,0l0,3.3l-2.5,0L47,29.9l2.3,0l0,3.3l-2.8,0L45.8,37l-3.4,0l0.7-3.8l-3.7,0L38.7,37l-3.4,0L36,33.2z
M43.7,29.9l0.6-3.8l-3.7,0L40,29.9L43.7,29.9z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 70 70" style="enable-background:new 0 0 70 70;" xml:space="preserve">
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="1.7738" y1="31.2729" x2="40.1662" y2="31.2729">
<stop offset="0" style="stop-color:#905CFB"/>
<stop offset="6.772543e-002" style="stop-color:#776CF9"/>
<stop offset="0.1729" style="stop-color:#5681F7"/>
<stop offset="0.2865" style="stop-color:#3B92F5"/>
<stop offset="0.4097" style="stop-color:#269FF4"/>
<stop offset="0.5474" style="stop-color:#17A9F3"/>
<stop offset="0.7111" style="stop-color:#0FAEF2"/>
<stop offset="0.9677" style="stop-color:#0CB0F2"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M39.7,47.9l-6.1-34c-0.4-2.4-1.2-4.8-2.7-7.1c-2-3.2-5.2-5.4-8.8-6.3
C7.9-2.9-2.6,11.3,3.6,23.9c0,0,0,0,0,0l14.8,31.7c0.4,1,1,2,1.7,2.9c1.2,1.6,2.8,2.8,4.7,3.4C34.4,64.9,42.1,56.4,39.7,47.9z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="5.3113" y1="9.6691" x2="69.2278" y2="43.8664">
<stop offset="0" style="stop-color:#905CFB"/>
<stop offset="6.772543e-002" style="stop-color:#776CF9"/>
<stop offset="0.1729" style="stop-color:#5681F7"/>
<stop offset="0.2865" style="stop-color:#3B92F5"/>
<stop offset="0.4097" style="stop-color:#269FF4"/>
<stop offset="0.5474" style="stop-color:#17A9F3"/>
<stop offset="0.7111" style="stop-color:#0FAEF2"/>
<stop offset="0.9677" style="stop-color:#0CB0F2"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M67.4,26.5c-1.4-2.2-3.4-3.9-5.7-4.9L25.5,1.7l0,0c-1-0.5-2.1-1-3.3-1.3
C6.7-3.2-4.4,13.8,5.5,27c1.5,2,3.6,3.6,6,4.5L48,47.9c0.8,0.5,1.6,0.8,2.5,1.1C64.5,53.4,75.1,38.6,67.4,26.5z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-19.2836" y1="70.8198" x2="55.9833" y2="33.1863">
<stop offset="0" style="stop-color:#3BEA62"/>
<stop offset="0.117" style="stop-color:#31DE80"/>
<stop offset="0.3025" style="stop-color:#24CEA8"/>
<stop offset="0.4844" style="stop-color:#1AC1C9"/>
<stop offset="0.6592" style="stop-color:#12B7DF"/>
<stop offset="0.8238" style="stop-color:#0EB2ED"/>
<stop offset="0.9677" style="stop-color:#0CB0F2"/>
</linearGradient>
<path style="fill:url(#SVGID_3_);" d="M67.4,26.5c-1.8-2.8-4.6-4.8-7.9-5.6c-3.5-0.8-6.8-0.5-9.6,0.7L11.4,36.1
c0,0-0.2,0.1-0.6,0.4C0.9,40.4-4,53.3,4,64c1.8,2.4,4.3,4.2,7.1,5c5.3,1.6,10.1,1,14-1.1c0,0,0.1,0,0.1,0l37.6-20.1
c0,0,0,0,0.1-0.1C69.5,43.9,72.6,34.6,67.4,26.5z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="38.9439" y1="5.8503" x2="5.4232" y2="77.5093">
<stop offset="0" style="stop-color:#3BEA62"/>
<stop offset="9.397750e-002" style="stop-color:#2FDB87"/>
<stop offset="0.196" style="stop-color:#24CEA8"/>
<stop offset="0.3063" style="stop-color:#1BC3C3"/>
<stop offset="0.4259" style="stop-color:#14BAD8"/>
<stop offset="0.5596" style="stop-color:#10B5E7"/>
<stop offset="0.7185" style="stop-color:#0DB1EF"/>
<stop offset="0.9677" style="stop-color:#0CB0F2"/>
</linearGradient>
<path style="fill:url(#SVGID_4_);" d="M50.3,12.8c1.2-2.7,1.1-6-0.9-9c-1.1-1.8-2.9-3-4.9-3.5c-4.5-1.1-8.3,1-10.1,4.2L3.5,42
c0,0,0,0,0,0.1C-0.9,47.9-1.6,56.5,4,64c1.8,2.4,4.3,4.2,7.1,5c10.5,3.3,19.3-2.5,22.1-10.8L50.3,12.8z"/>
</g>
<g>
<rect x="13.4" y="13.4" style="fill:#000000;" width="43.2" height="43.2"/>
<rect x="17.5" y="48.5" style="fill:#FFFFFF;" width="16.2" height="2.7"/>
<polygon style="fill:#FFFFFF;" points="22.9,22.7 17.5,22.7 17.5,19.1 32.3,19.1 32.3,22.7 26.8,22.7 26.8,37 22.9,37 "/>
<path style="fill:#FFFFFF;" d="M32.5,28.1L32.5,28.1c0-5.1,3.8-9.3,9.3-9.3c3.4,0,5.4,1.1,7.1,2.8l-2.5,2.9c-1.4-1.3-2.8-2-4.6-2
c-3,0-5.2,2.5-5.2,5.6V28c0,3.1,2.1,5.6,5.2,5.6c2,0,3.3-0.8,4.7-2.1l2.5,2.5c-1.8,2-3.9,3.2-7.3,3.2
C36.4,37.3,32.5,33.2,32.5,28.1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -4,17 +4,23 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
## Getting Started
- [Download](https://sonarr.tv/#download) (Linux, MacOS, Windows, Docker, etc.)
- [Installation](https://github.com/Sonarr/Sonarr/wiki/Installation)
- [FAQ](https://github.com/Sonarr/Sonarr/wiki/FAQ)
- [Wiki](https://github.com/Sonarr/Sonarr/wiki)
- [API Documentation](https://github.com/Sonarr/Sonarr/wiki/API)
- [Download/Installation](https://sonarr.tv/#downloads-v3)
- [FAQ](https://wiki.servarr.com/sonarr/faq)
- [Wiki](https://wiki.servarr.com/Sonarr)
- [(WIP) API Documentation](https://github.com/Sonarr/Sonarr/wiki/API)
- [Donate](https://sonarr.tv/donate)
## Support
Note: GitHub Issues are for Bugs and Feature Requests Only
- [Donate](https://sonarr.tv/donate)
- [Forums](https://forums.sonarr.tv/)
- [Discord](https://discord.gg/M6BvZn5)
- [GitHub - Bugs and Feature Requests Only](https://github.com/Sonarr/Sonarr/issues)
- [IRC](https://web.libera.chat/?channels=#sonarr)
- [Reddit](https://www.reddit.com/r/sonarr)
- [Wiki](https://wiki.servarr.com/sonarr)
## Features
@@ -32,42 +38,38 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
- Full support for specials and multi-episode releases
- And a beautiful UI
## Configuring Development Environment:
## Contributing
### Requirements
### Development
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
<a href="https://github.com/Sonarr/Sonarr/graphs/contributors"><img src="https://opencollective.com/Sonarr/contributors.svg?width=890&button=false" /></a>
- [Visual Studio 2017](https://www.visualstudio.com/vs)
- [Git](https://git-scm.com/downloads)
- [NodeJS](https://nodejs.org/en/download)
- [Yarn](https://yarnpkg.com)
### Supporters
### Setup
This project would not be possible without the support of our users and software providers.
[**Become a sponsor or backer**](https://opencollective.com/sonarr) to help us out!
- Make sure all the required software mentioned above are installed
- Clone the repository recursively to get Sonarr and it's submodules
- You can do this by running `git clone --recursive https://github.com/Sonarr/Sonarr.git`
- Install the required Node Packages using `yarn`
#### Sponsors
### Backend Development
[![Sponsors](https://opencollective.com/sonarr/tiers/sponsor.svg)](https://opencollective.com/sonarr/contribute/sponsor-21443/checkout)
- Run `yarn build` to build the UI
- Open `Sonarr.sln` in Visual Studio
- Make sure `Sonarr.Console` is set as the startup project
- Build `Sonarr.Windows` and `Sonarr.Mono` projects
- Build Solution
#### Flexible Sponsors
### UI Development
[![Flexible Sponsors](https://opencollective.com/sonarr/tiers/flexible-sponsor.svg?avatarHeight=54)](https://opencollective.com/sonarr/contribute/flexible-sponsor-21457/checkout)
- Run `yarn watch` to build UI and rebuild automatically when changes are detected
- Run Sonarr.Console.exe (or debug in Visual Studio)
#### Backers
[![Backers](https://opencollective.com/sonarr/tiers/backer.svg?avatarHeight=48)](https://opencollective.com/sonarr/contribute/backer-21442/checkout)
#### JetBrains
Thank you to [<img src="/Logo/Jetbrains/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
* [<img src="/Logo/Jetbrains/teamcity.svg" alt="TeamCity" width="32"> TeamCity](http://www.jetbrains.com/teamcity/)
* [<img src="/Logo/Jetbrains/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
* [<img src="/Logo/Jetbrains/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
### Licenses
- [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
- Copyright 2010-2020
### Sponsors
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
- [ReSharper](http://www.jetbrains.com/resharper/)
- [TeamCity](http://www.jetbrains.com/teamcity/)
- Copyright 2010-2021

View File

@@ -141,7 +141,7 @@ Build()
ProgressEnd 'Build'
}
RunGulp()
RunWebpack()
{
ProgressStart 'yarn install'
yarn install
@@ -149,9 +149,9 @@ RunGulp()
LintUI
ProgressStart 'Running gulp'
CheckExitCode yarn run build --production
ProgressEnd 'Running gulp'
ProgressStart 'Running webpack'
CheckExitCode yarn run build --env production
ProgressEnd 'Running webpack'
}
CreateMdbs()
@@ -447,7 +447,7 @@ esac
UpdateVersionNumber
Build
CreateReleaseInfo
RunGulp
RunWebpack
PackageMono
PackageMacOS
PackageMacOSApp

View File

@@ -1,4 +1,5 @@
fromdos ./debian/*
chmod ugo-x ./debian/*
cp -r ./debian ./debian_backup
BuildVersion=${dependent_build_number:-3.10.0.999}

View File

@@ -1 +1 @@
8
10

View File

@@ -8,7 +8,10 @@ db_input high sonarr/owning_group || true
db_endblock
db_go
db_beginblock
db_input low sonarr/owning_umask || true
db_input low sonarr/config_directory || true
db_endblock
db_go
exit 0

View File

@@ -9,6 +9,8 @@ db_get sonarr/owning_user
USER="$RET"
db_get sonarr/owning_group
GROUP="$RET"
db_get sonarr/owning_umask
UMASK="$RET"
db_get sonarr/config_directory
CONFDIR="$RET"
@@ -64,9 +66,11 @@ fi
# Create data directory
if [ ! -d "$CONFDIR" ]; then
mkdir -p "$CONFDIR"
chown -R $USER:$GROUP "$CONFDIR"
fi
# Set permissions on data directory (always do this instead only on creation in case user was changed via dpkg-reconfigure)
chown -R $USER:$GROUP "$CONFDIR"
#BEGIN BUILTIN UPDATER
# Apply patch if present
if [ "$UPDATER" = "BuiltIn" ] && [ -f /usr/lib/sonarr/bin_patch/release_info ]; then
@@ -92,7 +96,7 @@ fi
chown -R $USER:$GROUP /usr/lib/sonarr
# Update sonarr.service file
sed -i "s:User=sonarr:User=$USER:g; s:Group=sonarr:Group=$GROUP:g; s:-data=/var/lib/sonarr:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
sed -i "s:User=\w*:User=$USER:g; s:Group=\w*:Group=$GROUP:g; s:UMask=[0-9]*:UMask=$UMASK:g; s:-data=.*$:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
#BEGIN BUILTIN UPDATER
if [ "$UPDATER" = "BuiltIn" ]; then

View File

@@ -9,7 +9,7 @@ UPDATER={updater}
# Existing nzbdrone packages do not have startup scripts and the process might still be running.
# If the user manually installed nzbdrone then the process might still be running too.
if [ $1 = "install" ]; then
psNzbDrone=`ps ax -o'user,pid,ppid,unit,args' | grep mono.*NzbDrone\\\\.exe || true`
psNzbDrone=`ps ax -o'user:20,pid,ppid,unit,args' | grep mono.*NzbDrone\\\\.exe || true`
if [ ! -z "$psNzbDrone" ]; then
# Get the user and optional systemd unit
psNzbDroneUser=`echo "$psNzbDrone" | tr -s ' ' | cut -d ' ' -f 1`

View File

@@ -1,2 +1,3 @@
ignores msbuild
ignores libmediainfo0v5
ignores libc6

View File

@@ -1,3 +1,6 @@
# This file is owned by the sonarr package, DO NOT MODIFY MANUALLY
# Instead use 'dpkg-reconfigure -plow sonarr' to modify User/Group/UMask/-data
# Or use systemd built-in override functionality using 'systemctl edit sonarr'
[Unit]
Description=Sonarr Daemon
After=network.target

View File

@@ -14,6 +14,12 @@ Description: Sonarr group:
Any media files created by Sonarr will be writeable by this group.
It's advisable to keep the group the same between download client, Sonarr and media centers.
Template: sonarr/owning_umask
Type: string
Default: 0002
Description: Sonarr umask:
Specifies the umask of the files created by Sonarr. 0002 means the files will be created with 664 as permissions.
Template: sonarr/config_directory
Type: string
Default: /var/lib/sonarr

View File

@@ -1,16 +1,25 @@
FROM ubuntu:xenial AS builder
FROM ubuntu:focal AS builder
ENV DEBIAN_FRONTEND noninteractive
ENV MONO_VERSION 5.18
RUN apt-get update && \
apt-get -y -o Dpkg::Options::="--force-confold" install --no-install-recommends \
apt-transport-https \
wget dirmngr gpg gpg-agent \
# add-apt-repository for PPAs
software-properties-common && \
rm -rf /var/lib/apt/lists/*
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb http://download.mono-project.com/repo/debian stable-xenial/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list && \
echo "deb http://download.mono-project.com/repo/debian stable-focal main" > /etc/apt/sources.list.d/mono-official-stable.list && \
apt-get update && apt-get install -y \
devscripts build-essential tofrodos \
dh-make dh-systemd \
cli-common-dev \
mono-complete \
sqlite3 libcurl3 mediainfo
sqlite3 libcurl4 mediainfo
RUN apt-get upgrade -y
RUN apt-cache policy mono-complete
RUN apt-cache policy cli-common-dev

View File

@@ -37,13 +37,14 @@ Compression=lzma2/normal
AppContact={#ForumsURL}
VersionInfoVersion={#BuildNumber}
SetupLogging=yes
OutputDir=output
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
@@ -59,11 +60,12 @@ Name: "{userstartup}\{#AppName}"; Filename: "{app}\Sonarr.exe"; WorkingDir: "{ap
[InstallDelete]
Name: "{commonappdata}\NzbDrone\bin"; Type: filesandordirs
Name: "{app}"; Type: filesandordirs
[Run]
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u"; Flags: runhidden waituntilterminated;
Filename: "{app}\Sonarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i"; Flags: runhidden waituntilterminated; Tasks: windowsService
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Removing previous Windows Service"; Parameters: "/u /exitimmediately"; Flags: runhidden waituntilterminated;
Filename: "{app}\Sonarr.Console.exe"; Description: "Enable Access from Other Devices"; StatusMsg: "Enabling Remote access"; Parameters: "/registerurl /exitimmediately"; Flags: postinstall runascurrentuser runhidden waituntilterminated; Tasks: startupShortcut none;
Filename: "{app}\Sonarr.Console.exe"; StatusMsg: "Installing Windows Service"; Parameters: "/i /exitimmediately"; Flags: runhidden waituntilterminated; Tasks: windowsService
Filename: "{app}\Sonarr.exe"; Description: "Open Sonarr Web UI"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
Filename: "{app}\Sonarr.exe"; Description: "Start Sonarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
@@ -75,7 +77,8 @@ function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{commonappdata}\NzbDrone\bin\NzbDrone.Console.exe'), '/u', '', 0, ewWaitUntilTerminated, ResultCode)
Exec('net', 'stop nzbdrone', '', 0, ewWaitUntilTerminated, ResultCode)
Exec('sc', 'delete nzbdrone', '', 0, ewWaitUntilTerminated, ResultCode)
end;
function Framework472IsNotInstalled(): Boolean;

View File

@@ -17,6 +17,9 @@ RUN fromdos /startup.sh
WORKDIR /data/
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
RUN groupadd sonarrtst -g 4020 && useradd sonarrtst -u 4021 -g 4020 -m -s /bin/bash
USER sonarrtst
CMD bash /startup.sh

View File

@@ -25,5 +25,8 @@ RUN fromdos /startup.sh
WORKDIR /data/
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
RUN groupadd sonarrtst -g 4020 && useradd sonarrtst -u 4021 -g 4020 -m -s /bin/bash
USER sonarrtst
CMD bash /startup.sh

View File

@@ -75,7 +75,7 @@
"function-parentheses-newline-inside": "never-multi-line",
"function-parentheses-space-inside": "never",
"function-url-quotes": "always",
"function-url-scheme-blacklist": [
"function-url-scheme-disallowed-list": [
"data"
],
"function-whitespace-after": "always",

View File

@@ -0,0 +1,262 @@
const path = require('path');
const webpack = require('webpack');
const FileManagerPlugin = require('filemanager-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const LiveReloadPlugin = require('webpack-livereload-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = (env) => {
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = !!env.production;
const isProfiling = isProduction && !!env.profile;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const config = {
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
}
},
output: {
path: distFolder,
publicPath: '/',
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
},
performance: {
hints: false
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css'
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.ejs',
filename: 'index.html',
publicPath: '/'
}),
new FileManagerPlugin({
events: {
onEnd: {
copy: [
// HTML
{
source: 'frontend/src/*.html',
destination: distFolder
},
// Fonts
{
source: 'frontend/src/Content/Fonts/*.*',
destination: path.join(distFolder, 'Content/Fonts')
},
// Icon Images
{
source: 'frontend/src/Content/Images/Icons/*.*',
destination: path.join(distFolder, 'Content/Images/Icons')
},
// Images
{
source: 'frontend/src/Content/Images/*.*',
destination: path.join(distFolder, 'Content/Images')
},
// Robots
{
source: 'frontend/src/Content/robots.txt',
destination: path.join(distFolder, 'Content/robots.txt')
}
]
}
}
}),
new LiveReloadPlugin()
],
resolveLoader: {
modules: [
'node_modules',
'frontend/build/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
return config;
};

View File

@@ -1,17 +0,0 @@
const gulp = require('gulp');
require('./clean');
require('./copy');
require('./webpack');
gulp.task('build',
gulp.series('clean',
gulp.parallel(
'webpack',
'copyHtml',
'copyFonts',
'copyImages'
)
)
);

View File

@@ -1,8 +0,0 @@
const gulp = require('gulp');
const del = require('del');
const paths = require('./helpers/paths');
gulp.task('clean', () => {
return del([paths.dest.root]);
});

View File

@@ -1,34 +0,0 @@
const path = require('path');
const gulp = require('gulp');
const print = require('gulp-print').default;
const cache = require('gulp-cached');
const livereload = require('gulp-livereload');
const paths = require('./helpers/paths.js');
gulp.task('copyHtml', () => {
return gulp.src(paths.src.html, { base: paths.src.root })
.pipe(cache('copyHtml'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyFonts', () => {
return gulp.src(
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyFonts'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyImages', () => {
return gulp.src(
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyImages'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});

View File

@@ -1,5 +0,0 @@
require('./build.js');
require('./clean.js');
require('./copy.js');
require('./watch.js');
require('./webpack.js');

View File

@@ -1,6 +0,0 @@
const colors = require('ansi-colors');
module.exports = function errorHandler(error) {
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
this.emit('end');
};

View File

@@ -1,23 +0,0 @@
const root = './frontend/src';
const paths = {
src: {
root,
html: `${root}/*.html`,
scripts: `${root}/**/*.js`,
content: `${root}/Content/`,
fonts: `${root}/Content/Fonts/`,
images: `${root}/Content/Images/`,
exclude: {
libs: `!${root}/JsLibraries/**`
}
},
dest: {
root: './_output/UI/',
content: './_output/UI/Content/',
fonts: './_output/UI/Content/Fonts/',
images: './_output/UI/Content/Images/'
}
};
module.exports = paths;

View File

@@ -1,18 +0,0 @@
const gulp = require('gulp');
const livereload = require('gulp-livereload');
const gulpWatch = require('gulp-watch');
const paths = require('./helpers/paths.js');
require('./copy.js');
require('./webpack.js');
function watch() {
livereload.listen({ start: true });
gulp.task('webpackWatch')();
gulpWatch(paths.src.html, gulp.series('copyHtml'));
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
}
gulp.task('watch', gulp.series('build', watch));

View File

@@ -1,263 +0,0 @@
const gulp = require('gulp');
const webpackStream = require('webpack-stream');
const livereload = require('gulp-livereload');
const path = require('path');
const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1;
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
const inlineWebWorkers = true;
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const cssVarsFiles = [
'../src/Styles/Variables/colors',
'../src/Styles/Variables/dimensions',
'../src/Styles/Variables/fonts',
'../src/Styles/Variables/animations',
'../src/Styles/Variables/zIndexes'
].map(require.resolve);
// Override the way HtmlWebpackPlugin injects the scripts
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
const head = assetTags.head.map((v) => {
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${v.attributes.href.replace('\\', '/')}` };
return this.createHtmlTag(v);
});
const body = assetTags.body.map((v) => {
v.attributes = { src: `/${v.attributes.src}` };
return this.createHtmlTag(v);
});
return html
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
};
const plugins = [
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: path.join('Content', 'styles.css')
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html'
})
];
const config = {
mode: isProduction ? 'production' : 'development',
devtool: '#source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
}
},
output: {
path: distFolder,
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
chunkIds: 'named',
splitChunks: {
chunks: 'initial'
}
},
performance: {
hints: false
},
plugins,
resolveLoader: {
modules: [
'node_modules',
'frontend/gulp/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
name: '[name].js',
inline: inlineWebWorkers,
fallback: !inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
config: {
ctx: {
cssVarsFiles
},
path: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
gulp.task('webpack', () => {
return webpackStream(config)
.pipe(gulp.dest('_output/UI'));
});
gulp.task('webpackWatch', () => {
config.watch = true;
return webpackStream(config)
.on('error', errorHandler)
.pipe(gulp.dest('_output/UI'))
.on('error', errorHandler)
.pipe(livereload())
.on('error', errorHandler);
});

View File

@@ -1,23 +1,32 @@
const reload = require('require-nocache')(module);
module.exports = (ctx, configPath, options) => {
const config = {
plugins: {
'postcss-mixins': {
mixinsDir: [
'frontend/src/Styles/Mixins'
]
},
'postcss-simple-vars': {
variables: () =>
ctx.options.cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
},
'postcss-color-function': {},
'postcss-nested': {}
}
};
const cssVarsFiles = [
'./src/Styles/Variables/colors',
'./src/Styles/Variables/dimensions',
'./src/Styles/Variables/fonts',
'./src/Styles/Variables/animations',
'./src/Styles/Variables/zIndexes'
].map(require.resolve);
return config;
const mixinsFiles = [
'frontend/src/Styles/Mixins/cover.css',
'frontend/src/Styles/Mixins/linkOverlay.css',
'frontend/src/Styles/Mixins/scroller.css',
'frontend/src/Styles/Mixins/truncate.css'
];
module.exports = {
plugins: [
['postcss-mixins', {
mixinsFiles
}],
['postcss-simple-vars', {
variables: () =>
cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
}],
'postcss-color-function',
'postcss-nested'
]
};

View File

@@ -1,123 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { align, icons } from 'Helpers/Props';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import BlacklistRowConnector from './BlacklistRowConnector';
class Blacklist extends Component {
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
items,
columns,
totalRecords,
isClearingBlacklistExecuting,
onClearBlacklistPress,
...otherProps
} = this.props;
return (
<PageContent title="Blacklist">
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Clear"
iconName={icons.CLEAR}
isSpinning={isClearingBlacklistExecuting}
onPress={onClearBlacklistPress}
/>
</PageToolbarSection>
<PageToolbarSection alignContent={align.RIGHT}>
<TableOptionsModalWrapper
{...otherProps}
columns={columns}
>
<PageToolbarButton
label="Options"
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
</PageToolbarSection>
</PageToolbar>
<PageContentBody>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>Unable to load blacklist</div>
}
{
isPopulated && !error && !items.length &&
<div>
No history blacklist
</div>
}
{
isPopulated && !error && !!items.length &&
<div>
<Table
columns={columns}
{...otherProps}
>
<TableBody>
{
items.map((item) => {
return (
<BlacklistRowConnector
key={item.id}
columns={columns}
{...item}
/>
);
})
}
</TableBody>
</Table>
<TablePager
totalRecords={totalRecords}
isFetching={isFetching}
{...otherProps}
/>
</div>
}
</PageContentBody>
</PageContent>
);
}
}
Blacklist.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
onClearBlacklistPress: PropTypes.func.isRequired
};
export default Blacklist;

View File

@@ -1,154 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import withCurrentPage from 'Components/withCurrentPage';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import * as blacklistActions from 'Store/Actions/blacklistActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import Blacklist from './Blacklist';
function createMapStateToProps() {
return createSelector(
(state) => state.blacklist,
createCommandExecutingSelector(commandNames.CLEAR_BLACKLIST),
(blacklist, isClearingBlacklistExecuting) => {
return {
isClearingBlacklistExecuting,
...blacklist
};
}
);
}
const mapDispatchToProps = {
...blacklistActions,
executeCommand
};
class BlacklistConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
useCurrentPage,
fetchBlacklist,
gotoBlacklistFirstPage
} = this.props;
registerPagePopulator(this.repopulate);
if (useCurrentPage) {
fetchBlacklist();
} else {
gotoBlacklistFirstPage();
}
}
componentDidUpdate(prevProps) {
if (prevProps.isClearingBlacklistExecuting && !this.props.isClearingBlacklistExecuting) {
this.props.gotoBlacklistFirstPage();
}
}
componentWillUnmount() {
this.props.clearBlacklist();
unregisterPagePopulator(this.repopulate);
}
//
// Control
repopulate = () => {
this.props.fetchBlacklist();
}
//
// Listeners
onFirstPagePress = () => {
this.props.gotoBlacklistFirstPage();
}
onPreviousPagePress = () => {
this.props.gotoBlacklistPreviousPage();
}
onNextPagePress = () => {
this.props.gotoBlacklistNextPage();
}
onLastPagePress = () => {
this.props.gotoBlacklistLastPage();
}
onPageSelect = (page) => {
this.props.gotoBlacklistPage({ page });
}
onSortPress = (sortKey) => {
this.props.setBlacklistSort({ sortKey });
}
onTableOptionChange = (payload) => {
this.props.setBlacklistTableOption(payload);
if (payload.pageSize) {
this.props.gotoBlacklistFirstPage();
}
}
onClearBlacklistPress = () => {
this.props.executeCommand({ name: commandNames.CLEAR_BLACKLIST });
}
onTableOptionChange = (payload) => {
this.props.setBlacklistTableOption(payload);
if (payload.pageSize) {
this.props.gotoBlacklistFirstPage();
}
}
//
// Render
render() {
return (
<Blacklist
onFirstPagePress={this.onFirstPagePress}
onPreviousPagePress={this.onPreviousPagePress}
onNextPagePress={this.onNextPagePress}
onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect}
onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange}
onClearBlacklistPress={this.onClearBlacklistPress}
{...this.props}
/>
);
}
}
BlacklistConnector.propTypes = {
useCurrentPage: PropTypes.bool.isRequired,
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchBlacklist: PropTypes.func.isRequired,
gotoBlacklistFirstPage: PropTypes.func.isRequired,
gotoBlacklistPreviousPage: PropTypes.func.isRequired,
gotoBlacklistNextPage: PropTypes.func.isRequired,
gotoBlacklistLastPage: PropTypes.func.isRequired,
gotoBlacklistPage: PropTypes.func.isRequired,
setBlacklistSort: PropTypes.func.isRequired,
setBlacklistTableOption: PropTypes.func.isRequired,
clearBlacklist: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default withCurrentPage(
connect(createMapStateToProps, mapDispatchToProps)(BlacklistConnector)
);

View File

@@ -0,0 +1,232 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import { align, icons, kinds } from 'Helpers/Props';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import BlocklistRowConnector from './BlocklistRowConnector';
class Blocklist extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {},
isConfirmRemoveModalOpen: false,
items: props.items
};
}
componentDidUpdate(prevProps) {
const {
items
} = this.props;
if (hasDifferentItems(prevProps.items, items)) {
this.setState((state) => {
return {
...removeOldSelectedState(state, getRemovedItems(prevProps.items, items)),
items
};
});
return;
}
}
//
// Control
getSelectedIds = () => {
return getSelectedIds(this.state.selectedState);
}
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = () => {
this.props.onRemoveSelected(this.getSelectedIds());
this.setState({ isConfirmRemoveModalOpen: false });
}
onConfirmRemoveModalClose = () => {
this.setState({ isConfirmRemoveModalOpen: false });
}
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
items,
columns,
totalRecords,
isRemoving,
isClearingBlocklistExecuting,
onClearBlocklistPress,
...otherProps
} = this.props;
const {
allSelected,
allUnselected,
selectedState,
isConfirmRemoveModalOpen
} = this.state;
const selectedIds = this.getSelectedIds();
return (
<PageContent title="Blocklist">
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Remove Selected"
iconName={icons.REMOVE}
isDisabled={!selectedIds.length}
isSpinning={isRemoving}
onPress={this.onRemoveSelectedPress}
/>
<PageToolbarButton
label="Clear"
iconName={icons.CLEAR}
isSpinning={isClearingBlocklistExecuting}
onPress={onClearBlocklistPress}
/>
</PageToolbarSection>
<PageToolbarSection alignContent={align.RIGHT}>
<TableOptionsModalWrapper
{...otherProps}
columns={columns}
>
<PageToolbarButton
label="Options"
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
</PageToolbarSection>
</PageToolbar>
<PageContentBody>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>Unable to load blocklist</div>
}
{
isPopulated && !error && !items.length &&
<div>
No history blocklist
</div>
}
{
isPopulated && !error && !!items.length &&
<div>
<Table
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
columns={columns}
{...otherProps}
onSelectAllChange={this.onSelectAllChange}
>
<TableBody>
{
items.map((item) => {
return (
<BlocklistRowConnector
key={item.id}
isSelected={selectedState[item.id] || false}
columns={columns}
{...item}
onSelectedChange={this.onSelectedChange}
/>
);
})
}
</TableBody>
</Table>
<TablePager
totalRecords={totalRecords}
isFetching={isFetching}
{...otherProps}
/>
</div>
}
</PageContentBody>
<ConfirmModal
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title="Remove Selected"
message={'Are you sure you want to remove the selected items from the blocklist?'}
confirmLabel="Remove Selected"
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}
/>
</PageContent>
);
}
}
Blocklist.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isRemoving: PropTypes.bool.isRequired,
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
onRemoveSelected: PropTypes.func.isRequired,
onClearBlocklistPress: PropTypes.func.isRequired
};
export default Blocklist;

View File

@@ -0,0 +1,160 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import withCurrentPage from 'Components/withCurrentPage';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import * as blocklistActions from 'Store/Actions/blocklistActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import Blocklist from './Blocklist';
function createMapStateToProps() {
return createSelector(
(state) => state.blocklist,
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
(blocklist, isClearingBlocklistExecuting) => {
return {
isClearingBlocklistExecuting,
...blocklist
};
}
);
}
const mapDispatchToProps = {
...blocklistActions,
executeCommand
};
class BlocklistConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
useCurrentPage,
fetchBlocklist,
gotoBlocklistFirstPage
} = this.props;
registerPagePopulator(this.repopulate);
if (useCurrentPage) {
fetchBlocklist();
} else {
gotoBlocklistFirstPage();
}
}
componentDidUpdate(prevProps) {
if (prevProps.isClearingBlocklistExecuting && !this.props.isClearingBlocklistExecuting) {
this.props.gotoBlocklistFirstPage();
}
}
componentWillUnmount() {
this.props.clearBlocklist();
unregisterPagePopulator(this.repopulate);
}
//
// Control
repopulate = () => {
this.props.fetchBlocklist();
}
//
// Listeners
onFirstPagePress = () => {
this.props.gotoBlocklistFirstPage();
}
onPreviousPagePress = () => {
this.props.gotoBlocklistPreviousPage();
}
onNextPagePress = () => {
this.props.gotoBlocklistNextPage();
}
onLastPagePress = () => {
this.props.gotoBlocklistLastPage();
}
onPageSelect = (page) => {
this.props.gotoBlocklistPage({ page });
}
onRemoveSelected = (ids) => {
this.props.removeBlocklistItems({ ids });
}
onSortPress = (sortKey) => {
this.props.setBlocklistSort({ sortKey });
}
onTableOptionChange = (payload) => {
this.props.setBlocklistTableOption(payload);
if (payload.pageSize) {
this.props.gotoBlocklistFirstPage();
}
}
onClearBlocklistPress = () => {
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
}
onTableOptionChange = (payload) => {
this.props.setBlocklistTableOption(payload);
if (payload.pageSize) {
this.props.gotoBlocklistFirstPage();
}
}
//
// Render
render() {
return (
<Blocklist
onFirstPagePress={this.onFirstPagePress}
onPreviousPagePress={this.onPreviousPagePress}
onNextPagePress={this.onNextPagePress}
onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect}
onRemoveSelected={this.onRemoveSelected}
onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange}
onClearBlocklistPress={this.onClearBlocklistPress}
{...this.props}
/>
);
}
}
BlocklistConnector.propTypes = {
useCurrentPage: PropTypes.bool.isRequired,
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchBlocklist: PropTypes.func.isRequired,
gotoBlocklistFirstPage: PropTypes.func.isRequired,
gotoBlocklistPreviousPage: PropTypes.func.isRequired,
gotoBlocklistNextPage: PropTypes.func.isRequired,
gotoBlocklistLastPage: PropTypes.func.isRequired,
gotoBlocklistPage: PropTypes.func.isRequired,
removeBlocklistItems: PropTypes.func.isRequired,
setBlocklistSort: PropTypes.func.isRequired,
setBlocklistTableOption: PropTypes.func.isRequired,
clearBlocklist: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default withCurrentPage(
connect(createMapStateToProps, mapDispatchToProps)(BlocklistConnector)
);

View File

@@ -9,7 +9,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
class BlacklistDetailsModal extends Component {
class BlocklistDetailsModal extends Component {
//
// Render
@@ -77,7 +77,7 @@ class BlacklistDetailsModal extends Component {
}
}
BlacklistDetailsModal.propTypes = {
BlocklistDetailsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired,
@@ -86,4 +86,4 @@ BlacklistDetailsModal.propTypes = {
onModalClose: PropTypes.func.isRequired
};
export default BlacklistDetailsModal;
export default BlocklistDetailsModal;

View File

@@ -2,16 +2,17 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons, kinds } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import EpisodeLanguage from 'Episode/EpisodeLanguage';
import EpisodeQuality from 'Episode/EpisodeQuality';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import BlacklistDetailsModal from './BlacklistDetailsModal';
import styles from './BlacklistRow.css';
import BlocklistDetailsModal from './BlocklistDetailsModal';
import styles from './BlocklistRow.css';
class BlacklistRow extends Component {
class BlocklistRow extends Component {
//
// Lifecycle
@@ -40,6 +41,7 @@ class BlacklistRow extends Component {
render() {
const {
id,
series,
sourceTitle,
language,
@@ -48,12 +50,20 @@ class BlacklistRow extends Component {
protocol,
indexer,
message,
isSelected,
columns,
onSelectedChange,
onRemovePress
} = this.props;
return (
<TableRow>
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
{
columns.map((column) => {
const {
@@ -142,7 +152,7 @@ class BlacklistRow extends Component {
/>
<IconButton
title="Remove from blacklist"
title="Remove from blocklist"
name={icons.REMOVE}
kind={kinds.DANGER}
onPress={onRemovePress}
@@ -155,7 +165,7 @@ class BlacklistRow extends Component {
})
}
<BlacklistDetailsModal
<BlocklistDetailsModal
isOpen={this.state.isDetailsModalOpen}
sourceTitle={sourceTitle}
protocol={protocol}
@@ -169,7 +179,7 @@ class BlacklistRow extends Component {
}
BlacklistRow.propTypes = {
BlocklistRow.propTypes = {
id: PropTypes.number.isRequired,
series: PropTypes.object.isRequired,
sourceTitle: PropTypes.string.isRequired,
@@ -179,8 +189,10 @@ BlacklistRow.propTypes = {
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,
message: PropTypes.string,
isSelected: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired
};
export default BlacklistRow;
export default BlocklistRow;

View File

@@ -1,8 +1,8 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
import { removeBlocklistItem } from 'Store/Actions/blocklistActions';
import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
import BlacklistRow from './BlacklistRow';
import BlocklistRow from './BlocklistRow';
function createMapStateToProps() {
return createSelector(
@@ -18,9 +18,9 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
onRemovePress() {
dispatch(removeFromBlacklist({ id: props.id }));
dispatch(removeBlocklistItem({ id: props.id }));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(BlacklistRow);
export default connect(createMapStateToProps, createMapDispatchToProps)(BlocklistRow);

View File

@@ -1,7 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import Link from 'Components/Link/Link';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
@@ -22,6 +23,7 @@ function HistoryDetails(props) {
const {
indexer,
releaseGroup,
preferredWordScore,
nzbInfoUrl,
downloadClient,
downloadId,
@@ -40,24 +42,35 @@ function HistoryDetails(props) {
/>
{
!!indexer &&
indexer ?
<DescriptionListItem
title="Indexer"
data={indexer}
/>
/> :
null
}
{
!!releaseGroup &&
releaseGroup ?
<DescriptionListItem
descriptionClassName={styles.description}
title="Release Group"
data={releaseGroup}
/>
/> :
null
}
{
!!nzbInfoUrl &&
preferredWordScore && preferredWordScore !== '0' ?
<DescriptionListItem
title="Preferred Word Score"
data={formatPreferredWordScore(preferredWordScore)}
/> :
null
}
{
nzbInfoUrl ?
<span>
<DescriptionListItemTitle>
Info URL
@@ -66,39 +79,44 @@ function HistoryDetails(props) {
<DescriptionListItemDescription>
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
</DescriptionListItemDescription>
</span>
</span> :
null
}
{
!!downloadClient &&
downloadClient ?
<DescriptionListItem
title="Download Client"
data={downloadClient}
/>
/> :
null
}
{
!!downloadId &&
downloadId ?
<DescriptionListItem
title="Grab ID"
data={downloadId}
/>
/> :
null
}
{
!!indexer &&
age || ageHours || ageMinutes ?
<DescriptionListItem
title="Age (when grabbed)"
data={formatAge(age, ageHours, ageMinutes)}
/>
/> :
null
}
{
!!publishedDate &&
publishedDate ?
<DescriptionListItem
title="Published Date"
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
/>
/> :
null
}
</DescriptionList>
);
@@ -118,11 +136,12 @@ function HistoryDetails(props) {
/>
{
!!message &&
message ?
<DescriptionListItem
title="Message"
data={message}
/>
/> :
null
}
</DescriptionList>
);
@@ -130,6 +149,7 @@ function HistoryDetails(props) {
if (eventType === 'downloadFolderImported') {
const {
preferredWordScore,
droppedPath,
importedPath
} = data;
@@ -143,21 +163,32 @@ function HistoryDetails(props) {
/>
{
!!droppedPath &&
droppedPath ?
<DescriptionListItem
descriptionClassName={styles.description}
title="Source"
data={droppedPath}
/>
/> :
null
}
{
!!importedPath &&
importedPath ?
<DescriptionListItem
descriptionClassName={styles.description}
title="Imported To"
data={importedPath}
/>
/> :
null
}
{
preferredWordScore && preferredWordScore !== '0' ?
<DescriptionListItem
title="Preferred Word Score"
data={formatPreferredWordScore(preferredWordScore)}
/> :
null
}
</DescriptionList>
);
@@ -165,7 +196,8 @@ function HistoryDetails(props) {
if (eventType === 'episodeFileDeleted') {
const {
reason
reason,
preferredWordScore
} = data;
let reasonMessage = '';
@@ -195,6 +227,15 @@ function HistoryDetails(props) {
title="Reason"
data={reasonMessage}
/>
{
preferredWordScore && preferredWordScore !== '0' ?
<DescriptionListItem
title="Preferred Word Score"
data={formatPreferredWordScore(preferredWordScore)}
/> :
null
}
</DescriptionList>
);
}
@@ -246,11 +287,12 @@ function HistoryDetails(props) {
/>
{
!!message &&
message ?
<DescriptionListItem
title="Message"
data={message}
/>
/> :
null
}
</DescriptionList>
);

View File

@@ -10,6 +10,12 @@
width: 80px;
}
.preferredWordScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px;
}
.releaseGroup {
composes: cell from '~Components/Table/Cells/TableRowCell.css';

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import { icons } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
@@ -194,6 +195,17 @@ class HistoryRow extends Component {
);
}
if (name === 'preferredWordScore') {
return (
<TableRowCell
key={name}
className={styles.preferredWordScore}
>
{formatPreferredWordScore(data.preferredWordScore)}
</TableRowCell>
);
}
if (name === 'releaseGroup') {
return (
<TableRowCell

View File

@@ -31,6 +31,8 @@ class Queue extends Component {
constructor(props, context) {
super(props, context);
this._shouldBlockRefresh = false;
this.state = {
allSelected: false,
allUnselected: false,
@@ -42,6 +44,14 @@ class Queue extends Component {
};
}
shouldComponentUpdate() {
if (this._shouldBlockRefresh) {
return false;
}
return true;
}
componentDidUpdate(prevProps) {
const {
items,
@@ -82,6 +92,10 @@ class Queue extends Component {
//
// Listeners
onQueueRowModalOpenOrClose = (isOpen) => {
this._shouldBlockRefresh = isOpen;
}
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
@@ -97,15 +111,19 @@ class Queue extends Component {
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true });
this.setState({ isConfirmRemoveModalOpen: true }, () => {
this._shouldBlockRefresh = true;
});
}
onRemoveSelectedConfirmed = (payload) => {
this._shouldBlockRefresh = false;
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
this.setState({ isConfirmRemoveModalOpen: false });
}
onConfirmRemoveModalClose = () => {
this._shouldBlockRefresh = false;
this.setState({ isConfirmRemoveModalOpen: false });
}
@@ -205,7 +223,7 @@ class Queue extends Component {
}
{
isPopulated && !hasError && !items.length &&
isAllPopulated && !hasError && !items.length &&
<div>
Queue is empty
</div>
@@ -234,6 +252,7 @@ class Queue extends Component {
columns={columns}
{...item}
onSelectedChange={this.onSelectedChange}
onQueueRowModalOpenOrClose={this.onQueueRowModalOpenOrClose}
/>
);
})
@@ -260,6 +279,17 @@ class Queue extends Component {
return !!(item && item.seriesId && item.episodeId);
})
)}
allPending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
if (!item) {
return false;
}
return item.status === 'delay' || item.status === 'downloadClientUnavailable';
})
)}
onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose}
/>

View File

@@ -48,6 +48,7 @@ class QueueConnector extends Component {
const {
useCurrentPage,
fetchQueue,
fetchQueueStatus,
gotoQueueFirstPage
} = this.props;
@@ -58,6 +59,8 @@ class QueueConnector extends Component {
} else {
gotoQueueFirstPage();
}
fetchQueueStatus();
}
componentDidUpdate(prevProps) {
@@ -168,6 +171,7 @@ QueueConnector.propTypes = {
useCurrentPage: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchQueue: PropTypes.func.isRequired,
fetchQueueStatus: PropTypes.func.isRequired,
gotoQueueFirstPage: PropTypes.func.isRequired,
gotoQueuePreviousPage: PropTypes.func.isRequired,
gotoQueueNextPage: PropTypes.func.isRequired,

View File

@@ -19,6 +19,7 @@ import QueueStatusCell from './QueueStatusCell';
import TimeleftCell from './TimeleftCell';
import RemoveQueueItemModal from './RemoveQueueItemModal';
import styles from './QueueRow.css';
import formatBytes from 'Utilities/Number/formatBytes';
class QueueRow extends Component {
@@ -41,20 +42,33 @@ class QueueRow extends Component {
this.setState({ isRemoveQueueItemModalOpen: true });
}
onRemoveQueueItemModalConfirmed = (blacklist) => {
this.props.onRemoveQueueItemPress(blacklist);
onRemoveQueueItemModalConfirmed = (blocklist) => {
const {
onRemoveQueueItemPress,
onQueueRowModalOpenOrClose
} = this.props;
onQueueRowModalOpenOrClose(false);
onRemoveQueueItemPress(blocklist);
this.setState({ isRemoveQueueItemModalOpen: false });
}
onRemoveQueueItemModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isRemoveQueueItemModalOpen: false });
}
onInteractiveImportPress = () => {
this.props.onQueueRowModalOpenOrClose(true);
this.setState({ isInteractiveImportModalOpen: true });
}
onInteractiveImportModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isInteractiveImportModalOpen: false });
}
@@ -267,6 +281,12 @@ class QueueRow extends Component {
);
}
if (name === 'size') {
return (
<TableRowCell key={name}>{formatBytes(size)}</TableRowCell>
);
}
if (name === 'outputPath') {
return (
<TableRowCell key={name}>
@@ -357,6 +377,7 @@ class QueueRow extends Component {
isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title}
canIgnore={!!series}
isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose}
/>
@@ -397,7 +418,8 @@ QueueRow.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired,
onRemoveQueueItemPress: PropTypes.func.isRequired
onRemoveQueueItemPress: PropTypes.func.isRequired,
onQueueRowModalOpenOrClose: PropTypes.func.isRequired
};
QueueRow.defaultProps = {

View File

@@ -3,3 +3,7 @@
width: 30px;
}
.noMessages {
margin-bottom: 10px;
}

View File

@@ -12,7 +12,10 @@ function getDetailedPopoverBody(statusMessages) {
{
statusMessages.map(({ title, messages }) => {
return (
<div key={title}>
<div
key={title}
className={messages.length ? undefined: styles.noMessages}
>
{title}
<ul>
{

View File

@@ -21,7 +21,7 @@ class RemoveQueueItemModal extends Component {
this.state = {
remove: true,
blacklist: false
blocklist: false
};
}
@@ -31,7 +31,7 @@ class RemoveQueueItemModal extends Component {
resetState = function() {
this.setState({
remove: true,
blacklist: false
blocklist: false
});
}
@@ -42,8 +42,8 @@ class RemoveQueueItemModal extends Component {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value });
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
}
onRemoveConfirmed = () => {
@@ -65,10 +65,11 @@ class RemoveQueueItemModal extends Component {
const {
isOpen,
sourceTitle,
canIgnore
canIgnore,
isPending
} = this.props;
const { remove, blacklist } = this.state;
const { remove, blocklist } = this.state;
return (
<Modal
@@ -88,28 +89,32 @@ class RemoveQueueItemModal extends Component {
Are you sure you want to remove '{sourceTitle}' from the queue?
</div>
{
isPending ?
null :
<FormGroup>
<FormLabel>Remove From Download Client</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning="Removing will remove the download and the file(s) from the download client."
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>Remove From Download Client</FormLabel>
<FormLabel>Add Release To Blocklist</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning="Removing will remove the download and the file(s) from the download client."
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Blacklist Release</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blacklist"
value={blacklist}
name="blocklist"
value={blocklist}
helpText="Starts a search for this episode again and prevents this release from being grabbed again"
onChange={this.onBlacklistChange}
onChange={this.onBlocklistChange}
/>
</FormGroup>
@@ -137,6 +142,7 @@ RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired,
isPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -22,7 +22,7 @@ class RemoveQueueItemsModal extends Component {
this.state = {
remove: true,
blacklist: false
blocklist: false
};
}
@@ -32,7 +32,7 @@ class RemoveQueueItemsModal extends Component {
resetState = function() {
this.setState({
remove: true,
blacklist: false
blocklist: false
});
}
@@ -43,8 +43,8 @@ class RemoveQueueItemsModal extends Component {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value });
onBlocklistChange = ({ value }) => {
this.setState({ blocklist: value });
}
onRemoveConfirmed = () => {
@@ -66,10 +66,11 @@ class RemoveQueueItemsModal extends Component {
const {
isOpen,
selectedCount,
canIgnore
canIgnore,
allPending
} = this.props;
const { remove, blacklist } = this.state;
const { remove, blocklist } = this.state;
return (
<Modal
@@ -89,30 +90,34 @@ class RemoveQueueItemsModal extends Component {
Are you sure you want to remove {selectedCount} item{selectedCount > 1 ? 's' : ''} from the queue?
</div>
<FormGroup>
<FormLabel>Remove From Download Client</FormLabel>
{
allPending ?
null :
<FormGroup>
<FormLabel>Remove From Download Client</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning="Removing will remove the download and the file(s) from the download client."
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning="Removing will remove the download and the file(s) from the download client."
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup>
<FormLabel>
Blacklist Release{selectedCount > 1 ? 's' : ''}
Add Release{selectedCount > 1 ? 's' : ''} To Blocklist
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blacklist"
value={blacklist}
name="blocklist"
value={blocklist}
helpText="Prevents Sonarr from automatically grabbing this episode again"
onChange={this.onBlacklistChange}
onChange={this.onBlocklistChange}
/>
</FormGroup>
@@ -140,6 +145,7 @@ RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired,
allPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -154,7 +154,7 @@ class AddNewSeries extends Component {
<div className={styles.noResults}>Couldn't find any results for '{term}'</div>
<div>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
<div>
<Link to="https://github.com/Sonarr/Sonarr/wiki/FAQ#why-cant-i-add-a-new-series-when-i-know-the-tvdb-id">
<Link to="https://wiki.servarr.com/sonarr/faq#why-cant-i-add-a-new-series-when-i-know-the-tvdb-id">
Why can't I find my show?
</Link>
</div>

View File

@@ -30,9 +30,7 @@ class AddNewSeriesModalContent extends Component {
this.state = {
seriesType: props.initialSeriesType === seriesTypes.STANDARD ?
props.seriesType.value :
props.initialSeriesType,
searchForMissingEpisodes: false,
searchForCutoffUnmetEpisodes: false
props.initialSeriesType
};
}
@@ -45,14 +43,6 @@ class AddNewSeriesModalContent extends Component {
//
// Listeners
onSearchForMissingEpisodesChange = ({ value }) => {
this.setState({ searchForMissingEpisodes: value });
}
onSearchForCutoffUnmetEpisodesChange = ({ value }) => {
this.setState({ searchForCutoffUnmetEpisodes: value });
}
onQualityProfileIdChange = ({ value }) => {
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
}
@@ -63,14 +53,10 @@ class AddNewSeriesModalContent extends Component {
onAddSeriesPress = () => {
const {
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
seriesType
} = this.state;
this.props.onAddSeriesPress(
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
seriesType
);
}
@@ -91,6 +77,8 @@ class AddNewSeriesModalContent extends Component {
languageProfileId,
seriesType,
seasonFolder,
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
folder,
tags,
showLanguageProfile,
@@ -101,11 +89,6 @@ class AddNewSeriesModalContent extends Component {
...otherProps
} = this.props;
const {
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -264,29 +247,29 @@ class AddNewSeriesModalContent extends Component {
<div>
<label className={styles.searchLabelContainer}>
<span className={styles.searchLabel}>
Start search for missing episodes
Start search for missing episodes
</span>
<CheckInput
containerClassName={styles.searchInputContainer}
className={styles.searchInput}
name="searchForMissingEpisodes"
value={searchForMissingEpisodes}
onChange={this.onSearchForMissingEpisodesChange}
onChange={onInputChange}
{...searchForMissingEpisodes}
/>
</label>
<label className={styles.searchLabelContainer}>
<span className={styles.searchLabel}>
Start search for cutoff unmet episodes
Start search for cutoff unmet episodes
</span>
<CheckInput
containerClassName={styles.searchInputContainer}
className={styles.searchInput}
name="searchForCutoffUnmetEpisodes"
value={searchForCutoffUnmetEpisodes}
onChange={this.onSearchForCutoffUnmetEpisodesChange}
onChange={onInputChange}
{...searchForCutoffUnmetEpisodes}
/>
</label>
</div>
@@ -319,6 +302,8 @@ AddNewSeriesModalContent.propTypes = {
languageProfileId: PropTypes.object,
seriesType: PropTypes.object.isRequired,
seasonFolder: PropTypes.object.isRequired,
searchForMissingEpisodes: PropTypes.object.isRequired,
searchForCutoffUnmetEpisodes: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
tags: PropTypes.object.isRequired,
showLanguageProfile: PropTypes.bool.isRequired,

View File

@@ -55,7 +55,7 @@ class AddNewSeriesModalContentConnector extends Component {
this.props.setAddSeriesDefault({ [name]: value });
}
onAddSeriesPress = (searchForMissingEpisodes, searchForCutoffUnmetEpisodes, seriesType) => {
onAddSeriesPress = (seriesType) => {
const {
tvdbId,
rootFolderPath,
@@ -63,6 +63,8 @@ class AddNewSeriesModalContentConnector extends Component {
qualityProfileId,
languageProfileId,
seasonFolder,
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes,
tags
} = this.props;
@@ -74,9 +76,9 @@ class AddNewSeriesModalContentConnector extends Component {
languageProfileId: languageProfileId.value,
seriesType,
seasonFolder: seasonFolder.value,
tags: tags.value,
searchForMissingEpisodes,
searchForCutoffUnmetEpisodes
searchForMissingEpisodes: searchForMissingEpisodes.value,
searchForCutoffUnmetEpisodes: searchForCutoffUnmetEpisodes.value,
tags: tags.value
});
}
@@ -102,6 +104,8 @@ AddNewSeriesModalContentConnector.propTypes = {
languageProfileId: PropTypes.object,
seriesType: PropTypes.object.isRequired,
seasonFolder: PropTypes.object.isRequired,
searchForMissingEpisodes: PropTypes.object.isRequired,
searchForCutoffUnmetEpisodes: PropTypes.object.isRequired,
tags: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired,
setAddSeriesDefault: PropTypes.func.isRequired,

View File

@@ -105,7 +105,7 @@ class AddNewSeriesSearchResult extends Component {
{
!title.contains(year) && year ?
<span className={styles.year}>
({year})
({year})
</span> :
null
}
@@ -168,7 +168,7 @@ class AddNewSeriesSearchResult extends Component {
kind={kinds.DANGER}
size={sizes.LARGE}
>
Ended
Ended
</Label> :
null
}
@@ -179,7 +179,7 @@ class AddNewSeriesSearchResult extends Component {
kind={kinds.INFO}
size={sizes.LARGE}
>
Upcoming
Upcoming
</Label> :
null
}

View File

@@ -174,7 +174,7 @@ class ImportSeriesSelectSeries extends Component {
kind={kinds.WARNING}
/>
No match found!
No match found!
</div> :
null
}
@@ -189,7 +189,7 @@ class ImportSeriesSelectSeries extends Component {
kind={kinds.WARNING}
/>
Search failed, please try again later.
Search failed, please try again later.
</div> :
null
}

View File

@@ -30,3 +30,9 @@
.importButtonIcon {
margin-right: 8px;
}
.addErrorAlert {
composes: alert from '~Components/Alert.css';
margin: 20px 0;
}

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons, kinds, sizes } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -47,21 +48,27 @@ class ImportSeriesSelectFolder extends Component {
isWindows,
isFetching,
isPopulated,
isSaving,
error,
saveError,
items
} = this.props;
const hasRootFolders = items.length > 0;
return (
<PageContent title="Import Series">
<PageContentBody>
{
isFetching && !isPopulated &&
<LoadingIndicator />
isFetching && !isPopulated ?
<LoadingIndicator /> :
null
}
{
!isFetching && !!error &&
<div>Unable to load root folders</div>
!isFetching && error ?
<div>Unable to load root folders</div> :
null
}
{
@@ -78,13 +85,16 @@ class ImportSeriesSelectFolder extends Component {
Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
</li>
<li className={styles.tip}>
Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span> Additionally, each series must be in its own folder within the root/library folder.
</li>
<li className={styles.tip}>
Do not use for importing downloads from your download client, this is only for existing organized libraries, not unsorted files.
</li>
</ul>
</div>
{
items.length > 0 ?
hasRootFolders ?
<div className={styles.recentFolders}>
<FieldSet legend="Root Folders">
<RootFolders
@@ -94,35 +104,51 @@ class ImportSeriesSelectFolder extends Component {
items={items}
/>
</FieldSet>
<Button
kind={kinds.PRIMARY}
size={sizes.LARGE}
onPress={this.onAddNewRootFolderPress}
>
<Icon
className={styles.importButtonIcon}
name={icons.DRIVE}
/>
Choose another folder
</Button>
</div> :
<div className={styles.startImport}>
<Button
kind={kinds.PRIMARY}
size={sizes.LARGE}
onPress={this.onAddNewRootFolderPress}
>
<Icon
className={styles.importButtonIcon}
name={icons.DRIVE}
/>
Start Import
</Button>
</div>
null
}
{
!isSaving && saveError ?
<Alert
className={styles.addErrorAlert}
kind={kinds.DANGER}
>
Unable to add root folder
<ul>
{
saveError.responseJSON.map((e, index) => {
return (
<li key={index}>
{e.errorMessage}
</li>
);
})
}
</ul>
</Alert> :
null
}
<div className={hasRootFolders ? undefined : styles.startImport}>
<Button
kind={kinds.PRIMARY}
size={sizes.LARGE}
onPress={this.onAddNewRootFolderPress}
>
<Icon
className={styles.importButtonIcon}
name={icons.DRIVE}
/>
{
hasRootFolders ?
'Choose another folder' :
'Start Import'
}
</Button>
</div>
<FileBrowserModal
isOpen={this.state.isAddNewRootFolderModalOpen}
name="rootFolderPath"
@@ -142,7 +168,9 @@ ImportSeriesSelectFolder.propTypes = {
isWindows: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired,
error: PropTypes.object,
saveError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
onNewRootFolderSelect: PropTypes.func.isRequired
};

View File

@@ -13,7 +13,7 @@ import SeriesDetailsPageConnector from 'Series/Details/SeriesDetailsPageConnecto
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import HistoryConnector from 'Activity/History/HistoryConnector';
import QueueConnector from 'Activity/Queue/QueueConnector';
import BlacklistConnector from 'Activity/Blacklist/BlacklistConnector';
import BlocklistConnector from 'Activity/Blocklist/BlocklistConnector';
import MissingConnector from 'Wanted/Missing/MissingConnector';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
import Settings from 'Settings/Settings';
@@ -118,8 +118,8 @@ function AppRoutes(props) {
/>
<Route
path="/activity/blacklist"
component={BlacklistConnector}
path="/activity/blocklist"
component={BlocklistConnector}
/>
{/*

View File

@@ -25,7 +25,7 @@
}
.time {
flex: 0 0 120px;
flex: 0 0 125px;
margin-right: 10px;
border: none !important;
}

View File

@@ -1,3 +1,5 @@
$fullColorGradient: rgba(244, 245, 246, 0.2);
.event {
overflow-x: hidden;
margin: 4px 2px;
@@ -36,6 +38,11 @@
margin-left: 3px;
}
.statusContainer {
display: flex;
align-items: center;
}
.statusIcon {
margin-left: 3px;
}
@@ -51,6 +58,10 @@
.downloaded {
border-left-color: $successColor !important;
&:global(.fullColor) {
background-color: rgba(39, 194, 76, 0.4) !important;
}
&:global(.colorImpaired) {
border-left-color: color($successColor, saturation(+15%)) !important;
}
@@ -58,37 +69,73 @@
.downloading {
border-left-color: $purple !important;
&:global(.fullColor) {
background-color: rgba(122, 67, 182, 0.4) !important;
}
}
.unmonitored {
border-left-color: $gray !important;
&:global(.fullColor) {
background-color: rgba(173, 173, 173, 0.5) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}
.onAir {
border-left-color: $warningColor !important;
&:global(.fullColor) {
background-color: rgba(255, 165, 0, 0.6) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}
.missing {
border-left-color: $dangerColor !important;
&:global(.fullColor) {
background-color: rgba(240, 80, 80, 0.6) !important;
}
&:global(.colorImpaired) {
border-left-color: color($dangerColor saturation(+15%)) !important;
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}
.unaired {
border-left-color: $primaryColor !important;
&:global(.fullColor) {
background-color: rgba(93, 156, 236, 0.4) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}

View File

@@ -1,6 +1,6 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import classNames from 'classnames';
import { icons, kinds } from 'Helpers/Props';
import formatTime from 'Utilities/Date/formatTime';
@@ -62,6 +62,7 @@ class CalendarEvent extends Component {
showFinaleIcon,
showSpecialIcon,
showCutoffUnmetIcon,
fullColorEvents,
timeFormat,
colorImpairedMode
} = this.props;
@@ -80,12 +81,13 @@ class CalendarEvent extends Component {
const seasonStatistics = season.statistics || {};
return (
<div>
<Fragment>
<Link
className={classNames(
styles.event,
styles[statusStyle],
colorImpairedMode && 'colorImpaired'
colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)}
component="div"
onPress={this.onPress}
@@ -95,95 +97,107 @@ class CalendarEvent extends Component {
{series.title}
</div>
{
missingAbsoluteNumber &&
<Icon
className={styles.statusIcon}
name={icons.WARNING}
title="Episode does not have an absolute episode number"
/>
}
<div className={styles.statusContainer}>
{
missingAbsoluteNumber ?
<Icon
className={styles.statusIcon}
name={icons.WARNING}
title="Episode does not have an absolute episode number"
/> :
null
}
{
!!queueItem &&
<span className={styles.statusIcon}>
<CalendarEventQueueDetails
{...queueItem}
/>
</span>
}
{
queueItem ?
<span className={styles.statusIcon}>
<CalendarEventQueueDetails
{...queueItem}
/>
</span> :
null
}
{
!queueItem && grabbed &&
<Icon
className={styles.statusIcon}
name={icons.DOWNLOADING}
title="Episode is downloading"
/>
}
{
!queueItem && grabbed ?
<Icon
className={styles.statusIcon}
name={icons.DOWNLOADING}
title="Episode is downloading"
/> :
null
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.qualityCutoffNotMet &&
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={kinds.WARNING}
title="Quality cutoff has not been met"
/>
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.qualityCutoffNotMet ?
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title="Quality cutoff has not been met"
/> :
null
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.languageCutoffNotMet &&
!episodeFile.qualityCutoffNotMet &&
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={kinds.WARNING}
title="Language cutoff has not been met"
/>
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.languageCutoffNotMet &&
!episodeFile.qualityCutoffNotMet ?
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title="Language cutoff has not been met"
/> :
null
}
{
episodeNumber === 1 && seasonNumber > 0 &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.INFO}
title={seasonNumber === 1 ? 'Series premiere' : 'Season premiere'}
/>
}
{
episodeNumber === 1 && seasonNumber > 0 ?
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.INFO}
darken={fullColorEvents}
title={seasonNumber === 1 ? 'Series premiere' : 'Season premiere'}
/> :
null
}
{
showFinaleIcon &&
episodeNumber !== 1 &&
seasonNumber > 0 &&
episodeNumber === seasonStatistics.totalEpisodeCount &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.WARNING}
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
/>
}
{
showFinaleIcon &&
episodeNumber !== 1 &&
seasonNumber > 0 &&
episodeNumber === seasonStatistics.totalEpisodeCount ?
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
/> :
null
}
{
showSpecialIcon &&
(episodeNumber === 0 || seasonNumber === 0) &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.PINK}
title="Special"
/>
}
{
showSpecialIcon &&
(episodeNumber === 0 || seasonNumber === 0) ?
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.PINK}
darken={fullColorEvents}
title="Special"
/> :
null
}
</div>
</div>
{
showEpisodeInformation &&
showEpisodeInformation ?
<div className={styles.episodeInfo}>
<div className={styles.episodeTitle}>
{title}
@@ -193,11 +207,12 @@ class CalendarEvent extends Component {
{seasonNumber}x{padNumber(episodeNumber, 2)}
{
series.seriesType === 'anime' && absoluteEpisodeNumber &&
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span>
series.seriesType === 'anime' && absoluteEpisodeNumber ?
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span> : null
}
</div>
</div>
</div> :
null
}
<div className={styles.airTime}>
@@ -214,7 +229,7 @@ class CalendarEvent extends Component {
showOpenSeriesButton={true}
onModalClose={this.onDetailsModalClose}
/>
</div>
</Fragment>
);
}
}
@@ -236,6 +251,7 @@ CalendarEvent.propTypes = {
showFinaleIcon: PropTypes.bool.isRequired,
showSpecialIcon: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired,
onEventModalOpenToggle: PropTypes.func.isRequired

View File

@@ -74,6 +74,7 @@ class CalendarEventGroup extends Component {
showEpisodeInformation,
showFinaleIcon,
timeFormat,
fullColorEvents,
colorImpairedMode,
onEventModalOpenToggle
} = this.props;
@@ -133,7 +134,8 @@ class CalendarEventGroup extends Component {
className={classNames(
styles.eventGroup,
styles[statusStyle],
colorImpairedMode && 'colorImpaired'
colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)}
>
<div className={styles.info}>
@@ -144,7 +146,7 @@ class CalendarEventGroup extends Component {
{
isMissingAbsoluteNumber &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.WARNING}
title="Episode does not have an absolute episode number"
/>
@@ -153,7 +155,7 @@ class CalendarEventGroup extends Component {
{
anyDownloading &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.DOWNLOADING}
title="An episode is downloading"
/>
@@ -162,9 +164,10 @@ class CalendarEventGroup extends Component {
{
firstEpisode.episodeNumber === 1 && seasonNumber > 0 &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.INFO}
kind={kinds.INFO}
darken={fullColorEvents}
title={seasonNumber === 1 ? 'Series Premiere' : 'Season Premiere'}
/>
}
@@ -175,9 +178,9 @@ class CalendarEventGroup extends Component {
seasonNumber > 0 &&
lastEpisode.episodeNumber === series.seasons.find((season) => season.seasonNumber === seasonNumber).statistics.totalEpisodeCount &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.INFO}
kind={kinds.WARNING}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
/>
}
@@ -237,6 +240,7 @@ CalendarEventGroup.propTypes = {
isDownloading: PropTypes.bool.isRequired,
showEpisodeInformation: PropTypes.bool.isRequired,
showFinaleIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired,
onEventModalOpenToggle: PropTypes.func.isRequired

View File

@@ -7,19 +7,23 @@ import styles from './Legend.css';
function Legend(props) {
const {
view,
showFinaleIcon,
showSpecialIcon,
showCutoffUnmetIcon,
fullColorEvents,
colorImpairedMode
} = props;
const iconsToShow = [];
const isAgendaView = view === 'agenda';
if (showFinaleIcon) {
iconsToShow.push(
<LegendIconItem
name="Finale"
icon={icons.INFO}
kind={kinds.WARNING}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
tooltip="Series or season finale"
/>
);
@@ -31,6 +35,7 @@ function Legend(props) {
name="Special"
icon={icons.INFO}
kind={kinds.PINK}
darken={fullColorEvents}
tooltip="Special episode"
/>
);
@@ -41,7 +46,7 @@ function Legend(props) {
<LegendIconItem
name="Cutoff Not Met"
icon={icons.EPISODE_FILE}
kind={kinds.WARNING}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
tooltip="Quality or language cutoff has not been met"
/>
);
@@ -53,12 +58,16 @@ function Legend(props) {
<LegendItem
status="unaired"
tooltip="Episode hasn't aired yet"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="unmonitored"
tooltip="Episode is unmonitored"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
@@ -68,12 +77,16 @@ function Legend(props) {
status="onAir"
name="On Air"
tooltip="Episode is currently airing"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="missing"
tooltip="Episode has aired and is missing from disk"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
@@ -82,12 +95,16 @@ function Legend(props) {
<LegendItem
status="downloading"
tooltip="Episode is currently downloading"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="downloaded"
tooltip="Episode was downloaded and sorted"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
@@ -97,6 +114,7 @@ function Legend(props) {
name="Premiere"
icon={icons.INFO}
kind={kinds.INFO}
darken={true}
tooltip="Series or season premiere"
/>
@@ -115,9 +133,11 @@ function Legend(props) {
}
Legend.propTypes = {
view: PropTypes.string.isRequired,
showFinaleIcon: PropTypes.bool.isRequired,
showSpecialIcon: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
};

View File

@@ -6,10 +6,12 @@ import Legend from './Legend';
function createMapStateToProps() {
return createSelector(
(state) => state.calendar.options,
(state) => state.calendar.view,
createUISettingsSelector(),
(calendarOptions, uiSettings) => {
(calendarOptions, view, uiSettings) => {
return {
...calendarOptions,
view,
colorImpairedMode: uiSettings.enableColorImpairedMode
};
}

View File

@@ -8,6 +8,7 @@ function LegendIconItem(props) {
name,
icon,
kind,
darken,
tooltip
} = props;
@@ -19,6 +20,7 @@ function LegendIconItem(props) {
<Icon
className={styles.icon}
name={icon}
darken={darken}
kind={kind}
/>
@@ -31,7 +33,12 @@ LegendIconItem.propTypes = {
name: PropTypes.string.isRequired,
icon: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired,
darken: PropTypes.bool.isRequired,
tooltip: PropTypes.string.isRequired
};
LegendIconItem.defaultProps = {
darken: false
};
export default LegendIconItem;

View File

@@ -9,6 +9,8 @@ function LegendItem(props) {
name,
status,
tooltip,
isAgendaView,
fullColorEvents,
colorImpairedMode
} = props;
@@ -17,7 +19,8 @@ function LegendItem(props) {
className={classNames(
styles.legendItem,
styles[status],
colorImpairedMode && 'colorImpaired'
colorImpairedMode && 'colorImpaired',
fullColorEvents && !isAgendaView && 'fullColor'
)}
title={tooltip}
>
@@ -30,6 +33,8 @@ LegendItem.propTypes = {
name: PropTypes.string,
status: PropTypes.string.isRequired,
tooltip: PropTypes.string.isRequired,
isAgendaView: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
};

View File

@@ -25,14 +25,16 @@ class CalendarOptionsModalContent extends Component {
firstDayOfWeek,
calendarWeekColumnHeader,
timeFormat,
enableColorImpairedMode
enableColorImpairedMode,
fullColorEvents
} = props;
this.state = {
firstDayOfWeek,
calendarWeekColumnHeader,
timeFormat,
enableColorImpairedMode
enableColorImpairedMode,
fullColorEvents
};
}
@@ -96,6 +98,7 @@ class CalendarOptionsModalContent extends Component {
showFinaleIcon,
showSpecialIcon,
showCutoffUnmetIcon,
fullColorEvents,
onModalClose
} = this.props;
@@ -174,6 +177,18 @@ class CalendarOptionsModalContent extends Component {
onChange={this.onOptionInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Full Color Events</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="fullColorEvents"
value={fullColorEvents}
helpText="Altered style to color the entire event with the status color, instead of just the left edge. Does not apply to Agenda"
onChange={this.onOptionInputChange}
/>
</FormGroup>
</Form>
</FieldSet>
@@ -214,7 +229,9 @@ class CalendarOptionsModalContent extends Component {
value={timeFormat}
onChange={this.onGlobalInputChange}
/>
</FormGroup><FormGroup>
</FormGroup>
<FormGroup>
<FormLabel>Enable Color-Impaired Mode</FormLabel>
<FormInputGroup
@@ -225,7 +242,6 @@ class CalendarOptionsModalContent extends Component {
onChange={this.onGlobalInputChange}
/>
</FormGroup>
</Form>
</FieldSet>
</ModalBody>
@@ -250,6 +266,7 @@ CalendarOptionsModalContent.propTypes = {
calendarWeekColumnHeader: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
enableColorImpairedMode: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
dispatchSetCalendarOption: PropTypes.func.isRequired,
dispatchSaveUISettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -1,7 +1,7 @@
export const APPLICATION_UPDATE = 'ApplicationUpdate';
export const BACKUP = 'Backup';
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
export const CLEAR_BLACKLIST = 'ClearBlacklist';
export const CLEAR_BLOCKLIST = 'ClearBlocklist';
export const CLEAR_LOGS = 'ClearLog';
export const CUTOFF_UNMET_EPISODE_SEARCH = 'CutoffUnmetEpisodeSearch';
export const DELETE_LOG_FILES = 'DeleteLogFiles';

View File

@@ -16,4 +16,9 @@
color: #3a3f51;
font-size: 21px;
line-height: inherit;
&.small {
color: #909293;
font-size: 18px;
}
}

View File

@@ -1,5 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import { sizes } from 'Helpers/Props';
import styles from './FieldSet.css';
class FieldSet extends Component {
@@ -9,13 +11,14 @@ class FieldSet extends Component {
render() {
const {
size,
legend,
children
} = this.props;
return (
<fieldset className={styles.fieldSet}>
<legend className={styles.legend}>
<legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}>
{legend}
</legend>
{children}
@@ -26,8 +29,13 @@ class FieldSet extends Component {
}
FieldSet.propTypes = {
size: PropTypes.oneOf(sizes.all).isRequired,
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
children: PropTypes.node
};
FieldSet.defaultProps = {
size: sizes.MEDIUM
};
export default FieldSet;

View File

@@ -128,7 +128,7 @@ class FileBrowserModalContent extends Component {
className={styles.mappedDrivesWarning}
kind={kinds.WARNING}
>
Mapped network drives are not available when running as a Windows Service, see the <Link className={styles.faqLink} to="https://github.com/Sonarr/Sonarr/wiki/FAQ">FAQ</Link> for more information.
Mapped network drives are not available when running as a Windows Service, see the <Link className={styles.faqLink} to="https://wiki.servarr.com/sonarr/faq#why-cant-sonarr-see-my-files-on-a-remote-server">FAQ</Link> for more information.
</Alert>
}

View File

@@ -12,6 +12,7 @@ import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
import SeriesStatusFilterBuilderRowValue from './SeriesStatusFilterBuilderRowValue';
import SeriesTypeFilterBuilderRowValue from './SeriesTypeFilterBuilderRowValue';
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
import styles from './FilterBuilderRow.css';
@@ -75,6 +76,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.SERIES_STATUS:
return SeriesStatusFilterBuilderRowValue;
case filterBuilderValueTypes.SERIES_TYPES:
return SeriesTypeFilterBuilderRowValue;
case filterBuilderValueTypes.TAG:
return TagFilterBuilderRowValueConnector;

View File

@@ -16,7 +16,7 @@ function createTagListSelector() {
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
filterType !== filterTypes.EQUAL &&
filterType !== filterBuilderTypes.NOT_EQUAL ||
filterType !== filterTypes.NOT_EQUAL ||
!selectedFilterBuilderProp.optionsSelector
) {
return [];

View File

@@ -1,5 +1,5 @@
.tag {
height: 21px;
display: flex;
&.isLastTag {
.or {
@@ -18,4 +18,5 @@
.or {
margin: 0 3px;
color: $themeDarkColor;
line-height: 31px;
}

View File

@@ -6,7 +6,7 @@ import styles from './FilterBuilderRowValueTag.css';
function FilterBuilderRowValueTag(props) {
return (
<span
<div
className={styles.tag}
>
<TagInputTag
@@ -15,12 +15,13 @@ function FilterBuilderRowValueTag(props) {
/>
{
!props.isLastTag &&
<span className={styles.or}>
props.isLastTag ?
null :
<div className={styles.or}>
or
</span>
</div>
}
</span>
</div>
);
}

View File

@@ -0,0 +1,19 @@
import React from 'react';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const seriesTypeList = [
{ id: 'anime', name: 'Anime' },
{ id: 'daily', name: 'Daily' },
{ id: 'standard', name: 'Standard' }
];
function SeriesTypeFilterBuilderRowValue(props) {
return (
<FilterBuilderRowValue
tagList={seriesTypeList}
{...props}
/>
);
}
export default SeriesTypeFilterBuilderRowValue;

View File

@@ -24,7 +24,7 @@ function CustomFiltersModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Custom Filters
Custom Filters
</ModalHeader>
<ModalBody>
@@ -58,7 +58,7 @@ function CustomFiltersModalContent(props) {
<Button
onPress={onModalClose}
>
Close
Close
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
import { fetchOptions, clearOptions, defaultState } from 'Store/Actions/providerOptionActions';
import DeviceInput from './DeviceInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
(state) => state.providerOptions,
(state) => state.providerOptions.devices || defaultState,
(value, devices) => {
return {
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
}
componentWillUnmount = () => {
this.props.dispatchClearOptions();
this.props.dispatchClearOptions({ section: 'devices' });
}
//
@@ -65,6 +65,7 @@ class DeviceInputConnector extends Component {
} = this.props;
dispatchFetchOptions({
section: 'devices',
action: 'getDevices',
provider,
providerData

View File

@@ -5,6 +5,10 @@
align-items: center;
}
.editableContainer {
width: 100%;
}
.hasError {
composes: hasError from '~Components/Form/Input.css';
}
@@ -22,6 +26,16 @@
margin-left: 12px;
}
.dropdownArrowContainerEditable {
position: absolute;
top: 0;
right: 0;
padding-right: 17px;
width: 30%;
height: 35px;
text-align: right;
}
.dropdownArrowContainerDisabled {
composes: dropdownArrowContainer;
@@ -66,3 +80,26 @@
border-radius: 4px;
background-color: $white;
}
.loading {
display: inline-block;
margin: 5px -5px 5px 0;
}
.mobileCloseButtonContainer {
display: flex;
justify-content: flex-end;
height: 40px;
border-bottom: 1px solid $borderColor;
}
.mobileCloseButton {
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
&:hover {
color: $modalCloseButtonHoverColor;
}
}

View File

@@ -10,10 +10,12 @@ import { icons, sizes, scrollDirections } from 'Helpers/Props';
import Icon from 'Components/Icon';
import Portal from 'Components/Portal';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Measure from 'Components/Measure';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
import Scroller from 'Components/Scroller/Scroller';
import TextInput from './TextInput';
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
import HintedSelectInputOption from './HintedSelectInputOption';
import styles from './EnhancedSelectInput.css';
@@ -168,11 +170,21 @@ class EnhancedSelectInput extends Component {
}
}
onFocus = () => {
if (this.state.isOpen) {
this._removeListener();
this.setState({ isOpen: false });
}
}
onBlur = () => {
// Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
const origIndex = getSelectedIndex(this.props);
if (origIndex !== this.state.selectedIndex) {
this.setState({ selectedIndex: origIndex });
if (!this.props.isEditable) {
// Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
const origIndex = getSelectedIndex(this.props);
if (origIndex !== this.state.selectedIndex) {
this.setState({ selectedIndex: origIndex });
}
}
}
@@ -250,6 +262,10 @@ class EnhancedSelectInput extends Component {
this._addListener();
}
if (!this.state.isOpen && this.props.onOpen) {
this.props.onOpen();
}
this.setState({ isOpen: !this.state.isOpen });
}
@@ -292,15 +308,19 @@ class EnhancedSelectInput extends Component {
const {
className,
disabledClassName,
name,
value,
values,
isDisabled,
isEditable,
isFetching,
hasError,
hasWarning,
valueOptions,
selectedValueOptions,
selectedValueComponent: SelectedValueComponent,
optionComponent: OptionComponent
optionComponent: OptionComponent,
onChange
} = this.props;
const {
@@ -326,40 +346,94 @@ class EnhancedSelectInput extends Component {
whitelist={['width']}
onMeasure={this.onMeasure}
>
<Link
className={classNames(
className,
hasError && styles.hasError,
hasWarning && styles.hasWarning,
isDisabled && disabledClassName
)}
isDisabled={isDisabled}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
onPress={this.onPress}
>
<SelectedValueComponent
value={value}
values={values}
{...selectedValueOptions}
{...selectedOption}
isDisabled={isDisabled}
isMultiSelect={isMultiSelect}
>
{selectedOption ? selectedOption.value : null}
</SelectedValueComponent>
{
isEditable ?
<div
className={styles.editableContainer}
>
<TextInput
className={className}
name={name}
value={value}
readOnly={isDisabled}
hasError={hasError}
hasWarning={hasWarning}
onFocus={this.onFocus}
onBlur={this.onBlur}
onChange={onChange}
/>
<Link
className={classNames(
styles.dropdownArrowContainerEditable,
isDisabled ?
styles.dropdownArrowContainerDisabled :
styles.dropdownArrowContainer)
}
onPress={this.onPress}
>
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
<div
className={isDisabled ?
styles.dropdownArrowContainerDisabled :
styles.dropdownArrowContainer
}
>
<Icon
name={icons.CARET_DOWN}
/>
</div>
</Link>
{
!isFetching &&
<Icon
name={icons.CARET_DOWN}
/>
}
</Link>
</div> :
<Link
className={classNames(
className,
hasError && styles.hasError,
hasWarning && styles.hasWarning,
isDisabled && disabledClassName
)}
isDisabled={isDisabled}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
onPress={this.onPress}
>
<SelectedValueComponent
value={value}
values={values}
{...selectedValueOptions}
{...selectedOption}
isDisabled={isDisabled}
isMultiSelect={isMultiSelect}
>
{selectedOption ? selectedOption.value : null}
</SelectedValueComponent>
<div
className={isDisabled ?
styles.dropdownArrowContainerDisabled :
styles.dropdownArrowContainer
}
>
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
{
!isFetching &&
<Icon
name={icons.CARET_DOWN}
/>
}
</div>
</Link>
}
</Measure>
</div>
)}
@@ -444,6 +518,18 @@ class EnhancedSelectInput extends Component {
scrollDirection={scrollDirections.NONE}
>
<Scroller className={styles.optionsModalScroller}>
<div className={styles.mobileCloseButtonContainer}>
<Link
className={styles.mobileCloseButton}
onPress={this.onOptionsModalClose}
>
<Icon
name={icons.CLOSE}
size={18}
/>
</Link>
</div>
{
values.map((v, index) => {
const hasParent = v.parentKey !== undefined;
@@ -483,12 +569,15 @@ EnhancedSelectInput.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
isEditable: PropTypes.bool.isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
valueOptions: PropTypes.object.isRequired,
selectedValueOptions: PropTypes.object.isRequired,
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
optionComponent: PropTypes.elementType,
onOpen: PropTypes.func,
onChange: PropTypes.func.isRequired
};
@@ -496,6 +585,8 @@ EnhancedSelectInput.defaultProps = {
className: styles.enhancedSelect,
disabledClassName: styles.isDisabled,
isDisabled: false,
isFetching: false,
isEditable: false,
valueOptions: {},
selectedValueOptions: {},
selectedValueComponent: HintedSelectInputSelectedValue,

View File

@@ -0,0 +1,159 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchOptions, clearOptions, defaultState } from 'Store/Actions/providerOptionActions';
import EnhancedSelectInput from './EnhancedSelectInput';
const importantFieldNames = [
'baseUrl',
'apiPath',
'apiKey'
];
function getProviderDataKey(providerData) {
if (!providerData || !providerData.fields) {
return null;
}
const fields = providerData.fields
.filter((f) => importantFieldNames.includes(f.name))
.map((f) => f.value);
return fields;
}
function getSelectOptions(items) {
if (!items) {
return [];
}
return items.map((option) => {
return {
key: option.value,
value: option.name,
hint: option.hint,
parentKey: option.parentValue
};
});
}
function createMapStateToProps() {
return createSelector(
(state, { selectOptionsProviderAction }) => state.providerOptions[selectOptionsProviderAction] || defaultState,
(options) => {
if (options) {
return {
isFetching: options.isFetching,
values: getSelectOptions(options.items)
};
}
}
);
}
const mapDispatchToProps = {
dispatchFetchOptions: fetchOptions,
dispatchClearOptions: clearOptions
};
class EnhancedSelectInputConnector extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
refetchRequired: false
};
}
componentDidMount = () => {
this._populate();
}
componentDidUpdate = (prevProps) => {
const prevKey = getProviderDataKey(prevProps.providerData);
const nextKey = getProviderDataKey(this.props.providerData);
if (!_.isEqual(prevKey, nextKey)) {
this.setState({ refetchRequired: true });
}
}
componentWillUnmount = () => {
this._cleanup();
}
//
// Listeners
onOpen = () => {
if (this.state.refetchRequired) {
this._populate();
}
}
//
// Control
_populate() {
const {
provider,
providerData,
selectOptionsProviderAction,
dispatchFetchOptions
} = this.props;
if (selectOptionsProviderAction) {
this.setState({ refetchRequired: false });
dispatchFetchOptions({
section: selectOptionsProviderAction,
action: selectOptionsProviderAction,
provider,
providerData
});
}
}
_cleanup() {
const {
selectOptionsProviderAction,
dispatchClearOptions
} = this.props;
if (selectOptionsProviderAction) {
dispatchClearOptions({ section: selectOptionsProviderAction });
}
}
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onOpen={this.onOpen}
/>
);
}
}
EnhancedSelectInputConnector.propTypes = {
provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
selectOptionsProviderAction: PropTypes.string,
onChange: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
dispatchFetchOptions: PropTypes.func.isRequired,
dispatchClearOptions: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(EnhancedSelectInputConnector);

View File

@@ -20,7 +20,7 @@
.optionCheckInput {
composes: input from '~./CheckInput.css';
margin-top: 0px;
margin-top: 0;
}
.isSelected {
@@ -54,4 +54,8 @@
&:last-child {
border: none;
}
&:hover {
background-color: unset;
}
}

View File

@@ -12,7 +12,9 @@ class EnhancedSelectInputOption extends Component {
//
// Listeners
onPress = () => {
onPress = (e) => {
e.preventDefault();
const {
id,
onSelect
@@ -32,6 +34,7 @@ class EnhancedSelectInputOption extends Component {
const {
className,
id,
depth,
isSelected,
isDisabled,
isHidden,
@@ -54,6 +57,11 @@ class EnhancedSelectInputOption extends Component {
onPress={this.onPress}
>
{
depth !== 0 &&
<div style={{ width: `${depth * 20}px` }} />
}
{
isMultiSelect &&
<CheckInput
@@ -84,6 +92,7 @@ class EnhancedSelectInputOption extends Component {
EnhancedSelectInputOption.propTypes = {
className: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
depth: PropTypes.number.isRequired,
isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
isHidden: PropTypes.bool.isRequired,
@@ -95,6 +104,7 @@ EnhancedSelectInputOption.propTypes = {
EnhancedSelectInputOption.defaultProps = {
className: styles.option,
depth: 0,
isDisabled: false,
isHidden: false,
isMultiSelect: false

View File

@@ -18,9 +18,12 @@ import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
import SeriesTypeSelectInput from './SeriesTypeSelectInput';
import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import TagInputConnector from './TagInputConnector';
import TagSelectInputConnector from './TagSelectInputConnector';
import TextTagInputConnector from './TextTagInputConnector';
import TextInput from './TextInput';
import UMaskInput from './UMaskInput';
import FormInputHelpText from './FormInputHelpText';
import styles from './FormInputGroup.css';
@@ -71,6 +74,9 @@ function getComponent(type) {
case inputTypes.SELECT:
return EnhancedSelectInput;
case inputTypes.DYNAMIC_SELECT:
return EnhancedSelectInputConnector;
case inputTypes.SERIES_TYPE_SELECT:
return SeriesTypeSelectInput;
@@ -80,6 +86,12 @@ function getComponent(type) {
case inputTypes.TEXT_TAG:
return TextTagInputConnector;
case inputTypes.TAG_SELECT:
return TagSelectInputConnector;
case inputTypes.UMASK:
return UMaskInput;
default:
return TextInput;
}
@@ -187,7 +199,7 @@ function FormInputGroup(props) {
}
{
!checkInput && helpTextWarning &&
(!checkInput || helpText) && helpTextWarning &&
<FormInputHelpText
text={helpTextWarning}
isWarning={true}

View File

@@ -9,6 +9,7 @@ function HintedSelectInputOption(props) {
id,
value,
hint,
depth,
isSelected,
isDisabled,
isMultiSelect,
@@ -19,6 +20,7 @@ function HintedSelectInputOption(props) {
return (
<EnhancedSelectInputOption
id={id}
depth={depth}
isSelected={isSelected}
isDisabled={isDisabled}
isHidden={isDisabled}
@@ -48,6 +50,7 @@ HintedSelectInputOption.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.string.isRequired,
hint: PropTypes.node,
depth: PropTypes.number,
isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
isMultiSelect: PropTypes.bool.isRequired,

View File

@@ -3,10 +3,17 @@ 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) {
e.preventDefault();
e.nativeEvent.stopImmediatePropagation();
}
function PasswordInput(props) {
return (
<TextInput
{...props}
onCopy={onCopy}
/>
);
}

View File

@@ -6,7 +6,7 @@ import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
function getType(type, value) {
function getType({ type, selectOptionsProviderAction }) {
switch (type) {
case 'captcha':
return inputTypes.CAPTCHA;
@@ -23,9 +23,14 @@ function getType(type, value) {
case 'filePath':
return inputTypes.PATH;
case 'select':
if (selectOptionsProviderAction) {
return inputTypes.DYNAMIC_SELECT;
}
return inputTypes.SELECT;
case 'tag':
return inputTypes.TEXT_TAG;
case 'tagSelect':
return inputTypes.TAG_SELECT;
case 'textbox':
return inputTypes.TEXT;
case 'oAuth':
@@ -85,7 +90,7 @@ function ProviderFieldFormGroup(props) {
<FormLabel>{label}</FormLabel>
<FormInputGroup
type={getType(type, value)}
type={getType(props)}
name={name}
label={label}
helpText={helpText}
@@ -105,7 +110,8 @@ function ProviderFieldFormGroup(props) {
const selectOptionsShape = {
name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired
value: PropTypes.number.isRequired,
hint: PropTypes.string
};
ProviderFieldFormGroup.propTypes = {
@@ -122,6 +128,7 @@ ProviderFieldFormGroup.propTypes = {
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,
selectOptions: PropTypes.arrayOf(PropTypes.shape(selectOptionsShape)),
selectOptionsProviderAction: PropTypes.string,
onChange: PropTypes.func.isRequired
};

View File

@@ -10,13 +10,16 @@ const ADD_NEW_KEY = 'addNew';
function createMapStateToProps() {
return createSelector(
(state) => state.rootFolders,
(state, { value }) => value,
(state, { includeMissingValue }) => includeMissingValue,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, includeNoChange) => {
(rootFolders, value, includeMissingValue, includeNoChange) => {
const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
freeSpace: rootFolder.freeSpace
freeSpace: rootFolder.freeSpace,
isMissing: false
};
});
@@ -24,7 +27,8 @@ function createMapStateToProps() {
values.unshift({
key: 'noChange',
value: 'No Change',
isDisabled: true
isDisabled: true,
isMissing: false
});
}
@@ -37,6 +41,15 @@ function createMapStateToProps() {
});
}
if (includeMissingValue && !values.find((v) => v.key === value)) {
values.push({
key: value,
value,
isMissing: true,
isDisabled: true
});
}
values.push({
key: ADD_NEW_KEY,
value: 'Add a new path'

View File

@@ -27,3 +27,9 @@
color: $darkGray;
font-size: $smallFontSize;
}
.isMissing {
margin-left: 15px;
color: $dangerColor;
font-size: $smallFontSize;
}

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