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

Compare commits

...

234 Commits

Author SHA1 Message Date
Taloth Saldono
473b146ae2 Include ReferenceAssemblies for build agent 2019-12-29 00:43:05 +01:00
Taloth Saldono
bdc0bb4441 Added Lidarr NuGet package url to get the sqlite package 2019-12-29 00:31:51 +01:00
Taloth Saldono
96c5a37ca8 Added netstandard and System.Runtime Facades from mono 5.18 2019-12-29 00:31:51 +01:00
Taloth Saldono
ce0b7e1077 Increased mono dependency from 5.4 to 5.18 for debian 2019-12-29 00:31:51 +01:00
Taloth Saldono
29cde083f9 Added .NET Framework 4.7.2 requirement check to windows installer 2019-12-28 18:01:46 +01:00
Taloth Saldono
8faebc01ee Updated build scripts and added support for Visual Studio 2019 2019-12-28 18:01:46 +01:00
Taloth Saldono
2ea154b863 Updated Sqlite to x64 and upgraded to 1.0.111 2019-12-28 18:01:46 +01:00
Taloth Saldono
d02dfc9ff1 Updated MediaInfo to x64 2019-12-25 11:45:29 +01:00
Taloth Saldono
7bcfa3d7b2 Updated Interop.NetFwTypeLib to x64 2019-12-25 11:44:44 +01:00
Taloth Saldono
1c1f9cddff Switched projects to x64 2019-12-25 00:40:18 +01:00
Taloth Saldono
8308d65375 Use Newtonsoft in TinyTwitter 2019-12-24 17:37:04 +01:00
Taloth Saldono
d64d59ff27 Moved Windows-only Permission function to Sonarr.Windows 2019-12-24 17:18:07 +01:00
Taloth Saldono
8da6f7d7f4 Removed unused dialects from Marr so it compiles with less dependencies. 2019-12-24 13:47:04 +01: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
681 changed files with 9861 additions and 27187 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/)

186
build.sh
View File

@@ -1,15 +1,18 @@
#! /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
sqlitePackageDir="$HOME/.nuget/packages/system.data.sqlite.core.lidarr/1.0.111-5"
nuget='tools/nuget/nuget.exe';
vswhere='tools/vswhere/vswhere.exe';
@@ -47,7 +50,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,16 +90,17 @@ 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
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
echo $msBuildDir
export PATH=$msBuildDir:$PATH
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Clean //m
$nuget restore $slnFile
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
}
BuildWithXbuild()
@@ -103,13 +108,13 @@ BuildWithXbuild()
export MONO_IOMAP=case
CheckExitCode xbuild /t:Clean $slnFile
mono $nuget restore $slnFile
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x64 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
}
LintUI()
{
ProgressStart 'ESLint'
CheckExitCode yarn eslint
CheckExitCode yarn lint
ProgressEnd 'ESLint'
ProgressStart 'Stylelint'
@@ -122,6 +127,7 @@ Build()
ProgressStart 'Build'
rm -rf $outputFolder
rm -rf $testPackageFolder
if [ $runtime = "dotnet" ] ; then
BuildWithMSBuild
@@ -131,9 +137,6 @@ Build()
CleanFolder $outputFolder false
echo "Removing Mono.Posix.dll"
rm $outputFolder/Mono.Posix.dll
ProgressEnd 'Build'
}
@@ -167,6 +170,36 @@ CreateMdbs()
fi
}
PatchMono()
{
local path=$1
# Copy over the netstandard.dll facade since mono has no separate package for it and includes it in mono-devel
for assembly in netstandard System.Runtime
do
echo "Copy Mono-specific facade $assembly.dll"
cp $sourceFolder/Libraries/Mono/$assembly.dll $path/$assembly.dll
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,16 +223,11 @@ 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
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
rm $outputFolderLinux/Sonarr.exe*
for file in $outputFolderLinux/Sonarr.Console.exe*; do
@@ -210,7 +238,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'
}
@@ -229,11 +257,11 @@ PackageMacOS()
echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOS
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS
echo "Adding sqlite dylib"
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
ProgressEnd 'Creating MacOS Package'
}
@@ -254,11 +282,11 @@ PackageMacOSApp()
echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Adding sqlite dylib"
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
echo "Removing Update Folder"
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update
@@ -266,54 +294,88 @@ 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
cp ./test.sh $testPackageFolderLinux/
dos2unix $testPackageFolderLinux/test.sh
echo "Copying CurlSharp libraries"
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
echo "Removing Sonarr.Windows"
rm $testPackageFolderLinux/Sonarr.Windows.*
ProgressEnd 'Creating Test Package'
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 +383,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 +417,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.18),
libmono-cil-dev (>= 5.18),
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.18), 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 __Internal
%:
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.18
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,29 @@
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 \
libmono-system-net-http4.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

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

