From 23670bca1231239632dfce5c92c9cad42186b7a3 Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 30 Aug 2019 22:50:03 -0400 Subject: [PATCH] New: Upstream Updates Co-Authored-By: Mark McDowall --- .../src/AddMovie/AddNewMovie/AddNewMovie.js | 10 +- .../SelectFolder/ImportMovieSelectFolder.js | 4 +- .../src/Calendar/Events/CalendarEvent.css | 9 +- .../Calendar/Events/CalendarEventGroup.css | 7 +- .../FileBrowser/FileBrowserModalContent.js | 6 +- .../Builder/FilterBuilderModalContent.js | 4 +- frontend/src/Components/Filter/FilterModal.js | 12 + .../Components/Form/EnhancedSelectInput.js | 2 +- frontend/src/Components/Form/PathInput.js | 32 +- frontend/src/Components/Form/TagInput.css | 1 - .../src/Components/Form/TagInputInput.css | 5 +- frontend/src/Components/Form/TagInputTag.css | 5 + frontend/src/Components/Form/TagInputTag.js | 3 +- frontend/src/Components/Menu/Menu.js | 27 ++ frontend/src/Components/Menu/MenuContent.js | 3 + frontend/src/Components/Menu/MenuItem.css | 6 +- frontend/src/Components/Menu/MenuItem.js | 14 +- frontend/src/Components/Modal/Modal.css | 6 + frontend/src/Components/Modal/Modal.js | 21 +- frontend/src/Components/Modal/ModalBody.js | 2 +- .../src/Components/Page/PageContentBody.js | 14 + frontend/src/Components/Scroller/Scroller.css | 9 + frontend/src/Components/Scroller/Scroller.js | 2 +- frontend/src/Components/SignalRConnector.js | 5 + frontend/src/Components/Table/Table.css | 13 +- frontend/src/Components/Table/Table.js | 20 +- frontend/src/Components/Table/VirtualTable.js | 13 +- frontend/src/Components/Tooltip/Tooltip.js | 2 +- .../src/Helpers/Props/scrollDirections.js | 3 +- .../InteractiveImportModalContent.js | 7 +- .../InteractiveImportModalContentConnector.js | 5 + .../InteractiveSearchRow.css | 18 + .../InteractiveSearch/InteractiveSearchRow.js | 6 +- frontend/src/Movie/Details/MovieDetails.js | 3 +- frontend/src/RootFolder/RootFolderRow.css | 11 +- frontend/src/RootFolder/RootFolderRow.js | 40 +- .../src/Settings/General/LoggingSettings.js | 12 +- .../MediaManagement/MediaManagement.js | 36 ++ .../RootFolder/AddRootFolder.css | 7 + .../RootFolder/AddRootFolder.js | 71 +++ .../RootFolder/AddRootFolderConnector.js | 13 + .../Quality/EditQualityProfileModalContent.js | 6 +- .../Tags/Details/TagDetailsModalContent.js | 1 + frontend/src/Settings/Tags/Tag.css | 3 +- frontend/src/Store/Actions/movieActions.js | 2 +- frontend/src/Styles/Variables/colors.js | 1 + frontend/src/System/Status/Health/Health.js | 2 + .../src/System/Tasks/Queued/QueuedTaskRow.css | 2 +- frontend/src/Utilities/isMobile.js | 7 - frontend/src/Utilities/mobile.js | 12 + frontend/src/Utilities/scrollLock.js | 13 + .../ManualImport/ManualImportModule.cs | 2 +- .../Qualities/CustomFormatModule.cs | 4 +- .../AutomationTest.cs | 4 +- .../DiskTests/DiskProviderFixtureBase.cs | 4 + .../DiskTests/DiskTransferServiceFixture.cs | 1 + .../IOperatingSystemVersionInfo.cs | 9 - src/NzbDrone.Common/NzbDrone.Common.csproj | 1 - .../Converters/Int32ConverterFixture.cs | 2 +- .../CutoffSpecificationFixture.cs | 15 + .../NzbgetTests/NzbgetFixture.cs | 6 +- .../Framework/TestBaseTests.cs | 61 --- .../FixFutureIndexerStatusTimesFixture.cs | 119 +++++ .../FetchAndParseRssServiceFixture.cs | 78 --- .../FetchAndParseRssServiceFixture.cs | 65 --- .../IndexerTests/RarbgTests/RarbgFixture.cs | 2 +- .../IndexerTests/SeedConfigProviderFixture.cs | 34 ++ .../FreeSpaceSpecificationFixture.cs | 7 +- .../SameFileSpecificationFixture.cs | 11 + .../UpgradeMediaFileServiceFixture.cs | 4 +- .../NotificationTests/GrowlProviderTest.cs | 63 --- .../NzbDrone.Core.Test.csproj | 2 + .../RecycleBinProviderTests/CleanupFixture.cs | 11 +- .../RootFolderServiceFixture.cs | 1 - .../Configuration/ConfigFileProvider.cs | 2 +- .../Configuration/ConfigService.cs | 13 + .../Configuration/IConfigService.cs | 2 + .../RssSync/HistorySpecification.cs | 4 +- .../Specifications/UpgradableSpecification.cs | 4 +- .../Download/Clients/Hadouken/Hadouken.cs | 6 +- .../Clients/Hadouken/HadoukenProxy.cs | 2 +- .../Download/Clients/Nzbget/Nzbget.cs | 2 +- .../Download/Clients/Sabnzbd/Sabnzbd.cs | 13 +- .../Indexers/Rarbg/RarbgParser.cs | 2 +- .../MovieImport/Manual/ManualImportService.cs | 26 +- .../Specifications/FreeSpaceSpecification.cs | 2 +- .../Specifications/SameFileSpecification.cs | 10 +- .../MediaFiles/RecycleBinProvider.cs | 66 +-- .../Messaging/Commands/CommandExecutor.cs | 2 +- .../MetadataSource/SkyHook/SkyHookProxy.cs | 4 +- .../Movies/RefreshMovieService.cs | 2 +- .../Notifications/Plex/PlexTv/PlexTvProxy.cs | 2 +- .../Augmenters/AugmentWithReleaseInfo.cs | 2 +- src/NzbDrone.Core/Parser/LanguageParser.cs | 2 +- src/NzbDrone.Core/Parser/Parser.cs | 4 +- src/NzbDrone.Core/Parser/QualityParser.cs | 2 +- .../RootFolders/RootFolderService.cs | 31 +- .../Validation/NzbDroneValidator.cs | 17 - .../MiddleWare/NzbDroneVersionMiddleWare.cs | 2 +- .../Config/MediaManagementConfigModule.cs | 2 + .../Config/MediaManagementConfigResource.cs | 4 + .../Indexers/ReleaseModuleBase.cs | 3 +- .../ManualImport/ManualImportModule.cs | 3 +- .../Qualities/CustomFormatModule.cs | 4 +- .../Restrictions/RestrictionModule.cs | 8 +- src/Radarr.Api.V2/Tags/TagDetailsModule.cs | 4 +- src/Radarr.Api.V2/Tags/TagModule.cs | 8 +- .../Extensions/RequestExtensions.cs | 12 + yarn.lock | 456 +++++++++--------- 109 files changed, 1060 insertions(+), 712 deletions(-) create mode 100644 frontend/src/Components/Form/TagInputTag.css create mode 100644 frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.css create mode 100644 frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.js create mode 100644 frontend/src/Settings/MediaManagement/RootFolder/AddRootFolderConnector.js delete mode 100644 frontend/src/Utilities/isMobile.js create mode 100644 frontend/src/Utilities/mobile.js create mode 100644 frontend/src/Utilities/scrollLock.js delete mode 100644 src/NzbDrone.Common/EnvironmentInfo/IOperatingSystemVersionInfo.cs delete mode 100644 src/NzbDrone.Core.Test/Framework/TestBaseTests.cs create mode 100644 src/NzbDrone.Core.Test/Housekeeping/Housekeepers/FixFutureIndexerStatusTimesFixture.cs delete mode 100644 src/NzbDrone.Core.Test/IndexerSearchTests/FetchAndParseRssServiceFixture.cs delete mode 100644 src/NzbDrone.Core.Test/IndexerTests/FetchAndParseRssServiceFixture.cs create mode 100644 src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs delete mode 100644 src/NzbDrone.Core.Test/NotificationTests/GrowlProviderTest.cs delete mode 100644 src/NzbDrone.Core/Validation/NzbDroneValidator.cs diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js index 01905a441..0960e4e2e 100644 --- a/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js +++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovie.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import getErrorMessage from 'Utilities/Object/getErrorMessage'; import { icons } from 'Helpers/Props'; import Button from 'Components/Link/Button'; import Link from 'Components/Link/Link'; @@ -120,8 +121,13 @@ class AddNewMovie extends Component { } { - !isFetching && !!error && -
Failed to load search results, please try again.
+ !isFetching && !!error ? +
+
+ Failed to load search results, please try again. +
+
{getErrorMessage(error)}
+
: null } { diff --git a/frontend/src/AddMovie/ImportMovie/SelectFolder/ImportMovieSelectFolder.js b/frontend/src/AddMovie/ImportMovie/SelectFolder/ImportMovieSelectFolder.js index 2c6161fbf..43312b970 100644 --- a/frontend/src/AddMovie/ImportMovie/SelectFolder/ImportMovieSelectFolder.js +++ b/frontend/src/AddMovie/ImportMovie/SelectFolder/ImportMovieSelectFolder.js @@ -99,10 +99,10 @@ class ImportMovieSelectFolder extends Component { Some tips to ensure the import goes smoothly: diff --git a/frontend/src/Calendar/Events/CalendarEvent.css b/frontend/src/Calendar/Events/CalendarEvent.css index 4ebc8bc12..9acf6dcf5 100644 --- a/frontend/src/Calendar/Events/CalendarEvent.css +++ b/frontend/src/Calendar/Events/CalendarEvent.css @@ -16,10 +16,13 @@ display: flex; } +.episodeInfo { + color: $calendarTextDim; +} + .seriesTitle, .episodeTitle { @add-mixin truncate; - flex: 1 0 1px; margin-right: 10px; } @@ -37,6 +40,10 @@ margin-left: 3px; } +.airTime { + color: $calendarTextDim; +} + /* * Status */ diff --git a/frontend/src/Calendar/Events/CalendarEventGroup.css b/frontend/src/Calendar/Events/CalendarEventGroup.css index ae259153b..e5be0fd92 100644 --- a/frontend/src/Calendar/Events/CalendarEventGroup.css +++ b/frontend/src/Calendar/Events/CalendarEventGroup.css @@ -14,7 +14,6 @@ .seriesTitle { @add-mixin truncate; - flex: 1 0 1px; margin-right: 10px; color: #3a3f51; @@ -23,10 +22,12 @@ .airTime { flex: 1 0 1px; + color: $calendarTextDim; } .episodeInfo { margin-left: 10px; + color: $calendarTextDim; } .absoluteEpisodeNumber { @@ -80,3 +81,7 @@ .premiere { composes: premiere from '~Calendar/Events/CalendarEvent.css'; } + +.unaired { + composes: unaired from '~Calendar/Events/CalendarEvent.css'; +} diff --git a/frontend/src/Components/FileBrowser/FileBrowserModalContent.js b/frontend/src/Components/FileBrowser/FileBrowserModalContent.js index cc8bc7529..591dada49 100644 --- a/frontend/src/Components/FileBrowser/FileBrowserModalContent.js +++ b/frontend/src/Components/FileBrowser/FileBrowserModalContent.js @@ -144,6 +144,7 @@ class FileBrowserModalContent extends Component { { !!error && @@ -152,7 +153,10 @@ class FileBrowserModalContent extends Component { { isPopulated && !error && - +
{ emptyParent && diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js b/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js index ed3bc2409..62c3f0197 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderModalContent.js @@ -132,6 +132,7 @@ class FilterBuilderModalContent extends Component { filterBuilderProps, isSaving, saveError, + onCancelPress, onModalClose } = this.props; @@ -190,7 +191,7 @@ class FilterBuilderModalContent extends Component { - @@ -220,6 +221,7 @@ FilterBuilderModalContent.propTypes = { dispatchDeleteCustomFilter: PropTypes.func.isRequired, onSaveCustomFilterPress: PropTypes.func.isRequired, dispatchSetFilter: PropTypes.func.isRequired, + onCancelPress: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Components/Filter/FilterModal.js b/frontend/src/Components/Filter/FilterModal.js index 750d1ed48..729f380e7 100644 --- a/frontend/src/Components/Filter/FilterModal.js +++ b/frontend/src/Components/Filter/FilterModal.js @@ -34,6 +34,17 @@ class FilterModal extends Component { }); } + onCancelPress = () => { + if (this.state.filterBuilder) { + this.setState({ + filterBuilder: false, + id: null + }); + } else { + this.onModalClose(); + } + } + onModalClose = () => { this.setState({ filterBuilder: false, @@ -67,6 +78,7 @@ class FilterModal extends Component { : { - this.props.onChange({ - name: this.props.name, - value: newValue - }); + onInputChange = ({ value }) => { + this.setState({ value }); } onInputKeyDown = (event) => { @@ -77,6 +83,11 @@ class PathInput extends Component { } onInputBlur = () => { + this.props.onChange({ + name: this.props.name, + value: this.state.value + }); + this.props.onClearPaths(); } @@ -108,13 +119,18 @@ class PathInput extends Component { const { className, name, - value, paths, includeFiles, hasFileBrowser, onChange, ...otherProps } = this.props; + + const { + value, + isFileBrowserModalOpen + } = this.state; + return (
{ @@ -144,7 +160,7 @@ class PathInput extends Component { diff --git a/frontend/src/Components/Menu/Menu.js b/frontend/src/Components/Menu/Menu.js index 560d98bf3..fadbcc69e 100644 --- a/frontend/src/Components/Menu/Menu.js +++ b/frontend/src/Components/Menu/Menu.js @@ -39,6 +39,7 @@ class Menu extends Component { this._scheduleUpdate = null; this._menuButtonId = getUniqueElememtId(); + this._menuContentId = getUniqueElememtId(); this.state = { isMenuOpen: false, @@ -99,12 +100,14 @@ class Menu extends Component { window.addEventListener('resize', this.onWindowResize); window.addEventListener('scroll', this.onWindowScroll, { capture: true }); window.addEventListener('click', this.onWindowClick); + window.addEventListener('touchstart', this.onTouchStart); } _removeListener() { window.removeEventListener('resize', this.onWindowResize); window.removeEventListener('scroll', this.onWindowScroll, { capture: true }); window.removeEventListener('click', this.onWindowClick); + window.removeEventListener('touchstart', this.onTouchStart); } // @@ -123,6 +126,30 @@ class Menu extends Component { } } + onTouchStart = (event) => { + const menuButton = document.getElementById(this._menuButtonId); + const menuContent = document.getElementById(this._menuContentId); + + if (!menuButton || !menuContent) { + return; + } + + if (event.targetTouches.length !== 1) { + return; + } + + const target = event.targetTouches[0].target; + + if ( + !menuButton.contains(target) && + !menuContent.contains(target) && + this.state.isMenuOpen + ) { + this.setState({ isMenuOpen: false }); + this._removeListener(); + } + } + onWindowResize = () => { this.setMaxHeight(); } diff --git a/frontend/src/Components/Menu/MenuContent.js b/frontend/src/Components/Menu/MenuContent.js index fbeb9ddce..e158187d5 100644 --- a/frontend/src/Components/Menu/MenuContent.js +++ b/frontend/src/Components/Menu/MenuContent.js @@ -12,6 +12,7 @@ class MenuContent extends Component { const { forwardedRef, className, + id, children, style, isOpen @@ -19,6 +20,7 @@ class MenuContent extends Component { return (
{children} @@ -28,11 +34,13 @@ class MenuItem extends Component { MenuItem.propTypes = { className: PropTypes.string, - children: PropTypes.node.isRequired + children: PropTypes.node.isRequired, + isDisabled: PropTypes.bool.isRequired }; MenuItem.defaultProps = { - className: styles.menuItem + className: styles.menuItem, + isDisabled: false }; export default MenuItem; diff --git a/frontend/src/Components/Modal/Modal.css b/frontend/src/Components/Modal/Modal.css index b9d702f86..d7269ea46 100644 --- a/frontend/src/Components/Modal/Modal.css +++ b/frontend/src/Components/Modal/Modal.css @@ -29,6 +29,12 @@ overflow: hidden !important; } +.modalOpenIOS { + position: fixed; + right: 0; + left: 0; +} + /* * Sizes */ diff --git a/frontend/src/Components/Modal/Modal.js b/frontend/src/Components/Modal/Modal.js index 8dfe43433..3b6485ce3 100644 --- a/frontend/src/Components/Modal/Modal.js +++ b/frontend/src/Components/Modal/Modal.js @@ -4,6 +4,8 @@ import ReactDOM from 'react-dom'; import classNames from 'classnames'; import elementClass from 'element-class'; import getUniqueElememtId from 'Utilities/getUniqueElementId'; +import { isIOS } from 'Utilities/mobile'; +import { setScrollLock } from 'Utilities/scrollLock'; import * as keyCodes from 'Utilities/Constants/keyCodes'; import { sizes } from 'Helpers/Props'; import ErrorBoundary from 'Components/Error/ErrorBoundary'; @@ -31,6 +33,7 @@ class Modal extends Component { this._node = document.getElementById('portal-root'); this._backgroundRef = null; this._modalId = getUniqueElememtId(); + this._bodyScrollTop = 0; } componentDidMount() { @@ -69,7 +72,14 @@ class Modal extends Component { window.addEventListener('keydown', this.onKeyDown); if (openModals.length === 1) { - elementClass(document.body).add(styles.modalOpen); + if (isIOS()) { + setScrollLock(true); + const scrollTop = document.body.scrollTop; + this._bodyScrollTop = scrollTop; + elementClass(document.body).add(styles.modalOpenIOS); + } else { + elementClass(document.body).add(styles.modalOpen); + } } } @@ -78,7 +88,14 @@ class Modal extends Component { window.removeEventListener('keydown', this.onKeyDown); if (openModals.length === 0) { - elementClass(document.body).remove(styles.modalOpen); + setScrollLock(false); + + if (isIOS()) { + elementClass(document.body).remove(styles.modalOpenIOS); + document.body.scrollTop = this._bodyScrollTop; + } else { + elementClass(document.body).remove(styles.modalOpen); + } } } diff --git a/frontend/src/Components/Modal/ModalBody.js b/frontend/src/Components/Modal/ModalBody.js index a35f2ecf5..6edde4790 100644 --- a/frontend/src/Components/Modal/ModalBody.js +++ b/frontend/src/Components/Modal/ModalBody.js @@ -48,7 +48,7 @@ ModalBody.propTypes = { className: PropTypes.string, innerClassName: PropTypes.string, children: PropTypes.node, - scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL]) + scrollDirection: PropTypes.oneOf(scrollDirections.all) }; ModalBody.defaultProps = { diff --git a/frontend/src/Components/Page/PageContentBody.js b/frontend/src/Components/Page/PageContentBody.js index 81bd9b29b..5c277d8f5 100644 --- a/frontend/src/Components/Page/PageContentBody.js +++ b/frontend/src/Components/Page/PageContentBody.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { isLocked } from 'Utilities/scrollLock'; import { scrollDirections } from 'Helpers/Props'; import OverlayScroller from 'Components/Scroller/OverlayScroller'; import Scroller from 'Components/Scroller/Scroller'; @@ -7,6 +8,17 @@ import styles from './PageContentBody.css'; class PageContentBody extends Component { + // + // Listeners + + onScroll = (props) => { + const { onScroll } = this.props; + + if (this.props.onScroll && !isLocked()) { + onScroll(props); + } + } + // // Render @@ -27,6 +39,7 @@ class PageContentBody extends Component { className={className} scrollDirection={scrollDirections.VERTICAL} {...otherProps} + onScroll={this.onScroll} >
{children} @@ -41,6 +54,7 @@ PageContentBody.propTypes = { innerClassName: PropTypes.string, isSmallScreen: PropTypes.bool.isRequired, children: PropTypes.node.isRequired, + onScroll: PropTypes.func, dispatch: PropTypes.func }; diff --git a/frontend/src/Components/Scroller/Scroller.css b/frontend/src/Components/Scroller/Scroller.css index c8783a8de..4dbd395cd 100644 --- a/frontend/src/Components/Scroller/Scroller.css +++ b/frontend/src/Components/Scroller/Scroller.css @@ -2,6 +2,7 @@ @add-mixin scrollbar; @add-mixin scrollbarTrack; @add-mixin scrollbarThumb; + -webkit-overflow-scrolling: touch; } .none { @@ -26,3 +27,11 @@ overflow-x: auto; } } + +.both { + overflow: scroll; + + &.autoScroll { + overflow: auto; + } +} diff --git a/frontend/src/Components/Scroller/Scroller.js b/frontend/src/Components/Scroller/Scroller.js index 701ac0cf4..f4ce7781f 100644 --- a/frontend/src/Components/Scroller/Scroller.js +++ b/frontend/src/Components/Scroller/Scroller.js @@ -66,7 +66,7 @@ class Scroller extends Component { Scroller.propTypes = { className: PropTypes.string, - scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL]).isRequired, + scrollDirection: PropTypes.oneOf(scrollDirections.all).isRequired, autoScroll: PropTypes.bool.isRequired, scrollTop: PropTypes.number, children: PropTypes.node, diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index cbf48a031..66d1d3cf6 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -9,6 +9,7 @@ import titleCase from 'Utilities/String/titleCase'; import { fetchCommands, updateCommand, finishCommand } from 'Store/Actions/commandActions'; import { setAppValue, setVersion } from 'Store/Actions/appActions'; import { update, updateItem, removeItem } from 'Store/Actions/baseActions'; +import { fetchMovies } from 'Store/Actions/movieActions'; import { fetchHealth } from 'Store/Actions/systemActions'; import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; @@ -72,6 +73,7 @@ const mapDispatchToProps = { dispatchFetchQueue: fetchQueue, dispatchFetchQueueDetails: fetchQueueDetails, dispatchFetchRootFolders: fetchRootFolders, + dispatchFetchMovies: fetchMovies, dispatchFetchTags: fetchTags, dispatchFetchTagDetails: fetchTagDetails }; @@ -258,6 +260,7 @@ class SignalRConnector extends Component { const { dispatchFetchCommands, + dispatchFetchMovies, dispatchSetAppValue } = this.props; @@ -265,6 +268,7 @@ class SignalRConnector extends Component { // are in sync after reconnecting. if (this.props.isReconnecting || this.props.isDisconnected) { + dispatchFetchMovies(); dispatchFetchCommands(); repopulatePage(); } @@ -346,6 +350,7 @@ SignalRConnector.propTypes = { dispatchFetchQueue: PropTypes.func.isRequired, dispatchFetchQueueDetails: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired, + dispatchFetchMovies: PropTypes.func.isRequired, dispatchFetchTags: PropTypes.func.isRequired, dispatchFetchTagDetails: PropTypes.func.isRequired }; diff --git a/frontend/src/Components/Table/Table.css b/frontend/src/Components/Table/Table.css index 46d49826a..bdfdec641 100644 --- a/frontend/src/Components/Table/Table.css +++ b/frontend/src/Components/Table/Table.css @@ -1,5 +1,7 @@ .tableContainer { - overflow-x: auto; + &.horizontalScroll { + overflow-x: auto; + } } .table { @@ -10,7 +12,12 @@ @media only screen and (max-width: $breakpointSmall) { .tableContainer { - overflow-y: hidden; - width: 100%; + min-width: 100%; + width: fit-content; + + &.horizontalScroll { + overflow-y: hidden; + width: 100%; + } } } diff --git a/frontend/src/Components/Table/Table.js b/frontend/src/Components/Table/Table.js index 2c33efd29..dbd60bf5f 100644 --- a/frontend/src/Components/Table/Table.js +++ b/frontend/src/Components/Table/Table.js @@ -1,6 +1,7 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; +import classNames from 'classnames'; import { icons, scrollDirections } from 'Helpers/Props'; import IconButton from 'Components/Link/IconButton'; import Scroller from 'Components/Scroller/Scroller'; @@ -28,6 +29,7 @@ function getTableHeaderCellProps(props) { function Table(props) { const { className, + horizontalScroll, selectAll, columns, optionsComponent, @@ -41,14 +43,22 @@ function Table(props) { return (
{ - selectAll && - + selectAll ? + : + null } { @@ -111,6 +121,7 @@ function Table(props) { Table.propTypes = { className: PropTypes.string, + horizontalScroll: PropTypes.bool.isRequired, selectAll: PropTypes.bool.isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired, optionsComponent: PropTypes.elementType, @@ -123,6 +134,7 @@ Table.propTypes = { Table.defaultProps = { className: styles.table, + horizontalScroll: true, selectAll: false }; diff --git a/frontend/src/Components/Table/VirtualTable.js b/frontend/src/Components/Table/VirtualTable.js index a13807647..258d31b00 100644 --- a/frontend/src/Components/Table/VirtualTable.js +++ b/frontend/src/Components/Table/VirtualTable.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { WindowScroller } from 'react-virtualized'; +import { isLocked } from 'Utilities/scrollLock'; import { scrollDirections } from 'Helpers/Props'; import Measure from 'Components/Measure'; import Scroller from 'Components/Scroller/Scroller'; @@ -83,6 +84,16 @@ class VirtualTable extends Component { } } + onScroll = (props) => { + if (isLocked()) { + return; + } + + const { onScroll } = this.props; + + onScroll(props); + } + // // Render @@ -107,7 +118,7 @@ class VirtualTable extends Component { {({ height, isScrolling }) => { return ( diff --git a/frontend/src/Components/Tooltip/Tooltip.js b/frontend/src/Components/Tooltip/Tooltip.js index ce9decc5a..3f8b5ad06 100644 --- a/frontend/src/Components/Tooltip/Tooltip.js +++ b/frontend/src/Components/Tooltip/Tooltip.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { Manager, Popper, Reference } from 'react-popper'; import classNames from 'classnames'; -import isMobileUtil from 'Utilities/isMobile'; +import { isMobile as isMobileUtil } from 'Utilities/mobile'; import { kinds, tooltipPositions } from 'Helpers/Props'; import Portal from 'Components/Portal'; import styles from './Tooltip.css'; diff --git a/frontend/src/Helpers/Props/scrollDirections.js b/frontend/src/Helpers/Props/scrollDirections.js index 5e4a4fe08..1ae61143b 100644 --- a/frontend/src/Helpers/Props/scrollDirections.js +++ b/frontend/src/Helpers/Props/scrollDirections.js @@ -1,5 +1,6 @@ export const NONE = 'none'; +export const BOTH = 'both'; export const HORIZONTAL = 'horizontal'; export const VERTICAL = 'vertical'; -export const all = [NONE, HORIZONTAL, VERTICAL]; +export const all = [NONE, HORIZONTAL, VERTICAL, BOTH]; diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js index 71363f1f0..7ead3f98f 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js @@ -5,7 +5,7 @@ import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getSelectedIds from 'Utilities/Table/getSelectedIds'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; -import { align, icons, kinds } from 'Helpers/Props'; +import { align, icons, kinds, scrollDirections } from 'Helpers/Props'; import Button from 'Components/Link/Button'; import Icon from 'Components/Icon'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; @@ -73,7 +73,7 @@ const filterExistingFilesOptions = { const importModeOptions = [ { key: 'move', value: 'Move Files' }, - { key: 'copy', value: 'Copy Files' } + { key: 'copy', value: 'Hardlink/Copy Files' } ]; const SELECT = 'select'; @@ -217,7 +217,7 @@ class InteractiveImportModalContent extends Component { Manual Import - {title || folder} - + { showFilterExistingFiles &&
@@ -270,6 +270,7 @@ class InteractiveImportModalContent extends Component { isPopulated && !!items.length && !isFetching && !isFetching &&
- + @@ -143,7 +143,7 @@ class InteractiveSearchRow extends Component { - + {indexer} @@ -151,7 +151,7 @@ class InteractiveSearchRow extends Component { {formatBytes(size)} - + { protocol === 'torrent' && - - {path} - + { + isUnavailable ? +
+ {path} + + +
: + + + {path} + + }
- {formatBytes(freeSpace) || '-'} + {(isUnavailable || isNaN(freeSpace)) ? '-' : formatBytes(freeSpace)} - {unmappedFoldersCount} + {isUnavailable ? '-' : unmappedFolders.length} @@ -52,13 +68,13 @@ function RootFolderRow(props) { RootFolderRow.propTypes = { id: PropTypes.number.isRequired, path: PropTypes.string.isRequired, - freeSpace: PropTypes.number.isRequired, + accessible: PropTypes.bool.isRequired, + freeSpace: PropTypes.number, unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired, onDeletePress: PropTypes.func.isRequired }; RootFolderRow.defaultProps = { - freeSpace: 0, unmappedFolders: [] }; diff --git a/frontend/src/Settings/General/LoggingSettings.js b/frontend/src/Settings/General/LoggingSettings.js index e7853328e..39ec2fb63 100644 --- a/frontend/src/Settings/General/LoggingSettings.js +++ b/frontend/src/Settings/General/LoggingSettings.js @@ -6,6 +6,12 @@ import FormGroup from 'Components/Form/FormGroup'; import FormLabel from 'Components/Form/FormLabel'; import FormInputGroup from 'Components/Form/FormInputGroup'; +const logLevelOptions = [ + { key: 'info', value: 'Info' }, + { key: 'debug', value: 'Debug' }, + { key: 'trace', value: 'Trace' } +]; + function LoggingSettings(props) { const { settings, @@ -16,12 +22,6 @@ function LoggingSettings(props) { logLevel } = settings; - const logLevelOptions = [ - { key: 'info', value: 'Info' }, - { key: 'debug', value: 'Debug' }, - { key: 'trace', value: 'Trace' } - ]; - return (
diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js index 6c0bd7fa0..fd85461d3 100644 --- a/frontend/src/Settings/MediaManagement/MediaManagement.js +++ b/frontend/src/Settings/MediaManagement/MediaManagement.js @@ -12,6 +12,7 @@ import FormLabel from 'Components/Form/FormLabel'; import FormInputGroup from 'Components/Form/FormInputGroup'; import RootFoldersConnector from 'RootFolder/RootFoldersConnector'; import NamingConnector from './Naming/NamingConnector'; +import AddRootFolderConnector from './RootFolder/AddRootFolderConnector'; const rescanAfterRefreshOptions = [ { key: 'always', value: 'Always' }, @@ -135,6 +136,23 @@ class MediaManagement extends Component { } + + Minimum Free Space + + + + + + + Recycling Bin Cleanup + + +
{ @@ -374,6 +409,7 @@ class MediaManagement extends Component {
+
diff --git a/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.css b/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.css new file mode 100644 index 000000000..19b1880be --- /dev/null +++ b/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.css @@ -0,0 +1,7 @@ +.addRootFolderButtonContainer { + margin-top: 20px; +} + +.importButtonIcon { + margin-right: 8px; +} diff --git a/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.js b/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.js new file mode 100644 index 000000000..3da2a55b9 --- /dev/null +++ b/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolder.js @@ -0,0 +1,71 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { icons, kinds, sizes } from 'Helpers/Props'; +import Button from 'Components/Link/Button'; +import Icon from 'Components/Icon'; +import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal'; +import styles from './AddRootFolder.css'; + +class AddRootFolder extends Component { + + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + this.state = { + isAddNewRootFolderModalOpen: false + }; + } + + // + // Lifecycle + + onAddNewRootFolderPress = () => { + this.setState({ isAddNewRootFolderModalOpen: true }); + } + + onNewRootFolderSelect = ({ value }) => { + this.props.onNewRootFolderSelect(value); + } + + onAddRootFolderModalClose = () => { + this.setState({ isAddNewRootFolderModalOpen: false }); + } + + // + // Render + + render() { + return ( +
+ + + +
+ ); + } +} + +AddRootFolder.propTypes = { + onNewRootFolderSelect: PropTypes.func.isRequired +}; + +export default AddRootFolder; diff --git a/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolderConnector.js b/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolderConnector.js new file mode 100644 index 000000000..cd5f4c50d --- /dev/null +++ b/frontend/src/Settings/MediaManagement/RootFolder/AddRootFolderConnector.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import AddRootFolder from './AddRootFolder'; +import { addRootFolder } from 'Store/Actions/rootFolderActions'; + +function createMapDispatchToProps(dispatch) { + return { + onNewRootFolderSelect(path) { + dispatch(addRootFolder({ path })); + } + }; +} + +export default connect(null, createMapDispatchToProps)(AddRootFolder); diff --git a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js index 9b2d4b6d1..6f6a85ae7 100644 --- a/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Quality/EditQualityProfileModalContent.js @@ -238,7 +238,11 @@ class EditQualityProfileModalContent extends Component { id ?