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

Compare commits

...

256 Commits

Author SHA1 Message Date
Taloth Saldono
808836a65f Help text 2020-02-15 01:30:27 +01:00
Taloth Saldono
6f7e505bed Added Enabled & IndexerId to Edit Release Profile UI 2020-02-14 23:50:50 +01:00
Jacob
ecf1d75954 ui v1 2020-02-14 23:21:22 +01:00
netpok
c5257de436 New: Added aired-before field to kodi metadata to sort specials
closes #3073
2020-02-14 22:42:24 +01:00
Taloth Saldono
cde5a6d1a4 Fixed stylelint errors 2020-02-11 21:41:16 +01:00
Taloth Saldono
b601c8bcfe New: Added advanced subtitle/audio language filter to {MediaInfo ..}
closes #3367
2020-02-11 21:13:13 +01:00
Taloth Saldono
023c8260f2 Added Norwegian Bokmal alias 2020-02-11 20:14:10 +01:00
Taloth Saldono
51e2e084af Added try-catch for DateTime.TryParse edgecase
closes #3518
2020-02-09 17:05:45 +01:00
Taloth Saldono
fc5dd8137f Support for VS2019 build environment 2020-02-07 21:16:53 +01:00
Taloth Saldono
268fc46ef7 Fixed: Representation of episode start time when not starting at the full hour in am/pm notation 2020-02-01 22:50:16 +01:00
Mark McDowall
010c65af9c Fixed: Don't monitor new seasons if series is not monitored
Fixes #3547
2020-02-01 13:03:11 -08:00
Mark McDowall
db42256dc3 Improve default series type handling (for daily series)
New: Display default series type when adding new/existing series when available
Fixed: Don't override series type on series refresh
2020-01-31 17:51:30 -08:00
Mark McDowall
e9b537b6e6 Fixed: Rejecting import for a release that was grabbed again 2020-01-31 17:51:30 -08:00
Mark McDowall
c615ef476a Fixed: Typo in unmonitored series tooltip
Fixes #3538
2020-01-31 17:51:30 -08:00
Mark McDowall
b93e8da235 Fixed: Force grabbing selected delayed items in queue 2020-01-31 17:51:30 -08:00
Pika
74a0a57468 BTN: Fix name 2020-01-19 18:40:06 +01:00
Петр Шургалин
b19d665817 Fixed: RestClient does not use global proxy settings 2020-01-19 16:41:31 +01:00
Taloth Saldono
10dc884fa8 Fixed: Posters not always showing when searching for new shows 2020-01-12 22:27:56 +01:00
Taloth Saldono
d8446c2d5a New: Added tvdb Upcoming series status 2020-01-12 22:27:55 +01:00
Mark McDowall
d3cd46bb51 New: Limit recent folders in Manual import to 10 and descending order
Closes #3491
2020-01-07 17:36:57 -08:00
Mark McDowall
bc0da03caf Fix proptype warning for id of EnhancedSelectInputOption 2020-01-07 17:11:45 -08:00
Mark McDowall
c0a356261b New: Added help text for qualities in groups
Closes #3495
2020-01-07 17:00:12 -08:00
Mark McDowall
fa4060b7fe Fixed: Previously imported downloads reappear in queue
Fixes #3496
2020-01-07 16:55:13 -08:00
Taloth Saldono
29117fc222 Fixed missing interface for the CheckForFinishedDownloadCommand backward compat handling
fixes #3492
2020-01-05 14:37:52 +01:00
julakali
24ba5e5bda Use msbuild instead of the deprecated xbuild 2020-01-04 17:54:25 -08:00
gl3nni3
2d94857369 Fixed: Replace duplicate slashes from file names when importing
Fixes #3470
2020-01-04 17:52:45 -08:00
Mark McDowall
c6ea7d7e63 Option to ignore items when removing from queue instead of removing from client
New: Option to not remove item from download client when removing from queue

Closes #1710
2020-01-04 17:49:39 -08:00
Mark McDowall
3916495329 Monitor and Process downloads separately
New: Queue remains up to date while importing file from remote file system
Fixed: Failed downloads still in queue won't result in failed search

Closes #668
Closes #907
Fixes #2973
2020-01-04 17:49:39 -08:00
Mark McDowall
4e965e59a9 Fixed: Parsing of Extended Multi-episode format file names 2019-12-30 09:27:24 -08:00
Taloth Saldono
0acb3aa32b Fixed: Regression in Multi-Episode format parser in previous release
fixes #3481
2019-12-30 13:06:25 +01:00
Mark McDowall
9189d8bf4d Fixed: Parsing of poorly named double episode releases
Fixes #3439
2019-12-29 02:32:30 -08:00
Mark McDowall
ec0c96bde4 Remove website prefixes with dashes in URL 2019-12-29 02:32:30 -08:00
Mark McDowall
562c8c4afe Fixed: Improved quality parsing from truncated release names
Closes #3345
2019-12-29 02:32:30 -08:00
Mark McDowall
fd6d4493c4 Fixed: Details for episode history flashing on mobile devices 2019-12-29 02:32:30 -08:00
Jayden
1a2419e096 Fix typo in remove queue item modal 2019-12-29 01:49:49 -08:00
Taloth Saldono
b86cfd49ef Fixed redirect test 2019-12-24 11:52:26 +01:00
Taloth Saldono
d421ff9736 Increased max redirects from 3 to 5
closes #3449
2019-12-24 11:27:58 +01:00
Taloth Saldono
92c61701f2 Fixed: Imports of multi-episode files did not trigger the download completion event and thus apply the PostImport category for supported download clients
fixes #3403
2019-12-24 11:27:58 +01:00
Wu Haotian
d45d9e356c New: Improve Chinese language detection 2019-12-24 11:26:30 +01:00
Fossil
098f9a2675 Remove PFMonkey.com from Presets
Indexer closed in 2018
2019-12-24 11:06:58 +01:00
Fossil
0347dab82e Add new X265 category to NZB Finder 2019-12-24 11:06:58 +01:00
Taloth Saldono
9aa89a0df9 Fixed: Inserting literal { or } in renaming format using {{ or }}
fixes #3434
2019-12-24 10:58:47 +01:00
Taloth Saldono
556bd11725 Disable pooling rather than clearing it 2019-12-18 23:09:10 +01:00
Taloth Saldono
07f5c21a07 Clear the connection pool in the backdoor migration to prevent occasional conflicts with following migrations 2019-12-18 20:39:31 +01:00
Taloth Saldono
93b20960b8 Fixed regex in Backup list 2019-12-08 11:00:56 +01:00
Taloth Saldono
3cbdd6bfd3 Fixed: Rare scenario where early Radarr version messes up Sonarr database 2019-12-07 21:56:44 +01:00
Mark McDowall
c3c38880e6 Fixed: Test All not clearing health error
Fixes #3409
2019-12-06 17:42:40 -08:00
Mark McDowall
415bbf5b3b Fixed: Update deleted series health after refreshing series 2019-12-06 17:42:40 -08:00
Taloth Saldono
186cb02748 Added NUnit3TestAdapter nuget so it can work without VS extension 2019-12-06 19:49:27 +01:00
Taloth Saldono
4aaccb909f Cleanse getnzb url 2019-12-06 19:49:27 +01:00
Jef LeCompte
2daf7dd01a Fixed: Handle qBittorrent "moving" state 2019-12-04 09:02:39 -08:00
Taloth Saldono
ab9ed73e55 New: Added version number to backup filename 2019-11-28 21:23:48 +01:00
Mark McDowall
a4a33fe167 Fixed: Letter jump bar on series list not working correctly with banners 2019-11-26 17:41:40 -08:00
Mark McDowall
e6fbd10031 Improved some log messages 2019-11-26 17:41:40 -08:00
Mark McDowall
9868d96fec Fixed: Delete files from Series Mass Editor not actually deleting files 2019-11-26 17:41:40 -08:00
Mark McDowall
0d1c2ac40c Fixed react error when displaying a series search result for an existing series 2019-11-26 17:41:40 -08:00
Mark McDowall
a6d0dddaf7 Fixed: Trying to add a series when root folders hadn't populated
Fixed #3387
2019-11-26 17:41:40 -08:00
Taloth Saldono
06d57e8f32 Fixed: Refresh Deleted & Upcoming shows as frequently as Continuing ones 2019-11-24 23:47:22 +01:00
Taloth Saldono
70a40edc5d Tiny fix in test, left-over from my on-windows test. 2019-11-19 21:55:48 +01:00
Taloth Saldono
95d64208d0 Fixed: File imports on cloud drives slow due to transaction logic 2019-11-19 17:35:19 +01:00
Taloth Saldono
e28b2e8328 Fixed: Corrupt image files when downloading from redirecting Url
closes #3401
2019-11-15 18:39:31 +01:00
Mark McDowall
4123745a6b Fixed: Interactive search results failing to show when processing failed
Closes #3377
2019-11-08 09:12:42 -08:00
Mark McDowall
70bb4d71e6 Fixed: Parsing of poor standard file names using dashes for separators 2019-11-03 09:26:39 -08:00
Mark McDowall
dd314e1741 Fixed: Deletion of empty episode sub folders when an episode file is deleted 2019-11-03 09:04:39 -08:00
Mark McDowall
3cbb489ac6 Default id for MenuContent 2019-10-30 10:25:55 -07:00
Mark McDowall
101df4cbf1 Ensure Season Folder Format will correctly replace illegal characters 2019-10-30 09:41:40 -07:00
Mark McDowall
42263a0ec0 Fixed: Set Default Sort Key for Blacklist Endpoint 2019-10-30 09:41:13 -07:00
Mark McDowall
d402f7514e Fixed: Manual Import failing to show files when processing fails 2019-10-28 12:50:09 -07:00
Mark McDowall
84e6674e23 Fixed: Actually run Recycle Bin cleanup 2019-10-28 12:50:09 -07:00
Mark McDowall
afcfaace19 Fixed: Remove background from apple-touch-icon 2019-10-28 12:50:09 -07:00
Mark McDowall
5a3bd8cfe5 Fixed: Set permissions on extra and subtitle files 2019-10-28 12:50:09 -07:00
Mark McDowall
f0c90a4744 Fixed: Log matching scene mapping for title 2019-10-28 12:50:09 -07:00
Mark McDowall
3baed292e1 Fixed: Allow Interactive Season Search when all episodes are unmonitored 2019-10-28 12:50:09 -07:00
Mark McDowall
d41a2cad73 Fixed: Include releases that failed to parse in search results 2019-10-28 12:50:09 -07:00
Mark McDowall
ffccc3be38 Fixed: Kodi episode metadata missing uniqueid
Fixes #3308
2019-10-28 12:50:09 -07:00
Mark McDowall
ef1e8d7ef3 Fixed: Don't parse packs missing season number 2019-10-28 12:50:09 -07:00
Taloth Saldono
7af891216d Another failing test 2019-10-20 22:11:10 +02:00
Taloth Saldono
aa80500b35 Missing test on EventDrivenHealthCheck 2019-10-20 21:51:12 +02:00
Taloth Saldono
b72fbe06f7 Adding missing series Deleted UI elements 2019-10-20 21:38:15 +02:00
Taloth Saldono
41a63a5418 Fixed test failing due to rng 2019-10-20 20:58:12 +02:00
Taloth Saldono
e8ce7898c1 use TestContext.Progress rather than Console.WriteLine for NzbDroneRunner output 2019-10-20 20:22:28 +02:00
Taloth Saldono
687a45c564 Added docker to run tests on various mono versions 2019-10-20 20:21:22 +02:00
ta264
3ac3dd3ca5 New: Swap to ImageSharp library for resizing posters 2019-10-20 20:21:22 +02:00
Qstick
f2efebf7d9 New: Option to send notification when a Health Check warning occurs
closes #3253
2019-10-19 17:32:08 +02:00
ta264
7b68ce49d5 Fix .gitattributes and normalize to LF in repository
Existing `*text eol=lf` is malformed (no space after *) so does
nothing.

CONTRIBUTING.md states 'We checkout Windows and commit *nix'.  The
correct way to achieve this is `* text=auto`. `* text eol=lf` would
force line endings to LF on checkout.