@@ -0,0 +1,121 @@
opt_parallel=
opt_version=
opt_mode=both
while getopts 'pv:m:r?h' c
do
case $c in
p) opt_parallel=1 ;;
v) opt_version=$OPTARG ;;
m) opt_mode=$OPTARG ;;
r) opt_report=1 ;;
?|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"
printf " -r only report\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
MONO_VERSIONS=""
# Future versions
MONO_VERSIONS="$MONO_VERSIONS 6.8=preview-xenial"
# Semi-Supported versions
MONO_VERSIONS="$MONO_VERSIONS 6.6 6.4 6.0"
# Supported versions
MONO_VERSIONS="$MONO_VERSIONS 5.20 5.18"
# Legacy unsupported versions (but appear to work)
MONO_VERSIONS="$MONO_VERSIONS 5.16 5.14 5.12"
# Legacy unsupported versions
MONO_VERSIONS="$MONO_VERSIONS 5.10 5.8 5.4 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_report" != "1" ]; then
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
fi
grep "<test-run" ../../_tests_results/**/*.xml | sed -r 's/.*?mono-([0-9.]+(-s)?).*?_([IU]).*?\.xml.*?failed="([0-9]*)".*/\1\t\3:\tfailed \4/g' | sort -V -t.

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

@@ -1,3 +1,4 @@
.message {
.messageRemove {
margin-bottom: 30px;
color: $dangerColor;
}

View File

@@ -69,10 +69,14 @@ class RemoveQueueItemModal extends Component {
</ModalHeader>
<ModalBody>
<div className={styles.message}>
<div>
Are you sure you want to remove '{sourceTitle}' from the queue?
</div>
<div className={styles.messageRemove}>
Removing will remove the download and the file(s) from the download client.
</div>
<FormGroup>
<FormLabel>Blacklist Release</FormLabel>
<FormInputGroup

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

@@ -66,9 +66,11 @@ class AddNewSeriesModalContent extends Component {
languageProfileId,
seriesType,
seasonFolder,
folder,
tags,
showLanguageProfile,
isSmallScreen,
isWindows,
onModalClose,
onInputChange,
...otherProps
@@ -115,6 +117,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}
/>
@@ -260,9 +271,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
};
}

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,7 @@ class AddNewSeriesSearchResult extends Component {
overview,
statistics,
ratings,
folder,
images,
isExistingSeries,
isSmallScreen
@@ -72,11 +77,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 +95,7 @@ class AddNewSeriesSearchResult extends Component {
/>
}
<div>
<div className={styles.content}>
<div className={styles.title}>
{title}
@@ -110,6 +117,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>
@@ -152,7 +171,7 @@ class AddNewSeriesSearchResult extends Component {
{overview}
</div>
</div>
</Link>
</div>
<AddNewSeriesModal
isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
@@ -160,8 +179,9 @@ class AddNewSeriesSearchResult extends Component {
title={title}
year={year}
overview={overview}
folder={folder}
images={images}
onModalClose={this.onAddSerisModalClose}
onModalClose={this.onAddSeriesModalClose}
/>
</div>
);
@@ -178,6 +198,7 @@ AddNewSeriesSearchResult.propTypes = {
overview: PropTypes.string,
statistics: PropTypes.object.isRequired,
ratings: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingSeries: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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
};
@@ -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
};

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Manager, Popper, Reference } from 'react-popper';
import classNames from 'classnames';
import isMobileUtil from 'Utilities/isMobile';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import { kinds, tooltipPositions } from 'Helpers/Props';
import Portal from 'Components/Portal';
import styles from './Tooltip.css';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -72,6 +72,8 @@ function createMapStateToProps() {
relativePath: episodeFile.relativePath,
language: episodeFile.language,
quality: episodeFile.quality,
languageCutoffNotMet: episodeFile.languageCutoffNotMet,
qualityCutoffNotMet: episodeFile.qualityCutoffNotMet,
...episode
};
});

View File

