1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-18 21:35:27 -04: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
+7 -17
View File
@@ -1,22 +1,12 @@
# Auto detect text files and perform LF normalization # 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 # Custom for Visual Studio
*.cs diff=csharp *.cs diff=csharp
*.sln merge=union *.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
+7 -2
View File
@@ -45,6 +45,10 @@ _dotCover*
# DevExpress CodeRush # DevExpress CodeRush
src/.cr/ src/.cr/
# Emacs
*~
\#*\#
# NCrunch # NCrunch
*.ncrunch* *.ncrunch*
.*crunch*.local.xml .*crunch*.local.xml
@@ -115,7 +119,9 @@ node_modules/
_output* _output*
_rawPackage/ _rawPackage/
_dotTrace* _dotTrace*
_tests/ _tests*
_publish*
_temp*
*.Result.xml *.Result.xml
setup/Output/ setup/Output/
*.~is *.~is
@@ -133,6 +139,5 @@ output/*
.DS_Store .DS_Store
_start _start
_temp_*/**/*
src/.idea/ src/.idea/
-4
View File
@@ -1,4 +0,0 @@
[submodule "src/ExternalModules/CurlSharp"]
path = src/ExternalModules/CurlSharp
url = https://github.com/Sonarr/CurlSharp.git
branch = master
+1 -1
View File
@@ -20,7 +20,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
### Requirements ### Requirements
* [Visual Studio 2017] (https://www.visualstudio.com/vs/) * [Visual Studio 2017](https://www.visualstudio.com/vs/)
* [Git](https://git-scm.com/downloads) * [Git](https://git-scm.com/downloads)
* [NodeJS](https://nodejs.org/en/download/) * [NodeJS](https://nodejs.org/en/download/)
* [Yarn](https://yarnpkg.com/) * [Yarn](https://yarnpkg.com/)
+125 -61
View File
@@ -1,15 +1,18 @@
#! /bin/bash #! /bin/bash
msBuildVersion='15.0' msBuildVersion='15.0'
outputFolder='./_output' outputFolder='./_output'
outputFolderWindows='./_output_windows'
outputFolderLinux='./_output_linux' outputFolderLinux='./_output_linux'
outputFolderMacOS='./_output_macos' outputFolderMacOS='./_output_macos'
outputFolderMacOSApp='./_output_macos_app' outputFolderMacOSApp='./_output_macos_app'
testPackageFolder='./_tests/' testPackageFolder='./_tests'
testSearchPattern='*.Test/bin/x86/Release' testPackageFolderWindows='./_tests_windows'
testPackageFolderLinux='./_tests_linux'
sourceFolder='./src' sourceFolder='./src'
slnFile=$sourceFolder/Sonarr.sln slnFile=$sourceFolder/Sonarr.sln
updateFolder=$outputFolder/Sonarr.Update updateSubFolder=Sonarr.Update
updateFolderMono=$outputFolderLinux/Sonarr.Update
sqlitePackageDir="$HOME/.nuget/packages/system.data.sqlite.core.lidarr/1.0.111-5"
nuget='tools/nuget/nuget.exe'; nuget='tools/nuget/nuget.exe';
vswhere='tools/vswhere/vswhere.exe'; vswhere='tools/vswhere/vswhere.exe';
@@ -47,7 +50,8 @@ UpdateVersionNumber()
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4` verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
BUILD_NUMBER=$verMajorMinorRevision.$verBuild BUILD_NUMBER=$verMajorMinorRevision.$verBuild
echo "##teamcity[buildNumber '$BUILD_NUMBER']" 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 fi
} }
@@ -86,16 +90,17 @@ CleanFolder()
BuildWithMSBuild() BuildWithMSBuild()
{ {
installationPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -property installationPath` msBuildPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -find MSBuild\\\\\*\*\\\\Bin\\\\MSBuild.exe`
installationPath=${installationPath/C:\\/\/c\/} msBuildPath=${msBuildPath/C:\\/\/c\/}
installationPath=${installationPath//\\/\/} msBuildPath=${msBuildPath//\\/\/}
msBuild="$installationPath/MSBuild/$msBuildVersion/Bin" msBuildDir=$(dirname "$msBuildPath")
echo $msBuild
export PATH=$msBuild:$PATH echo $msBuildDir
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
export PATH=$msBuildDir:$PATH
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Clean //m
$nuget restore $slnFile $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() BuildWithXbuild()
@@ -103,13 +108,13 @@ BuildWithXbuild()
export MONO_IOMAP=case export MONO_IOMAP=case
CheckExitCode xbuild /t:Clean $slnFile CheckExitCode xbuild /t:Clean $slnFile
mono $nuget restore $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() LintUI()
{ {
ProgressStart 'ESLint' ProgressStart 'ESLint'
CheckExitCode yarn eslint CheckExitCode yarn lint
ProgressEnd 'ESLint' ProgressEnd 'ESLint'
ProgressStart 'Stylelint' ProgressStart 'Stylelint'
@@ -122,6 +127,7 @@ Build()
ProgressStart 'Build' ProgressStart 'Build'
rm -rf $outputFolder rm -rf $outputFolder
rm -rf $testPackageFolder
if [ $runtime = "dotnet" ] ; then if [ $runtime = "dotnet" ] ; then
BuildWithMSBuild BuildWithMSBuild
@@ -131,9 +137,6 @@ Build()
CleanFolder $outputFolder false CleanFolder $outputFolder false
echo "Removing Mono.Posix.dll"
rm $outputFolder/Mono.Posix.dll
ProgressEnd 'Build' ProgressEnd 'Build'
} }
@@ -167,6 +170,36 @@ CreateMdbs()
fi 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() PackageMono()
{ {
ProgressStart 'Creating Mono Package' ProgressStart 'Creating Mono Package'
@@ -190,16 +223,11 @@ PackageMono()
rm -f $outputFolderLinux/sqlite3.* rm -f $outputFolderLinux/sqlite3.*
rm -f $outputFolderLinux/MediaInfo.* rm -f $outputFolderLinux/MediaInfo.*
PatchMono $outputFolderLinux
echo "Adding Sonarr.Core.dll.config (for dllmap)" echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux 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" echo "Renaming Sonarr.Console.exe to Sonarr.exe"
rm $outputFolderLinux/Sonarr.exe* rm $outputFolderLinux/Sonarr.exe*
for file in $outputFolderLinux/Sonarr.Console.exe*; do for file in $outputFolderLinux/Sonarr.Console.exe*; do
@@ -210,7 +238,7 @@ PackageMono()
rm $outputFolderLinux/Sonarr.Windows.* rm $outputFolderLinux/Sonarr.Windows.*
echo "Adding Sonarr.Mono to UpdatePackage" echo "Adding Sonarr.Mono to UpdatePackage"
cp $outputFolderLinux/Sonarr.Mono.* $updateFolderMono cp $outputFolderLinux/Sonarr.Mono.* $outputFolderLinux/$updateSubFolder/
ProgressEnd 'Creating Mono Package' ProgressEnd 'Creating Mono Package'
} }
@@ -229,11 +257,11 @@ PackageMacOS()
echo "Copying Binaries" echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOS cp -r $outputFolderLinux/* $outputFolderMacOS
echo "Adding sqlite dylibs" echo "Adding sqlite dylib"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
echo "Adding MediaInfo dylib" echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
ProgressEnd 'Creating MacOS Package' ProgressEnd 'Creating MacOS Package'
} }
@@ -254,11 +282,11 @@ PackageMacOSApp()
echo "Copying Binaries" echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Adding sqlite dylibs" echo "Adding sqlite dylib"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
echo "Adding MediaInfo dylib" 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" echo "Removing Update Folder"
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update
@@ -266,54 +294,88 @@ PackageMacOSApp()
ProgressEnd 'Creating macOS App Package' ProgressEnd 'Creating macOS App Package'
} }
PackageTests() PackageTestsMono()
{ {
ProgressStart 'Creating Test Package' ProgressStart 'Creating Mono Test Package'
rm -rf $testPackageFolder rm -rf $testPackageFolderLinux
mkdir $testPackageFolder
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \; echo "Copying Binaries"
cp -r $testPackageFolder $testPackageFolderLinux
if [ $runtime = "dotnet" ] ; then 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 else
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
fi fi
cp $outputFolder/*.dll $testPackageFolder echo "Creating MDBs"
cp ./test.sh $testPackageFolder CreateMdbs $testPackageFolderLinux
echo "Creating MDBs for tests" echo "Removing PDBs"
CreateMdbs $testPackageFolder find $testPackageFolderLinux -name "*.pdb" -exec rm "{}" \;
rm -f $testPackageFolder/*.log.config PatchMono $testPackageFolderLinux
CleanFolder $testPackageFolder true
echo "Adding Sonarr.Core.dll.config (for dllmap)" 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 ./test.sh $testPackageFolderLinux/
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder dos2unix $testPackageFolderLinux/test.sh
echo "Copying CurlSharp libraries" echo "Removing Sonarr.Windows"
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder 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" 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" 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() PublishArtifacts()
@@ -321,10 +383,11 @@ PublishArtifacts()
ProgressStart 'Publishing Artifacts' ProgressStart 'Publishing Artifacts'
# Tests # Tests
echo "##teamcity[publishArtifacts '_tests/** => tests.zip']" echo "##teamcity[publishArtifacts '$testPackageFolderWindows/** => tests.windows.zip']"
echo "##teamcity[publishArtifacts '$testPackageFolderLinux/** => tests.linux.zip']"
# Releases # 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 '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.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']" echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
@@ -354,6 +417,7 @@ RunGulp
PackageMono PackageMono
PackageMacOS PackageMacOS
PackageMacOSApp PackageMacOSApp
PackageTests PackageTestsMono
CleanupWindowsPackage PackageTestsWindows
PackageWindows
PublishArtifacts PublishArtifacts
+7
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
+23 -7
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 "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BuildVersion 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 # Restore debian directory to the original files
rm -rf ./debian 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 "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BootstrapVersion echo Running debuild for $BootstrapVersion
debuild -b if [ -z "${TEST_OUTPUT}" ]; then
debuild -b
else
debuild -us -uc -b
fi
echo Moving stuff around echo Moving stuff around
mv ../sonarr_*.deb ./ mv ../sonarr_*.deb ./
mv ../sonarr_*.changes ./ mv ../sonarr_*.changes ./
rm ../sonarr_*.build rm ../sonarr_*.build
echo Signing Package if [ -z "${TEST_OUTPUT}" ]; then
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb" echo Signing Package
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb" dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
echo running alien echo running alien
alien -r -v ./*.deb 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
+6 -5
View File
@@ -7,15 +7,16 @@ Vcs-Git: git@github.com:Sonarr/Sonarr.git
Vcs-Browser: https://github.com/Sonarr/Sonarr Vcs-Browser: https://github.com/Sonarr/Sonarr
Build-Depends: debhelper (>= 9), Build-Depends: debhelper (>= 9),
dh-systemd (>= 1.5), dh-systemd (>= 1.5),
mono-devel (>= 4.6), mono-devel (>= 5.18),
libmono-cil-dev (>= 4.6), libmono-cil-dev (>= 5.18),
cli-common-dev (>= 0.5.7) cli-common-dev (>= 0.9+xamarin5)
Package: sonarr Package: sonarr
Architecture: all Architecture: all
Provides: nzbdrone Provides: nzbdrone
Conflicts: nzbdrone Conflicts: nzbdrone
Replaces: nzbdrone Replaces: nzbdrone
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52), mono-runtime (>= 5.4), ${cli:Depends}, ${misc:Depends} 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: sqlite3 (>= 3.7), mediainfo (>= 0.7.52), ${cli:Recommends} Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03)
Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
Description: Internet PVR Description: Internet PVR
+2 -2
View File
@@ -1,2 +1,2 @@
sonarr_bin/* /usr/lib/sonarr/bin sonarr_bin/* usr/lib/sonarr/bin
package_info /usr/lib/sonarr package_info usr/lib/sonarr
+1 -1
View File
@@ -104,4 +104,4 @@ fi
#DEBHELPER# #DEBHELPER#
exit 0 exit 0
+19 -4
View File
@@ -22,10 +22,25 @@ if [ $1 = "install" ]; then
fi fi
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
# The user used a systemd auto-startup for NzbDrone, we can deal with that. if [ "$psNzbDroneUnit" = "sonarr.service" ]; then
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..." # Conflicts with our new sonarr.service so we have to remove it
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and removing..."
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null 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 else
# We don't support auto migration for other startup methods, so bail. # We don't support auto migration for other startup methods, so bail.
# This leaves the sonarr package in an incomplete state. # This leaves the sonarr package in an incomplete state.
+2 -10
View File
@@ -1,17 +1,9 @@
#!/usr/bin/make -f #!/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. # Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1 #export DH_VERBOSE=1
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven, EXCLUDE_MODULEREFS = crypt32 httpapi __Internal
# but SharpRaven doesn't use any functions that need System.Native
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
%: %:
dh $@ --with=systemd --with=cli dh $@ --with=systemd --with=cli
@@ -20,7 +12,7 @@ EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
override_dh_installinit: override_dh_installinit:
true true
# Sonarr like debug symbols for logging # Sonarr likes debug symbols for logging
override_dh_clistrip: override_dh_clistrip:
override_dh_makeclilibs: override_dh_makeclilibs:
+2 -2
View File
@@ -1,2 +1,2 @@
recommends libcurl3 ignores msbuild
ignores msbuild ignores libmediainfo0v5
+23
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
+18
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
+22
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
+29
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
+15
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
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.
-6
View File
@@ -1,6 +0,0 @@
module.exports = [
'>0.25%',
'not ie 11',
'not op_mini all',
'not chrome < 60'
];
+42 -11
View File
@@ -5,14 +5,17 @@ const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler'); const errorHandler = require('./helpers/errorHandler');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const browsers = require('../browsers'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const uiFolder = 'UI'; const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..'); const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src'); const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1; const isProduction = process.argv.indexOf('--production') > -1;
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder); console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction); console.log('isProduction:', isProduction);
const cssVarsFiles = [ const cssVarsFiles = [
@@ -23,6 +26,22 @@ const cssVarsFiles = [
'../src/Styles/Variables/zIndexes' '../src/Styles/Variables/zIndexes'
].map(require.resolve); ].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 = [ const plugins = [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__DEV__: !isProduction, __DEV__: !isProduction,
@@ -30,7 +49,12 @@ const plugins = [
}), }),
new MiniCssExtractPlugin({ 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: { entry: {
preload: 'preload.js',
vendor: 'vendor.js',
index: 'index.js' index: 'index.js'
}, },
@@ -64,12 +86,20 @@ const config = {
}, },
output: { output: {
filename: path.join('_output', uiFolder, '[name].js'), path: distFolder,
filename: '[name].js',
sourceMapFilename: '[file].map' sourceMapFilename: '[file].map'
}, },
optimization: { optimization: {
chunkIds: 'named' chunkIds: 'named',
splitChunks: {
chunks: 'initial'
}
},
performance: {
hints: false
}, },
plugins, plugins,
@@ -100,7 +130,7 @@ const config = {
loose: true, loose: true,
debug: false, debug: false,
useBuiltIns: 'entry', useBuiltIns: 'entry',
targets: browsers corejs: 3
} }
] ]
] ]
@@ -119,8 +149,9 @@ const config = {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
importLoaders: 1, importLoaders: 1,
localIdentName: '[name]/[local]/[hash:base64:5]', modules: {
modules: true localIdentName: '[name]/[local]/[hash:base64:5]'
}
} }
}, },
{ {
@@ -184,7 +215,7 @@ const config = {
gulp.task('webpack', () => { gulp.task('webpack', () => {
return webpackStream(config) return webpackStream(config)
.pipe(gulp.dest('./')); .pipe(gulp.dest('_output/UI'));
}); });
gulp.task('webpackWatch', () => { gulp.task('webpackWatch', () => {
@@ -192,7 +223,7 @@ gulp.task('webpackWatch', () => {
return webpackStream(config) return webpackStream(config)
.on('error', errorHandler) .on('error', errorHandler)
.pipe(gulp.dest('./')) .pipe(gulp.dest('_output/UI'))
.on('error', errorHandler) .on('error', errorHandler)
.pipe(livereload()) .pipe(livereload())
.on('error', errorHandler); .on('error', errorHandler);
+1 -5
View File
@@ -1,5 +1,4 @@
const reload = require('require-nocache')(module); const reload = require('require-nocache')(module);
const browsers = require('./browsers');
module.exports = (ctx, configPath, options) => { module.exports = (ctx, configPath, options) => {
const config = { const config = {
@@ -16,10 +15,7 @@ module.exports = (ctx, configPath, options) => {
}, {}) }, {})
}, },
'postcss-color-function': {}, 'postcss-color-function': {},
'postcss-nested': {}, 'postcss-nested': {}
autoprefixer: {
browsers
}
} }
}; };
@@ -1,3 +1,4 @@
.message { .messageRemove {
margin-bottom: 30px; margin-bottom: 30px;
color: $dangerColor;
} }
@@ -69,10 +69,14 @@ class RemoveQueueItemModal extends Component {
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div className={styles.message}> <div>
Are you sure you want to remove '{sourceTitle}' from the queue? Are you sure you want to remove '{sourceTitle}' from the queue?
</div> </div>
<div className={styles.messageRemove}>
Removing will remove the download and the file(s) from the download client.
</div>
<FormGroup> <FormGroup>
<FormLabel>Blacklist Release</FormLabel> <FormLabel>Blacklist Release</FormLabel>
<FormInputGroup <FormInputGroup
@@ -35,14 +35,20 @@
.message { .message {
margin-top: 30px; margin-top: 30px;
text-align: center; text-align: center;
font-weight: 300;
font-size: $largeFontSize;
} }
.helpText { .helpText {
margin-bottom: 10px; margin-bottom: 10px;
font-weight: 300;
font-size: 24px; font-size: 24px;
} }
.noSeriesText {
margin-top: 80px;
margin-bottom: 20px;
}
.noResults { .noResults {
margin-bottom: 10px; margin-bottom: 10px;
font-weight: 300; font-weight: 300;
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { icons } from 'Helpers/Props'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import { icons, kinds } from 'Helpers/Props';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
@@ -78,7 +79,8 @@ class AddNewSeries extends Component {
render() { render() {
const { const {
error, error,
items items,
hasExistingSeries
} = this.props; } = this.props;
const term = this.state.term; const term = this.state.term;
@@ -121,8 +123,13 @@ class AddNewSeries extends Component {
} }
{ {
!isFetching && !!error && !isFetching && !!error ?
<div>Failed to load search results, please try again.</div> <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.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>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
</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 /> <div />
</PageContentBodyConnector> </PageContentBodyConnector>
</PageContent> </PageContent>
@@ -176,6 +204,7 @@ AddNewSeries.propTypes = {
isAdding: PropTypes.bool.isRequired, isAdding: PropTypes.bool.isRequired,
addError: PropTypes.object, addError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
hasExistingSeries: PropTypes.bool.isRequired,
onSeriesLookupChange: PropTypes.func.isRequired, onSeriesLookupChange: PropTypes.func.isRequired,
onClearSeriesLookup: PropTypes.func.isRequired onClearSeriesLookup: PropTypes.func.isRequired
}; };
@@ -10,13 +10,15 @@ import AddNewSeries from './AddNewSeries';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.addSeries, (state) => state.addSeries,
(state) => state.series.items.length,
(state) => state.router.location, (state) => state.router.location,
(addSeries, location) => { (addSeries, existingSeriesCount, location) => {
const { params } = parseUrl(location.search); const { params } = parseUrl(location.search);
return { return {
...addSeries,
term: params.term, term: params.term,
...addSeries hasExistingSeries: existingSeriesCount > 0
}; };
} }
); );
@@ -66,9 +66,11 @@ class AddNewSeriesModalContent extends Component {
languageProfileId, languageProfileId,
seriesType, seriesType,
seasonFolder, seasonFolder,
folder,
tags, tags,
showLanguageProfile, showLanguageProfile,
isSmallScreen, isSmallScreen,
isWindows,
onModalClose, onModalClose,
onInputChange, onInputChange,
...otherProps ...otherProps
@@ -115,6 +117,15 @@ class AddNewSeriesModalContent extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT} type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath" name="rootFolderPath"
valueOptions={{
seriesFolder: folder,
isWindows
}}
selectedValueOptions={{
seriesFolder: folder,
isWindows
}}
helpText={`'${folder}' subfolder will be created automatically`}
onChange={onInputChange} onChange={onInputChange}
{...rootFolderPath} {...rootFolderPath}
/> />
@@ -260,9 +271,11 @@ AddNewSeriesModalContent.propTypes = {
languageProfileId: PropTypes.object, languageProfileId: PropTypes.object,
seriesType: PropTypes.object.isRequired, seriesType: PropTypes.object.isRequired,
seasonFolder: PropTypes.object.isRequired, seasonFolder: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
tags: PropTypes.object.isRequired, tags: PropTypes.object.isRequired,
showLanguageProfile: PropTypes.bool.isRequired, showLanguageProfile: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
isWindows: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onAddSeriesPress: PropTypes.func.isRequired onAddSeriesPress: PropTypes.func.isRequired
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions'; import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import selectSettings from 'Store/Selectors/selectSettings'; import selectSettings from 'Store/Selectors/selectSettings';
import AddNewSeriesModalContent from './AddNewSeriesModalContent'; import AddNewSeriesModalContent from './AddNewSeriesModalContent';
@@ -12,7 +13,8 @@ function createMapStateToProps() {
(state) => state.addSeries, (state) => state.addSeries,
(state) => state.settings.languageProfiles, (state) => state.settings.languageProfiles,
createDimensionsSelector(), createDimensionsSelector(),
(addSeriesState, languageProfiles, dimensions) => { createSystemStatusSelector(),
(addSeriesState, languageProfiles, dimensions, systemStatus) => {
const { const {
isAdding, isAdding,
addError, addError,
@@ -32,6 +34,7 @@ function createMapStateToProps() {
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
validationErrors, validationErrors,
validationWarnings, validationWarnings,
isWindows: systemStatus.isWindows,
...settings ...settings
}; };
} }
@@ -1,10 +1,15 @@
.searchResult { .searchResult {
display: flex; position: relative;
margin: 20px 0; margin: 20px 0;
padding: 20px; padding: 20px;
width: 100%; width: 100%;
background-color: $white;
color: inherit; color: inherit;
}
.underlay {
@add-mixin cover;
background-color: $white;
transition: background 500ms; transition: background 500ms;
&:hover { &:hover {
@@ -14,13 +19,25 @@
} }
} }
.overlay {
@add-mixin linkOverlay;
position: relative;
display: flex;
}
.poster { .poster {
flex: 0 0 170px; flex: 0 0 170px;
margin-right: 20px; margin-right: 20px;
height: 250px; height: 250px;
} }
.content {
flex: 0 1 100%;
}
.title { .title {
display: flex;
font-weight: 300; font-weight: 300;
font-size: 36px; font-size: 36px;
} }
@@ -30,6 +47,18 @@
color: $disabledColor; color: $disabledColor;
} }
.tvdbLink {
composes: link from '~Components/Link/Link.css';
margin-top: -4px;
margin-left: auto;
color: $textColor;
}
.tvdbLinkIcon {
margin-left: 10px;
}
.alreadyExistsIcon { .alreadyExistsIcon {
margin-left: 10px; margin-left: 10px;
color: #37bc9b; color: #37bc9b;
@@ -24,7 +24,7 @@ class AddNewSeriesSearchResult extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (!prevProps.isExistingSeries && this.props.isExistingSeries) { if (!prevProps.isExistingSeries && this.props.isExistingSeries) {
this.onAddSerisModalClose(); this.onAddSeriesModalClose();
} }
} }
@@ -35,10 +35,14 @@ class AddNewSeriesSearchResult extends Component {
this.setState({ isNewAddSeriesModalOpen: true }); this.setState({ isNewAddSeriesModalOpen: true });
} }
onAddSerisModalClose = () => { onAddSeriesModalClose = () => {
this.setState({ isNewAddSeriesModalOpen: false }); this.setState({ isNewAddSeriesModalOpen: false });
} }
onTVDBLinkPress = (event) => {
event.stopPropagation();
}
// //
// Render // Render
@@ -53,6 +57,7 @@ class AddNewSeriesSearchResult extends Component {
overview, overview,
statistics, statistics,
ratings, ratings,
folder,
images, images,
isExistingSeries, isExistingSeries,
isSmallScreen isSmallScreen
@@ -72,11 +77,13 @@ class AddNewSeriesSearchResult extends Component {
} }
return ( return (
<div> <div className={styles.searchResult}>
<Link <Link
className={styles.searchResult} className={styles.underlay}
{...linkProps} {...linkProps}
> />
<div className={styles.overlay}>
{ {
isSmallScreen ? isSmallScreen ?
null : null :
@@ -88,7 +95,7 @@ class AddNewSeriesSearchResult extends Component {
/> />
} }
<div> <div className={styles.content}>
<div className={styles.title}> <div className={styles.title}>
{title} {title}
@@ -110,6 +117,18 @@ class AddNewSeriesSearchResult extends Component {
/> : /> :
null 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>
<div> <div>
@@ -152,7 +171,7 @@ class AddNewSeriesSearchResult extends Component {
{overview} {overview}
</div> </div>
</div> </div>
</Link> </div>
<AddNewSeriesModal <AddNewSeriesModal
isOpen={isNewAddSeriesModalOpen && !isExistingSeries} isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
@@ -160,8 +179,9 @@ class AddNewSeriesSearchResult extends Component {
title={title} title={title}
year={year} year={year}
overview={overview} overview={overview}
folder={folder}
images={images} images={images}
onModalClose={this.onAddSerisModalClose} onModalClose={this.onAddSeriesModalClose}
/> />
</div> </div>
); );
@@ -178,6 +198,7 @@ AddNewSeriesSearchResult.propTypes = {
overview: PropTypes.string, overview: PropTypes.string,
statistics: PropTypes.object.isRequired, statistics: PropTypes.object.isRequired,
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingSeries: PropTypes.bool.isRequired, isExistingSeries: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired isSmallScreen: PropTypes.bool.isRequired
@@ -75,10 +75,10 @@ class ImportSeriesSelectFolder extends Component {
Some tips to ensure the import goes smoothly: Some tips to ensure the import goes smoothly:
<ul> <ul>
<li className={styles.tip}> <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>
<li className={styles.tip}> <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> </li>
</ul> </ul>
</div> </div>
+3 -3
View File
@@ -2,8 +2,8 @@
flex: 1 0 14.28%; flex: 1 0 14.28%;
overflow: hidden; overflow: hidden;
min-height: 70px; min-height: 70px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid $calendarBorderColor;
border-left: 1px solid $borderColor; border-left: 1px solid $calendarBorderColor;
} }
.isSingleDay { .isSingleDay {
@@ -12,7 +12,7 @@
.dayOfMonth { .dayOfMonth {
padding-right: 5px; padding-right: 5px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid $calendarBorderColor;
text-align: right; text-align: right;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
.days { .days {
display: flex; display: flex;
border-right: 1px solid $borderColor; border-right: 1px solid $calendarBorderColor;
} }
.day, .day,
@@ -16,10 +16,13 @@
display: flex; display: flex;
} }
.episodeInfo {
color: $calendarTextDim;
}
.seriesTitle, .seriesTitle,
.episodeTitle { .episodeTitle {
@add-mixin truncate; @add-mixin truncate;
flex: 1 0 1px; flex: 1 0 1px;
margin-right: 10px; margin-right: 10px;
} }
@@ -37,6 +40,10 @@
margin-left: 3px; margin-left: 3px;
} }
.airTime {
color: $calendarTextDim;
}
/* /*
* Status * Status
*/ */
@@ -200,7 +200,7 @@ class CalendarEvent extends Component {
</div> </div>
} }
<div> <div className={styles.airTime}>
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })} {formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
</div> </div>
</Link> </Link>
@@ -14,7 +14,6 @@
.seriesTitle { .seriesTitle {
@add-mixin truncate; @add-mixin truncate;
flex: 1 0 1px; flex: 1 0 1px;
margin-right: 10px; margin-right: 10px;
color: #3a3f51; color: #3a3f51;
@@ -23,10 +22,12 @@
.airTime { .airTime {
flex: 1 0 1px; flex: 1 0 1px;
color: $calendarTextDim;
} }
.episodeInfo { .episodeInfo {
margin-left: 10px; margin-left: 10px;
color: $calendarTextDim;
} }
.absoluteEpisodeNumber { .absoluteEpisodeNumber {
@@ -80,3 +81,7 @@
.premiere { .premiere {
composes: premiere from '~Calendar/Events/CalendarEvent.css'; composes: premiere from '~Calendar/Events/CalendarEvent.css';
} }
.unaired {
composes: unaired from '~Calendar/Events/CalendarEvent.css';
}
@@ -56,7 +56,7 @@ class ErrorBoundary extends Component {
ErrorBoundary.propTypes = { ErrorBoundary.propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
errorComponent: PropTypes.func.isRequired errorComponent: PropTypes.elementType.isRequired
}; };
export default ErrorBoundary; export default ErrorBoundary;
@@ -144,6 +144,7 @@ class FileBrowserModalContent extends Component {
<Scroller <Scroller
ref={this.setScrollerRef} ref={this.setScrollerRef}
className={styles.scroller} className={styles.scroller}
scrollDirection={scrollDirections.BOTH}
> >
{ {
!!error && !!error &&
@@ -152,7 +153,10 @@ class FileBrowserModalContent extends Component {
{ {
isPopulated && !error && isPopulated && !error &&
<Table columns={columns}> <Table
horizontalScroll={false}
columns={columns}
>
<TableBody> <TableBody>
{ {
emptyParent && emptyParent &&
@@ -132,6 +132,7 @@ class FilterBuilderModalContent extends Component {
filterBuilderProps, filterBuilderProps,
isSaving, isSaving,
saveError, saveError,
onCancelPress,
onModalClose onModalClose
} = this.props; } = this.props;
@@ -190,7 +191,7 @@ class FilterBuilderModalContent extends Component {
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onCancelPress}>
Cancel Cancel
</Button> </Button>
@@ -220,6 +221,7 @@ FilterBuilderModalContent.propTypes = {
dispatchDeleteCustomFilter: PropTypes.func.isRequired, dispatchDeleteCustomFilter: PropTypes.func.isRequired,
onSaveCustomFilterPress: PropTypes.func.isRequired, onSaveCustomFilterPress: PropTypes.func.isRequired,
dispatchSetFilter: PropTypes.func.isRequired, dispatchSetFilter: PropTypes.func.isRequired,
onCancelPress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
@@ -3,16 +3,21 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import sortByName from 'Utilities/Array/sortByName'; import sortByName from 'Utilities/Array/sortByName';
import { filterBuilderTypes } from 'Helpers/Props'; import { filterBuilderTypes } from 'Helpers/Props';
import * as filterTypes from 'Helpers/Props/filterTypes';
import FilterBuilderRowValue from './FilterBuilderRowValue'; import FilterBuilderRowValue from './FilterBuilderRowValue';
function createTagListSelector() { function createTagListSelector() {
return createSelector( return createSelector(
(state, { filterType }) => filterType,
(state, { sectionItems }) => sectionItems, (state, { sectionItems }) => sectionItems,
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp, (state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
(sectionItems, selectedFilterBuilderProp) => { (filterType, sectionItems, selectedFilterBuilderProp) => {
if ( if (
selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER || (selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
filterType !== filterTypes.EQUAL &&
filterType !== filterBuilderTypes.NOT_EQUAL ||
!selectedFilterBuilderProp.optionsSelector
) { ) {
return []; return [];
} }
@@ -1,4 +1,6 @@
.tag { .tag {
height: 21px;
&.isLastTag { &.isLastTag {
.or { .or {
display: none; display: none;
@@ -29,10 +29,10 @@ function CustomFiltersModalContent(props) {
<ModalBody> <ModalBody>
{ {
customFilters.map((customFilter, index) => { customFilters.map((customFilter) => {
return ( return (
<CustomFilter <CustomFilter
key={index} key={customFilter.id}
id={customFilter.id} id={customFilter.id}
label={customFilter.label} label={customFilter.label}
filters={customFilter.filters} filters={customFilter.filters}
@@ -34,6 +34,17 @@ class FilterModal extends Component {
}); });
} }
onCancelPress = () => {
if (this.state.filterBuilder) {
this.setState({
filterBuilder: false,
id: null
});
} else {
this.onModalClose();
}
}
onModalClose = () => { onModalClose = () => {
this.setState({ this.setState({
filterBuilder: false, filterBuilder: false,
@@ -67,6 +78,7 @@ class FilterModal extends Component {
<FilterBuilderModalContentConnector <FilterBuilderModalContentConnector
{...otherProps} {...otherProps}
id={id} id={id}
onCancelPress={this.onCancelPress}
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
/> : /> :
<CustomFiltersModalContentConnector <CustomFiltersModalContentConnector
@@ -37,6 +37,7 @@
.suggestionsList { .suggestionsList {
margin: 5px 0; margin: 5px 0;
padding-left: 0; padding-left: 0;
max-height: 200px;
list-style-type: none; list-style-type: none;
} }
@@ -176,7 +176,7 @@ class AutoSuggestInput extends Component {
className: classNames( className: classNames(
className, className,
hasError && styles.hasError, hasError && styles.hasError,
hasWarning && styles.hasWarning, hasWarning && styles.hasWarning
), ),
name, name,
value, value,
@@ -234,7 +234,7 @@ AutoSuggestInput.propTypes = {
minHeight: PropTypes.number.isRequired, minHeight: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired, maxHeight: PropTypes.number.isRequired,
getSuggestionValue: PropTypes.func.isRequired, getSuggestionValue: PropTypes.func.isRequired,
renderInputComponent: PropTypes.func, renderInputComponent: PropTypes.elementType,
renderSuggestion: PropTypes.func.isRequired, renderSuggestion: PropTypes.func.isRequired,
onInputChange: PropTypes.func, onInputChange: PropTypes.func,
onInputKeyDown: PropTypes.func, onInputKeyDown: PropTypes.func,
@@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fetchDevices, clearDevices } from 'Store/Actions/deviceActions'; import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
import DeviceInput from './DeviceInput'; import DeviceInput from './DeviceInput';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { value }) => value, (state, { value }) => value,
(state) => state.devices, (state) => state.providerOptions,
(value, devices) => { (value, devices) => {
return { return {
@@ -37,8 +37,8 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchFetchDevices: fetchDevices, dispatchFetchOptions: fetchOptions,
dispatchClearDevices: clearDevices dispatchClearOptions: clearOptions
}; };
class DeviceInputConnector extends Component { class DeviceInputConnector extends Component {
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
} }
componentWillUnmount = () => { componentWillUnmount = () => {
// this.props.dispatchClearDevices(); this.props.dispatchClearOptions();
} }
// //
@@ -61,10 +61,14 @@ class DeviceInputConnector extends Component {
const { const {
provider, provider,
providerData, providerData,
dispatchFetchDevices dispatchFetchOptions
} = this.props; } = this.props;
dispatchFetchDevices({ provider, providerData }); dispatchFetchOptions({
action: 'getDevices',
provider,
providerData
});
} }
// //
@@ -92,8 +96,8 @@ DeviceInputConnector.propTypes = {
providerData: PropTypes.object.isRequired, providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
dispatchFetchDevices: PropTypes.func.isRequired, dispatchFetchOptions: PropTypes.func.isRequired,
dispatchClearDevices: PropTypes.func.isRequired dispatchClearOptions: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector); export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector);
@@ -4,7 +4,7 @@ import React, { Component } from 'react';
import { Manager, Popper, Reference } from 'react-popper'; import { Manager, Popper, Reference } from 'react-popper';
import classNames from 'classnames'; import classNames from 'classnames';
import getUniqueElememtId from 'Utilities/getUniqueElementId'; 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 * as keyCodes from 'Utilities/Constants/keyCodes';
import { icons, sizes, scrollDirections } from 'Helpers/Props'; import { icons, sizes, scrollDirections } from 'Helpers/Props';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
@@ -262,6 +262,7 @@ class EnhancedSelectInput extends Component {
isDisabled, isDisabled,
hasError, hasError,
hasWarning, hasWarning,
valueOptions,
selectedValueOptions, selectedValueOptions,
selectedValueComponent: SelectedValueComponent, selectedValueComponent: SelectedValueComponent,
optionComponent: OptionComponent optionComponent: OptionComponent
@@ -363,6 +364,7 @@ class EnhancedSelectInput extends Component {
key={v.key} key={v.key}
id={v.key} id={v.key}
isSelected={index === selectedIndex} isSelected={index === selectedIndex}
{...valueOptions}
{...v} {...v}
isMobile={false} isMobile={false}
onSelect={this.onSelect} onSelect={this.onSelect}
@@ -404,6 +406,7 @@ class EnhancedSelectInput extends Component {
key={v.key} key={v.key}
id={v.key} id={v.key}
isSelected={index === selectedIndex} isSelected={index === selectedIndex}
{...valueOptions}
{...v} {...v}
isMobile={true} isMobile={true}
onSelect={this.onSelect} onSelect={this.onSelect}
@@ -431,9 +434,10 @@ EnhancedSelectInput.propTypes = {
isDisabled: PropTypes.bool, isDisabled: PropTypes.bool,
hasError: PropTypes.bool, hasError: PropTypes.bool,
hasWarning: PropTypes.bool, hasWarning: PropTypes.bool,
valueOptions: PropTypes.object.isRequired,
selectedValueOptions: PropTypes.object.isRequired, selectedValueOptions: PropTypes.object.isRequired,
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired, selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
optionComponent: PropTypes.func, optionComponent: PropTypes.elementType,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired
}; };
@@ -441,6 +445,7 @@ EnhancedSelectInput.defaultProps = {
className: styles.enhancedSelect, className: styles.enhancedSelect,
disabledClassName: styles.isDisabled, disabledClassName: styles.isDisabled,
isDisabled: false, isDisabled: false,
valueOptions: {},
selectedValueOptions: {}, selectedValueOptions: {},
selectedValueComponent: HintedSelectInputSelectedValue, selectedValueComponent: HintedSelectInputSelectedValue,
optionComponent: HintedSelectInputOption optionComponent: HintedSelectInputOption
+24 -8
View File
@@ -18,10 +18,19 @@ class PathInput extends Component {
this._node = document.getElementById('portal-root'); this._node = document.getElementById('portal-root');
this.state = { this.state = {
value: props.value,
isFileBrowserModalOpen: false isFileBrowserModalOpen: false
}; };
} }
componentDidUpdate(prevProps) {
const { value } = this.props;
if (prevProps.value !== value) {
this.setState({ value });
}
}
// //
// Control // Control
@@ -51,11 +60,8 @@ class PathInput extends Component {
// //
// Listeners // Listeners
onInputChange = (event, { newValue }) => { onInputChange = ({ value }) => {
this.props.onChange({ this.setState({ value });
name: this.props.name,
value: newValue
});
} }
onInputKeyDown = (event) => { onInputKeyDown = (event) => {
@@ -77,6 +83,11 @@ class PathInput extends Component {
} }
onInputBlur = () => { onInputBlur = () => {
this.props.onChange({
name: this.props.name,
value: this.state.value
});
this.props.onClearPaths(); this.props.onClearPaths();
} }
@@ -108,13 +119,18 @@ class PathInput extends Component {
const { const {
className, className,
name, name,
value,
paths, paths,
includeFiles, includeFiles,
hasFileBrowser, hasFileBrowser,
onChange, onChange,
...otherProps ...otherProps
} = this.props; } = this.props;
const {
value,
isFileBrowserModalOpen
} = this.state;
return ( return (
<div className={className}> <div className={className}>
<AutoSuggestInput <AutoSuggestInput
@@ -130,7 +146,7 @@ class PathInput extends Component {
onSuggestionSelected={this.onSuggestionSelected} onSuggestionSelected={this.onSuggestionSelected}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested} onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onChange={onChange} onChange={this.onInputChange}
/> />
{ {
@@ -144,7 +160,7 @@ class PathInput extends Component {
</FormInputButton> </FormInputButton>
<FileBrowserModal <FileBrowserModal
isOpen={this.state.isFileBrowserModalOpen} isOpen={isFileBrowserModalOpen}
name={name} name={name}
value={value} value={value}
includeFiles={includeFiles} includeFiles={includeFiles}
@@ -20,7 +20,7 @@ function getType(type) {
return inputTypes.NUMBER; return inputTypes.NUMBER;
case 'path': case 'path':
return inputTypes.PATH; return inputTypes.PATH;
case 'filepath': case 'filePath':
return inputTypes.PATH; return inputTypes.PATH;
case 'select': case 'select':
return inputTypes.SELECT; return inputTypes.SELECT;
@@ -28,7 +28,7 @@ function getType(type) {
return inputTypes.TEXT_TAG; return inputTypes.TEXT_TAG;
case 'textbox': case 'textbox':
return inputTypes.TEXT; return inputTypes.TEXT;
case 'oauth': case 'oAuth':
return inputTypes.OAUTH; return inputTypes.OAUTH;
default: default:
return inputTypes.TEXT; return inputTypes.TEXT;
@@ -60,6 +60,7 @@ function ProviderFieldFormGroup(props) {
value, value,
type, type,
advanced, advanced,
hidden,
pending, pending,
errors, errors,
warnings, warnings,
@@ -68,6 +69,13 @@ function ProviderFieldFormGroup(props) {
...otherProps ...otherProps
} = props; } = props;
if (
hidden === 'hidden' ||
(hidden === 'hiddenIfNotSet' && !value)
) {
return null;
}
return ( return (
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
@@ -86,7 +94,7 @@ function ProviderFieldFormGroup(props) {
errors={errors} errors={errors}
warnings={warnings} warnings={warnings}
pending={pending} pending={pending}
includeFiles={type === 'filepath' ? true : undefined} includeFiles={type === 'filePath' ? true : undefined}
onChange={onChange} onChange={onChange}
{...otherProps} {...otherProps}
/> />
@@ -108,6 +116,7 @@ ProviderFieldFormGroup.propTypes = {
value: PropTypes.any, value: PropTypes.any,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired, advanced: PropTypes.bool.isRequired,
hidden: PropTypes.string,
pending: PropTypes.bool.isRequired, pending: PropTypes.bool.isRequired,
errors: PropTypes.arrayOf(PropTypes.object).isRequired, errors: PropTypes.arrayOf(PropTypes.object).isRequired,
warnings: PropTypes.arrayOf(PropTypes.object).isRequired, warnings: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -13,7 +12,7 @@ function createMapStateToProps() {
(state) => state.rootFolders, (state) => state.rootFolders,
(state, { includeNoChange }) => includeNoChange, (state, { includeNoChange }) => includeNoChange,
(rootFolders, includeNoChange) => { (rootFolders, includeNoChange) => {
const values = _.map(rootFolders.items, (rootFolder) => { const values = rootFolders.items.map((rootFolder) => {
return { return {
key: rootFolder.path, key: rootFolder.path,
value: rootFolder.path, value: rootFolder.path,
@@ -85,7 +84,7 @@ class RootFolderSelectInputConnector extends Component {
onChange onChange
} = this.props; } = 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]; const defaultValue = values[0];
if (defaultValue.key === ADD_NEW_KEY) { 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 // Listeners
@@ -13,6 +13,15 @@
} }
} }
.value {
display: flex;
}
.seriesFolder {
flex: 0 0 auto;
color: $disabledColor;
}
.freeSpace { .freeSpace {
margin-left: 15px; margin-left: 15px;
color: $darkGray; color: $darkGray;
@@ -7,14 +7,20 @@ import styles from './RootFolderSelectInputOption.css';
function RootFolderSelectInputOption(props) { function RootFolderSelectInputOption(props) {
const { const {
id,
value, value,
freeSpace, freeSpace,
seriesFolder,
isMobile, isMobile,
isWindows,
...otherProps ...otherProps
} = props; } = props;
const slashCharacter = isWindows ? '\\' : '/';
return ( return (
<EnhancedSelectInputOption <EnhancedSelectInputOption
id={id}
isMobile={isMobile} isMobile={isMobile}
{...otherProps} {...otherProps}
> >
@@ -23,7 +29,18 @@ function RootFolderSelectInputOption(props) {
isMobile && styles.isMobile 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 && freeSpace != null &&
@@ -37,9 +54,12 @@ function RootFolderSelectInputOption(props) {
} }
RootFolderSelectInputOption.propTypes = { RootFolderSelectInputOption.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
freeSpace: PropTypes.number, freeSpace: PropTypes.number,
isMobile: PropTypes.bool.isRequired seriesFolder: PropTypes.string,
isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool
}; };
export default RootFolderSelectInputOption; export default RootFolderSelectInputOption;
@@ -7,10 +7,20 @@
overflow: hidden; overflow: hidden;
} }
.pathContainer {
@add-mixin truncate;
display: flex;
flex: 1 0 0;
}
.path { .path {
@add-mixin truncate; @add-mixin truncate;
flex: 0 1 auto;
}
flex: 1 0 0; .seriesFolder {
flex: 0 1 auto;
color: $disabledColor;
} }
.freeSpace { .freeSpace {
@@ -8,17 +8,32 @@ function RootFolderSelectInputSelectedValue(props) {
const { const {
value, value,
freeSpace, freeSpace,
seriesFolder,
includeFreeSpace, includeFreeSpace,
isWindows,
...otherProps ...otherProps
} = props; } = props;
const slashCharacter = isWindows ? '\\' : '/';
return ( return (
<EnhancedSelectInputSelectedValue <EnhancedSelectInputSelectedValue
className={styles.selectedValue} className={styles.selectedValue}
{...otherProps} {...otherProps}
> >
<div className={styles.path}> <div className={styles.pathContainer}>
{value} <div className={styles.path}>
{value}
</div>
{
seriesFolder ?
<div className={styles.seriesFolder}>
{slashCharacter}
{seriesFolder}
</div> :
null
}
</div> </div>
{ {
@@ -34,6 +49,8 @@ function RootFolderSelectInputSelectedValue(props) {
RootFolderSelectInputSelectedValue.propTypes = { RootFolderSelectInputSelectedValue.propTypes = {
value: PropTypes.string, value: PropTypes.string,
freeSpace: PropTypes.number, freeSpace: PropTypes.number,
seriesFolder: PropTypes.string,
isWindows: PropTypes.bool,
includeFreeSpace: PropTypes.bool.isRequired includeFreeSpace: PropTypes.bool.isRequired
}; };
+1 -1
View File
@@ -18,6 +18,6 @@
min-width: 20%; min-width: 20%;
max-width: 100%; max-width: 100%;
width: 0%; width: 0%;
height: 21px; height: 31px;
border: none; border: none;
} }
+2 -2
View File
@@ -226,7 +226,7 @@ class TagInput extends Component {
className={styles.internalInput} className={styles.internalInput}
inputContainerClassName={classNames( inputContainerClassName={classNames(
inputContainerClassName, inputContainerClassName,
isFocused && styles.isFocused, isFocused && styles.isFocused
)} )}
value={value} value={value}
suggestions={suggestions} suggestions={suggestions}
@@ -260,7 +260,7 @@ TagInput.propTypes = {
minQueryLength: PropTypes.number.isRequired, minQueryLength: PropTypes.number.isRequired,
hasError: PropTypes.bool, hasError: PropTypes.bool,
hasWarning: PropTypes.bool, hasWarning: PropTypes.bool,
tagComponent: PropTypes.func.isRequired, tagComponent: PropTypes.elementType.isRequired,
onTagAdd: PropTypes.func.isRequired, onTagAdd: PropTypes.func.isRequired,
onTagDelete: PropTypes.func.isRequired onTagDelete: PropTypes.func.isRequired
}; };
@@ -4,7 +4,9 @@
bottom: -1px; bottom: -1px;
left: -1px; left: -1px;
display: flex; display: flex;
align-items: start;
flex-wrap: wrap; flex-wrap: wrap;
padding: 6px 16px; padding: 1px 16px;
min-height: 33px;
cursor: default; cursor: default;
} }
@@ -67,7 +67,7 @@ TagInputInput.propTypes = {
inputProps: PropTypes.object.isRequired, inputProps: PropTypes.object.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired, kind: PropTypes.oneOf(kinds.all).isRequired,
isFocused: PropTypes.bool.isRequired, isFocused: PropTypes.bool.isRequired,
tagComponent: PropTypes.func.isRequired, tagComponent: PropTypes.elementType.isRequired,
onTagDelete: PropTypes.func.isRequired, onTagDelete: PropTypes.func.isRequired,
onInputContainerPress: PropTypes.func.isRequired onInputContainerPress: PropTypes.func.isRequired
}; };
@@ -0,0 +1,5 @@
.tag {
composes: link from '~Components/Link/Link.css';
height: 31px;
}
+2 -1
View File
@@ -4,6 +4,7 @@ import { kinds } from 'Helpers/Props';
import tagShape from 'Helpers/Props/Shapes/tagShape'; import tagShape from 'Helpers/Props/Shapes/tagShape';
import Label from 'Components/Label'; import Label from 'Components/Label';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import styles from './TagInputTag.css';
class TagInputTag extends Component { class TagInputTag extends Component {
@@ -31,9 +32,9 @@ class TagInputTag extends Component {
tag, tag,
kind kind
} = this.props; } = this.props;
return ( return (
<Link <Link
className={styles.tag}
tabIndex={-1} tabIndex={-1}
onPress={this.onDelete} onPress={this.onDelete}
> >
+2 -2
View File
@@ -95,8 +95,8 @@ FilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
buttonComponent: PropTypes.func.isRequired, buttonComponent: PropTypes.elementType.isRequired,
filterModalConnectorComponent: PropTypes.func, filterModalConnectorComponent: PropTypes.elementType,
filterModalConnectorComponentProps: PropTypes.object, filterModalConnectorComponentProps: PropTypes.object,
onFilterSelect: PropTypes.func.isRequired onFilterSelect: PropTypes.func.isRequired
}; };
+27
View File
@@ -39,6 +39,7 @@ class Menu extends Component {
this._scheduleUpdate = null; this._scheduleUpdate = null;
this._menuButtonId = getUniqueElememtId(); this._menuButtonId = getUniqueElememtId();
this._menuContentId = getUniqueElememtId();
this.state = { this.state = {
isMenuOpen: false, isMenuOpen: false,
@@ -99,12 +100,14 @@ class Menu extends Component {
window.addEventListener('resize', this.onWindowResize); window.addEventListener('resize', this.onWindowResize);
window.addEventListener('scroll', this.onWindowScroll, { capture: true }); window.addEventListener('scroll', this.onWindowScroll, { capture: true });
window.addEventListener('click', this.onWindowClick); window.addEventListener('click', this.onWindowClick);
window.addEventListener('touchstart', this.onTouchStart);
} }
_removeListener() { _removeListener() {
window.removeEventListener('resize', this.onWindowResize); window.removeEventListener('resize', this.onWindowResize);
window.removeEventListener('scroll', this.onWindowScroll, { capture: true }); window.removeEventListener('scroll', this.onWindowScroll, { capture: true });
window.removeEventListener('click', this.onWindowClick); 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 = () => { onWindowResize = () => {
this.setMaxHeight(); this.setMaxHeight();
} }
+6 -1
View File
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import getUniqueElementId from 'Utilities/getUniqueElementId';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
import styles from './MenuContent.css'; import styles from './MenuContent.css';
@@ -12,6 +13,7 @@ class MenuContent extends Component {
const { const {
forwardedRef, forwardedRef,
className, className,
id,
children, children,
style, style,
isOpen isOpen
@@ -19,6 +21,7 @@ class MenuContent extends Component {
return ( return (
<div <div
id={id}
ref={forwardedRef} ref={forwardedRef}
className={className} className={className}
style={style} style={style}
@@ -38,13 +41,15 @@ class MenuContent extends Component {
MenuContent.propTypes = { MenuContent.propTypes = {
forwardedRef: PropTypes.func, forwardedRef: PropTypes.func,
className: PropTypes.string, className: PropTypes.string,
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
style: PropTypes.object, style: PropTypes.object,
isOpen: PropTypes.bool isOpen: PropTypes.bool
}; };
MenuContent.defaultProps = { MenuContent.defaultProps = {
className: styles.menuContent className: styles.menuContent,
id: getUniqueElementId()
}; };
export default MenuContent; export default MenuContent;
+5 -1
View File
@@ -1,6 +1,5 @@
.menuItem { .menuItem {
@add-mixin truncate; @add-mixin truncate;
display: block; display: block;
flex-shrink: 0; flex-shrink: 0;
padding: 10px 20px; padding: 10px 20px;
@@ -17,3 +16,8 @@
text-decoration: none; text-decoration: none;
} }
} }
.isDisabled {
color: $disabledColor;
pointer-events: none;
}
+11 -3
View File
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import classNames from 'classnames';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import styles from './MenuItem.css'; import styles from './MenuItem.css';
@@ -12,12 +13,17 @@ class MenuItem extends Component {
const { const {
className, className,
children, children,
isDisabled,
...otherProps ...otherProps
} = this.props; } = this.props;
return ( return (
<Link <Link
className={className} className={classNames(
className,
isDisabled && styles.isDisabled
)}
isDisabled={isDisabled}
{...otherProps} {...otherProps}
> >
{children} {children}
@@ -28,11 +34,13 @@ class MenuItem extends Component {
MenuItem.propTypes = { MenuItem.propTypes = {
className: PropTypes.string, className: PropTypes.string,
children: PropTypes.node.isRequired children: PropTypes.node.isRequired,
isDisabled: PropTypes.bool.isRequired
}; };
MenuItem.defaultProps = { MenuItem.defaultProps = {
className: styles.menuItem className: styles.menuItem,
isDisabled: false
}; };
export default MenuItem; export default MenuItem;
+6
View File
@@ -29,6 +29,12 @@
overflow: hidden !important; overflow: hidden !important;
} }
.modalOpenIOS {
position: fixed;
right: 0;
left: 0;
}
/* /*
* Sizes * Sizes
*/ */
+19 -2
View File
@@ -4,6 +4,8 @@ import ReactDOM from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import elementClass from 'element-class'; import elementClass from 'element-class';
import getUniqueElememtId from 'Utilities/getUniqueElementId'; import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isIOS } from 'Utilities/mobile';
import { setScrollLock } from 'Utilities/scrollLock';
import * as keyCodes from 'Utilities/Constants/keyCodes'; import * as keyCodes from 'Utilities/Constants/keyCodes';
import { sizes } from 'Helpers/Props'; import { sizes } from 'Helpers/Props';
import ErrorBoundary from 'Components/Error/ErrorBoundary'; import ErrorBoundary from 'Components/Error/ErrorBoundary';
@@ -31,6 +33,7 @@ class Modal extends Component {
this._node = document.getElementById('portal-root'); this._node = document.getElementById('portal-root');
this._backgroundRef = null; this._backgroundRef = null;
this._modalId = getUniqueElememtId(); this._modalId = getUniqueElememtId();
this._bodyScrollTop = 0;
} }
componentDidMount() { componentDidMount() {
@@ -69,7 +72,14 @@ class Modal extends Component {
window.addEventListener('keydown', this.onKeyDown); window.addEventListener('keydown', this.onKeyDown);
if (openModals.length === 1) { 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); window.removeEventListener('keydown', this.onKeyDown);
if (openModals.length === 0) { 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);
}
} }
} }
+1 -1
View File
@@ -48,7 +48,7 @@ ModalBody.propTypes = {
className: PropTypes.string, className: PropTypes.string,
innerClassName: PropTypes.string, innerClassName: PropTypes.string,
children: PropTypes.node, children: PropTypes.node,
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL]) scrollDirection: PropTypes.oneOf(scrollDirections.all)
}; };
ModalBody.defaultProps = { ModalBody.defaultProps = {
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { isLocked } from 'Utilities/scrollLock';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import OverlayScroller from 'Components/Scroller/OverlayScroller'; import OverlayScroller from 'Components/Scroller/OverlayScroller';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
@@ -7,6 +8,17 @@ import styles from './PageContentBody.css';
class PageContentBody extends Component { class PageContentBody extends Component {
//
// Listeners
onScroll = (props) => {
const { onScroll } = this.props;
if (this.props.onScroll && !isLocked()) {
onScroll(props);
}
}
// //
// Render // Render
@@ -27,6 +39,7 @@ class PageContentBody extends Component {
className={className} className={className}
scrollDirection={scrollDirections.VERTICAL} scrollDirection={scrollDirections.VERTICAL}
{...otherProps} {...otherProps}
onScroll={this.onScroll}
> >
<div className={innerClassName}> <div className={innerClassName}>
{children} {children}
@@ -41,6 +54,7 @@ PageContentBody.propTypes = {
innerClassName: PropTypes.string, innerClassName: PropTypes.string,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
onScroll: PropTypes.func,
dispatch: PropTypes.func dispatch: PropTypes.func
}; };
@@ -14,4 +14,4 @@ function createMapStateToProps() {
); );
} }
export default connect(createMapStateToProps)(PageContentBody); export default connect(createMapStateToProps, null, null, { forwardRef: true })(PageContentBody);
@@ -94,7 +94,7 @@ PageSidebarItem.propTypes = {
isActiveParent: PropTypes.bool, isActiveParent: PropTypes.bool,
isParentItem: PropTypes.bool.isRequired, isParentItem: PropTypes.bool.isRequired,
isChildItem: PropTypes.bool.isRequired, isChildItem: PropTypes.bool.isRequired,
statusComponent: PropTypes.func, statusComponent: PropTypes.elementType,
children: PropTypes.node, children: PropTypes.node,
onPress: PropTypes.func onPress: PropTypes.func
}; };
@@ -3,7 +3,7 @@
} }
.thumb { .thumb {
min-height: 50px; min-height: 100px;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 5px; border-radius: 5px;
background-color: $scrollbarBackgroundColor; background-color: $scrollbarBackgroundColor;
@@ -4,6 +4,8 @@ import { Scrollbars } from 'react-custom-scrollbars';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import styles from './OverlayScroller.css'; import styles from './OverlayScroller.css';
const SCROLLBAR_SIZE = 10;
class OverlayScroller extends Component { class OverlayScroller extends Component {
// //
@@ -21,7 +23,11 @@ class OverlayScroller extends Component {
scrollTop scrollTop
} = this.props; } = this.props;
if (!this._isScrolling && scrollTop != null && scrollTop !== prevProps.scrollTop) { if (
!this._isScrolling &&
scrollTop != null &&
scrollTop !== prevProps.scrollTop
) {
this._scroller.scrollTop(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) => { _renderView = (props) => {
return ( return (
<div <div
@@ -91,6 +133,8 @@ class OverlayScroller extends Component {
ref={this._setScrollRef} ref={this._setScrollRef}
autoHide={autoHide} autoHide={autoHide}
hideTracksWhenNotNeeded={autoScroll} hideTracksWhenNotNeeded={autoScroll}
renderTrackHorizontal={this._renderTrackHorizontal}
renderTrackVertical={this._renderTrackVertical}
renderThumbHorizontal={this._renderThumb} renderThumbHorizontal={this._renderThumb}
renderThumbVertical={this._renderThumb} renderThumbVertical={this._renderThumb}
renderView={this._renderView} renderView={this._renderView}
@@ -2,6 +2,7 @@
@add-mixin scrollbar; @add-mixin scrollbar;
@add-mixin scrollbarTrack; @add-mixin scrollbarTrack;
@add-mixin scrollbarThumb; @add-mixin scrollbarThumb;
-webkit-overflow-scrolling: touch;
} }
.none { .none {
@@ -26,3 +27,11 @@
overflow-x: auto; overflow-x: auto;
} }
} }
.both {
overflow: scroll;
&.autoScroll {
overflow: auto;
}
}
+1 -1
View File
@@ -66,7 +66,7 @@ class Scroller extends Component {
Scroller.propTypes = { Scroller.propTypes = {
className: PropTypes.string, className: PropTypes.string,
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL]).isRequired, scrollDirection: PropTypes.oneOf(scrollDirections.all).isRequired,
autoScroll: PropTypes.bool.isRequired, autoScroll: PropTypes.bool.isRequired,
scrollTop: PropTypes.number, scrollTop: PropTypes.number,
children: PropTypes.node, children: PropTypes.node,
@@ -9,6 +9,7 @@ import titleCase from 'Utilities/String/titleCase';
import { fetchCommands, updateCommand, finishCommand } from 'Store/Actions/commandActions'; import { fetchCommands, updateCommand, finishCommand } from 'Store/Actions/commandActions';
import { setAppValue, setVersion } from 'Store/Actions/appActions'; import { setAppValue, setVersion } from 'Store/Actions/appActions';
import { update, updateItem, removeItem } from 'Store/Actions/baseActions'; import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
import { fetchSeries } from 'Store/Actions/seriesActions';
import { fetchHealth } from 'Store/Actions/systemActions'; import { fetchHealth } from 'Store/Actions/systemActions';
import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions'; import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
@@ -72,6 +73,7 @@ const mapDispatchToProps = {
dispatchFetchQueue: fetchQueue, dispatchFetchQueue: fetchQueue,
dispatchFetchQueueDetails: fetchQueueDetails, dispatchFetchQueueDetails: fetchQueueDetails,
dispatchFetchRootFolders: fetchRootFolders, dispatchFetchRootFolders: fetchRootFolders,
dispatchFetchSeries: fetchSeries,
dispatchFetchTags: fetchTags, dispatchFetchTags: fetchTags,
dispatchFetchTagDetails: fetchTagDetails dispatchFetchTagDetails: fetchTagDetails
}; };
@@ -288,6 +290,7 @@ class SignalRConnector extends Component {
const { const {
dispatchFetchCommands, dispatchFetchCommands,
dispatchFetchSeries,
dispatchSetAppValue dispatchSetAppValue
} = this.props; } = this.props;
@@ -295,6 +298,7 @@ class SignalRConnector extends Component {
// are in sync after reconnecting. // are in sync after reconnecting.
if (this.props.isReconnecting || this.props.isDisconnected) { if (this.props.isReconnecting || this.props.isDisconnected) {
dispatchFetchSeries();
dispatchFetchCommands(); dispatchFetchCommands();
repopulatePage(); repopulatePage();
} }
@@ -376,6 +380,7 @@ SignalRConnector.propTypes = {
dispatchFetchQueue: PropTypes.func.isRequired, dispatchFetchQueue: PropTypes.func.isRequired,
dispatchFetchQueueDetails: PropTypes.func.isRequired, dispatchFetchQueueDetails: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired,
dispatchFetchSeries: PropTypes.func.isRequired,
dispatchFetchTags: PropTypes.func.isRequired, dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchTagDetails: PropTypes.func.isRequired dispatchFetchTagDetails: PropTypes.func.isRequired
}; };
@@ -53,7 +53,7 @@ RelativeDateCell.propTypes = {
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired, longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
component: PropTypes.func, component: PropTypes.elementType,
dispatch: PropTypes.func dispatch: PropTypes.func
}; };
+10 -3
View File
@@ -1,5 +1,7 @@
.tableContainer { .tableContainer {
overflow-x: auto; &.horizontalScroll {
overflow-x: auto;
}
} }
.table { .table {
@@ -10,7 +12,12 @@
@media only screen and (max-width: $breakpointSmall) { @media only screen and (max-width: $breakpointSmall) {
.tableContainer { .tableContainer {
overflow-y: hidden; min-width: 100%;
width: 100%; width: fit-content;
&.horizontalScroll {
overflow-y: hidden;
width: 100%;
}
} }
} }
+17 -5
View File
@@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import { icons, scrollDirections } from 'Helpers/Props'; import { icons, scrollDirections } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
@@ -28,6 +29,7 @@ function getTableHeaderCellProps(props) {
function Table(props) { function Table(props) {
const { const {
className, className,
horizontalScroll,
selectAll, selectAll,
columns, columns,
optionsComponent, optionsComponent,
@@ -41,14 +43,22 @@ function Table(props) {
return ( return (
<Scroller <Scroller
className={styles.tableContainer} className={classNames(
scrollDirection={scrollDirections.HORIZONTAL} styles.tableContainer,
horizontalScroll && styles.horizontalScroll
)}
scrollDirection={
horizontalScroll ?
scrollDirections.HORIZONTAL :
scrollDirections.NONE
}
> >
<table className={className}> <table className={className}>
<TableHeader> <TableHeader>
{ {
selectAll && selectAll ?
<TableSelectAllHeaderCell {...otherProps} /> <TableSelectAllHeaderCell {...otherProps} /> :
null
} }
{ {
@@ -111,9 +121,10 @@ function Table(props) {
Table.propTypes = { Table.propTypes = {
className: PropTypes.string, className: PropTypes.string,
horizontalScroll: PropTypes.bool.isRequired,
selectAll: PropTypes.bool.isRequired, selectAll: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
optionsComponent: PropTypes.func, optionsComponent: PropTypes.elementType,
pageSize: PropTypes.number, pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool, canModifyColumns: PropTypes.bool,
children: PropTypes.node, children: PropTypes.node,
@@ -123,6 +134,7 @@ Table.propTypes = {
Table.defaultProps = { Table.defaultProps = {
className: styles.table, className: styles.table,
horizontalScroll: true,
selectAll: false selectAll: false
}; };
@@ -1,7 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { DragDropContext } from 'react-dnd'; import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend'; import HTML5Backend from 'react-dnd-html5-backend';
import { inputTypes } from 'Helpers/Props'; import { inputTypes } from 'Helpers/Props';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
@@ -127,116 +127,118 @@ class TableOptionsModal extends Component {
const isDraggingDown = isDragging && dropIndex > dragIndex; const isDraggingDown = isDragging && dropIndex > dragIndex;
return ( return (
<Modal <DndProvider backend={HTML5Backend}>
isOpen={isOpen} <Modal
onModalClose={onModalClose} isOpen={isOpen}
> onModalClose={onModalClose}
{ >
isOpen ? {
<ModalContent onModalClose={onModalClose}> isOpen ?
<ModalHeader> <ModalContent onModalClose={onModalClose}>
<ModalHeader>
Table Options Table Options
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<Form> <Form>
{ {
hasPageSize ? hasPageSize ?
<FormGroup> <FormGroup>
<FormLabel>Page Size</FormLabel> <FormLabel>Page Size</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.NUMBER} type={inputTypes.NUMBER}
name="pageSize" name="pageSize"
value={pageSize || 0} value={pageSize || 0}
helpText="Number of items to show on each page" helpText="Number of items to show on each page"
errors={pageSizeError ? [{ message: pageSizeError }] : undefined} errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
onChange={this.onPageSizeChange} 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"
/> />
</FormGroup> :
null
}
<div className={styles.columns}> {
{ OptionsComponent ?
columns.map((column, index) => { <OptionsComponent
const { onTableOptionChange={onTableOptionChange}
name, /> : null
label, }
columnLabel,
isVisible, {
isModifiable canModifyColumns ?
} = column; <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 ( return (
<TableOptionsColumnDragSource <TableOptionsColumn
key={name} key={name}
name={name} name={name}
label={label || columnLabel} label={label || columnLabel}
isVisible={isVisible} isVisible={isVisible}
isModifiable={true}
index={index} index={index}
isDragging={isDragging} isModifiable={false}
isDraggingUp={isDraggingUp}
isDraggingDown={isDraggingDown}
onVisibleChange={this.onVisibleChange} onVisibleChange={this.onVisibleChange}
onColumnDragMove={this.onColumnDragMove}
onColumnDragEnd={this.onColumnDragEnd}
/> />
); );
} })
}
return ( <TableOptionsColumnDragPreview />
<TableOptionsColumn </div>
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
index={index}
isModifiable={false}
onVisibleChange={this.onVisibleChange}
/>
);
})
}
<TableOptionsColumnDragPreview />
</div> </div>
</div> </FormGroup> :
</FormGroup> : null
null }
} </Form>
</Form> </ModalBody>
</ModalBody> <ModalFooter>
<ModalFooter> <Button
<Button onPress={onModalClose}
onPress={onModalClose} >
>
Close Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> : </ModalContent> :
null null
} }
</Modal> </Modal>
</DndProvider>
); );
} }
} }
@@ -246,7 +248,7 @@ TableOptionsModal.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
pageSize: PropTypes.number, pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool.isRequired, canModifyColumns: PropTypes.bool.isRequired,
optionsComponent: PropTypes.func, optionsComponent: PropTypes.elementType,
onTableOptionChange: PropTypes.func.isRequired, onTableOptionChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
@@ -255,4 +257,4 @@ TableOptionsModal.defaultProps = {
canModifyColumns: true canModifyColumns: true
}; };
export default DragDropContext(HTML5Backend)(TableOptionsModal); export default TableOptionsModal;
+15 -3
View File
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { WindowScroller } from 'react-virtualized'; import { WindowScroller } from 'react-virtualized';
import { isLocked } from 'Utilities/scrollLock';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import Measure from 'Components/Measure'; import Measure from 'Components/Measure';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
@@ -51,10 +52,10 @@ class VirtualTable extends Component {
} }
componentDidUpdate(prevProps, preState) { componentDidUpdate(prevProps, preState) {
const scrollIndex = this.props.scrollIndex; const { scrollIndex, rowHeight } = this.props;
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) { if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
const scrollTop = (scrollIndex + 1) * ROW_HEIGHT + 20; const scrollTop = (scrollIndex + 1) * rowHeight + 20;
this.props.onScroll({ scrollTop }); this.props.onScroll({ scrollTop });
} }
@@ -83,6 +84,16 @@ class VirtualTable extends Component {
} }
} }
onScroll = (props) => {
if (isLocked()) {
return;
}
const { onScroll } = this.props;
onScroll(props);
}
// //
// Render // Render
@@ -107,7 +118,7 @@ class VirtualTable extends Component {
<Measure onMeasure={this.onMeasure}> <Measure onMeasure={this.onMeasure}>
<WindowScroller <WindowScroller
scrollElement={isSmallScreen ? undefined : this._contentBodyNode} scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
onScroll={onScroll} onScroll={this.onScroll}
> >
{({ height, isScrolling }) => { {({ height, isScrolling }) => {
return ( return (
@@ -154,6 +165,7 @@ VirtualTable.propTypes = {
header: PropTypes.node.isRequired, header: PropTypes.node.isRequired,
headerHeight: PropTypes.number.isRequired, headerHeight: PropTypes.number.isRequired,
rowRenderer: PropTypes.func.isRequired, rowRenderer: PropTypes.func.isRequired,
rowHeight: PropTypes.number.isRequired,
onRender: PropTypes.func.isRequired, onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired onScroll: PropTypes.func.isRequired
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Manager, Popper, Reference } from 'react-popper'; import { Manager, Popper, Reference } from 'react-popper';
import classNames from 'classnames'; import classNames from 'classnames';
import isMobileUtil from 'Utilities/isMobile'; import { isMobile as isMobileUtil } from 'Utilities/mobile';
import { kinds, tooltipPositions } from 'Helpers/Props'; import { kinds, tooltipPositions } from 'Helpers/Props';
import Portal from 'Components/Portal'; import Portal from 'Components/Portal';
import styles from './Tooltip.css'; import styles from './Tooltip.css';
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

@@ -72,6 +72,8 @@ function createMapStateToProps() {
relativePath: episodeFile.relativePath, relativePath: episodeFile.relativePath,
language: episodeFile.language, language: episodeFile.language,
quality: episodeFile.quality, quality: episodeFile.quality,
languageCutoffNotMet: episodeFile.languageCutoffNotMet,
qualityCutoffNotMet: episodeFile.qualityCutoffNotMet,
...episode ...episode
}; };
}); });
@@ -1,11 +1,11 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import padNumber from 'Utilities/Number/padNumber'; import padNumber from 'Utilities/Number/padNumber';
import Label from 'Components/Label';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import EpisodeLanguage from 'Episode/EpisodeLanguage';
import EpisodeQuality from 'Episode/EpisodeQuality'; import EpisodeQuality from 'Episode/EpisodeQuality';
import styles from './EpisodeFileEditorRow'; import styles from './EpisodeFileEditorRow';
@@ -20,6 +20,8 @@ function EpisodeFileEditorRow(props) {
airDateUtc, airDateUtc,
language, language,
quality, quality,
qualityCutoffNotMet,
languageCutoffNotMet,
isSelected, isSelected,
onSelectedChange onSelectedChange
} = props; } = props;
@@ -52,14 +54,16 @@ function EpisodeFileEditorRow(props) {
/> />
<TableRowCell> <TableRowCell>
<Label> <EpisodeLanguage
{language.name} language={language}
</Label> isCutoffNotMet={languageCutoffNotMet}
/>
</TableRowCell> </TableRowCell>
<TableRowCell> <TableRowCell>
<EpisodeQuality <EpisodeQuality
quality={quality} quality={quality}
isCutoffNotMet={qualityCutoffNotMet}
/> />
</TableRowCell> </TableRowCell>
</TableRow> </TableRow>
@@ -76,6 +80,8 @@ EpisodeFileEditorRow.propTypes = {
airDateUtc: PropTypes.string.isRequired, airDateUtc: PropTypes.string.isRequired,
language: PropTypes.object.isRequired, language: PropTypes.object.isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
languageCutoffNotMet: PropTypes.bool.isRequired,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired onSelectedChange: PropTypes.func.isRequired
}; };
+1
View File
@@ -181,6 +181,7 @@ export const SCORE = fasUserPlus;
export const SEARCH = fasSearch; export const SEARCH = fasSearch;
export const SERIES_CONTINUING = fasPlay; export const SERIES_CONTINUING = fasPlay;
export const SERIES_ENDED = fasStop; export const SERIES_ENDED = fasStop;
export const SERIES_DELETED = fasExclamationTriangle;
export const SETTINGS = fasCogs; export const SETTINGS = fasCogs;
export const SHUTDOWN = fasPowerOff; export const SHUTDOWN = fasPowerOff;
export const SORT = fasSort; export const SORT = fasSort;
@@ -1,5 +1,6 @@
export const NONE = 'none'; export const NONE = 'none';
export const BOTH = 'both';
export const HORIZONTAL = 'horizontal'; export const HORIZONTAL = 'horizontal';
export const VERTICAL = 'vertical'; export const VERTICAL = 'vertical';
export const all = [NONE, HORIZONTAL, VERTICAL]; export const all = [NONE, HORIZONTAL, VERTICAL, BOTH];
@@ -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;
}
}
@@ -14,6 +14,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import SelectEpisodeRow from './SelectEpisodeRow'; import SelectEpisodeRow from './SelectEpisodeRow';
import styles from './SelectEpisodeModalContent.css';
const columns = [ const columns = [
{ {
@@ -79,10 +80,12 @@ class SelectEpisodeModalContent extends Component {
render() { render() {
const { const {
ids,
isFetching, isFetching,
isPopulated, isPopulated,
error, error,
items, items,
relativePath,
sortKey, sortKey,
sortDirection, sortDirection,
onSortPress, onSortPress,
@@ -97,10 +100,20 @@ class SelectEpisodeModalContent extends Component {
const errorMessage = getErrorMessage(error, 'Unable to load episodes'); 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 ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Manual Import - Select Episode(s) <div className={styles.header}>
Manual Import - Select Episode(s)
</div>
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@@ -152,17 +165,28 @@ class SelectEpisodeModalContent extends Component {
} }
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter className={styles.footer}>
<Button onPress={onModalClose}> <div className={styles.path}>
Cancel {
</Button> relativePath ?
relativePath :
`${selectedFilesCount} selected files`
}
</div>
<Button <div className={styles.buttons}>
kind={kinds.SUCCESS} <Button onPress={onModalClose}>
onPress={this.onEpisodesSelect} Cancel
> </Button>
Select Episodes
</Button> <Button
kind={kinds.SUCCESS}
isDisabled={!selectionIsValid}
onPress={this.onEpisodesSelect}
>
Select Episodes
</Button>
</div>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
); );
@@ -170,10 +194,12 @@ class SelectEpisodeModalContent extends Component {
} }
SelectEpisodeModalContent.propTypes = { SelectEpisodeModalContent.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
relativePath: PropTypes.string,
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.string, sortDirection: PropTypes.string,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
@@ -1,4 +1,3 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -56,7 +55,13 @@ class SelectEpisodeModalContentConnector extends Component {
} }
onEpisodesSelect = (episodeIds) => { 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) { if (episodeIds.indexOf(item.id) > -1) {
acc.push(item); acc.push(item);
} }
@@ -64,12 +69,22 @@ class SelectEpisodeModalContentConnector extends Component {
return acc; return acc;
}, []); }, []);
this.props.updateInteractiveImportItem({ const episodesPerFile = selectedEpisodes.length / ids.length;
id: this.props.id, const sortedEpisodes = selectedEpisodes.sort((a, b) => {
episodes: _.sortBy(episodes, 'episodeNumber') 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 = { SelectEpisodeModalContentConnector.propTypes = {
id: PropTypes.number.isRequired, ids: PropTypes.arrayOf(PropTypes.number).isRequired,
seriesId: PropTypes.number.isRequired, seriesId: PropTypes.number.isRequired,
seasonNumber: PropTypes.number.isRequired, seasonNumber: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -5,7 +5,7 @@ import getErrorMessage from 'Utilities/Object/getErrorMessage';
import getSelectedIds from 'Utilities/Table/getSelectedIds'; import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll'; import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected'; 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 Button from 'Components/Link/Button';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -20,6 +20,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'; import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal'; import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
@@ -84,12 +85,13 @@ const filterExistingFilesOptions = {
const importModeOptions = [ const importModeOptions = [
{ key: 'move', value: 'Move Files' }, { key: 'move', value: 'Move Files' },
{ key: 'copy', value: 'Copy Files' } { key: 'copy', value: 'Hardlink/Copy Files' }
]; ];
const SELECT = 'select'; const SELECT = 'select';
const SERIES = 'series'; const SERIES = 'series';
const SEASON = 'season'; const SEASON = 'season';
const EPISODE = 'episode';
const LANGUAGE = 'language'; const LANGUAGE = 'language';
const QUALITY = 'quality'; const QUALITY = 'quality';
@@ -208,12 +210,25 @@ class InteractiveImportModalContent extends Component {
} = this.state; } = this.state;
const selectedIds = this.getSelectedIds(); 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 errorMessage = getErrorMessage(error, 'Unable to load manual import items');
const bulkSelectOptions = [ const bulkSelectOptions = [
{ key: SELECT, value: 'Select...', disabled: true }, { key: SELECT, value: 'Select...', disabled: true },
{ key: SEASON, value: 'Select Season' }, { key: SEASON, value: 'Select Season' },
{ key: EPISODE, value: 'Select Episode(s)' },
{ key: LANGUAGE, value: 'Select Language' }, { key: LANGUAGE, value: 'Select Language' },
{ key: QUALITY, value: 'Select Quality' } { key: QUALITY, value: 'Select Quality' }
]; ];
@@ -231,7 +246,7 @@ class InteractiveImportModalContent extends Component {
Manual Import - {title || folder} Manual Import - {title || folder}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody scrollDirection={scrollDirections.BOTH}>
{ {
showFilterExistingFiles && showFilterExistingFiles &&
<div className={styles.filterContainer}> <div className={styles.filterContainer}>
@@ -284,6 +299,7 @@ class InteractiveImportModalContent extends Component {
isPopulated && !!items.length && !isFetching && !isFetching && isPopulated && !!items.length && !isFetching && !isFetching &&
<Table <Table
columns={columns} columns={columns}
horizontalScroll={false}
selectAll={true} selectAll={true}
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
@@ -374,6 +390,14 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose} onModalClose={this.onSelectModalClose}
/> />
<SelectEpisodeModal
isOpen={selectModalOpen === EPISODE}
ids={orderedSelectedIds}
seriesId={selectedItem && selectedItem.series && selectedItem.series.id}
seasonNumber={selectedItem && selectedItem.seasonNumber}
onModalClose={this.onSelectModalClose}
/>
<SelectLanguageModal <SelectLanguageModal
isOpen={selectModalOpen === LANGUAGE} isOpen={selectModalOpen === LANGUAGE}
ids={selectedIds} ids={selectedIds}
@@ -43,6 +43,7 @@ class InteractiveImportModalContentConnector extends Component {
componentDidMount() { componentDidMount() {
const { const {
downloadId, downloadId,
seriesId,
folder folder
} = this.props; } = this.props;
@@ -52,6 +53,7 @@ class InteractiveImportModalContentConnector extends Component {
this.props.dispatchFetchInteractiveImportItems({ this.props.dispatchFetchInteractiveImportItems({
downloadId, downloadId,
seriesId,
folder, folder,
filterExistingFiles filterExistingFiles
}); });
@@ -65,11 +67,13 @@ class InteractiveImportModalContentConnector extends Component {
if (prevState.filterExistingFiles !== filterExistingFiles) { if (prevState.filterExistingFiles !== filterExistingFiles) {
const { const {
downloadId, downloadId,
seriesId,
folder folder
} = this.props; } = this.props;
this.props.dispatchFetchInteractiveImportItems({ this.props.dispatchFetchInteractiveImportItems({
downloadId, downloadId,
seriesId,
folder, folder,
filterExistingFiles filterExistingFiles
}); });
@@ -185,6 +189,7 @@ class InteractiveImportModalContentConnector extends Component {
InteractiveImportModalContentConnector.propTypes = { InteractiveImportModalContentConnector.propTypes = {
downloadId: PropTypes.string, downloadId: PropTypes.string,
seriesId: PropTypes.number,
folder: PropTypes.string, folder: PropTypes.string,
filterExistingFiles: PropTypes.bool.isRequired, filterExistingFiles: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -337,9 +337,10 @@ class InteractiveImportRow extends Component {
<SelectEpisodeModal <SelectEpisodeModal
isOpen={isSelectEpisodeModalOpen} isOpen={isSelectEpisodeModalOpen}
id={id} ids={[id]}
seriesId={series && series.id} seriesId={series && series.id}
seasonNumber={seasonNumber} seasonNumber={seasonNumber}
relativePath={relativePath}
onModalClose={this.onSelectEpisodeModalClose} onModalClose={this.onSelectEpisodeModalClose}
/> />
@@ -1,9 +1,21 @@
.protocol {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
}
.title { .title {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
word-break: break-all; word-break: break-all;
} }
.indexer {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 85px;
}
.quality, .quality,
.language { .language {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
@@ -36,3 +48,9 @@
white-space: nowrap; white-space: nowrap;
} }
.peers {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 75px;
}
@@ -125,7 +125,7 @@ class InteractiveSearchRow extends Component {
return ( return (
<TableRow> <TableRow>
<TableRowCell> <TableRowCell className={styles.protocol}>
<ProtocolLabel <ProtocolLabel
protocol={protocol} protocol={protocol}
/> />
@@ -144,7 +144,7 @@ class InteractiveSearchRow extends Component {
</Link> </Link>
</TableRowCell> </TableRowCell>
<TableRowCell> <TableRowCell className={styles.indexer}>
{indexer} {indexer}
</TableRowCell> </TableRowCell>
@@ -152,7 +152,7 @@ class InteractiveSearchRow extends Component {
{formatBytes(size)} {formatBytes(size)}
</TableRowCell> </TableRowCell>
<TableRowCell> <TableRowCell className={styles.peers}>
{ {
protocol === 'torrent' && protocol === 'torrent' &&
<Peers <Peers
+10 -1
View File
@@ -1,7 +1,16 @@
.link { .link {
composes: link from '~Components/Link/Link.css'; 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, .freeSpace,
+28 -12
View File
@@ -1,7 +1,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import formatBytes from 'Utilities/Number/formatBytes'; 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 IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
@@ -12,30 +13,45 @@ function RootFolderRow(props) {
const { const {
id, id,
path, path,
accessible,
freeSpace, freeSpace,
unmappedFolders, unmappedFolders,
onDeletePress onDeletePress
} = props; } = props;
const unmappedFoldersCount = unmappedFolders.length || '-'; const isUnavailable = !accessible;
return ( return (
<TableRow> <TableRow>
<TableRowCell> <TableRowCell>
<Link {
className={styles.link} isUnavailable ?
to={`/add/import/${id}`} <div className={styles.unavailablePath}>
> {path}
{path}
</Link> <Label
className={styles.unavailableLabel}
kind={kinds.DANGER}
>
Unavailable
</Label>
</div> :
<Link
className={styles.link}
to={`/add/import/${id}`}
>
{path}
</Link>
}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.freeSpace}> <TableRowCell className={styles.freeSpace}>
{formatBytes(freeSpace) || '-'} {(isUnavailable || isNaN(freeSpace)) ? '-' : formatBytes(freeSpace)}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.unmappedFolders}> <TableRowCell className={styles.unmappedFolders}>
{unmappedFoldersCount} {isUnavailable ? '-' : unmappedFolders.length}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.actions}> <TableRowCell className={styles.actions}>
@@ -52,13 +68,13 @@ function RootFolderRow(props) {
RootFolderRow.propTypes = { RootFolderRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
freeSpace: PropTypes.number.isRequired, accessible: PropTypes.bool.isRequired,
freeSpace: PropTypes.number,
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired, unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
onDeletePress: PropTypes.func.isRequired onDeletePress: PropTypes.func.isRequired
}; };
RootFolderRow.defaultProps = { RootFolderRow.defaultProps = {
freeSpace: 0,
unmappedFolders: [] unmappedFolders: []
}; };
+1
View File
@@ -59,6 +59,7 @@ function RootFolders(props) {
key={rootFolder.id} key={rootFolder.id}
id={rootFolder.id} id={rootFolder.id}
path={rootFolder.path} path={rootFolder.path}
accessible={rootFolder.accessible}
freeSpace={rootFolder.freeSpace} freeSpace={rootFolder.freeSpace}
unmappedFolders={rootFolder.unmappedFolders} unmappedFolders={rootFolder.unmappedFolders}
/> />
+5 -3
View File
@@ -1,6 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import MonitorToggleButton from 'Components/MonitorToggleButton'; import MonitorToggleButton from 'Components/MonitorToggleButton';
import TableRow from 'Components/Table/TableRow'; 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 TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import SeriesTitleLink from 'Series/SeriesTitleLink'; import SeriesTitleLink from 'Series/SeriesTitleLink';
import SeasonPassSeason from './SeasonPassSeason'; import SeasonPassSeason from './SeasonPassSeason';
import { getSeriesStatusDetails } from 'Series/SeriesStatus';
import styles from './SeasonPassRow.css'; import styles from './SeasonPassRow.css';
class SeasonPassRow extends Component { class SeasonPassRow extends Component {
@@ -30,6 +30,8 @@ class SeasonPassRow extends Component {
onSeasonMonitoredPress onSeasonMonitoredPress
} = this.props; } = this.props;
const statusDetails = getSeriesStatusDetails(status);
return ( return (
<TableRow> <TableRow>
<TableSelectCell <TableSelectCell
@@ -41,8 +43,8 @@ class SeasonPassRow extends Component {
<TableRowCell className={styles.status}> <TableRowCell className={styles.status}>
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={status === 'ended' ? icons.SERIES_ENDED : icons.SERIES_CONTINUING} name={statusDetails.icon}
title={status === 'ended' ? 'Ended' : 'Continuing'} title={statusDetails.title}
/> />
</TableRowCell> </TableRowCell>

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