See:
https://git-scm.com/docs/gitattributes#Documentation/gitattributes.txt-Settostringvaluelf
2019-10-19 17:16:45 +02:00
Taloth Saldono
8a2a41fab0 New: Added health check warning to emphasis when a series was deleted instead of only logging it in System Events 2019-10-19 17:15:38 +02:00
Taloth Saldono
ceaaec5378 New: Parsing Saison season packs as alternative to Season
Just because we're in a good mood
2019-10-18 21:00:51 +02:00
sirloinofbeef
e49a3e7206 Updated XBMC notification strings to Kodi 2019-09-17 11:50:44 -07:00
Taloth Saldono
dc7986dbad Fixed regression in container registration. Additional logging in case of integration test startup failures 2019-09-13 17:11:20 +02:00
Taloth Saldono
2dfba130f5 Split up _tests into windows and linux 2019-09-13 12:29:31 +02:00
Taloth Saldono
155c7c409b Moved Platform version determination to static 2019-09-13 12:26:27 +02:00
Taloth Saldono
aacb8970f8 Fixed several failing/flaky mono unit tests 2019-09-13 11:41:18 +02:00
ta264
be66a0520d Fix integration tests on linux with debug build 2019-09-12 18:39:07 +02:00
Rhys Braunschweig
3fa3c45794 Add digits to Deluge's category validator 2019-09-09 22:59:50 +02:00
ta264
0f6da1873e Update unity 2019-09-09 22:59:01 +02:00
Taloth Saldono
1564208e83 Fixed tests 2019-09-07 15:07:06 +02:00
Taloth Saldono
e724e8db60 Fixed: Copy linux permission mask when moving folder to recycle bin folder
fixes #3161
2019-09-07 12:13:22 +02:00
Taloth Saldono
5a092a83cd Fixed: Disregard Real when user disabled proper preference 2019-09-07 12:12:57 +02:00
Taloth Saldono
ffefe5e8aa And a bunch of video codecs. Also fixed the dual-video channel issue. 2019-09-05 00:32:18 +02:00
Taloth Saldono
631fdd8a26 New: Additional Atmos detection in MediaInfo
ref Radarr/Radarr#3712
2019-09-04 23:26:17 +02:00
Taloth Saldono
53d7ef4014 Fixed third-party clients calling api without Accept header 2019-09-01 15:58:01 +02:00
Taloth Saldono
5c3ac79043 Removed obsolete code. 2019-09-01 11:28:07 +02:00
ta264
90fb1646e0 Updated Nancy to 2.0 2019-09-01 11:20:08 +02:00
ta264
54604e45e0 Emacs gitignore 2019-08-31 22:23:39 +02:00
Taloth Saldono
9ed0f9eee8 Fixed DownloadFile when file already exists 2019-08-31 21:35:01 +02:00
Taloth Saldono
b764c44318 Fixed tests 2019-08-31 12:25:13 +02:00
Taloth Saldono
adbd519061 noreferrer for images to allow images to be loaded from tvdb 2019-08-30 23:47:18 +02:00
Taloth Saldono
b0415299ca Fixed: Download mediacover using configured proxy.
closes #3283
2019-08-30 23:36:30 +02:00
Taloth Saldono
e96d05149c Added missing SentryEnabled check 2019-08-30 20:40:35 +02:00
Taloth Saldono
354ddcfee5 Fixed: Removed .Net update notice on Windows LTSB 2015 2019-08-30 20:39:54 +02:00
Taloth Saldono
6d232778e2 Fixed: Root Folder display when free diskspace cannot be determined (FreeBSD)
closes #3275
2019-08-27 23:48:34 +02:00
Taloth Saldono
95ee7daf21 New: Added Auth-* log entries for fail2ban purposes
closes #2760
2019-08-27 23:29:16 +02:00
Taloth Saldono
2238ac5d17 Fixed: Added missing ca-certificates-mono dependency to debian package
closes #3257
2019-08-26 23:07:15 +02:00
Taloth Saldono
c209c1c034 Typo 2019-08-26 22:35:21 +02:00
Taloth Saldono
b1eec16333 Updated way Sentry gets configured and enabled. 2019-08-26 22:35:21 +02:00
Taloth Saldono
e126c45fb3 Added BuildInfo.AppName to centralize 'Sonarr' 2019-08-26 22:35:21 +02:00
Taloth Saldono
c89ff93be4 Revised webpack bundling 2019-08-26 22:33:19 +02:00
Taloth Saldono
c82c27a5c5 Added active detection for updatecheck so we know which os/runtime versions don't need to be supported anymore. 2019-08-26 22:33:19 +02:00
Taloth Saldono
b3e84f407a New: Removed libcurl http fallback since mono 5.16+ doesn't need it. Also bumped minimum mono version check to 5.16 (5.20 is the best choice atm) 2019-08-26 22:33:19 +02:00
Taloth Saldono
72902c8984 Test framework version 2019-08-24 01:39:16 +02:00
Taloth Saldono
2c47c5eb99 Fixed: Missing debian package dependency and made them optional. 2019-08-24 01:23:04 +02:00
Taloth Saldono
398129f3e1 Cleanup obsolete files 2019-08-23 21:20:54 +02:00
Taloth Saldono
d74ab12d9e Flaky CommandExecutorFixture tests 2019-08-22 23:28:17 +02:00
Taloth Saldono
679c0599dd Flaky CommandExecutorFixture tests 2019-08-22 22:20:39 +02:00
Taloth Saldono
4d04ad5632 Fixed typos 2019-08-22 21:58:57 +02:00
Taloth Saldono
3fdc50b354 Fixed flaky test by flushing logs and getting them via the api 2019-08-22 21:49:06 +02:00
Taloth Saldono
7eeff32185 Another Daily format with no series title. 2019-08-21 21:43:36 +02:00
Taloth Saldono
d40f2cb852 Fixed assembly configuration/branch attribute generation. 2019-08-21 21:02:14 +02:00
Qstick
f9dc2fb6d5 New: Replace SharpRaven with new Sentry SDK
Co-Authored-By: ta264 <ta264@users.noreply.github.com>
2019-08-20 19:39:49 +02:00
Taloth Saldono
de31dfb11e Fixed several tests and test infrastructure issues 2019-08-20 19:39:49 +02:00
Taloth Saldono
ef6a648189 Fixed Automation Tests for Firefox and Sonarr v3 UI. 2019-08-20 19:39:49 +02:00
Taloth Saldono
09953e2af8 Updated xmlrpc and SocksProxy 2019-08-20 19:39:49 +02:00
Taloth Saldono
be240119e8 Updated Test harnass, NUnit to 3.12.0, NBuilder to 6.0.0, Moq to 4.12.0, FluentAssertions to 5.8.0 2019-08-20 19:39:49 +02:00
Taloth Saldono
2b7893c834 Updated NLog to 4.6.6, Newtonsoft.Json to 12.0.2, RestSharp to 106.6.10 2019-08-20 19:39:49 +02:00
Taloth Saldono
896e824ca1 Updated FluentValidation to 8.4.0 2019-08-20 19:39:49 +02:00
Taloth Saldono
7a94725808 Updated SharpZipLib to 1.2.0 2019-08-20 19:39:49 +02:00
Taloth Saldono
a66fb76e9a Converted all projects to the new csproj format. 2019-08-20 19:39:49 +02:00
Taloth Saldono
b453d48fee Removed excluded source files. 2019-08-20 19:39:49 +02:00
Mark McDowall
a7f2c07998 Fixed: Improve parsing of anime file names without standard release group/hash
Closes #3117
2019-08-17 13:05:20 -07:00
Mark McDowall
3cff878f74 New: Parse more poor p2p file naming
Closes #3266
2019-08-17 12:41:42 -07:00
Mark McDowall
665d536481 Fixed: Infinite spinner when toggling seasons on multiple series from season pass 2019-08-16 22:46:12 -07:00
Mark McDowall
ec6d407fbb Fixed: Special title matching when special title has an apostrophe
Closes #2872
2019-08-16 21:48:43 -07:00
Mark McDowall
72bc7ed6d4 Fixed: Waiting a long time for unavailable root folders
Closes #2877
2019-08-16 20:54:03 -07:00
Mark McDowall
ac407ca2c0 New: Show Hardlink/Copy in manual import 2019-08-16 00:05:22 -07:00
Mark McDowall
6af5f2b528 New: URL Base support for NZBVortex, Hadouken, qBittorrent and uTorrent
Closes #1651
2019-08-16 00:04:53 -07:00
Mark McDowall
8fd4a98fbe New: Sort by series year in series list
Closes #3245
2019-08-15 23:28:54 -07:00
Mark McDowall
07d553fae3 New: Sort preferred words in profile on save
Closes #3241
2019-08-15 23:23:36 -07:00
Mark McDowall
34e0eea173 Menu fixes
Fixed: Menus in modals on iOS
Fixed: Menu not closing on outside touch on mobile
2019-08-15 22:51:30 -07:00
Mark McDowall
73e6db9a12 Fixed: Scrolling of modals with tabular content in iOS
Fixes #3264
2019-08-15 22:48:39 -07:00
Mark McDowall
0df464ac03 Fixed prop type warning on MenuItem 2019-08-15 21:06:04 -07:00
Mark McDowall
78ee6afbae Fixed: Episode Progress custom filtering on series list page 2019-08-15 18:12:19 -07:00
Mark McDowall
31be74e6d3 New: Add Tabula Rasa Newznab Preset 2019-08-15 00:40:44 -07:00
Mark McDowall
767a09894a Health UI improvements
Fixed: Failing to get items from SABnzbd will report health error properly
Fixed: Some health checks not showing test all button on System: Health
2019-08-15 00:24:09 -07:00
datahodor
0d410d107d New: Treat MaxdomeHD as Web-DL 2019-08-14 23:22:07 -07:00
Ken Murphy
7829b18b3c New: User configurable minimum free disk space
Closes #3233
2019-08-14 22:14:59 -07:00
Mark McDowall
b2267a55ce New: Improved parsing of poorly named multi-episode anime-like releases
Closes #3259
2019-08-14 20:50:12 -07:00
Mark McDowall
eca016fe61 Fixed: Prevent moving to recycling bin causing a failed import 2019-08-13 17:31:05 -07:00
Mark McDowall
81723f7fa9 Retry HttpLogFixture 2019-08-12 22:18:06 -07:00
Mark McDowall
44c91fb90c Fixed: Ensure correct series is used for Manual File Import from series details page 2019-08-12 21:57:11 -07:00
Mark McDowall
7cb5bd9c95 And another one to retry 2019-08-11 00:30:11 -07:00
Mark McDowall
8196f6b9db New: Cleanup Recycling Bin folders older than X days (0 to disable) 2019-08-11 00:22:26 -07:00
Mark McDowall
d72b16531b New: Add TVDB Link to add new series search result 2019-08-11 00:03:22 -07:00
Mark McDowall
f333196efe Retry up to 5 times for disk tests that sometimes fail 2019-08-10 23:14:39 -07:00
Wu Haotian
6ea047dcb4 New: Add support for Lilith-Raws release group 2019-08-09 19:05:40 +02:00
Mark McDowall
ea65867b23 Fixed: Logging file release group for repack 2019-08-08 10:26:27 -07:00
Mark McDowall
c41200e762 csproj update to match the file rename 2019-08-07 20:14:47 -07:00
Mark McDowall
4c70afbb53 Make powershell test explicit 2019-08-07 19:10:29 -07:00
Qstick
0c1ce66053 Cleanup Multiple Compiler Warnings 2019-08-07 19:08:03 -07:00
rbraunschweig
fa8b8cebf9 More repost exclusions to clean release group 2019-08-07 18:57:39 -07:00
Mark McDowall
72fa89ba76 Fixed: Repack don't being grabbed when cutoff already met
Fixes #3250
2019-08-06 19:52:49 -07:00
Mark McDowall
dc7b4cebf2 New: Add warning that recycle bin will be cleaned up automatically after 1 week 2019-08-06 19:52:49 -07:00
emyarod
3c1dd94915 Fix README hyperlink formatting 2019-08-06 17:27:27 -07:00
Mark McDowall
3decbbac3a Fix RARBG parsing test 2019-08-06 08:54:50 -07:00
Mark McDowall
27f43569f5 Fixed: Edge case where import fails due to DB relationship mismatch
Closes #3243
2019-08-05 13:55:19 -07:00
Mark McDowall
bd9bded73b Fixed: Improved failed series search messaging
Closes #3187
2019-08-04 09:01:12 -07:00
Mark McDowall
0ce81e1ab6 Fix SeriesFolderAsRootFolderValidator 2019-08-04 08:38:36 -07:00
Mark McDowall
2926201694 Fixed: RARBG links in Interactive Search
Fixes #3239
2019-08-04 01:01:40 -07:00
Mark McDowall
059be2c853 New: Add root folder from Media Management settings 2019-08-04 01:01:40 -07:00
Mark McDowall
dd09f31abb New: Series folder hint when selecting a root folder while adding a new series 2019-08-04 01:01:40 -07:00
Mark McDowall
1da20da3ff Fixed: Season actions on mobile not indicating when they are disabled 2019-08-04 01:01:40 -07:00
Mark McDowall
341773830b Fixed: Modal scrolling causing app to scroll on iOS 2019-08-04 01:01:40 -07:00
Mark McDowall
c65452bb01 Fixed: Edit path on series index resetting cursor to end on change 2019-08-04 01:01:40 -07:00
Mark McDowall
34d81356a3 New: Limit filenames to a maximum of 255 characters
Closes #2699
2019-08-03 13:20:34 -07:00
Mark McDowall
c9b84a5202 Update yarn.lock 2019-08-02 17:39:27 -07:00
Mark McDowall
5394cc2dc9 Interactive search fixes
Fixed: Sorting of Quality column in Interactive Search
Fixed: column widths in Interactive Search
2019-07-31 14:54:59 -07:00
Mark McDowall
63141f339f New: Bulk select episodes in Manual Import 2019-07-30 19:07:13 -07:00
Mark McDowall
079a0b56c3 Fixed: Manual import from queue showing error when download name failed to parse 2019-07-30 08:44:55 -07:00
Mark McDowall
66721affe7 Fix setup package creation 2019-07-28 09:52:34 -07:00
Mark McDowall
d8ab23e9ba Fix setup package creation 2019-07-28 00:23:19 -07:00
Mark McDowall
11b0a5c9cc Appease eslint 2019-07-26 22:07:49 -07:00
Mark McDowall
d273a72cb3 Recycle bin file cleanup
Fixed: Recycle bin will clean up files older than 7 days and remove empty folders left behind
2019-07-26 22:07:25 -07:00
Mark McDowall
08641a6694 Update redux 2019-07-26 18:14:13 -07:00
Mark McDowall
9ac5c0d886 Upgrade sentry 2019-07-26 18:14:13 -07:00
Mark McDowall
8a57d33223 Upgrade del 2019-07-26 18:13:47 -07:00
Mark McDowall
1945b53e43 Upgrade various packages 2019-07-26 18:13:47 -07:00
Mark McDowall
161e1820a8 Upgrade React DND 2019-07-26 18:13:47 -07:00
Mark McDowall
22b5ac8622 Update react router packages 2019-07-26 18:13:47 -07:00
Mark McDowall
f75a70e42b Update react packages 2019-07-26 18:13:47 -07:00
Mark McDowall
c9076606be Upgrade CSS packages 2019-07-26 18:13:47 -07:00
Mark McDowall
853d22b947 Upgrade font awesome 2019-07-26 18:13:47 -07:00
Mark McDowall
82d6719d91 Upgrade linter packages 2019-07-26 18:13:47 -07:00
Mark McDowall
3f02c150e3 Set corejs version 2019-07-26 18:13:47 -07:00
Mark McDowall
d7d46a93a7 Update webpack packages 2019-07-26 18:13:47 -07:00
Mark McDowall
dfd51635a6 Upgrade gulp tooling 2019-07-26 18:13:47 -07:00
Mark McDowall
c04366d505 Update babel packages 2019-07-26 18:13:47 -07:00
Mark McDowall
fd89e88d40 Fixed: Manage Episodes not showing whether language/quality meets cutoff 2019-07-26 18:10:02 -07:00
Mark McDowall
894de923b9 Fixed: Don't reject standard/absolute numbering mismatch due to season number 2019-07-26 17:59:41 -07:00
Mark McDowall
c47e7cd91d Fixed: Canceling editing a custom filter won't close the Custom filter modal 2019-07-26 17:52:05 -07:00
Mark McDowall
e359347a3b Fixed: Anime season searches rejecting season packs 2019-07-26 17:52:05 -07:00
Mark McDowall
d320017e3c Cleanup migration 131 2019-07-26 17:52:05 -07:00
devbrian
e6c34f4311 Fixed: Season mismatch between file and folder not rejecting import 2019-07-26 17:51:54 -07:00
Mark McDowall
9b0c945086 Fix NZBGet Delete Status Copy test 2019-07-25 16:48:03 -07:00
Mark McDowall
bc85f5de1d Add logging to Windows setup 2019-07-25 07:49:32 -07:00
Mark McDowall
4df219161c New: Dim episode/air time on calendar 2019-07-24 20:08:26 -07:00
Mark McDowall
06f157e634 Fixed: Tags in settings getting cutoff 2019-07-24 19:59:27 -07:00
Mark McDowall
54addbdd28 Fixed: Don't ignore Delete:Copy items in NZBGet 2019-07-22 21:15:27 -07:00
Mark McDowall
0e721917e7 Fix stylelint once and for all (hopefully) 2019-07-22 19:44:07 -07:00
Mark McDowall
4dc7089f89 Fixed: Add tooltip to tag delete button when in use 2019-07-22 10:43:40 -07:00
Mark McDowall
30b5a35db2 Fixed: tag input alignment and height 2019-07-22 10:43:40 -07:00
Mark McDowall
dc8f81b536 Double instead of single quotes in CSS 2019-07-22 10:43:40 -07:00
Mark McDowall
a018770a18 UI fixes 2019-07-22 10:43:40 -07:00
Mark McDowall
5e4f7c5d8e Minor cleanup 2019-07-22 10:43:40 -07:00
Mark McDowall
1d9d665ed0 Fixed: Stripping subtitles from series titles after parsing
Closes #3219
2019-07-22 10:43:40 -07:00
linxchaos
68477c09a7 Improve grammar in Import Series 2019-07-21 17:13:31 -07:00
Mark McDowall
574b9086d4 Fix oAuth actions in UI 2019-07-17 17:25:20 -07:00
Mark McDowall
d74c323c66 Remove unused prop 2019-07-17 08:16:35 -07:00
Mark McDowall
8e85a1b84e Remove unused import 2019-07-16 22:54:54 -07:00
Mark McDowall
6ce1cb4325 Refetch series when signalR reconnects 2019-07-16 22:42:32 -07:00
Mark McDowall
fbbd85d8b2 Fix boolean for title prop warning 2019-07-16 22:42:22 -07:00
Mark McDowall
e611dc43c5 Fix stylelint 2019-07-16 16:30:00 -07:00
Mark McDowall
c86309cfc0 New: Show relative file name when selecting episode in Manual Import
Closes #3197
2019-07-16 10:52:54 -07:00
Taloth Saldono
ec74e9bce0 Incremented package version 2019-07-14 21:57:29 +02:00
Taloth Saldono
f371e8a523 Fixed stylelint errors 2019-07-14 21:57:29 +02:00
Taloth Saldono
57a059eecb Updated debian install script to handle old nzbdrone systemd unit named sonarr.service 2019-07-14 21:57:29 +02:00
Taloth Saldono
2cb149c647 Added alternative libcurl4 dependency to satisfy ubuntu cosmic. 2019-07-14 21:57:29 +02:00
Taloth Saldono
ee5371b582 Added alternative libmediainfo0 dependency for debian jessie
closes #3205
2019-07-14 21:57:29 +02:00
Taloth Saldono
70e4dbe3bd Updated debian build to fix stray msbuild dependency 2019-07-14 21:57:29 +02:00
Taloth Saldono
18ead9a64f Added MediaInfo AudioLanguagesAll.
closes #3190
2019-07-14 12:14:31 +02:00
Taloth Saldono
d2764cee2a Fixed: Heavy qbit api load when CDH Remove is disabled and Seeding time has been reached
ref #3108
2019-07-14 12:13:54 +02:00
Mark McDowall
082c098420 New: Include HDR is naming examples
Closes #3199
2019-07-11 17:51:48 -07:00
Mark McDowall
46a42e2901 New: Update examples for Kodi metadata
Closes #3201
2019-07-11 17:45:29 -07:00
Mark McDowall
c21cacd309 Fixed: Monitoring latest season ignoring unaired episodes
Fixes #3200
2019-07-11 17:45:04 -07:00
Mark McDowall
81ac359f71 Default to System Tray for Windows installer 2019-07-11 17:43:52 -07:00
Mark McDowall
f5b91c90bc Fixed: Parsing BD release group as Bluray quality 2019-07-09 17:23:50 -07:00
Mark McDowall
0a92a3012e Add warning to remove from queue dialog 2019-07-06 11:27:17 -07:00
Mark McDowall
ff8fc237e2 Darker border for calendar 2019-07-04 19:59:11 -07:00
Mark McDowall
b99d943b4d New: Wider and taller scroll bar for the click to scrollers out there 2019-07-04 19:53:18 -07:00
Mark McDowall
3199fe08e8 Custom Filter improvements
Fixed: Removing Custom Filter left spinner visible
Fixed: Custom filter/tag input being cutoff on iOS
2019-07-04 19:10:59 -07:00
Taloth Saldono
7503ce62af Fixed: Workaround for mono 5.16+ bug preventing the closure of sockets on timeouts (Jackett connections)
ref #2802
2019-07-02 20:52:12 +02:00
Taloth Saldono
df8ca250aa Fixed: Executing powershell and python scripts directly in Connect->Custom Scripts 2019-07-02 20:50:32 +02:00
Taloth Saldono
c71b4bde86 Added test for turkish FirstCharToUpper 2019-07-02 20:32:35 +02:00
Mark McDowall
3f67802e3d Fixed: Delay profile being ignored for non-revision upgrades 2019-07-01 00:45:05 -07:00
Mark McDowall
093ed23140 New: Improve logging when checking if release is an upgrade for an existing file 2019-07-01 00:44:37 -07:00
Mark McDowall
0f8dee7011 New: Treat WEBMux as WebRip
Closes #3186
2019-06-29 15:39:31 -07:00
Mark McDowall
0cb557b716 New: Improve help text for extra file importing 2019-06-29 15:39:31 -07:00
Mark McDowall
8137a776b6 New: Command line arguments for Custom Scripts are no longer supported 2019-06-29 15:33:49 -07:00
759 changed files with 11946 additions and 13806 deletions

