diff --git a/frontend/src/Components/Page/Toolbar/PageToolbarSection.tsx b/frontend/src/Components/Page/Toolbar/PageToolbarSection.tsx index e2aafb989..0a45fdf0d 100644 --- a/frontend/src/Components/Page/Toolbar/PageToolbarSection.tsx +++ b/frontend/src/Components/Page/Toolbar/PageToolbarSection.tsx @@ -16,10 +16,10 @@ const BUTTON_WIDTH = parseInt(dimensions.toolbarButtonWidth); const SEPARATOR_MARGIN = parseInt(dimensions.toolbarSeparatorMargin); const SEPARATOR_WIDTH = 2 * SEPARATOR_MARGIN + 1; -interface PageToolbarSectionProps { +export interface PageToolbarSectionProps { children?: - | (ReactElement | ReactElement) - | (ReactElement | ReactElement)[]; + | (ReactElement | ReactElement | null) + | (ReactElement | ReactElement | null)[]; alignContent?: Extract; collapseButtons?: boolean; } diff --git a/frontend/src/Settings/AdvancedSettingsButton.js b/frontend/src/Settings/AdvancedSettingsButton.js deleted file mode 100644 index 6ba40f6d7..000000000 --- a/frontend/src/Settings/AdvancedSettingsButton.js +++ /dev/null @@ -1,70 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import Icon from 'Components/Icon'; -import Link from 'Components/Link/Link'; -import { icons } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import styles from './AdvancedSettingsButton.css'; - -function AdvancedSettingsButton(props) { - const { - advancedSettings, - onAdvancedSettingsPress, - showLabel - } = props; - - return ( - - - - - - - - - - { - showLabel ? -
-
- {advancedSettings ? translate('HideAdvanced') : translate('ShowAdvanced')} -
-
: - null - } - - ); -} - -AdvancedSettingsButton.propTypes = { - advancedSettings: PropTypes.bool.isRequired, - onAdvancedSettingsPress: PropTypes.func.isRequired, - showLabel: PropTypes.bool.isRequired -}; - -AdvancedSettingsButton.defaultProps = { - showLabel: true -}; - -export default AdvancedSettingsButton; diff --git a/frontend/src/Settings/AdvancedSettingsButton.tsx b/frontend/src/Settings/AdvancedSettingsButton.tsx new file mode 100644 index 000000000..137e77f52 --- /dev/null +++ b/frontend/src/Settings/AdvancedSettingsButton.tsx @@ -0,0 +1,67 @@ +import classNames from 'classnames'; +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import AppState from 'App/State/AppState'; +import Icon from 'Components/Icon'; +import Link from 'Components/Link/Link'; +import { icons } from 'Helpers/Props'; +import { toggleAdvancedSettings } from 'Store/Actions/settingsActions'; +import translate from 'Utilities/String/translate'; +import styles from './AdvancedSettingsButton.css'; + +interface AdvancedSettingsButtonProps { + showLabel: boolean; +} + +function AdvancedSettingsButton({ showLabel }: AdvancedSettingsButtonProps) { + const showAdvancedSettings = useSelector( + (state: AppState) => state.settings.advancedSettings + ); + const dispatch = useDispatch(); + + const handlePress = useCallback(() => { + dispatch(toggleAdvancedSettings()); + }, [dispatch]); + + return ( + + + + + + + + + + {showLabel ? ( +
+
+ {showAdvancedSettings + ? translate('HideAdvanced') + : translate('ShowAdvanced')} +
+
+ ) : null} + + ); +} + +export default AdvancedSettingsButton; diff --git a/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx b/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx index 66c208f9a..3010c3628 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx +++ b/frontend/src/Settings/CustomFormats/CustomFormatSettingsPage.tsx @@ -5,7 +5,7 @@ import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import ParseToolbarButton from 'Parse/ParseToolbarButton'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector'; import ManageCustomFormatsToolbarButton from './CustomFormats/Manage/ManageCustomFormatsToolbarButton'; @@ -13,9 +13,7 @@ import ManageCustomFormatsToolbarButton from './CustomFormats/Manage/ManageCusto function CustomFormatSettingsPage() { return ( - diff --git a/frontend/src/Settings/DownloadClients/DownloadClientSettings.js b/frontend/src/Settings/DownloadClients/DownloadClientSettings.js index 8eec91d37..762b7aea0 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClientSettings.js +++ b/frontend/src/Settings/DownloadClients/DownloadClientSettings.js @@ -5,7 +5,7 @@ import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import { icons } from 'Helpers/Props'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import DownloadClientsConnector from './DownloadClients/DownloadClientsConnector'; import ManageDownloadClientsModal from './DownloadClients/Manage/ManageDownloadClientsModal'; @@ -71,7 +71,7 @@ class DownloadClientSettings extends Component { return ( - @@ -247,7 +244,6 @@ EditDownloadClientModalContent.propTypes = { onModalClose: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired, onTestPress: PropTypes.func.isRequired, - onAdvancedSettingsPress: PropTypes.func.isRequired, onDeleteDownloadClientPress: PropTypes.func }; diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContentConnector.js b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContentConnector.js index 3c9289763..8496cebfc 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContentConnector.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/EditDownloadClientModalContentConnector.js @@ -6,8 +6,7 @@ import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, - testDownloadClient, - toggleAdvancedSettings + testDownloadClient } from 'Store/Actions/settingsActions'; import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; import EditDownloadClientModalContent from './EditDownloadClientModalContent'; @@ -29,8 +28,7 @@ const mapDispatchToProps = { setDownloadClientValue, setDownloadClientFieldValue, saveDownloadClient, - testDownloadClient, - toggleAdvancedSettings + testDownloadClient }; class EditDownloadClientModalContentConnector extends Component { @@ -63,10 +61,6 @@ class EditDownloadClientModalContentConnector extends Component { this.props.testDownloadClient({ id: this.props.id }); }; - onAdvancedSettingsPress = () => { - this.props.toggleAdvancedSettings(); - }; - // // Render @@ -76,7 +70,6 @@ class EditDownloadClientModalContentConnector extends Component { {...this.props} onSavePress={this.onSavePress} onTestPress={this.onTestPress} - onAdvancedSettingsPress={this.onAdvancedSettingsPress} onInputChange={this.onInputChange} onFieldChange={this.onFieldChange} /> @@ -94,7 +87,6 @@ EditDownloadClientModalContentConnector.propTypes = { setDownloadClientFieldValue: PropTypes.func.isRequired, saveDownloadClient: PropTypes.func.isRequired, testDownloadClient: PropTypes.func.isRequired, - toggleAdvancedSettings: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/General/GeneralSettings.js b/frontend/src/Settings/General/GeneralSettings.js index e67a572e8..d70417aa7 100644 --- a/frontend/src/Settings/General/GeneralSettings.js +++ b/frontend/src/Settings/General/GeneralSettings.js @@ -8,7 +8,7 @@ import ConfirmModal from 'Components/Modal/ConfirmModal'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import { kinds } from 'Helpers/Props'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import AnalyticSettings from './AnalyticSettings'; import BackupSettings from './BackupSettings'; @@ -113,7 +113,7 @@ class GeneralSettings extends Component { return ( - diff --git a/frontend/src/Settings/ImportLists/ImportListSettings.js b/frontend/src/Settings/ImportLists/ImportListSettings.js index 6a7365158..a7fc843ce 100644 --- a/frontend/src/Settings/ImportLists/ImportListSettings.js +++ b/frontend/src/Settings/ImportLists/ImportListSettings.js @@ -5,7 +5,7 @@ import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import { icons } from 'Helpers/Props'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import ImportListExclusions from './ImportListExclusions/ImportListExclusions'; import ImportListsConnector from './ImportLists/ImportListsConnector'; @@ -81,7 +81,7 @@ class ImportListSettings extends Component { return ( - @@ -335,7 +333,6 @@ EditImportListModalContent.propTypes = { onModalClose: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired, onTestPress: PropTypes.func.isRequired, - onAdvancedSettingsPress: PropTypes.func.isRequired, onDeleteImportListPress: PropTypes.func }; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js index ebcbd4ae1..7ffc89e48 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContentConnector.js @@ -6,8 +6,7 @@ import { saveImportList, setImportListFieldValue, setImportListValue, - testImportList, - toggleAdvancedSettings + testImportList } from 'Store/Actions/settingsActions'; import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; import EditImportListModalContent from './EditImportListModalContent'; @@ -29,8 +28,7 @@ const mapDispatchToProps = { setImportListValue, setImportListFieldValue, saveImportList, - testImportList, - toggleAdvancedSettings + testImportList }; class EditImportListModalContentConnector extends Component { @@ -63,10 +61,6 @@ class EditImportListModalContentConnector extends Component { this.props.testImportList({ id: this.props.id }); }; - onAdvancedSettingsPress = () => { - this.props.toggleAdvancedSettings(); - }; - // // Render @@ -76,7 +70,6 @@ class EditImportListModalContentConnector extends Component { {...this.props} onSavePress={this.onSavePress} onTestPress={this.onTestPress} - onAdvancedSettingsPress={this.onAdvancedSettingsPress} onInputChange={this.onInputChange} onFieldChange={this.onFieldChange} /> @@ -94,7 +87,6 @@ EditImportListModalContentConnector.propTypes = { setImportListFieldValue: PropTypes.func.isRequired, saveImportList: PropTypes.func.isRequired, testImportList: PropTypes.func.isRequired, - toggleAdvancedSettings: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/Indexers/IndexerSettings.js b/frontend/src/Settings/Indexers/IndexerSettings.js index aa18acdf9..94e324c8e 100644 --- a/frontend/src/Settings/Indexers/IndexerSettings.js +++ b/frontend/src/Settings/Indexers/IndexerSettings.js @@ -5,7 +5,7 @@ import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import { icons } from 'Helpers/Props'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import IndexersConnector from './Indexers/IndexersConnector'; import ManageIndexersModal from './Indexers/Manage/ManageIndexersModal'; @@ -70,7 +70,7 @@ class IndexerSettings extends Component { return ( - @@ -266,7 +263,6 @@ EditIndexerModalContent.propTypes = { onModalClose: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired, onTestPress: PropTypes.func.isRequired, - onAdvancedSettingsPress: PropTypes.func.isRequired, onDeleteIndexerPress: PropTypes.func }; diff --git a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContentConnector.js b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContentConnector.js index b3796f9e5..5be01849d 100644 --- a/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContentConnector.js +++ b/frontend/src/Settings/Indexers/Indexers/EditIndexerModalContentConnector.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer, toggleAdvancedSettings } from 'Store/Actions/settingsActions'; +import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer } from 'Store/Actions/settingsActions'; import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; import EditIndexerModalContent from './EditIndexerModalContent'; @@ -23,8 +23,7 @@ const mapDispatchToProps = { setIndexerValue, setIndexerFieldValue, saveIndexer, - testIndexer, - toggleAdvancedSettings + testIndexer }; class EditIndexerModalContentConnector extends Component { @@ -57,10 +56,6 @@ class EditIndexerModalContentConnector extends Component { this.props.testIndexer({ id: this.props.id }); }; - onAdvancedSettingsPress = () => { - this.props.toggleAdvancedSettings(); - }; - // // Render @@ -70,7 +65,6 @@ class EditIndexerModalContentConnector extends Component { {...this.props} onSavePress={this.onSavePress} onTestPress={this.onTestPress} - onAdvancedSettingsPress={this.onAdvancedSettingsPress} onInputChange={this.onInputChange} onFieldChange={this.onFieldChange} /> @@ -86,7 +80,6 @@ EditIndexerModalContentConnector.propTypes = { item: PropTypes.object.isRequired, setIndexerValue: PropTypes.func.isRequired, setIndexerFieldValue: PropTypes.func.isRequired, - toggleAdvancedSettings: PropTypes.func.isRequired, saveIndexer: PropTypes.func.isRequired, testIndexer: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js index bb0e4da2e..d63a8a7d2 100644 --- a/frontend/src/Settings/MediaManagement/MediaManagement.js +++ b/frontend/src/Settings/MediaManagement/MediaManagement.js @@ -11,7 +11,7 @@ import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import { inputTypes, kinds, sizes } from 'Helpers/Props'; import RootFolders from 'RootFolder/RootFolders'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import Naming from './Naming/Naming'; import AddRootFolder from './RootFolder/AddRootFolder'; @@ -120,7 +120,7 @@ class MediaManagement extends Component { return ( - { - dispatch(clearPendingChanges({ section: SECTION })); + dispatch(clearPendingChanges({ section: 'settings.naming' })); }; }, [dispatch]); diff --git a/frontend/src/Settings/Metadata/MetadataSettings.js b/frontend/src/Settings/Metadata/MetadataSettings.js index 143a05956..f1b5b1bff 100644 --- a/frontend/src/Settings/Metadata/MetadataSettings.js +++ b/frontend/src/Settings/Metadata/MetadataSettings.js @@ -1,14 +1,14 @@ import React from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import Metadatas from './Metadata/Metadatas'; function MetadataSettings() { return ( - diff --git a/frontend/src/Settings/MetadataSource/MetadataSourceSettings.js b/frontend/src/Settings/MetadataSource/MetadataSourceSettings.js index 9f7dffc5d..cf2fd57ec 100644 --- a/frontend/src/Settings/MetadataSource/MetadataSourceSettings.js +++ b/frontend/src/Settings/MetadataSource/MetadataSourceSettings.js @@ -1,14 +1,14 @@ import React from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import TheTvdb from './TheTvdb'; function MetadataSourceSettings() { return ( - diff --git a/frontend/src/Settings/Notifications/NotificationSettings.js b/frontend/src/Settings/Notifications/NotificationSettings.js index 991624463..3195626d4 100644 --- a/frontend/src/Settings/Notifications/NotificationSettings.js +++ b/frontend/src/Settings/Notifications/NotificationSettings.js @@ -1,14 +1,14 @@ import React from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import NotificationsConnector from './Notifications/NotificationsConnector'; function NotificationSettings() { return ( - diff --git a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js index f52655289..791030833 100644 --- a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js +++ b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js @@ -33,7 +33,6 @@ function EditNotificationModalContent(props) { onModalClose, onSavePress, onTestPress, - onAdvancedSettingsPress, onDeleteNotificationPress, ...otherProps } = props; @@ -139,8 +138,6 @@ function EditNotificationModalContent(props) { } @@ -183,7 +180,6 @@ EditNotificationModalContent.propTypes = { onModalClose: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired, onTestPress: PropTypes.func.isRequired, - onAdvancedSettingsPress: PropTypes.func.isRequired, onDeleteNotificationPress: PropTypes.func }; diff --git a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContentConnector.js b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContentConnector.js index ce7a0c1ca..45199487b 100644 --- a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContentConnector.js +++ b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContentConnector.js @@ -6,8 +6,7 @@ import { saveNotification, setNotificationFieldValues, setNotificationValue, - testNotification, - toggleAdvancedSettings + testNotification } from 'Store/Actions/settingsActions'; import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector'; import EditNotificationModalContent from './EditNotificationModalContent'; @@ -29,8 +28,7 @@ const mapDispatchToProps = { setNotificationValue, setNotificationFieldValues, saveNotification, - testNotification, - toggleAdvancedSettings + testNotification }; class EditNotificationModalContentConnector extends Component { @@ -63,10 +61,6 @@ class EditNotificationModalContentConnector extends Component { this.props.testNotification({ id: this.props.id }); }; - onAdvancedSettingsPress = () => { - this.props.toggleAdvancedSettings(); - }; - // // Render @@ -76,7 +70,6 @@ class EditNotificationModalContentConnector extends Component { {...this.props} onSavePress={this.onSavePress} onTestPress={this.onTestPress} - onAdvancedSettingsPress={this.onAdvancedSettingsPress} onInputChange={this.onInputChange} onFieldChange={this.onFieldChange} /> @@ -94,7 +87,6 @@ EditNotificationModalContentConnector.propTypes = { setNotificationFieldValues: PropTypes.func.isRequired, saveNotification: PropTypes.func.isRequired, testNotification: PropTypes.func.isRequired, - toggleAdvancedSettings: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Settings/PendingChangesModal.js b/frontend/src/Settings/PendingChangesModal.js deleted file mode 100644 index 213445c65..000000000 --- a/frontend/src/Settings/PendingChangesModal.js +++ /dev/null @@ -1,77 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useEffect } from 'react'; -import keyboardShortcuts from 'Components/keyboardShortcuts'; -import Button from 'Components/Link/Button'; -import Modal from 'Components/Modal/Modal'; -import ModalBody from 'Components/Modal/ModalBody'; -import ModalContent from 'Components/Modal/ModalContent'; -import ModalFooter from 'Components/Modal/ModalFooter'; -import ModalHeader from 'Components/Modal/ModalHeader'; -import { kinds } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; - -function PendingChangesModal(props) { - const { - isOpen, - onConfirm, - onCancel, - bindShortcut, - unbindShortcut - } = props; - - useEffect(() => { - if (isOpen) { - bindShortcut('enter', onConfirm); - - return () => unbindShortcut('enter', onConfirm); - } - }, [bindShortcut, unbindShortcut, isOpen, onConfirm]); - - return ( - - - {translate('UnsavedChanges')} - - - {translate('PendingChangesMessage')} - - - - - - - - - - ); -} - -PendingChangesModal.propTypes = { - className: PropTypes.string, - isOpen: PropTypes.bool.isRequired, - kind: PropTypes.oneOf(kinds.all), - onConfirm: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - bindShortcut: PropTypes.func.isRequired, - unbindShortcut: PropTypes.func.isRequired -}; - -PendingChangesModal.defaultProps = { - kind: kinds.PRIMARY -}; - -export default keyboardShortcuts(PendingChangesModal); diff --git a/frontend/src/Settings/PendingChangesModal.tsx b/frontend/src/Settings/PendingChangesModal.tsx new file mode 100644 index 000000000..0172571da --- /dev/null +++ b/frontend/src/Settings/PendingChangesModal.tsx @@ -0,0 +1,55 @@ +import React, { useEffect } from 'react'; +import Button from 'Components/Link/Button'; +import Modal from 'Components/Modal/Modal'; +import ModalBody from 'Components/Modal/ModalBody'; +import ModalContent from 'Components/Modal/ModalContent'; +import ModalFooter from 'Components/Modal/ModalFooter'; +import ModalHeader from 'Components/Modal/ModalHeader'; +import useKeyboardShortcuts from 'Helpers/Hooks/useKeyboardShortcuts'; +import { kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; + +interface PendingChangesModalProps { + className?: string; + isOpen: boolean; + onConfirm: () => void; + onCancel: () => void; +} + +function PendingChangesModal({ + isOpen, + onConfirm, + onCancel, +}: PendingChangesModalProps) { + const { bindShortcut, unbindShortcut } = useKeyboardShortcuts(); + + useEffect(() => { + if (isOpen) { + bindShortcut('acceptConfirmModal', onConfirm); + } + + return () => unbindShortcut('acceptConfirmModal'); + }, [bindShortcut, unbindShortcut, isOpen, onConfirm]); + + return ( + + + {translate('UnsavedChanges')} + + {translate('PendingChangesMessage')} + + + + + + + + + ); +} + +export default PendingChangesModal; diff --git a/frontend/src/Settings/Profiles/Profiles.js b/frontend/src/Settings/Profiles/Profiles.js index e54c6fdbd..3452530eb 100644 --- a/frontend/src/Settings/Profiles/Profiles.js +++ b/frontend/src/Settings/Profiles/Profiles.js @@ -3,7 +3,7 @@ import { DndProvider } from 'react-dnd-multi-backend'; import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import DelayProfilesConnector from './Delay/DelayProfilesConnector'; import QualityProfilesConnector from './Quality/QualityProfilesConnector'; @@ -20,7 +20,7 @@ class Profiles extends Component { render() { return ( - + diff --git a/frontend/src/Settings/Quality/Quality.js b/frontend/src/Settings/Quality/Quality.js index 49c9df5d0..dbab2d363 100644 --- a/frontend/src/Settings/Quality/Quality.js +++ b/frontend/src/Settings/Quality/Quality.js @@ -5,7 +5,7 @@ import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import { icons } from 'Helpers/Props'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import QualityDefinitionsConnector from './Definition/QualityDefinitionsConnector'; import ResetQualityDefinitionsModal from './Reset/ResetQualityDefinitionsModal'; @@ -64,7 +64,7 @@ class Quality extends Component { return ( - - + - + {translate('MediaManagement')} @@ -25,10 +20,7 @@ function Settings() { {translate('MediaManagementSettingsSummary')} - + {translate('Profiles')} @@ -36,10 +28,7 @@ function Settings() { {translate('ProfilesSettingsSummary')} - + {translate('Quality')} @@ -47,10 +36,7 @@ function Settings() { {translate('QualitySettingsSummary')} - + {translate('CustomFormats')} @@ -58,10 +44,7 @@ function Settings() { {translate('CustomFormatsSettingsSummary')} - + {translate('Indexers')} @@ -69,10 +52,7 @@ function Settings() { {translate('IndexersSettingsSummary')} - + {translate('DownloadClients')} @@ -80,10 +60,7 @@ function Settings() { {translate('DownloadClientsSettingsSummary')} - + {translate('ImportLists')} @@ -91,10 +68,7 @@ function Settings() { {translate('ImportListsSettingsSummary')} - + {translate('Connect')} @@ -102,10 +76,7 @@ function Settings() { {translate('ConnectSettingsSummary')} - + {translate('Metadata')} @@ -113,10 +84,7 @@ function Settings() { {translate('MetadataSettingsSeriesSummary')} - + {translate('MetadataSource')} @@ -124,21 +92,13 @@ function Settings() { {translate('MetadataSourceSettingsSeriesSummary')} - + {translate('Tags')} -
- {translate('TagsSettingsSummary')} -
+
{translate('TagsSettingsSummary')}
- + {translate('General')} @@ -146,22 +106,14 @@ function Settings() { {translate('GeneralSettingsSummary')} - + {translate('Ui')} -
- {translate('UiSettingsSummary')} -
+
{translate('UiSettingsSummary')}
); } -Settings.propTypes = { -}; - export default Settings; diff --git a/frontend/src/Settings/SettingsToolbar.js b/frontend/src/Settings/SettingsToolbar.js deleted file mode 100644 index 048dcc66e..000000000 --- a/frontend/src/Settings/SettingsToolbar.js +++ /dev/null @@ -1,106 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts'; -import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; -import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; -import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; -import { icons } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import AdvancedSettingsButton from './AdvancedSettingsButton'; -import PendingChangesModal from './PendingChangesModal'; - -class SettingsToolbar extends Component { - - // - // Lifecycle - - componentDidMount() { - this.props.bindShortcut(shortcuts.SAVE_SETTINGS.key, this.saveSettings, { isGlobal: true }); - } - - // - // Control - - saveSettings = (event) => { - event.preventDefault(); - - const { - hasPendingChanges, - onSavePress - } = this.props; - - if (hasPendingChanges) { - onSavePress(); - } - }; - - // - // Render - - render() { - const { - advancedSettings, - showSave, - isSaving, - hasPendingChanges, - hasPendingLocation, - additionalButtons, - onSavePress, - onConfirmNavigation, - onCancelNavigation, - onAdvancedSettingsPress - } = this.props; - - return ( - - - - - { - showSave && - - } - - { - additionalButtons - } - - - - - ); - } -} - -SettingsToolbar.propTypes = { - advancedSettings: PropTypes.bool.isRequired, - showSave: PropTypes.bool.isRequired, - isSaving: PropTypes.bool, - hasPendingLocation: PropTypes.bool.isRequired, - hasPendingChanges: PropTypes.bool, - additionalButtons: PropTypes.node, - onSavePress: PropTypes.func, - onAdvancedSettingsPress: PropTypes.func.isRequired, - onConfirmNavigation: PropTypes.func.isRequired, - onCancelNavigation: PropTypes.func.isRequired, - bindShortcut: PropTypes.func.isRequired -}; - -SettingsToolbar.defaultProps = { - showSave: true -}; - -export default keyboardShortcuts(SettingsToolbar); diff --git a/frontend/src/Settings/SettingsToolbar.tsx b/frontend/src/Settings/SettingsToolbar.tsx new file mode 100644 index 000000000..26b653dc5 --- /dev/null +++ b/frontend/src/Settings/SettingsToolbar.tsx @@ -0,0 +1,149 @@ +import { Action, Location, UnregisterCallback } from 'history'; +import React, { + ReactElement, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { useHistory } from 'react-router'; +import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; +import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; +import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; +import useKeyboardShortcuts from 'Helpers/Hooks/useKeyboardShortcuts'; +import { icons } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; +import AdvancedSettingsButton from './AdvancedSettingsButton'; +import PendingChangesModal from './PendingChangesModal'; + +interface SettingsToolbarProps { + showSave?: boolean; + isSaving?: boolean; + hasPendingChanges?: boolean; + // TODO: This should do type checking like PageToolbarSectionProps, + // but this works for the time being. + additionalButtons?: ReactElement | null; + onSavePress?: () => void; +} + +function SettingsToolbar({ + showSave = true, + isSaving, + hasPendingChanges, + additionalButtons = null, + onSavePress, +}: SettingsToolbarProps) { + const { bindShortcut, unbindShortcut } = useKeyboardShortcuts(); + const history = useHistory(); + const [nextLocation, setNextLocation] = useState(null); + const [nextLocationAction, setNextLocationAction] = useState( + null + ); + const hasConfirmed = useRef(false); + const unblocker = useRef(); + + const handleConfirmNavigation = useCallback(() => { + if (!nextLocation) { + return; + } + + const path = `${nextLocation.pathname}${nextLocation.search}`; + + hasConfirmed.current = true; + + if (nextLocationAction === 'PUSH') { + history.push(path); + } else { + // Unfortunately back and forward both use POP, + // which means we don't actually know which direction + // the user wanted to go, assuming back. + + history.goBack(); + } + }, [nextLocation, nextLocationAction, history]); + + const handleCancelNavigation = useCallback(() => { + setNextLocation(null); + setNextLocationAction(null); + hasConfirmed.current = false; + }, []); + + const handleRouterLeaving = useCallback( + (routerLocation: Location, routerAction: Action) => { + if (hasConfirmed.current) { + setNextLocation(null); + setNextLocationAction(null); + hasConfirmed.current = false; + + return; + } + + if (hasPendingChanges) { + setNextLocation(routerLocation); + setNextLocationAction(routerAction); + + return false; + } + + return; + }, + [hasPendingChanges] + ); + + useEffect(() => { + unblocker.current = history.block(handleRouterLeaving); + + return () => { + unblocker.current?.(); + }; + }, [history, handleRouterLeaving]); + + useEffect(() => { + bindShortcut( + 'saveSettings', + () => { + if (hasPendingChanges) { + onSavePress?.(); + } + }, + { + isGlobal: true, + } + ); + + return () => { + unbindShortcut('saveSettings'); + }; + }, [hasPendingChanges, bindShortcut, unbindShortcut, onSavePress]); + + return ( + + + + {showSave ? ( + + ) : null} + + {additionalButtons} + + + + + ); +} + +export default SettingsToolbar; diff --git a/frontend/src/Settings/SettingsToolbarConnector.js b/frontend/src/Settings/SettingsToolbarConnector.js deleted file mode 100644 index 65d937ab8..000000000 --- a/frontend/src/Settings/SettingsToolbarConnector.js +++ /dev/null @@ -1,148 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; -import { toggleAdvancedSettings } from 'Store/Actions/settingsActions'; -import SettingsToolbar from './SettingsToolbar'; - -function mapStateToProps(state) { - return { - advancedSettings: state.settings.advancedSettings - }; -} - -const mapDispatchToProps = { - toggleAdvancedSettings -}; - -class SettingsToolbarConnector extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - nextLocation: null, - nextLocationAction: null, - confirmed: false - }; - - this._unblock = null; - } - - componentDidMount() { - this._unblock = this.props.history.block(this.routerWillLeave); - } - - componentWillUnmount() { - if (this._unblock) { - this._unblock(); - } - } - - // - // Control - - routerWillLeave = (nextLocation, nextLocationAction) => { - if (this.state.confirmed) { - this.setState({ - nextLocation: null, - nextLocationAction: null, - confirmed: false - }); - - return true; - } - - if (this.props.hasPendingChanges ) { - this.setState({ - nextLocation, - nextLocationAction - }); - - return false; - } - - return true; - }; - - // - // Listeners - - onAdvancedSettingsPress = () => { - this.props.toggleAdvancedSettings(); - }; - - onConfirmNavigation = () => { - const { - nextLocation, - nextLocationAction - } = this.state; - - const history = this.props.history; - - const path = `${nextLocation.pathname}${nextLocation.search}`; - - this.setState({ - confirmed: true - }, () => { - if (nextLocationAction === 'PUSH') { - history.push(path); - } else { - // Unfortunately back and forward both use POP, - // which means we don't actually know which direction - // the user wanted to go, assuming back. - - history.goBack(); - } - }); - }; - - onCancelNavigation = () => { - this.setState({ - nextLocation: null, - nextLocationAction: null, - confirmed: false - }); - }; - - // - // Render - - render() { - const hasPendingLocation = this.state.nextLocation !== null; - - return ( - - ); - } -} - -const historyShape = { - block: PropTypes.func.isRequired, - goBack: PropTypes.func.isRequired, - push: PropTypes.func.isRequired -}; - -SettingsToolbarConnector.propTypes = { - showSave: PropTypes.bool, - hasPendingChanges: PropTypes.bool.isRequired, - history: PropTypes.shape(historyShape).isRequired, - onSavePress: PropTypes.func, - toggleAdvancedSettings: PropTypes.func.isRequired -}; - -SettingsToolbarConnector.defaultProps = { - hasPendingChanges: false -}; - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SettingsToolbarConnector)); diff --git a/frontend/src/Settings/Tags/TagSettings.js b/frontend/src/Settings/Tags/TagSettings.js index ca8672603..b37b185f7 100644 --- a/frontend/src/Settings/Tags/TagSettings.js +++ b/frontend/src/Settings/Tags/TagSettings.js @@ -1,7 +1,7 @@ import React from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import translate from 'Utilities/String/translate'; import AutoTaggings from './AutoTagging/AutoTaggings'; import TagsConnector from './TagsConnector'; @@ -9,7 +9,7 @@ import TagsConnector from './TagsConnector'; function TagSettings() { return ( - diff --git a/frontend/src/Settings/UI/UISettings.js b/frontend/src/Settings/UI/UISettings.js index 00c5037d9..69ac2a9c7 100644 --- a/frontend/src/Settings/UI/UISettings.js +++ b/frontend/src/Settings/UI/UISettings.js @@ -10,7 +10,7 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import { inputTypes, kinds } from 'Helpers/Props'; -import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; +import SettingsToolbar from 'Settings/SettingsToolbar'; import themes from 'Styles/Themes'; import titleCase from 'Utilities/String/titleCase'; import translate from 'Utilities/String/translate'; @@ -78,7 +78,7 @@ class UISettings extends Component { return ( -