@@ -1,11 +1,11 @@
import PropTypes from 'prop-types';
import React from 'react';
import padNumber from 'Utilities/Number/padNumber';
import Label from 'Components/Label';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import EpisodeLanguage from 'Episode/EpisodeLanguage';
import EpisodeQuality from 'Episode/EpisodeQuality';
import styles from './EpisodeFileEditorRow';
@@ -20,6 +20,8 @@ function EpisodeFileEditorRow(props) {
airDateUtc,
language,
quality,
qualityCutoffNotMet,
languageCutoffNotMet,
isSelected,
onSelectedChange
} = props;
@@ -52,14 +54,16 @@ function EpisodeFileEditorRow(props) {
/>
<TableRowCell>
<Label>
{language.name}
</Label>
<EpisodeLanguage
language={language}
isCutoffNotMet={languageCutoffNotMet}
/>
</TableRowCell>
<TableRowCell>
<EpisodeQuality
quality={quality}
isCutoffNotMet={qualityCutoffNotMet}
/>
</TableRowCell>
</TableRow>
@@ -76,6 +80,8 @@ EpisodeFileEditorRow.propTypes = {
airDateUtc: PropTypes.string.isRequired,
language: PropTypes.object.isRequired,
quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
languageCutoffNotMet: PropTypes.bool.isRequired,
isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired
};

View File

@@ -181,6 +181,7 @@ export const SCORE = fasUserPlus;
export const SEARCH = fasSearch;
export const SERIES_CONTINUING = fasPlay;
export const SERIES_ENDED = fasStop;
export const SERIES_DELETED = fasExclamationTriangle;
export const SETTINGS = fasCogs;
export const SHUTDOWN = fasPowerOff;
export const SORT = fasSort;

View File

@@ -1,5 +1,6 @@
export const NONE = 'none';
export const BOTH = 'both';
export const HORIZONTAL = 'horizontal';
export const VERTICAL = 'vertical';
export const all = [NONE, HORIZONTAL, VERTICAL];
export const all = [NONE, HORIZONTAL, VERTICAL, BOTH];

View File

@@ -0,0 +1,32 @@
.footer {
composes: modalFooter from '~Components/Modal/ModalFooter.css';
display: flex;
justify-content: space-between;
overflow: hidden;
}
.path {
margin-right: 20px;
color: $dimColor;
}
.buttons {
display: flex;
}
@media only screen and (max-width: $breakpointSmall) {
.footer {
display: block;
}
.path {
margin-right: 0;
margin-bottom: 10px;
}
.buttons {
justify-content: space-between;
flex-grow: 1;
}
}

View File

@@ -14,6 +14,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import SelectEpisodeRow from './SelectEpisodeRow';
import styles from './SelectEpisodeModalContent.css';
const columns = [
{
@@ -79,10 +80,12 @@ class SelectEpisodeModalContent extends Component {
render() {
const {
ids,
isFetching,
isPopulated,
error,
items,
relativePath,
sortKey,
sortDirection,
onSortPress,
@@ -97,10 +100,20 @@ class SelectEpisodeModalContent extends Component {
const errorMessage = getErrorMessage(error, 'Unable to load episodes');
const selectedFilesCount = ids.length;
const selectedCount = this.getSelectedIds().length;
const selectionIsValid = (
selectedCount > 0 &&
selectedCount % selectedFilesCount === 0
);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Manual Import - Select Episode(s)
<div className={styles.header}>
Manual Import - Select Episode(s)
</div>
</ModalHeader>
<ModalBody>
@@ -152,17 +165,28 @@ class SelectEpisodeModalContent extends Component {
}
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
Cancel
</Button>
<ModalFooter className={styles.footer}>
<div className={styles.path}>
{
relativePath ?
relativePath :
`${selectedFilesCount} selected files`
}
</div>
<Button
kind={kinds.SUCCESS}
onPress={this.onEpisodesSelect}
>
Select Episodes
</Button>
<div className={styles.buttons}>
<Button onPress={onModalClose}>
Cancel
</Button>
<Button
kind={kinds.SUCCESS}
isDisabled={!selectionIsValid}
onPress={this.onEpisodesSelect}
>
Select Episodes
</Button>
</div>
</ModalFooter>
</ModalContent>
);
@@ -170,10 +194,12 @@ class SelectEpisodeModalContent extends Component {
}
SelectEpisodeModalContent.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
relativePath: PropTypes.string,
sortKey: PropTypes.string,
sortDirection: PropTypes.string,
onSortPress: PropTypes.func.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';
@@ -56,7 +55,13 @@ class SelectEpisodeModalContentConnector extends Component {
}
onEpisodesSelect = (episodeIds) => {
const episodes = _.reduce(this.props.items, (acc, item) => {
const {
ids,
items,
onModalClose
} = this.props;
const selectedEpisodes = items.reduce((acc, item) => {
if (episodeIds.indexOf(item.id) > -1) {
acc.push(item);
}
@@ -64,12 +69,22 @@ class SelectEpisodeModalContentConnector extends Component {
return acc;
}, []);
this.props.updateInteractiveImportItem({
id: this.props.id,
episodes: _.sortBy(episodes, 'episodeNumber')
const episodesPerFile = selectedEpisodes.length / ids.length;
const sortedEpisodes = selectedEpisodes.sort((a, b) => {
return a.seasonNumber - b.seasonNumber;
});
this.props.onModalClose(true);
ids.forEach((id, index) => {
const startingIndex = index * episodesPerFile;
const episodes = sortedEpisodes.slice(startingIndex, startingIndex + episodesPerFile);
this.props.updateInteractiveImportItem({
id,
episodes
});
});
onModalClose(true);
}
//
@@ -87,7 +102,7 @@ class SelectEpisodeModalContentConnector extends Component {
}
SelectEpisodeModalContentConnector.propTypes = {
id: PropTypes.number.isRequired,
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
seriesId: PropTypes.number.isRequired,
seasonNumber: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -5,7 +5,7 @@ import getErrorMessage from 'Utilities/Object/getErrorMessage';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import { align, icons, kinds } from 'Helpers/Props';
import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Icon from 'Components/Icon';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -20,6 +20,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
@@ -84,12 +85,13 @@ const filterExistingFilesOptions = {
const importModeOptions = [
{ key: 'move', value: 'Move Files' },
{ key: 'copy', value: 'Copy Files' }
{ key: 'copy', value: 'Hardlink/Copy Files' }
];
const SELECT = 'select';
const SERIES = 'series';
const SEASON = 'season';
const EPISODE = 'episode';
const LANGUAGE = 'language';
const QUALITY = 'quality';
@@ -208,12 +210,25 @@ class InteractiveImportModalContent extends Component {
} = this.state;
const selectedIds = this.getSelectedIds();
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
const orderedSelectedIds = items.reduce((acc, file) => {
if (selectedIds.includes(file.id)) {
acc.push(file.id);
}
return acc;
}, []);
const selectedItem = selectedIds.length ?
items.find((file) => file.id === selectedIds[0]) :
null;
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
const bulkSelectOptions = [
{ key: SELECT, value: 'Select...', disabled: true },
{ key: SEASON, value: 'Select Season' },
{ key: EPISODE, value: 'Select Episode(s)' },
{ key: LANGUAGE, value: 'Select Language' },
{ key: QUALITY, value: 'Select Quality' }
];
@@ -231,7 +246,7 @@ class InteractiveImportModalContent extends Component {
Manual Import - {title || folder}
</ModalHeader>
<ModalBody>
<ModalBody scrollDirection={scrollDirections.BOTH}>
{
showFilterExistingFiles &&
<div className={styles.filterContainer}>
@@ -284,6 +299,7 @@ class InteractiveImportModalContent extends Component {
isPopulated && !!items.length && !isFetching && !isFetching &&
<Table
columns={columns}
horizontalScroll={false}
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
@@ -374,6 +390,14 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose}
/>
<SelectEpisodeModal
isOpen={selectModalOpen === EPISODE}
ids={orderedSelectedIds}
seriesId={selectedItem && selectedItem.series && selectedItem.series.id}
seasonNumber={selectedItem && selectedItem.seasonNumber}
onModalClose={this.onSelectModalClose}
/>
<SelectLanguageModal
isOpen={selectModalOpen === LANGUAGE}
ids={selectedIds}

View File

@@ -43,6 +43,7 @@ class InteractiveImportModalContentConnector extends Component {
componentDidMount() {
const {
downloadId,
seriesId,
folder
} = this.props;
@@ -52,6 +53,7 @@ class InteractiveImportModalContentConnector extends Component {
this.props.dispatchFetchInteractiveImportItems({
downloadId,
seriesId,
folder,
filterExistingFiles
});
@@ -65,11 +67,13 @@ class InteractiveImportModalContentConnector extends Component {
if (prevState.filterExistingFiles !== filterExistingFiles) {
const {
downloadId,
seriesId,
folder
} = this.props;
this.props.dispatchFetchInteractiveImportItems({
downloadId,
seriesId,
folder,
filterExistingFiles
});
@@ -185,6 +189,7 @@ class InteractiveImportModalContentConnector extends Component {
InteractiveImportModalContentConnector.propTypes = {
downloadId: PropTypes.string,
seriesId: PropTypes.number,
folder: PropTypes.string,
filterExistingFiles: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -337,9 +337,10 @@ class InteractiveImportRow extends Component {
<SelectEpisodeModal
isOpen={isSelectEpisodeModalOpen}
id={id}
ids={[id]}
seriesId={series && series.id}
seasonNumber={seasonNumber}
relativePath={relativePath}
onModalClose={this.onSelectEpisodeModalClose}
/>

View File

@@ -1,9 +1,21 @@
.protocol {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
}
.title {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
word-break: break-all;
}
.indexer {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 85px;
}
.quality,
.language {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
@@ -36,3 +48,9 @@
white-space: nowrap;
}
.peers {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 75px;
}

View File

@@ -125,7 +125,7 @@ class InteractiveSearchRow extends Component {
return (
<TableRow>
<TableRowCell>
<TableRowCell className={styles.protocol}>
<ProtocolLabel
protocol={protocol}
/>
@@ -144,7 +144,7 @@ class InteractiveSearchRow extends Component {
</Link>
</TableRowCell>
<TableRowCell>
<TableRowCell className={styles.indexer}>
{indexer}
</TableRowCell>
@@ -152,7 +152,7 @@ class InteractiveSearchRow extends Component {
{formatBytes(size)}
</TableRowCell>
<TableRowCell>
<TableRowCell className={styles.peers}>
{
protocol === 'torrent' &&
<Peers

View File

@@ -1,7 +1,16 @@
.link {
composes: link from '~Components/Link/Link.css';
}
display: block;
.unavailablePath {
display: flex;
align-items: center;
}
.unavailableLabel {
composes: label from '~Components/Label.css';
margin-left: 10px;
}
.freeSpace,

View File

@@ -1,7 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import formatBytes from 'Utilities/Number/formatBytes';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import TableRow from 'Components/Table/TableRow';
@@ -12,30 +13,45 @@ function RootFolderRow(props) {
const {
id,
path,
accessible,
freeSpace,
unmappedFolders,
onDeletePress
} = props;
const unmappedFoldersCount = unmappedFolders.length || '-';
const isUnavailable = !accessible;
return (
<TableRow>
<TableRowCell>
<Link
className={styles.link}
to={`/add/import/${id}`}
>
{path}
</Link>
{
isUnavailable ?
<div className={styles.unavailablePath}>
{path}
<Label
className={styles.unavailableLabel}
kind={kinds.DANGER}
>
Unavailable
</Label>
</div> :
<Link
className={styles.link}
to={`/add/import/${id}`}
>
{path}
</Link>
}
</TableRowCell>
<TableRowCell className={styles.freeSpace}>
{formatBytes(freeSpace) || '-'}
{(isUnavailable || isNaN(freeSpace)) ? '-' : formatBytes(freeSpace)}
</TableRowCell>
<TableRowCell className={styles.unmappedFolders}>
{unmappedFoldersCount}
{isUnavailable ? '-' : unmappedFolders.length}
</TableRowCell>
<TableRowCell className={styles.actions}>
@@ -52,13 +68,13 @@ function RootFolderRow(props) {
RootFolderRow.propTypes = {
id: PropTypes.number.isRequired,
path: PropTypes.string.isRequired,
freeSpace: PropTypes.number.isRequired,
accessible: PropTypes.bool.isRequired,
freeSpace: PropTypes.number,
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
onDeletePress: PropTypes.func.isRequired
};
RootFolderRow.defaultProps = {
freeSpace: 0,
unmappedFolders: []
};

View File

@@ -59,6 +59,7 @@ function RootFolders(props) {
key={rootFolder.id}
id={rootFolder.id}
path={rootFolder.path}
accessible={rootFolder.accessible}
freeSpace={rootFolder.freeSpace}
unmappedFolders={rootFolder.unmappedFolders}
/>

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import TableRow from 'Components/Table/TableRow';
@@ -8,6 +7,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import SeasonPassSeason from './SeasonPassSeason';
import { getSeriesStatusDetails } from 'Series/SeriesStatus';
import styles from './SeasonPassRow.css';
class SeasonPassRow extends Component {
@@ -30,6 +30,8 @@ class SeasonPassRow extends Component {
onSeasonMonitoredPress
} = this.props;
const statusDetails = getSeriesStatusDetails(status);
return (
<TableRow>
<TableSelectCell
@@ -41,8 +43,8 @@ class SeasonPassRow extends Component {
<TableRowCell className={styles.status}>
<Icon
className={styles.statusIcon}
name={status === 'ended' ? icons.SERIES_ENDED : icons.SERIES_CONTINUING}
title={status === 'ended' ? 'Ended' : 'Continuing'}
name={statusDetails.icon}
title={statusDetails.title}
/>
</TableRowCell>

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