24
.gitattributes vendored
View File

@@ -1,22 +1,12 @@
# Auto detect text files and perform LF normalization
*text eol=lf
* text=auto
# Explicitly set bash scripts to have unix endings
# when checked out on windows
*.sh text eol=lf
distribution/debian/* text eol=lf
macOS/Sonarr text eol=lf
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

9
.gitignore vendored
View File

@@ -45,6 +45,10 @@ _dotCover*
# DevExpress CodeRush
src/.cr/
# Emacs
*~
\#*\#
# NCrunch
*.ncrunch*
.*crunch*.local.xml
@@ -115,7 +119,9 @@ node_modules/
_output*
_rawPackage/
_dotTrace*
_tests/
_tests*
_publish*
_temp*
*.Result.xml
setup/Output/
*.~is
@@ -133,6 +139,5 @@ output/*
.DS_Store
_start
_temp_*/**/*
src/.idea/

4
.gitmodules vendored
View File

@@ -1,4 +0,0 @@
[submodule "src/ExternalModules/CurlSharp"]
path = src/ExternalModules/CurlSharp
url = https://github.com/Sonarr/CurlSharp.git
branch = master

View File

@@ -20,7 +20,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
### Requirements
* [Visual Studio 2017] (https://www.visualstudio.com/vs/)
* [Visual Studio 2017](https://www.visualstudio.com/vs/)
* [Git](https://git-scm.com/downloads)
* [NodeJS](https://nodejs.org/en/download/)
* [Yarn](https://yarnpkg.com/)

185
build.sh
View File

@@ -1,15 +1,16 @@
#! /bin/bash
msBuildVersion='15.0'
outputFolder='./_output'
outputFolderWindows='./_output_windows'
outputFolderLinux='./_output_linux'
outputFolderMacOS='./_output_macos'
outputFolderMacOSApp='./_output_macos_app'
testPackageFolder='./_tests/'
testSearchPattern='*.Test/bin/x86/Release'
testPackageFolder='./_tests'
testPackageFolderWindows='./_tests_windows'
testPackageFolderLinux='./_tests_linux'
sourceFolder='./src'
slnFile=$sourceFolder/Sonarr.sln
updateFolder=$outputFolder/Sonarr.Update
updateFolderMono=$outputFolderLinux/Sonarr.Update
updateSubFolder=Sonarr.Update
nuget='tools/nuget/nuget.exe';
vswhere='tools/vswhere/vswhere.exe';
@@ -47,7 +48,8 @@ UpdateVersionNumber()
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
sed -i "s/^[[]assembly: Assembly\(File\|Informational\)\?Version[(]\"[0-9.*]\+\"[)]/[assembly: Assembly\1Version(\"$BUILD_NUMBER\")/g" ./src/NzbDrone*/Properties/AssemblyInfo.cs ./src/Sonarr*/Properties/AssemblyInfo.cs ./src/ServiceHelpers/*/Properties/AssemblyInfo.cs ./src/Common/CommonVersionInfo.cs
sed -i "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$BUILD_NUMBER<\/AssemblyVersion>/g" ./src/Directory.Build.props
sed -i "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BRANCH:-dev}<\/AssemblyConfiguration>/g" ./src/Directory.Build.props
fi
}
@@ -86,13 +88,14 @@ CleanFolder()
BuildWithMSBuild()
{
installationPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -property installationPath`
installationPath=${installationPath/C:\\/\/c\/}
installationPath=${installationPath//\\/\/}
msBuild="$installationPath/MSBuild/$msBuildVersion/Bin"
echo $msBuild
msBuildPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -find MSBuild\\\\\*\*\\\\Bin\\\\MSBuild.exe`
msBuildPath=${msBuildPath/C:\\/\/c\/}
msBuildPath=${msBuildPath//\\/\/}
msBuildDir=$(dirname "$msBuildPath")
export PATH=$msBuild:$PATH
echo $msBuildDir
export PATH=$msBuildDir:$PATH
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
$nuget restore $slnFile
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
@@ -101,15 +104,15 @@ BuildWithMSBuild()
BuildWithXbuild()
{
export MONO_IOMAP=case
CheckExitCode xbuild /t:Clean $slnFile
CheckExitCode msbuild /t:Clean $slnFile
mono $nuget restore $slnFile
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
CheckExitCode msbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
}
LintUI()
{
ProgressStart 'ESLint'
CheckExitCode yarn eslint
CheckExitCode yarn lint
ProgressEnd 'ESLint'
ProgressStart 'Stylelint'
@@ -122,6 +125,7 @@ Build()
ProgressStart 'Build'
rm -rf $outputFolder
rm -rf $testPackageFolder
if [ $runtime = "dotnet" ] ; then
BuildWithMSBuild
@@ -167,6 +171,48 @@ CreateMdbs()
fi
}
PatchMono()
{
local path=$1
# Below we deal with some mono incompatibilities with windows-only dotnet core/standard libs
# See: https://github.com/mono/mono/blob/master/tools/nuget-hash-extractor/download.sh
# That list defines assemblies that are prohibited from being loaded from the appdir, instead loading from mono GAC.
# We have debian dependencies to get these installed or facades from mono 5.10+
for assembly in System.IO.Compression System.Runtime.InteropServices.RuntimeInformation System.Net.Http System.Globalization.Extensions System.Text.Encoding.CodePages System.Threading.Overlapped
do
if [ -e $path/$assembly.dll ]; then
if [ -e $sourceFolder/Libraries/Mono/$assembly.dll ]; then
echo "Copy Mono-specific facade $assembly.dll (uses win32 interop)"
cp $sourceFolder/Libraries/Mono/$assembly.dll $path/$assembly.dll
else
echo "Remove $assembly.dll (uses win32 interop)"
rm $path/$assembly.dll
fi
fi
done
# Copy more stable version of Vectors for mono <5.12
if [ -e $path/System.Numerics.Vectors.dll ]; then
packageDir="$HOME/.nuget/packages/system.numerics.vectors/4.5.0"
if [ ! -d "$HOME/.nuget/packages/system.numerics.vectors/4.5.0" ]; then
# May reside in the NuGetFallback folder, which is harder to find
# Download somewhere to get the real cache populated
if [ $runtime = "dotnet" ] ; then
$nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
else
mono $nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
fi
rm -rf ./_temp/System.Numerics.Vectors
fi
# Copy the netstandard2.0 version rather than net46
cp "$packageDir/lib/netstandard2.0/System.Numerics.Vectors.dll" $path/
fi
}
PackageMono()
{
ProgressStart 'Creating Mono Package'
@@ -190,15 +236,14 @@ PackageMono()
rm -f $outputFolderLinux/sqlite3.*
rm -f $outputFolderLinux/MediaInfo.*
PatchMono $outputFolderLinux
echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderLinux
echo "Adding unix System.Runtime.InteropServices.RuntimeInformation.dll (for SharpRaven)"
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux/Sonarr.Update
# Remove Http binding redirect by renaming it
# We don't need this anymore once our minimum mono version is 5.10
sed -i "s/System.Net.Http/System.Net.Http.Mono/g" $outputFolderLinux/Sonarr.Console.exe.config
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
rm $outputFolderLinux/Sonarr.exe*
@@ -210,7 +255,7 @@ PackageMono()
rm $outputFolderLinux/Sonarr.Windows.*
echo "Adding Sonarr.Mono to UpdatePackage"
cp $outputFolderLinux/Sonarr.Mono.* $updateFolderMono
cp $outputFolderLinux/Sonarr.Mono.* $outputFolderLinux/$updateSubFolder/
ProgressEnd 'Creating Mono Package'
}
@@ -266,54 +311,92 @@ PackageMacOSApp()
ProgressEnd 'Creating macOS App Package'
}
PackageTests()
PackageTestsMono()
{
ProgressStart 'Creating Test Package'
ProgressStart 'Creating Mono Test Package'
rm -rf $testPackageFolder
mkdir $testPackageFolder
rm -rf $testPackageFolderLinux
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
echo "Copying Binaries"
cp -r $testPackageFolder $testPackageFolderLinux
if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
else
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
fi
cp $outputFolder/*.dll $testPackageFolder
cp ./test.sh $testPackageFolder
echo "Creating MDBs"
CreateMdbs $testPackageFolderLinux
echo "Creating MDBs for tests"
CreateMdbs $testPackageFolder
echo "Removing PDBs"
find $testPackageFolderLinux -name "*.pdb" -exec rm "{}" \;
rm -f $testPackageFolder/*.log.config
CleanFolder $testPackageFolder true
PatchMono $testPackageFolderLinux
echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolderLinux
echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder
# Remove Http binding redirect by renaming it
# We don't need this anymore once our minimum mono version is 5.10
sed -i "s/System.Net.Http/System.Net.Http.Mono/g" $testPackageFolderLinux/Sonarr.Common.Test.dll.config
echo "Copying CurlSharp libraries"
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
cp ./test.sh $testPackageFolderLinux/
dos2unix $testPackageFolderLinux/test.sh
ProgressEnd 'Creating Test Package'
echo "Removing Sonarr.Windows"
rm $testPackageFolderLinux/Sonarr.Windows.*
rm -f $testPackageFolderLinux/*.log.config
CleanFolder $testPackageFolderLinux true
ProgressEnd 'Creating Linux Test Package'
}
CleanupWindowsPackage()
PackageTestsWindows()
{
ProgressStart 'Cleaning Windows Package'
ProgressStart 'Creating Windows Test Package'
rm -rf $testPackageFolderWindows
echo "Copying Binaries"
cp -r $testPackageFolder $testPackageFolderWindows
if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
else
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
fi
cp ./test.sh $testPackageFolderWindows
echo "Removing Sonarr.Mono"
rm -f $outputFolder/Sonarr.Mono.*
rm -f $testPackageFolderWindows/Sonarr.Mono.*
rm -f $testPackageFolderWindows/*.log.config
CleanFolder $testPackageFolderWindows true
ProgressEnd 'Creating Windows Test Package'
}
PackageWindows()
{
ProgressStart 'Creating Windows Package'
rm -rf $outputFolderWindows
echo "Copying Binaries"
cp -r $outputFolder $outputFolderWindows
echo "Removing Sonarr.Mono"
rm -f $outputFolderWindows/Sonarr.Mono.*
echo "Adding Sonarr.Windows to UpdatePackage"
cp $outputFolder/Sonarr.Windows.* $updateFolder
cp $outputFolderWindows/Sonarr.Windows.* $outputFolderWindows/$updateSubFolder/
ProgressEnd 'Cleaning Windows Package'
ProgressEnd 'Creating Windows Package'
}
PublishArtifacts()
@@ -321,10 +404,11 @@ PublishArtifacts()
ProgressStart 'Publishing Artifacts'
# Tests
echo "##teamcity[publishArtifacts '_tests/** => tests.zip']"
echo "##teamcity[publishArtifacts '$testPackageFolderWindows/** => tests.windows.zip']"
echo "##teamcity[publishArtifacts '$testPackageFolderLinux/** => tests.linux.zip']"
# Releases
echo "##teamcity[publishArtifacts '$outputFolder/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderWindows/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
@@ -354,6 +438,7 @@ RunGulp
PackageMono
PackageMacOS
PackageMacOSApp
PackageTests
CleanupWindowsPackage
PackageTestsMono
PackageTestsWindows
PackageWindows
PublishArtifacts

View File

@@ -0,0 +1,7 @@
# Note, this script is only used for local dev tests, this is not the script used for building the official sonarr package
mkdir -p /${PWD}/../_output_debian
docker build -f docker-build/Dockerfile -t sonarr-packager ./docker-build
docker run --rm -v /${PWD}/../_output_linux:/data/sonarr_bin:ro -v /${PWD}:/data/build -v /${PWD}/../_output_debian:/data/output sonarr-packager

View File

@@ -19,7 +19,11 @@ sed -i '/#BEGIN BUILTIN UPDATER/,/#END BUILTIN UPDATER/d' debian/preinst debian/
echo "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BuildVersion
debuild -b
if [ -z "${TEST_OUTPUT}" ]; then
debuild -b
else
debuild -us -uc -b
fi
# Restore debian directory to the original files
rm -rf ./debian
@@ -32,16 +36,28 @@ sed -i '/#BEGIN BUILTIN UPDATER/d; /#END BUILTIN UPDATER/d' debian/preinst debia
echo "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BootstrapVersion
debuild -b
if [ -z "${TEST_OUTPUT}" ]; then
debuild -b
else
debuild -us -uc -b
fi
echo Moving stuff around
mv ../sonarr_*.deb ./
mv ../sonarr_*.changes ./
rm ../sonarr_*.build
echo Signing Package
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
if [ -z "${TEST_OUTPUT}" ]; then
echo Signing Package
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
echo running alien
alien -r -v ./*.deb
echo running alien
alien -r -v ./*.deb
else
echo "Exporting packages to ${TEST_OUTPUT}"
dpkg -e "sonarr_${BuildVersion}_all.deb" ${TEST_OUTPUT}/sonarr-build
dpkg -e "sonarr_${BootstrapVersion}_all.deb" ${TEST_OUTPUT}/sonarr-release
cp *.deb ${TEST_OUTPUT}/
fi

View File

@@ -7,15 +7,16 @@ Vcs-Git: git@github.com:Sonarr/Sonarr.git
Vcs-Browser: https://github.com/Sonarr/Sonarr
Build-Depends: debhelper (>= 9),
dh-systemd (>= 1.5),
mono-devel (>= 4.6),
libmono-cil-dev (>= 4.6),
cli-common-dev (>= 0.5.7)
mono-devel (>= 5.4),
libmono-cil-dev (>= 5.4),
cli-common-dev (>= 0.9+xamarin5)
Package: sonarr
Architecture: all
Provides: nzbdrone
Conflicts: nzbdrone
Replaces: nzbdrone
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52), mono-runtime (>= 5.4), ${cli:Depends}, ${misc:Depends}
Recommends: sqlite3 (>= 3.7), mediainfo (>= 0.7.52), ${cli:Recommends}
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52) | libmediainfo0 (>= 0.7.52), mono-runtime (>= 5.4), ca-certificates-mono, libmono-system-net-http4.0-cil (>= 4.0.0~alpha1), ${cli:Depends}, ${misc:Depends}
Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03)
Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
Description: Internet PVR

View File

@@ -1,2 +1,2 @@
sonarr_bin/* /usr/lib/sonarr/bin
package_info /usr/lib/sonarr
sonarr_bin/* usr/lib/sonarr/bin
package_info usr/lib/sonarr

View File

@@ -104,4 +104,4 @@ fi
#DEBHELPER#
exit 0
exit 0

View File

@@ -22,10 +22,25 @@ if [ $1 = "install" ]; then
fi
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
# The user used a systemd auto-startup for NzbDrone, we can deal with that.
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
if [ "$psNzbDroneUnit" = "sonarr.service" ]; then
# Conflicts with our new sonarr.service so we have to remove it
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and removing..."
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
if [ -f "/etc/systemd/system/$psNzbDroneUnit" ]; then
rm /etc/systemd/system/$psNzbDroneUnit
fi
if [ -f "/usr/lib/systemd/system/$psNzbDroneUnit" ]; then
rm /usr/lib/systemd/system/$psNzbDroneUnit
fi
deb-systemd-helper purge $psNzbDroneUnit >/dev/null
deb-systemd-helper unmask $psNzbDroneUnit >/dev/null
systemctl --system daemon-reload >/dev/null || true
else
# Just disable it, so the user can revisit the settings later
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
fi
else
# We don't support auto migration for other startup methods, so bail.
# This leaves the sonarr package in an incomplete state.

View File

@@ -1,17 +1,9 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven,
# but SharpRaven doesn't use any functions that need System.Native
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
EXCLUDE_MODULEREFS = crypt32 httpapi
%:
dh $@ --with=systemd --with=cli
@@ -20,7 +12,7 @@ EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
override_dh_installinit:
true
# Sonarr like debug symbols for logging
# Sonarr likes debug symbols for logging
override_dh_clistrip:
override_dh_makeclilibs:

View File

@@ -1,2 +1,2 @@
recommends libcurl3
ignores msbuild
ignores msbuild
ignores libmediainfo0v5

View File

@@ -0,0 +1,23 @@
FROM ubuntu:xenial AS builder
ENV DEBIAN_FRONTEND noninteractive
ENV MONO_VERSION 5.14
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 && \
apt-get update && apt-get install -y \
devscripts build-essential tofrodos \
dh-make dh-systemd \
cli-common-dev \
mono-complete \
sqlite3 libcurl3 mediainfo
RUN apt-cache policy mono-complete
RUN apt-cache policy cli-common-dev
COPY debian-start.sh /debian-start.sh
RUN fromdos /debian-start.sh
WORKDIR /data
VOLUME [ "/data/sonarr_bin", "/data/build", "/data/output" ]
CMD /debian-start.sh

View File

@@ -0,0 +1,18 @@
echo "Debian Build Dev bootstrap..."
export TEST_OUTPUT=/data/output
mkdir ${TEST_OUTPUT}
mkdir /data/temp
cp -rf /data/build/debian.sh /data/temp
cp -rf /data/build/debian /data/temp
cp -rf /data/sonarr_bin /data/temp/sonarr_bin
cd /data/temp
ls -al .
fromdos debian.sh
sh debian.sh

View File

@@ -0,0 +1,22 @@
FROM ubuntu:xenial
ENV DEBIAN_FRONTEND noninteractive
ARG MONO_VERSION=5.20
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
apt-get update && apt-get install -y \
tofrodos tzdata \
mono-complete \
sqlite3 mediainfo \
&& rm -rf /var/lib/apt/lists/*
COPY startup.sh /startup.sh
RUN fromdos /startup.sh
WORKDIR /data/
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
CMD bash /startup.sh

View File

@@ -0,0 +1,28 @@
FROM ubuntu:xenial
ENV DEBIAN_FRONTEND noninteractive
ARG MONO_VERSION=5.20
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 2009837CBFFD68F45BC180471F4F90DE2A9B4BF8 && \
echo "deb http://apt.sonarr.tv/ubuntu xenial main" > /etc/apt/sources.list.d/sonarr.list && \
apt-get update && apt-get install -y \
tofrodos tzdata \
sonarr \
sqlite3 mediainfo \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y \
libmono-system-runtime4.0-cil \
&& rm -rf /var/lib/apt/lists/*
COPY startup.sh /startup.sh
RUN fromdos /startup.sh
WORKDIR /data/
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
CMD bash /startup.sh

View File

@@ -0,0 +1,15 @@
echo "Preparing Test..."
mkdir -p /data/test
cp -r /data/_tests_linux/* /data/test/
cp -r /data/_output_linux /data/test/bin
cd /data/test
runTest()
{
bash test.sh Linux $1
cp TestResult.xml /data/_tests_results/TestResult_$1.xml
}
runTest Integration
runTest Unit

108
docker/tests/run-all.sh Normal file
View File

@@ -0,0 +1,108 @@
opt_parallel=
opt_version=
opt_mode=both
while getopts 'pv:m:?h' c
do
case $c in
p) opt_parallel=1 ;;
v) opt_version=$OPTARG ;;
m) opt_mode=$OPTARG ;;
?|h) printf "Usage: %s [-p] [-v mono-ver] [-m sonarr|complete]\n" $0
printf " -p run parallel\n"
printf " -v run specified mono version\n"
printf " -m run only mono-'complete' or 'sonarr' package variants\n"
exit 2
esac
done
# NOTE:
# each container has a 1gb tmpfs mounted since it greatly speeds up the normally intensive db operations
# make sure that the docker host has enough memory to handle about ~300 MB per container, so 2-3 GB total
# excess goes to the swap and will slow down the entire system
# Preferred versions
MONO_VERSIONS="6.8 6.6 6.4 6.0 5.20 5.18"
# Future versions
MONO_VERSIONS="$MONO_VERSIONS 6.10=preview-xenial"
# Supported versions
MONO_VERSIONS="$MONO_VERSIONS 5.16 5.14 5.12 5.10 5.8 5.4"
# Legacy unsupported versions
MONO_VERSIONS="$MONO_VERSIONS 5.0"
#MONO_VERSIONS="$MONO_VERSIONS 4.8=stable-wheezy/snapshots/4.8"
if [ "$opt_version" != "" ]; then
MONO_VERSIONS="$opt_version"
fi
mkdir -p ${PWD}/../../_tests_results
prepOne() {
local MONO_VERSION_PAIR=$1
MONO_VERSION_SPLIT=(${MONO_VERSION_PAIR//=/ })
MONO_VERSION=${MONO_VERSION_SPLIT[0]}
MONO_URL=${MONO_VERSION_SPLIT[1]:-"stable-xenial/snapshots/$MONO_VERSION"}
echo "Building Test Docker for mono $MONO_VERSION"
if [ "$opt_mode" != "sonarr" ]; then
docker build -t sonarr-test-$MONO_VERSION --build-arg MONO_VERSION=$MONO_VERSION --build-arg MONO_URL=$MONO_URL --file mono/complete/Dockerfile mono
fi
if [ "$opt_mode" != "complete" ] && [ "$MONO_VERSION" != "5.0" ]; then
docker build -t sonarr-test-$MONO_VERSION-sonarr --build-arg MONO_VERSION=$MONO_VERSION --build-arg MONO_URL=$MONO_URL --file mono/sonarr/Dockerfile mono
fi
}
runOne() {
local MONO_VERSION_PAIR=$1
MONO_VERSION_SPLIT=(${MONO_VERSION_PAIR//=/ })
MONO_VERSION=${MONO_VERSION_SPLIT[0]}
echo "Running Test Docker for mono $MONO_VERSION"
if [ "$opt_mode" != "sonarr" ]; then
dockerArgs="--rm"
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_linux:/data/_tests_linux:ro"
dockerArgs="$dockerArgs -v /${PWD}/../../_output_linux:/data/_output_linux:ro"
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_results/mono-$MONO_VERSION:/data/_tests_results"
dockerArgs="$dockerArgs --mount type=tmpfs,destination=//data/test,tmpfs-size=1g"
docker run $dockerArgs sonarr-test-$MONO_VERSION
fi
if [ "$opt_mode" != "complete" ] && [ "$MONO_VERSION" != "5.0" ]; then
dockerArgs="--rm"
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_linux:/data/_tests_linux:ro"
dockerArgs="$dockerArgs -v /${PWD}/../../_output_linux:/data/_output_linux:ro"
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_results/mono-$MONO_VERSION-sonarr:/data/_tests_results"
dockerArgs="$dockerArgs --mount type=tmpfs,destination=//data/test,tmpfs-size=1g"
docker run $dockerArgs sonarr-test-$MONO_VERSION-sonarr
fi
echo "Finished Test Docker for mono $MONO_VERSION"
}
if [ "$opt_parallel" == "1" ]; then
for MONO_VERSION_PAIR in $MONO_VERSIONS; do
prepOne "$MONO_VERSION_PAIR"
done
fi
for MONO_VERSION_PAIR in $MONO_VERSIONS; do
if [ "$opt_parallel" == "1" ]; then
runOne "$MONO_VERSION_PAIR" &
else
prepOne "$MONO_VERSION_PAIR"
runOne "$MONO_VERSION_PAIR"
fi
done
if [ "$opt_parallel" == "1" ]; then
echo "Waiting for all runs to finish"
wait
echo "Finished all runs"
fi

View File

@@ -1,6 +0,0 @@
module.exports = [
'>0.25%',
'not ie 11',
'not op_mini all',
'not chrome < 60'
];

View File

@@ -5,14 +5,17 @@ const path = require('path');
const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const browsers = require('../browsers');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1;
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
const cssVarsFiles = [
@@ -23,6 +26,22 @@ const cssVarsFiles = [
'../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,
@@ -30,7 +49,12 @@ const plugins = [
}),
new MiniCssExtractPlugin({
filename: path.join('_output', uiFolder, 'Content', 'styles.css')
filename: path.join('Content', 'styles.css')
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html'
})
];
@@ -47,8 +71,6 @@ const config = {
},
entry: {
preload: 'preload.js',
vendor: 'vendor.js',
index: 'index.js'
},
@@ -64,12 +86,20 @@ const config = {
},
output: {
filename: path.join('_output', uiFolder, '[name].js'),
path: distFolder,
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
chunkIds: 'named'
chunkIds: 'named',
splitChunks: {
chunks: 'initial'
}
},
performance: {
hints: false
},
plugins,
@@ -100,7 +130,7 @@ const config = {
loose: true,
debug: false,
useBuiltIns: 'entry',
targets: browsers
corejs: 3
}
]
]
@@ -119,8 +149,9 @@ const config = {
loader: 'css-loader',
options: {
importLoaders: 1,
localIdentName: '[name]/[local]/[hash:base64:5]',
modules: true
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
@@ -184,7 +215,7 @@ const config = {
gulp.task('webpack', () => {
return webpackStream(config)
.pipe(gulp.dest('./'));
.pipe(gulp.dest('_output/UI'));
});
gulp.task('webpackWatch', () => {
@@ -192,7 +223,7 @@ gulp.task('webpackWatch', () => {
return webpackStream(config)
.on('error', errorHandler)
.pipe(gulp.dest('./'))
.pipe(gulp.dest('_output/UI'))
.on('error', errorHandler)
.pipe(livereload())
.on('error', errorHandler);

View File

@@ -1,5 +1,4 @@
const reload = require('require-nocache')(module);
const browsers = require('./browsers');
module.exports = (ctx, configPath, options) => {
const config = {
@@ -16,10 +15,7 @@ module.exports = (ctx, configPath, options) => {
}, {})
},
'postcss-color-function': {},
'postcss-nested': {},
autoprefixer: {
browsers
}
'postcss-nested': {}
}
};

View File

@@ -232,6 +232,30 @@ function HistoryDetails(props) {
);
}
if (eventType === 'downloadIgnored') {
const {
message
} = data;
return (
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
data={sourceTitle}
/>
{
!!message &&
<DescriptionListItem
title="Message"
data={message}
/>
}
</DescriptionList>
);
}
return (
<DescriptionList>
<DescriptionListItem

View File

@@ -23,6 +23,8 @@ function getHeaderTitle(eventType) {
return 'Episode File Deleted';
case 'episodeFileRenamed':
return 'Episode File Renamed';
case 'downloadIgnored':
return 'Download Ignored';
default:
return 'Unknown';
}

View File

@@ -19,6 +19,8 @@ function getIconName(eventType) {
return icons.DELETE;
case 'episodeFileRenamed':
return icons.ORGANIZE;
case 'downloadIgnored':
return icons.IGNORE;
default:
return icons.UNKNOWN;
}
@@ -47,6 +49,8 @@ function getTooltip(eventType, data) {
return 'Episode file deleted';
case 'episodeFileRenamed':
return 'Episode file renamed';
case 'downloadIgnored':
return 'Episode Download Ignored';
default:
return 'Unknown event';
}

View File

@@ -71,7 +71,7 @@ class Queue extends Component {
const selectedIds = this.getSelectedIds();
const isPendingSelected = _.some(this.props.items, (item) => {
return selectedIds.indexOf(item.id) > -1 && item.status === 'Delay';
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
});
if (isPendingSelected !== this.state.isPendingSelected) {
@@ -107,8 +107,8 @@ class Queue extends Component {
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = (blacklist) => {
this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist);
onRemoveSelectedConfirmed = (payload) => {
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
this.setState({ isConfirmRemoveModalOpen: false });
}
@@ -132,7 +132,7 @@ class Queue extends Component {
totalRecords,
isGrabbing,
isRemoving,
isCheckForFinishedDownloadExecuting,
isRefreshMonitoredDownloadsExecuting,
onRefreshPress,
...otherProps
} = this.props;
@@ -145,10 +145,11 @@ class Queue extends Component {
isPendingSelected
} = this.state;
const isRefreshing = isFetching || isEpisodesFetching || isCheckForFinishedDownloadExecuting;
const isRefreshing = isFetching || isEpisodesFetching || isRefreshMonitoredDownloadsExecuting;
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length || items.every((e) => !e.episodeId));
const hasError = error || episodesError;
const selectedCount = this.getSelectedIds().length;
const selectedIds = this.getSelectedIds();
const selectedCount = selectedIds.length;
const disableSelectedActions = selectedCount === 0;
return (
@@ -259,6 +260,13 @@ class Queue extends Component {
<RemoveQueueItemsModal
isOpen={isConfirmRemoveModalOpen}
selectedCount={selectedCount}
canIgnore={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
return !!(item && item.seriesId && item.episodeId);
})
)}
onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose}
/>
@@ -279,7 +287,7 @@ Queue.propTypes = {
totalRecords: PropTypes.number,
isGrabbing: PropTypes.bool.isRequired,
isRemoving: PropTypes.bool.isRequired,
isCheckForFinishedDownloadExecuting: PropTypes.bool.isRequired,
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
onRefreshPress: PropTypes.func.isRequired,
onGrabSelectedPress: PropTypes.func.isRequired,
onRemoveSelectedPress: PropTypes.func.isRequired

View File

@@ -18,13 +18,13 @@ function createMapStateToProps() {
(state) => state.episodes,
(state) => state.queue.options,
(state) => state.queue.paged,
createCommandExecutingSelector(commandNames.CHECK_FOR_FINISHED_DOWNLOAD),
(episodes, options, queue, isCheckForFinishedDownloadExecuting) => {
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
(episodes, options, queue, isRefreshMonitoredDownloadsExecuting) => {
return {
isEpisodesFetching: episodes.isFetching,
isEpisodesPopulated: episodes.isPopulated,
episodesError: episodes.error,
isCheckForFinishedDownloadExecuting,
isRefreshMonitoredDownloadsExecuting,
...options,
...queue
};
@@ -129,7 +129,7 @@ class QueueConnector extends Component {
onRefreshPress = () => {
this.props.executeCommand({
name: commandNames.CHECK_FOR_FINISHED_DOWNLOAD
name: commandNames.REFRESH_MONITORED_DOWNLOADS
});
}
@@ -137,8 +137,8 @@ class QueueConnector extends Component {
this.props.grabQueueItems({ ids });
}
onRemoveSelectedPress = (ids, blacklist) => {
this.props.removeQueueItems({ ids, blacklist });
onRemoveSelectedPress = (payload) => {
this.props.removeQueueItems(payload);
}
//

View File

@@ -68,6 +68,7 @@ class QueueRow extends Component {
title,
status,
trackedDownloadStatus,
trackedDownloadState,
statusMessages,
errorMessage,
series,
@@ -100,8 +101,8 @@ class QueueRow extends Component {
} = this.state;
const progress = 100 - (sizeleft / size * 100);
const showInteractiveImport = status === 'Completed' && trackedDownloadStatus === 'Warning';
const isPending = status === 'Delay' || status === 'DownloadClientUnavailable';
const showInteractiveImport = status === 'completed' && trackedDownloadStatus === 'warning';
const isPending = status === 'delay' || status === 'downloadClientUnavailable';
return (
<TableRow>
@@ -129,6 +130,7 @@ class QueueRow extends Component {
sourceTitle={title}
status={status}
trackedDownloadStatus={trackedDownloadStatus}
trackedDownloadState={trackedDownloadState}
statusMessages={statusMessages}
errorMessage={errorMessage}
/>
@@ -350,6 +352,7 @@ class QueueRow extends Component {
<RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title}
canIgnore={!!(series && episode)}
onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose}
/>
@@ -365,6 +368,7 @@ QueueRow.propTypes = {
title: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string,
trackedDownloadState: PropTypes.string,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
series: PropTypes.object,

View File

@@ -43,8 +43,8 @@ class QueueRowConnector extends Component {
this.props.grabQueueItem({ id: this.props.id });
}
onRemoveQueueItemPress = (blacklist) => {
this.props.removeQueueItem({ id: this.props.id, blacklist });
onRemoveQueueItemPress = (payload) => {
this.props.removeQueueItem({ id: this.props.id, ...payload });
}
//

View File

@@ -37,13 +37,14 @@ function QueueStatusCell(props) {
const {
sourceTitle,
status,
trackedDownloadStatus = 'Ok',
trackedDownloadStatus,
trackedDownloadState,
statusMessages,
errorMessage
} = props;
const hasWarning = trackedDownloadStatus === 'Warning';
const hasError = trackedDownloadStatus === 'Error';
const hasWarning = trackedDownloadStatus === 'warning';
const hasError = trackedDownloadStatus === 'error';
// status === 'downloading'
let iconName = icons.DOWNLOADING;
@@ -54,46 +55,58 @@ function QueueStatusCell(props) {
iconKind = kinds.WARNING;
}
if (status === 'Paused') {
if (status === 'paused') {
iconName = icons.PAUSED;
title = 'Paused';
}
if (status === 'Queued') {
if (status === 'queued') {
iconName = icons.QUEUED;
title = 'Queued';
}
if (status === 'Completed') {
if (status === 'completed') {
iconName = icons.DOWNLOADED;
title = 'Downloaded';
if (trackedDownloadState === 'importPending') {
title += ' - Waiting to Import';
}
if (trackedDownloadState === 'importing') {
title += ' - Importing';
}
if (trackedDownloadState === 'failedPending') {
title += ' - Waiting to Process';
}
}
if (status === 'Delay') {
if (status === 'delay') {
iconName = icons.PENDING;
title = 'Pending';
}
if (status === 'DownloadClientUnavailable') {
if (status === 'downloadClientUnavailable') {
iconName = icons.PENDING;
iconKind = kinds.WARNING;
title = 'Pending - Download client is unavailable';
}
if (status === 'Failed') {
if (status === 'failed') {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = 'Download failed';
}
if (status === 'Warning') {
if (status === 'warning') {
iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING;
title = `Download warning: ${errorMessage || 'check download client for more details'}`;
}
if (hasError) {
if (status === 'Completed') {
if (status === 'completed') {
iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER;
title = `Import failed: ${sourceTitle}`;
@@ -125,9 +138,15 @@ function QueueStatusCell(props) {
QueueStatusCell.propTypes = {
sourceTitle: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string,
trackedDownloadStatus: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string
};
QueueStatusCell.defaultProps = {
trackedDownloadStatus: 'Ok',
trackedDownloadState: 'Downloading'
};
export default QueueStatusCell;

View File

@@ -1,3 +0,0 @@
.message {
margin-bottom: 30px;
}

View File

@@ -10,7 +10,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './RemoveQueueItemModal.css';
class RemoveQueueItemModal extends Component {
@@ -21,26 +20,41 @@ class RemoveQueueItemModal extends Component {
super(props, context);
this.state = {
remove: true,
blacklist: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blacklist: false
});
}
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value });
}
onRemoveQueueItemConfirmed = () => {
const blacklist = this.state.blacklist;
onRemoveConfirmed = () => {
const state = this.state;
this.setState({ blacklist: false });
this.props.onRemovePress(blacklist);
this.resetState();
this.props.onRemovePress(state);
}
onModalClose = () => {
this.setState({ blacklist: false });
this.resetState();
this.props.onModalClose();
}
@@ -50,10 +64,11 @@ class RemoveQueueItemModal extends Component {
render() {
const {
isOpen,
sourceTitle
sourceTitle,
canIgnore
} = this.props;
const blacklist = this.state.blacklist;
const { remove, blacklist } = this.state;
return (
<Modal
@@ -69,17 +84,31 @@ class RemoveQueueItemModal extends Component {
</ModalHeader>
<ModalBody>
<div className={styles.message}>
<div>
Are you sure you want to remove '{sourceTitle}' from the queue?
</div>
<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>Blacklist Release</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blacklist"
value={blacklist}
helpText="Prevents Sonarr from automatically grabbing this episode again"
helpText="Starts a search for this episode again and prevents this release from being grabbed again"
onChange={this.onBlacklistChange}
/>
</FormGroup>
@@ -93,7 +122,7 @@ class RemoveQueueItemModal extends Component {
<Button
kind={kinds.DANGER}
onPress={this.onRemoveQueueItemConfirmed}
onPress={this.onRemoveConfirmed}
>
Remove
</Button>
@@ -107,6 +136,7 @@ class RemoveQueueItemModal extends Component {
RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -21,26 +21,41 @@ class RemoveQueueItemsModal extends Component {
super(props, context);
this.state = {
remove: true,
blacklist: false
};
}
//
// Listeners
// Control
resetState = function() {
this.setState({
remove: true,
blacklist: false
});
}
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value });
}
onRemoveQueueItemConfirmed = () => {
const blacklist = this.state.blacklist;
onRemoveConfirmed = () => {
const state = this.state;
this.setState({ blacklist: false });
this.props.onRemovePress(blacklist);
this.resetState();
this.props.onRemovePress(state);
}
onModalClose = () => {
this.setState({ blacklist: false });
this.resetState();
this.props.onModalClose();
}
@@ -50,10 +65,11 @@ class RemoveQueueItemsModal extends Component {
render() {
const {
isOpen,
selectedCount
selectedCount,
canIgnore
} = this.props;
const blacklist = this.state.blacklist;
const { remove, blacklist } = this.state;
return (
<Modal
@@ -74,7 +90,23 @@ class RemoveQueueItemsModal extends Component {
</div>
<FormGroup>
<FormLabel>Blacklist Release</FormLabel>
<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>
Blacklist Release{selectedCount > 1 ? 's' : ''}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blacklist"
@@ -93,7 +125,7 @@ class RemoveQueueItemsModal extends Component {
<Button
kind={kinds.DANGER}
onPress={this.onRemoveQueueItemConfirmed}
onPress={this.onRemoveConfirmed}
>
Remove
</Button>
@@ -107,6 +139,7 @@ class RemoveQueueItemsModal extends Component {
RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -35,14 +35,20 @@
.message {
margin-top: 30px;
text-align: center;
font-weight: 300;
font-size: $largeFontSize;
}
.helpText {
margin-bottom: 10px;
font-weight: 300;
font-size: 24px;
}
.noSeriesText {
margin-top: 80px;
margin-bottom: 20px;
}
.noResults {
margin-bottom: 10px;
font-weight: 300;

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import { icons, kinds } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link';
import Icon from 'Components/Icon';
@@ -78,7 +79,8 @@ class AddNewSeries extends Component {
render() {
const {
error,
items
items,
hasExistingSeries
} = this.props;
const term = this.state.term;
@@ -121,8 +123,13 @@ class AddNewSeries extends Component {
}
{
!isFetching && !!error &&
<div>Failed to load search results, please try again.</div>
!isFetching && !!error ?
<div className={styles.message}>
<div className={styles.helpText}>
Failed to load search results, please try again.
</div>
<div>{getErrorMessage(error)}</div>
</div> : null
}
{
@@ -155,13 +162,34 @@ class AddNewSeries extends Component {
}
{
!term &&
term ?
null :
<div className={styles.message}>
<div className={styles.helpText}>It's easy to add a new series, just start typing the name the series you want to add.</div>
<div className={styles.helpText}>
It's easy to add a new series, just start typing the name the series you want to add.
</div>
<div>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
</div>
}
{
!term && !hasExistingSeries ?
<div className={styles.message}>
<div className={styles.noSeriesText}>
You haven't added any series yet, do you want to import some or all of your series first?
</div>
<div>
<Button
to="/add/import"
kind={kinds.PRIMARY}
>
Import Existing Series
</Button>
</div>
</div> :
null
}
<div />
</PageContentBodyConnector>
</PageContent>
@@ -176,6 +204,7 @@ AddNewSeries.propTypes = {
isAdding: PropTypes.bool.isRequired,
addError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
hasExistingSeries: PropTypes.bool.isRequired,
onSeriesLookupChange: PropTypes.func.isRequired,
onClearSeriesLookup: PropTypes.func.isRequired
};

View File

@@ -10,13 +10,15 @@ import AddNewSeries from './AddNewSeries';
function createMapStateToProps() {
return createSelector(
(state) => state.addSeries,
(state) => state.series.items.length,
(state) => state.router.location,
(addSeries, location) => {
(addSeries, existingSeriesCount, location) => {
const { params } = parseUrl(location.search);
return {
...addSeries,
term: params.term,
...addSeries
hasExistingSeries: existingSeriesCount > 0
};
}
);

View File

@@ -14,6 +14,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import Popover from 'Components/Tooltip/Popover';
import SeriesPoster from 'Series/SeriesPoster';
import * as seriesTypes from 'Utilities/Series/seriesTypes';
import SeriesMonitoringOptionsPopoverContent from 'AddSeries/SeriesMonitoringOptionsPopoverContent';
import SeriesTypePopoverContent from 'AddSeries/SeriesTypePopoverContent';
import styles from './AddNewSeriesModalContent.css';
@@ -27,10 +28,19 @@ class AddNewSeriesModalContent extends Component {
super(props, context);
this.state = {
seriesType: props.initialSeriesType === seriesTypes.STANDARD ?
props.seriesType.value :
props.initialSeriesType,
searchForMissingEpisodes: false
};
}
componentDidUpdate(prevProps) {
if (this.props.seriesType.value !== prevProps.seriesType.value) {
this.setState({ seriesType: this.props.seriesType.value });
}
}
//
// Listeners
@@ -47,7 +57,12 @@ class AddNewSeriesModalContent extends Component {
}
onAddSeriesPress = () => {
this.props.onAddSeriesPress(this.state.searchForMissingEpisodes);
const {
searchForMissingEpisodes,
seriesType
} = this.state;
this.props.onAddSeriesPress(searchForMissingEpisodes, seriesType);
}
//
@@ -66,9 +81,11 @@ class AddNewSeriesModalContent extends Component {
languageProfileId,
seriesType,
seasonFolder,
folder,
tags,
showLanguageProfile,
isSmallScreen,
isWindows,
onModalClose,
onInputChange,
...otherProps
@@ -115,6 +132,15 @@ class AddNewSeriesModalContent extends Component {
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
valueOptions={{
seriesFolder: folder,
isWindows
}}
selectedValueOptions={{
seriesFolder: folder,
isWindows
}}
helpText={`'${folder}' subfolder will be created automatically`}
onChange={onInputChange}
{...rootFolderPath}
/>
@@ -189,6 +215,7 @@ class AddNewSeriesModalContent extends Component {
name="seriesType"
onChange={onInputChange}
{...seriesType}
value={this.state.seriesType}
/>
</FormGroup>
@@ -251,6 +278,7 @@ AddNewSeriesModalContent.propTypes = {
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
overview: PropTypes.string,
initialSeriesType: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isAdding: PropTypes.bool.isRequired,
addError: PropTypes.object,
@@ -260,9 +288,11 @@ AddNewSeriesModalContent.propTypes = {
languageProfileId: PropTypes.object,
seriesType: PropTypes.object.isRequired,
seasonFolder: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
tags: PropTypes.object.isRequired,
showLanguageProfile: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
isWindows: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired,
onAddSeriesPress: PropTypes.func.isRequired

View File

@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import AddNewSeriesModalContent from './AddNewSeriesModalContent';
@@ -12,7 +13,8 @@ function createMapStateToProps() {
(state) => state.addSeries,
(state) => state.settings.languageProfiles,
createDimensionsSelector(),
(addSeriesState, languageProfiles, dimensions) => {
createSystemStatusSelector(),
(addSeriesState, languageProfiles, dimensions, systemStatus) => {
const {
isAdding,
addError,
@@ -32,6 +34,7 @@ function createMapStateToProps() {
isSmallScreen: dimensions.isSmallScreen,
validationErrors,
validationWarnings,
isWindows: systemStatus.isWindows,
...settings
};
}
@@ -52,14 +55,13 @@ class AddNewSeriesModalContentConnector extends Component {
this.props.setAddSeriesDefault({ [name]: value });
}
onAddSeriesPress = (searchForMissingEpisodes) => {
onAddSeriesPress = (searchForMissingEpisodes, seriesType) => {
const {
tvdbId,
rootFolderPath,
monitor,
qualityProfileId,
languageProfileId,
seriesType,
seasonFolder,
tags
} = this.props;
@@ -70,7 +72,7 @@ class AddNewSeriesModalContentConnector extends Component {
monitor: monitor.value,
qualityProfileId: qualityProfileId.value,
languageProfileId: languageProfileId.value,
seriesType: seriesType.value,
seriesType,
seasonFolder: seasonFolder.value,
tags: tags.value,
searchForMissingEpisodes

View File

@@ -1,10 +1,15 @@
.searchResult {
display: flex;
position: relative;
margin: 20px 0;
padding: 20px;
width: 100%;
background-color: $white;
color: inherit;
}
.underlay {
@add-mixin cover;
background-color: $white;
transition: background 500ms;
&:hover {
@@ -14,13 +19,25 @@
}
}
.overlay {
@add-mixin linkOverlay;
position: relative;
display: flex;
}
.poster {
flex: 0 0 170px;
margin-right: 20px;
height: 250px;
}
.content {
flex: 0 1 100%;
}
.title {
display: flex;
font-weight: 300;
font-size: 36px;
}
@@ -30,6 +47,18 @@
color: $disabledColor;
}
.tvdbLink {
composes: link from '~Components/Link/Link.css';
margin-top: -4px;
margin-left: auto;
color: $textColor;
}
.tvdbLinkIcon {
margin-left: 10px;
}
.alreadyExistsIcon {
margin-left: 10px;
color: #37bc9b;

View File

@@ -24,7 +24,7 @@ class AddNewSeriesSearchResult extends Component {
componentDidUpdate(prevProps) {
if (!prevProps.isExistingSeries && this.props.isExistingSeries) {
this.onAddSerisModalClose();
this.onAddSeriesModalClose();
}
}
@@ -35,10 +35,14 @@ class AddNewSeriesSearchResult extends Component {
this.setState({ isNewAddSeriesModalOpen: true });
}
onAddSerisModalClose = () => {
onAddSeriesModalClose = () => {
this.setState({ isNewAddSeriesModalOpen: false });
}
onTVDBLinkPress = (event) => {
event.stopPropagation();
}
//
// Render
@@ -53,6 +57,8 @@ class AddNewSeriesSearchResult extends Component {
overview,
statistics,
ratings,
folder,
seriesType,
images,
isExistingSeries,
isSmallScreen
@@ -72,11 +78,13 @@ class AddNewSeriesSearchResult extends Component {
}
return (
<div>
<div className={styles.searchResult}>
<Link
className={styles.searchResult}
className={styles.underlay}
{...linkProps}
>
/>
<div className={styles.overlay}>
{
isSmallScreen ?
null :
@@ -88,7 +96,7 @@ class AddNewSeriesSearchResult extends Component {
/>
}
<div>
<div className={styles.content}>
<div className={styles.title}>
{title}
@@ -110,6 +118,18 @@ class AddNewSeriesSearchResult extends Component {
/> :
null
}
<Link
className={styles.tvdbLink}
to={`http://www.thetvdb.com/?tab=series&id=${tvdbId}`}
onPress={this.onTVDBLinkPress}
>
<Icon
className={styles.tvdbLinkIcon}
name={icons.EXTERNAL_LINK}
size={28}
/>
</Link>
</div>
<div>
@@ -146,13 +166,24 @@ class AddNewSeriesSearchResult extends Component {
</Label> :
null
}
{
status === 'upcoming' ?
<Label
kind={kinds.INFO}
size={sizes.LARGE}
>
Upcoming
</Label> :
null
}
</div>
<div className={styles.overview}>
{overview}
</div>
</div>
</Link>
</div>
<AddNewSeriesModal
isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
@@ -160,8 +191,10 @@ class AddNewSeriesSearchResult extends Component {
title={title}
year={year}
overview={overview}
folder={folder}
initialSeriesType={seriesType}
images={images}
onModalClose={this.onAddSerisModalClose}
onModalClose={this.onAddSeriesModalClose}
/>
</div>
);
@@ -178,6 +211,8 @@ AddNewSeriesSearchResult.propTypes = {
overview: PropTypes.string,
statistics: PropTypes.object.isRequired,
ratings: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
seriesType: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingSeries: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired

View File

@@ -90,6 +90,7 @@ function ImportSeriesRow(props) {
<ImportSeriesSelectSeriesConnector
id={id}
isExistingSeries={isExistingSeries}
onInputChange={onInputChange}
/>
</VirtualTableRowCell>
</VirtualTableRow>

View File

@@ -1,10 +1,10 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { queueLookupSeries, setImportSeriesValue } from 'Store/Actions/importSeriesActions';
import createImportSeriesItemSelector from 'Store/Selectors/createImportSeriesItemSelector';
import * as seriesTypes from 'Utilities/Series/seriesTypes';
import ImportSeriesSelectSeries from './ImportSeriesSelectSeries';
function createMapStateToProps() {
@@ -41,13 +41,23 @@ class ImportSeriesSelectSeriesConnector extends Component {
onSeriesSelect = (tvdbId) => {
const {
id,
items
items,
onInputChange
} = this.props;
const selectedSeries = items.find((item) => item.tvdbId === tvdbId);
this.props.setImportSeriesValue({
id,
selectedSeries: _.find(items, { tvdbId })
selectedSeries
});
if (selectedSeries.seriesType !== seriesTypes.STANDARD) {
onInputChange({
name: 'seriesType',
value: selectedSeries.seriesType
});
}
}
//
@@ -69,6 +79,7 @@ ImportSeriesSelectSeriesConnector.propTypes = {
items: PropTypes.arrayOf(PropTypes.object),
selectedSeries: PropTypes.object,
isSelected: PropTypes.bool,
onInputChange: PropTypes.func.isRequired,
queueLookupSeries: PropTypes.func.isRequired,
setImportSeriesValue: PropTypes.func.isRequired
};

View File

@@ -75,10 +75,10 @@ class ImportSeriesSelectFolder extends Component {
Some tips to ensure the import goes smoothly:
<ul>
<li className={styles.tip}>
Make sure your files include the quality in the name. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
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>
</li>
</ul>
</div>

View File

@@ -2,8 +2,8 @@
flex: 1 0 14.28%;
overflow: hidden;
min-height: 70px;
border-bottom: 1px solid $borderColor;
border-left: 1px solid $borderColor;
border-bottom: 1px solid $calendarBorderColor;
border-left: 1px solid $calendarBorderColor;
}
.isSingleDay {
@@ -12,7 +12,7 @@
.dayOfMonth {
padding-right: 5px;
border-bottom: 1px solid $borderColor;
border-bottom: 1px solid $calendarBorderColor;
text-align: right;
}

View File

@@ -1,6 +1,6 @@
.days {
display: flex;
border-right: 1px solid $borderColor;
border-right: 1px solid $calendarBorderColor;
}
.day,

View File

@@ -16,10 +16,13 @@
display: flex;
}
.episodeInfo {
color: $calendarTextDim;
}
.seriesTitle,
.episodeTitle {
@add-mixin truncate;
flex: 1 0 1px;
margin-right: 10px;
}
@@ -37,6 +40,10 @@
margin-left: 3px;
}
.airTime {
color: $calendarTextDim;
}
/*
* Status
*/

View File

@@ -200,7 +200,7 @@ class CalendarEvent extends Component {
</div>
}
<div>
<div className={styles.airTime}>
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
</div>
</Link>

View File

@@ -14,7 +14,6 @@
.seriesTitle {
@add-mixin truncate;
flex: 1 0 1px;
margin-right: 10px;
color: #3a3f51;
@@ -23,10 +22,12 @@
.airTime {
flex: 1 0 1px;
color: $calendarTextDim;
}
.episodeInfo {
margin-left: 10px;
color: $calendarTextDim;
}
.absoluteEpisodeNumber {
@@ -80,3 +81,7 @@
.premiere {
composes: premiere from '~Calendar/Events/CalendarEvent.css';
}
.unaired {
composes: unaired from '~Calendar/Events/CalendarEvent.css';
}

View File

@@ -1,6 +1,6 @@
export const APPLICATION_UPDATE = 'ApplicationUpdate';
export const BACKUP = 'Backup';
export const CHECK_FOR_FINISHED_DOWNLOAD = 'CheckForFinishedDownload';
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
export const CLEAR_BLACKLIST = 'ClearBlacklist';
export const CLEAR_LOGS = 'ClearLog';
export const CUTOFF_UNMET_EPISODE_SEARCH = 'CutoffUnmetEpisodeSearch';

View File

@@ -56,7 +56,7 @@ class ErrorBoundary extends Component {
ErrorBoundary.propTypes = {
children: PropTypes.node.isRequired,
errorComponent: PropTypes.func.isRequired
errorComponent: PropTypes.elementType.isRequired
};
export default ErrorBoundary;

View File

@@ -144,6 +144,7 @@ class FileBrowserModalContent extends Component {
<Scroller
ref={this.setScrollerRef}
className={styles.scroller}
scrollDirection={scrollDirections.BOTH}
>
{
!!error &&
@@ -152,7 +153,10 @@ class FileBrowserModalContent extends Component {
{
isPopulated && !error &&
<Table columns={columns}>
<Table
horizontalScroll={false}
columns={columns}
>
<TableBody>
{
emptyParent &&

View File

@@ -132,6 +132,7 @@ class FilterBuilderModalContent extends Component {
filterBuilderProps,
isSaving,
saveError,
onCancelPress,
onModalClose
} = this.props;
@@ -190,7 +191,7 @@ class FilterBuilderModalContent extends Component {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
<Button onPress={onCancelPress}>
Cancel
</Button>
@@ -220,6 +221,7 @@ FilterBuilderModalContent.propTypes = {
dispatchDeleteCustomFilter: PropTypes.func.isRequired,
onSaveCustomFilterPress: PropTypes.func.isRequired,
dispatchSetFilter: PropTypes.func.isRequired,
onCancelPress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -3,16 +3,21 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import sortByName from 'Utilities/Array/sortByName';
import { filterBuilderTypes } from 'Helpers/Props';
import * as filterTypes from 'Helpers/Props/filterTypes';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createTagListSelector() {
return createSelector(
(state, { filterType }) => filterType,
(state, { sectionItems }) => sectionItems,
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
(sectionItems, selectedFilterBuilderProp) => {
(filterType, sectionItems, selectedFilterBuilderProp) => {
if (
selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
filterType !== filterTypes.EQUAL &&
filterType !== filterBuilderTypes.NOT_EQUAL ||
!selectedFilterBuilderProp.optionsSelector
) {
return [];
}

View File

@@ -1,4 +1,6 @@
.tag {
height: 21px;
&.isLastTag {
.or {
display: none;

View File

@@ -1,15 +1,16 @@
import React from 'react';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const protocols = [
const seriesStatusList = [
{ id: 'continuing', name: 'Continuing' },
{ id: 'upcoming', name: 'Upcoming' },
{ id: 'ended', name: 'Ended' }
];
function SeriesStatusFilterBuilderRowValue(props) {
return (
<FilterBuilderRowValue
tagList={protocols}
tagList={seriesStatusList}
{...props}
/>
);

View File

@@ -29,10 +29,10 @@ function CustomFiltersModalContent(props) {
<ModalBody>
{
customFilters.map((customFilter, index) => {
customFilters.map((customFilter) => {
return (
<CustomFilter
key={index}
key={customFilter.id}
id={customFilter.id}
label={customFilter.label}
filters={customFilter.filters}

View File

@@ -34,6 +34,17 @@ class FilterModal extends Component {
});
}
onCancelPress = () => {
if (this.state.filterBuilder) {
this.setState({
filterBuilder: false,
id: null
});
} else {
this.onModalClose();
}
}
onModalClose = () => {
this.setState({
filterBuilder: false,
@@ -67,6 +78,7 @@ class FilterModal extends Component {
<FilterBuilderModalContentConnector
{...otherProps}
id={id}
onCancelPress={this.onCancelPress}
onModalClose={this.onModalClose}
/> :
<CustomFiltersModalContentConnector

View File

@@ -37,6 +37,7 @@
.suggestionsList {
margin: 5px 0;
padding-left: 0;
max-height: 200px;
list-style-type: none;
}

View File

@@ -176,7 +176,7 @@ class AutoSuggestInput extends Component {
className: classNames(
className,
hasError && styles.hasError,
hasWarning && styles.hasWarning,
hasWarning && styles.hasWarning
),
name,
value,
@@ -234,7 +234,7 @@ AutoSuggestInput.propTypes = {
minHeight: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
getSuggestionValue: PropTypes.func.isRequired,
renderInputComponent: PropTypes.func,
renderInputComponent: PropTypes.elementType,
renderSuggestion: PropTypes.func.isRequired,
onInputChange: PropTypes.func,
onInputKeyDown: PropTypes.func,

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 { fetchDevices, clearDevices } from 'Store/Actions/deviceActions';
import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
import DeviceInput from './DeviceInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
(state) => state.devices,
(state) => state.providerOptions,
(value, devices) => {
return {
@@ -37,8 +37,8 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
dispatchFetchDevices: fetchDevices,
dispatchClearDevices: clearDevices
dispatchFetchOptions: fetchOptions,
dispatchClearOptions: clearOptions
};
class DeviceInputConnector extends Component {
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
}
componentWillUnmount = () => {
// this.props.dispatchClearDevices();
this.props.dispatchClearOptions();
}
//
@@ -61,10 +61,14 @@ class DeviceInputConnector extends Component {
const {
provider,
providerData,
dispatchFetchDevices
dispatchFetchOptions
} = this.props;
dispatchFetchDevices({ provider, providerData });
dispatchFetchOptions({
action: 'getDevices',
provider,
providerData
});
}
//
@@ -92,8 +96,8 @@ DeviceInputConnector.propTypes = {
providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchDevices: PropTypes.func.isRequired,
dispatchClearDevices: PropTypes.func.isRequired
dispatchFetchOptions: PropTypes.func.isRequired,
dispatchClearOptions: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector);

View File

@@ -4,7 +4,7 @@ import React, { Component } from 'react';
import { Manager, Popper, Reference } from 'react-popper';
import classNames from 'classnames';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import isMobileUtil from 'Utilities/isMobile';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import { icons, sizes, scrollDirections } from 'Helpers/Props';
import Icon from 'Components/Icon';
@@ -262,6 +262,7 @@ class EnhancedSelectInput extends Component {
isDisabled,
hasError,
hasWarning,
valueOptions,
selectedValueOptions,
selectedValueComponent: SelectedValueComponent,
optionComponent: OptionComponent
@@ -363,6 +364,7 @@ class EnhancedSelectInput extends Component {
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
{...valueOptions}
{...v}
isMobile={false}
onSelect={this.onSelect}
@@ -404,6 +406,7 @@ class EnhancedSelectInput extends Component {
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
{...valueOptions}
{...v}
isMobile={true}
onSelect={this.onSelect}
@@ -431,9 +434,10 @@ EnhancedSelectInput.propTypes = {
isDisabled: PropTypes.bool,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
valueOptions: PropTypes.object.isRequired,
selectedValueOptions: PropTypes.object.isRequired,
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
optionComponent: PropTypes.func,
optionComponent: PropTypes.elementType,
onChange: PropTypes.func.isRequired
};
@@ -441,6 +445,7 @@ EnhancedSelectInput.defaultProps = {
className: styles.enhancedSelect,
disabledClassName: styles.isDisabled,
isDisabled: false,
valueOptions: {},
selectedValueOptions: {},
selectedValueComponent: HintedSelectInputSelectedValue,
optionComponent: HintedSelectInputOption

View File

@@ -63,7 +63,7 @@ class EnhancedSelectInputOption extends Component {
EnhancedSelectInputOption.propTypes = {
className: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
isHidden: PropTypes.bool.isRequired,

View File

@@ -14,6 +14,7 @@ import PasswordInput from './PasswordInput';
import PathInputConnector from './PathInputConnector';
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
import LanguageProfileSelectInputConnector from './LanguageProfileSelectInputConnector';
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
import SeriesTypeSelectInput from './SeriesTypeSelectInput';
import EnhancedSelectInput from './EnhancedSelectInput';
@@ -61,6 +62,9 @@ function getComponent(type) {
case inputTypes.LANGUAGE_PROFILE_SELECT:
return LanguageProfileSelectInputConnector;
case inputTypes.INDEXER_SELECT:
return IndexerSelectInputConnector;
case inputTypes.ROOT_FOLDER_SELECT:
return RootFolderSelectInputConnector;

View File

@@ -0,0 +1,106 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import sortByName from 'Utilities/Array/sortByName';
import { fetchIndexers } from 'Store/Actions/settingsActions';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.indexers,
(state, { includeAny }) => includeAny,
(indexers, includeAny) => {
const {
isFetching,
isPopulated,
error,
items
} = indexers;
const values = _.map(items.sort(sortByName), (indexer) => {
return {
key: indexer.id,
value: indexer.name
};
});
if (includeAny) {
values.unshift({
key: 0,
value: '(Any)'
});
}
return {
isFetching,
isPopulated,
error,
values
};
}
);
}
const mapDispatchToProps = {
dispatchFetchIndexers: fetchIndexers
};
class IndexerSelectInputConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.isPopulated) {
this.props.dispatchFetchIndexers();
}
const {
name,
value,
values
} = this.props;
if (!value || !_.some(values, (option) => parseInt(option.key) === value)) {
this.onChange({ name, value: 0 });
}
}
//
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
}
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
IndexerSelectInputConnector.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
includeAny: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchIndexers: PropTypes.func.isRequired
};
IndexerSelectInputConnector.defaultProps = {
includeAny: false
};
export default connect(createMapStateToProps, mapDispatchToProps)(IndexerSelectInputConnector);

View File

@@ -18,10 +18,19 @@ class PathInput extends Component {
this._node = document.getElementById('portal-root');
this.state = {
value: props.value,
isFileBrowserModalOpen: false
};
}
componentDidUpdate(prevProps) {
const { value } = this.props;
if (prevProps.value !== value) {
this.setState({ value });
}
}
//
// Control
@@ -51,11 +60,8 @@ class PathInput extends Component {
//
// Listeners
onInputChange = (event, { newValue }) => {
this.props.onChange({
name: this.props.name,
value: newValue
});
onInputChange = ({ value }) => {
this.setState({ value });
}
onInputKeyDown = (event) => {
@@ -77,6 +83,11 @@ class PathInput extends Component {
}
onInputBlur = () => {
this.props.onChange({
name: this.props.name,
value: this.state.value
});
this.props.onClearPaths();
}
@@ -108,13 +119,18 @@ class PathInput extends Component {
const {
className,
name,
value,
paths,
includeFiles,
hasFileBrowser,
onChange,
...otherProps
} = this.props;
const {
value,
isFileBrowserModalOpen
} = this.state;
return (
<div className={className}>
<AutoSuggestInput
@@ -130,7 +146,7 @@ class PathInput extends Component {
onSuggestionSelected={this.onSuggestionSelected}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onChange={onChange}
onChange={this.onInputChange}
/>
{
@@ -144,7 +160,7 @@ class PathInput extends Component {
</FormInputButton>
<FileBrowserModal
isOpen={this.state.isFileBrowserModalOpen}
isOpen={isFileBrowserModalOpen}
name={name}
value={value}
includeFiles={includeFiles}

View File

@@ -20,7 +20,7 @@ function getType(type) {
return inputTypes.NUMBER;
case 'path':
return inputTypes.PATH;
case 'filepath':
case 'filePath':
return inputTypes.PATH;
case 'select':
return inputTypes.SELECT;
@@ -28,7 +28,7 @@ function getType(type) {
return inputTypes.TEXT_TAG;
case 'textbox':
return inputTypes.TEXT;
case 'oauth':
case 'oAuth':
return inputTypes.OAUTH;
default:
return inputTypes.TEXT;
@@ -60,6 +60,7 @@ function ProviderFieldFormGroup(props) {
value,
type,
advanced,
hidden,
pending,
errors,
warnings,
@@ -68,6 +69,13 @@ function ProviderFieldFormGroup(props) {
...otherProps
} = props;
if (
hidden === 'hidden' ||
(hidden === 'hiddenIfNotSet' && !value)
) {
return null;
}
return (
<FormGroup
advancedSettings={advancedSettings}
@@ -86,7 +94,7 @@ function ProviderFieldFormGroup(props) {
errors={errors}
warnings={warnings}
pending={pending}
includeFiles={type === 'filepath' ? true : undefined}
includeFiles={type === 'filePath' ? true : undefined}
onChange={onChange}
{...otherProps}
/>
@@ -108,6 +116,7 @@ ProviderFieldFormGroup.propTypes = {
value: PropTypes.any,
type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired,
hidden: PropTypes.string,
pending: PropTypes.bool.isRequired,
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@@ -13,7 +12,7 @@ function createMapStateToProps() {
(state) => state.rootFolders,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, includeNoChange) => {
const values = _.map(rootFolders.items, (rootFolder) => {
const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
@@ -85,7 +84,7 @@ class RootFolderSelectInputConnector extends Component {
onChange
} = this.props;
if (!value || !_.some(values, (v) => v.key === value) || value === ADD_NEW_KEY) {
if (!value || !values.some((v) => v.key === value) || value === ADD_NEW_KEY) {
const defaultValue = values[0];
if (defaultValue.key === ADD_NEW_KEY) {
@@ -96,6 +95,27 @@ class RootFolderSelectInputConnector extends Component {
}
}
componentDidUpdate(prevProps) {
const {
name,
value,
values,
onChange
} = this.props;
if (prevProps.values === values) {
return;
}
if (!value && values.length && values.some((v) => !!v.key && v.key !== ADD_NEW_KEY)) {
const defaultValue = values[0];
if (defaultValue.key !== ADD_NEW_KEY) {
onChange({ name, value: defaultValue.key });
}
}
}
//
// Listeners

View File

@@ -13,6 +13,15 @@
}
}
.value {
display: flex;
}
.seriesFolder {
flex: 0 0 auto;
color: $disabledColor;
}
.freeSpace {
margin-left: 15px;
color: $darkGray;

View File

@@ -7,14 +7,20 @@ import styles from './RootFolderSelectInputOption.css';
function RootFolderSelectInputOption(props) {
const {
id,
value,
freeSpace,
seriesFolder,
isMobile,
isWindows,
...otherProps
} = props;
const slashCharacter = isWindows ? '\\' : '/';
return (
<EnhancedSelectInputOption
id={id}
isMobile={isMobile}
{...otherProps}
>
@@ -23,7 +29,18 @@ function RootFolderSelectInputOption(props) {
isMobile && styles.isMobile
)}
>
<div>{value}</div>
<div className={styles.value}>
{value}
{
seriesFolder && id !== 'addNew' ?
<div className={styles.seriesFolder}>
{slashCharacter}
{seriesFolder}
</div> :
null
}
</div>
{
freeSpace != null &&
@@ -37,9 +54,12 @@ function RootFolderSelectInputOption(props) {
}
RootFolderSelectInputOption.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
isMobile: PropTypes.bool.isRequired
seriesFolder: PropTypes.string,
isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool
};
export default RootFolderSelectInputOption;

View File

@@ -7,10 +7,20 @@
overflow: hidden;
}
.pathContainer {
@add-mixin truncate;
display: flex;
flex: 1 0 0;
}
.path {
@add-mixin truncate;
flex: 0 1 auto;
}
flex: 1 0 0;
.seriesFolder {
flex: 0 1 auto;
color: $disabledColor;
}
.freeSpace {

View File

@@ -8,17 +8,32 @@ function RootFolderSelectInputSelectedValue(props) {
const {
value,
freeSpace,
seriesFolder,
includeFreeSpace,
isWindows,
...otherProps
} = props;
const slashCharacter = isWindows ? '\\' : '/';
return (
<EnhancedSelectInputSelectedValue
className={styles.selectedValue}
{...otherProps}
>
<div className={styles.path}>
{value}
<div className={styles.pathContainer}>
<div className={styles.path}>
{value}
</div>
{
seriesFolder ?
<div className={styles.seriesFolder}>
{slashCharacter}
{seriesFolder}
</div> :
null
}
</div>
{
@@ -34,6 +49,8 @@ function RootFolderSelectInputSelectedValue(props) {
RootFolderSelectInputSelectedValue.propTypes = {
value: PropTypes.string,
freeSpace: PropTypes.number,
seriesFolder: PropTypes.string,
isWindows: PropTypes.bool,
includeFreeSpace: PropTypes.bool.isRequired
};

View File

@@ -1,11 +1,12 @@
import PropTypes from 'prop-types';
import React from 'react';
import * as seriesTypes from 'Utilities/Series/seriesTypes';
import SelectInput from './SelectInput';
const seriesTypeOptions = [
{ key: 'standard', value: 'Standard' },
{ key: 'daily', value: 'Daily' },
{ key: 'anime', value: 'Anime' }
{ key: seriesTypes.STANDARD, value: 'Standard' },
{ key: seriesTypes.DAILY, value: 'Daily' },
{ key: seriesTypes.ANIME, value: 'Anime' }
];
function SeriesTypeSelectInput(props) {

View File

@@ -18,6 +18,6 @@
min-width: 20%;
max-width: 100%;
width: 0%;
height: 21px;
height: 31px;
border: none;
}

View File

@@ -226,7 +226,7 @@ class TagInput extends Component {
className={styles.internalInput}
inputContainerClassName={classNames(
inputContainerClassName,
isFocused && styles.isFocused,
isFocused && styles.isFocused
)}
value={value}
suggestions={suggestions}
@@ -260,7 +260,7 @@ TagInput.propTypes = {
minQueryLength: PropTypes.number.isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
tagComponent: PropTypes.func.isRequired,
tagComponent: PropTypes.elementType.isRequired,
onTagAdd: PropTypes.func.isRequired,
onTagDelete: PropTypes.func.isRequired
};

View File

@@ -4,7 +4,9 @@
bottom: -1px;
left: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;
padding: 6px 16px;
padding: 1px 16px;
min-height: 33px;
cursor: default;
}

View File

@@ -67,7 +67,7 @@ TagInputInput.propTypes = {
inputProps: PropTypes.object.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired,
isFocused: PropTypes.bool.isRequired,
tagComponent: PropTypes.func.isRequired,
tagComponent: PropTypes.elementType.isRequired,
onTagDelete: PropTypes.func.isRequired,
onInputContainerPress: PropTypes.func.isRequired
};

View File

@@ -0,0 +1,5 @@
.tag {
composes: link from '~Components/Link/Link.css';
height: 31px;
}

View File

@@ -4,6 +4,7 @@ import { kinds } from 'Helpers/Props';
import tagShape from 'Helpers/Props/Shapes/tagShape';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import styles from './TagInputTag.css';
class TagInputTag extends Component {
@@ -31,9 +32,9 @@ class TagInputTag extends Component {
tag,
kind
} = this.props;
return (
<Link
className={styles.tag}
tabIndex={-1}
onPress={this.onDelete}
>

View File

@@ -95,8 +95,8 @@ FilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
buttonComponent: PropTypes.func.isRequired,
filterModalConnectorComponent: PropTypes.func,
buttonComponent: PropTypes.elementType.isRequired,
filterModalConnectorComponent: PropTypes.elementType,
filterModalConnectorComponentProps: PropTypes.object,
onFilterSelect: PropTypes.func.isRequired
};

View File

@@ -39,6 +39,7 @@ class Menu extends Component {
this._scheduleUpdate = null;
this._menuButtonId = getUniqueElememtId();
this._menuContentId = getUniqueElememtId();
this.state = {
isMenuOpen: false,
@@ -99,12 +100,14 @@ class Menu extends Component {
window.addEventListener('resize', this.onWindowResize);
window.addEventListener('scroll', this.onWindowScroll, { capture: true });
window.addEventListener('click', this.onWindowClick);
window.addEventListener('touchstart', this.onTouchStart);
}
_removeListener() {
window.removeEventListener('resize', this.onWindowResize);
window.removeEventListener('scroll', this.onWindowScroll, { capture: true });
window.removeEventListener('click', this.onWindowClick);
window.removeEventListener('touchstart', this.onTouchStart);
}
//
@@ -123,6 +126,30 @@ class Menu extends Component {
}
}
onTouchStart = (event) => {
const menuButton = document.getElementById(this._menuButtonId);
const menuContent = document.getElementById(this._menuContentId);
if (!menuButton || !menuContent) {
return;
}
if (event.targetTouches.length !== 1) {
return;
}
const target = event.targetTouches[0].target;
if (
!menuButton.contains(target) &&
!menuContent.contains(target) &&
this.state.isMenuOpen
) {
this.setState({ isMenuOpen: false });
this._removeListener();
}
}
onWindowResize = () => {
this.setMaxHeight();
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import getUniqueElementId from 'Utilities/getUniqueElementId';
import Scroller from 'Components/Scroller/Scroller';
import styles from './MenuContent.css';
@@ -12,6 +13,7 @@ class MenuContent extends Component {
const {
forwardedRef,
className,
id,
children,
style,
isOpen
@@ -19,6 +21,7 @@ class MenuContent extends Component {
return (
<div
id={id}
ref={forwardedRef}
className={className}
style={style}
@@ -38,13 +41,15 @@ class MenuContent extends Component {
MenuContent.propTypes = {
forwardedRef: PropTypes.func,
className: PropTypes.string,
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
style: PropTypes.object,
isOpen: PropTypes.bool
};
MenuContent.defaultProps = {
className: styles.menuContent
className: styles.menuContent,
id: getUniqueElementId()
};
export default MenuContent;

View File

@@ -1,6 +1,5 @@
.menuItem {
@add-mixin truncate;
display: block;
flex-shrink: 0;
padding: 10px 20px;
@@ -17,3 +16,8 @@
text-decoration: none;
}
}
.isDisabled {
color: $disabledColor;
pointer-events: none;
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import Link from 'Components/Link/Link';
import styles from './MenuItem.css';
@@ -12,12 +13,17 @@ class MenuItem extends Component {
const {
className,
children,
isDisabled,
...otherProps
} = this.props;
return (
<Link
className={className}
className={classNames(
className,
isDisabled && styles.isDisabled
)}
isDisabled={isDisabled}
{...otherProps}
>
{children}
@@ -28,11 +34,13 @@ class MenuItem extends Component {
MenuItem.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired
children: PropTypes.node.isRequired,
isDisabled: PropTypes.bool.isRequired
};
MenuItem.defaultProps = {
className: styles.menuItem
className: styles.menuItem,
isDisabled: false
};
export default MenuItem;

View File

@@ -29,6 +29,12 @@
overflow: hidden !important;
}
.modalOpenIOS {
position: fixed;
right: 0;
left: 0;
}
/*
* Sizes
*/

View File

@@ -4,6 +4,8 @@ import ReactDOM from 'react-dom';
import classNames from 'classnames';
import elementClass from 'element-class';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isIOS } from 'Utilities/mobile';
import { setScrollLock } from 'Utilities/scrollLock';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import { sizes } from 'Helpers/Props';
import ErrorBoundary from 'Components/Error/ErrorBoundary';
@@ -31,6 +33,7 @@ class Modal extends Component {
this._node = document.getElementById('portal-root');
this._backgroundRef = null;
this._modalId = getUniqueElememtId();
this._bodyScrollTop = 0;
}
componentDidMount() {
@@ -69,7 +72,14 @@ class Modal extends Component {
window.addEventListener('keydown', this.onKeyDown);
if (openModals.length === 1) {
elementClass(document.body).add(styles.modalOpen);
if (isIOS()) {
setScrollLock(true);
const scrollTop = document.body.scrollTop;
this._bodyScrollTop = scrollTop;
elementClass(document.body).add(styles.modalOpenIOS);
} else {
elementClass(document.body).add(styles.modalOpen);
}
}
}
@@ -78,7 +88,14 @@ class Modal extends Component {
window.removeEventListener('keydown', this.onKeyDown);
if (openModals.length === 0) {
elementClass(document.body).remove(styles.modalOpen);
setScrollLock(false);
if (isIOS()) {
elementClass(document.body).remove(styles.modalOpenIOS);
document.body.scrollTop = this._bodyScrollTop;
} else {
elementClass(document.body).remove(styles.modalOpen);
}
}
}

View File

@@ -48,7 +48,7 @@ ModalBody.propTypes = {
className: PropTypes.string,
innerClassName: PropTypes.string,
children: PropTypes.node,
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL])
scrollDirection: PropTypes.oneOf(scrollDirections.all)
};
ModalBody.defaultProps = {

View File

@@ -7,7 +7,7 @@ import styles from './MonitorToggleButton.css';
function getTooltip(monitored, isDisabled) {
if (isDisabled) {
return 'Cannot toogle monitored state when series is unmonitored';
return 'Cannot toggle monitored state when series is unmonitored';
}
if (monitored) {

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { isLocked } from 'Utilities/scrollLock';
import { scrollDirections } from 'Helpers/Props';
import OverlayScroller from 'Components/Scroller/OverlayScroller';
import Scroller from 'Components/Scroller/Scroller';
@@ -7,6 +8,17 @@ import styles from './PageContentBody.css';
class PageContentBody extends Component {
//
// Listeners
onScroll = (props) => {
const { onScroll } = this.props;
if (this.props.onScroll && !isLocked()) {
onScroll(props);
}
}
//
// Render
@@ -27,6 +39,7 @@ class PageContentBody extends Component {
className={className}
scrollDirection={scrollDirections.VERTICAL}
{...otherProps}
onScroll={this.onScroll}
>
<div className={innerClassName}>
{children}
@@ -41,6 +54,7 @@ PageContentBody.propTypes = {
innerClassName: PropTypes.string,
isSmallScreen: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onScroll: PropTypes.func,
dispatch: PropTypes.func
};

View File

@@ -14,4 +14,4 @@ function createMapStateToProps() {
);
}
export default connect(createMapStateToProps)(PageContentBody);
export default connect(createMapStateToProps, null, null, { forwardRef: true })(PageContentBody);

View File

@@ -94,7 +94,7 @@ PageSidebarItem.propTypes = {
isActiveParent: PropTypes.bool,
isParentItem: PropTypes.bool.isRequired,
isChildItem: PropTypes.bool.isRequired,
statusComponent: PropTypes.func,
statusComponent: PropTypes.elementType,
children: PropTypes.node,
onPress: PropTypes.func
};

View File

@@ -3,7 +3,7 @@
}
.thumb {
min-height: 50px;
min-height: 100px;
border: 1px solid transparent;
border-radius: 5px;
background-color: $scrollbarBackgroundColor;

View File

@@ -4,6 +4,8 @@ import { Scrollbars } from 'react-custom-scrollbars';
import { scrollDirections } from 'Helpers/Props';
import styles from './OverlayScroller.css';
const SCROLLBAR_SIZE = 10;
class OverlayScroller extends Component {
//
@@ -21,7 +23,11 @@ class OverlayScroller extends Component {
scrollTop
} = this.props;
if (!this._isScrolling && scrollTop != null && scrollTop !== prevProps.scrollTop) {
if (
!this._isScrolling &&
scrollTop != null &&
scrollTop !== prevProps.scrollTop
) {
this._scroller.scrollTop(scrollTop);
}
}
@@ -42,6 +48,42 @@ class OverlayScroller extends Component {
);
}
_renderTrackHorizontal = ({ style, props }) => {
const finalStyle = {
...style,
right: 2,
bottom: 2,
left: 2,
borderRadius: 3,
height: SCROLLBAR_SIZE
};
return (
<div
style={finalStyle}
{...props}
/>
);
}
_renderTrackVertical = ({ style, props }) => {
const finalStyle = {
...style,
right: 2,
bottom: 2,
top: 2,
borderRadius: 3,
width: SCROLLBAR_SIZE
};
return (
<div
style={finalStyle}
{...props}
/>
);
}
_renderView = (props) => {
return (
<div
@@ -91,6 +133,8 @@ class OverlayScroller extends Component {
ref={this._setScrollRef}
autoHide={autoHide}
hideTracksWhenNotNeeded={autoScroll}
renderTrackHorizontal={this._renderTrackHorizontal}
renderTrackVertical={this._renderTrackVertical}
renderThumbHorizontal={this._renderThumb}
renderThumbVertical={this._renderThumb}
renderView={this._renderView}

View File

@@ -2,6 +2,7 @@
@add-mixin scrollbar;
@add-mixin scrollbarTrack;
@add-mixin scrollbarThumb;
-webkit-overflow-scrolling: touch;
}
.none {
@@ -26,3 +27,11 @@
overflow-x: auto;
}
}
.both {
overflow: scroll;
&.autoScroll {
overflow: auto;
}
}

View File

@@ -66,7 +66,7 @@ class Scroller extends Component {
Scroller.propTypes = {
className: PropTypes.string,
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL]).isRequired,
scrollDirection: PropTypes.oneOf(scrollDirections.all).isRequired,
autoScroll: PropTypes.bool.isRequired,
scrollTop: PropTypes.number,
children: PropTypes.node,

View File

@@ -9,6 +9,7 @@ import titleCase from 'Utilities/String/titleCase';
import { fetchCommands, updateCommand, finishCommand } from 'Store/Actions/commandActions';
import { setAppValue, setVersion } from 'Store/Actions/appActions';
import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
import { fetchSeries } from 'Store/Actions/seriesActions';
import { fetchHealth } from 'Store/Actions/systemActions';
import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
@@ -72,6 +73,7 @@ const mapDispatchToProps = {
dispatchFetchQueue: fetchQueue,
dispatchFetchQueueDetails: fetchQueueDetails,
dispatchFetchRootFolders: fetchRootFolders,
dispatchFetchSeries: fetchSeries,
dispatchFetchTags: fetchTags,
dispatchFetchTagDetails: fetchTagDetails
};
@@ -260,7 +262,7 @@ class SignalRConnector extends Component {
}
handleSystemTask = () => {
// No-op for now, we may want this later
this.props.dispatchFetchCommands();
}
handleRootfolder = () => {
@@ -288,6 +290,7 @@ class SignalRConnector extends Component {
const {
dispatchFetchCommands,
dispatchFetchSeries,
dispatchSetAppValue
} = this.props;
@@ -295,6 +298,7 @@ class SignalRConnector extends Component {
// are in sync after reconnecting.
if (this.props.isReconnecting || this.props.isDisconnected) {
dispatchFetchSeries();
dispatchFetchCommands();
repopulatePage();
}
@@ -376,6 +380,7 @@ SignalRConnector.propTypes = {
dispatchFetchQueue: PropTypes.func.isRequired,
dispatchFetchQueueDetails: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired,
dispatchFetchSeries: PropTypes.func.isRequired,
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchTagDetails: PropTypes.func.isRequired
};

View File

@@ -53,7 +53,7 @@ RelativeDateCell.propTypes = {
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
component: PropTypes.func,
component: PropTypes.elementType,
dispatch: PropTypes.func
};

View File

@@ -1,5 +1,7 @@
.tableContainer {
overflow-x: auto;
&.horizontalScroll {
overflow-x: auto;
}
}
.table {
@@ -10,7 +12,12 @@
@media only screen and (max-width: $breakpointSmall) {
.tableContainer {
overflow-y: hidden;
width: 100%;
min-width: 100%;
width: fit-content;
&.horizontalScroll {
overflow-y: hidden;
width: 100%;
}
}
}

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import { icons, scrollDirections } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import Scroller from 'Components/Scroller/Scroller';
@@ -28,6 +29,7 @@ function getTableHeaderCellProps(props) {
function Table(props) {
const {
className,
horizontalScroll,
selectAll,
columns,
optionsComponent,
@@ -41,14 +43,22 @@ function Table(props) {
return (
<Scroller
className={styles.tableContainer}
scrollDirection={scrollDirections.HORIZONTAL}
className={classNames(
styles.tableContainer,
horizontalScroll && styles.horizontalScroll
)}
scrollDirection={
horizontalScroll ?
scrollDirections.HORIZONTAL :
scrollDirections.NONE
}
>
<table className={className}>
<TableHeader>
{
selectAll &&
<TableSelectAllHeaderCell {...otherProps} />
selectAll ?
<TableSelectAllHeaderCell {...otherProps} /> :
null
}
{
@@ -111,9 +121,10 @@ function Table(props) {
Table.propTypes = {
className: PropTypes.string,
horizontalScroll: PropTypes.bool.isRequired,
selectAll: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
optionsComponent: PropTypes.func,
optionsComponent: PropTypes.elementType,
pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool,
children: PropTypes.node,
@@ -123,6 +134,7 @@ Table.propTypes = {
Table.defaultProps = {
className: styles.table,
horizontalScroll: true,
selectAll: false
};

View File

@@ -1,7 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { DragDropContext } from 'react-dnd';
import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import { inputTypes } from 'Helpers/Props';
import Button from 'Components/Link/Button';
@@ -127,116 +127,118 @@ class TableOptionsModal extends Component {
const isDraggingDown = isDragging && dropIndex > dragIndex;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
{
isOpen ?
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
<DndProvider backend={HTML5Backend}>
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
{
isOpen ?
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Table Options
</ModalHeader>
</ModalHeader>
<ModalBody>
<Form>
{
hasPageSize ?
<FormGroup>
<FormLabel>Page Size</FormLabel>
<ModalBody>
<Form>
{
hasPageSize ?
<FormGroup>
<FormLabel>Page Size</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="pageSize"
value={pageSize || 0}
helpText="Number of items to show on each page"
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
onChange={this.onPageSizeChange}
/>
</FormGroup> :
null
}
{
OptionsComponent ?
<OptionsComponent
onTableOptionChange={onTableOptionChange}
/> : null
}
{
canModifyColumns ?
<FormGroup>
<FormLabel>Columns</FormLabel>
<div>
<FormInputHelpText
text="Choose which columns are visible and which order they appear in"
<FormInputGroup
type={inputTypes.NUMBER}
name="pageSize"
value={pageSize || 0}
helpText="Number of items to show on each page"
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
onChange={this.onPageSizeChange}
/>
</FormGroup> :
null
}
<div className={styles.columns}>
{
columns.map((column, index) => {
const {
name,
label,
columnLabel,
isVisible,
isModifiable
} = column;
{
OptionsComponent ?
<OptionsComponent
onTableOptionChange={onTableOptionChange}
/> : null
}
{
canModifyColumns ?
<FormGroup>
<FormLabel>Columns</FormLabel>
<div>
<FormInputHelpText
text="Choose which columns are visible and which order they appear in"
/>
<div className={styles.columns}>
{
columns.map((column, index) => {
const {
name,
label,
columnLabel,
isVisible,
isModifiable
} = column;
if (isModifiable !== false) {
return (
<TableOptionsColumnDragSource
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
isModifiable={true}
index={index}
isDragging={isDragging}
isDraggingUp={isDraggingUp}
isDraggingDown={isDraggingDown}
onVisibleChange={this.onVisibleChange}
onColumnDragMove={this.onColumnDragMove}
onColumnDragEnd={this.onColumnDragEnd}
/>
);
}
if (isModifiable !== false) {
return (
<TableOptionsColumnDragSource
<TableOptionsColumn
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
isModifiable={true}
index={index}
isDragging={isDragging}
isDraggingUp={isDraggingUp}
isDraggingDown={isDraggingDown}
isModifiable={false}
onVisibleChange={this.onVisibleChange}
onColumnDragMove={this.onColumnDragMove}
onColumnDragEnd={this.onColumnDragEnd}
/>
);
}
})
}
return (
<TableOptionsColumn
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
index={index}
isModifiable={false}
onVisibleChange={this.onVisibleChange}
/>
);
})
}
<TableOptionsColumnDragPreview />
<TableOptionsColumnDragPreview />
</div>
</div>
</div>
</FormGroup> :
null
}
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
</FormGroup> :
null
}
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
Close
</Button>
</ModalFooter>
</ModalContent> :
null
}
</Modal>
</Button>
</ModalFooter>
</ModalContent> :
null
}
</Modal>
</DndProvider>
);
}
}
@@ -246,7 +248,7 @@ TableOptionsModal.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool.isRequired,
optionsComponent: PropTypes.func,
optionsComponent: PropTypes.elementType,
onTableOptionChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
@@ -255,4 +257,4 @@ TableOptionsModal.defaultProps = {
canModifyColumns: true
};
export default DragDropContext(HTML5Backend)(TableOptionsModal);
export default TableOptionsModal;

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { WindowScroller } from 'react-virtualized';
import { isLocked } from 'Utilities/scrollLock';
import { scrollDirections } from 'Helpers/Props';
import Measure from 'Components/Measure';
import Scroller from 'Components/Scroller/Scroller';
@@ -51,10 +52,10 @@ class VirtualTable extends Component {
}
componentDidUpdate(prevProps, preState) {
const scrollIndex = this.props.scrollIndex;
const { scrollIndex, rowHeight } = this.props;
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
const scrollTop = (scrollIndex + 1) * ROW_HEIGHT + 20;
const scrollTop = (scrollIndex + 1) * rowHeight + 20;
this.props.onScroll({ scrollTop });
}
@@ -83,6 +84,16 @@ class VirtualTable extends Component {
}
}
onScroll = (props) => {
if (isLocked()) {
return;
}
const { onScroll } = this.props;
onScroll(props);
}
//
// Render
@@ -107,7 +118,7 @@ class VirtualTable extends Component {
<Measure onMeasure={this.onMeasure}>
<WindowScroller
scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
onScroll={onScroll}
onScroll={this.onScroll}
>
{({ height, isScrolling }) => {
return (
@@ -154,6 +165,7 @@ VirtualTable.propTypes = {
header: PropTypes.node.isRequired,
headerHeight: PropTypes.number.isRequired,
rowRenderer: PropTypes.func.isRequired,
rowHeight: PropTypes.number.isRequired,
onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
};

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