From 9ce473d9bb296f557b57bcd41cb7eb9f91712a8e Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 28 Dec 2024 12:10:18 -0800 Subject: [PATCH] Convert Quality Settings to TypeScript --- frontend/src/App/AppRoutes.tsx | 4 +- frontend/src/App/State/SettingsAppState.ts | 9 ++ .../Helpers/Hooks/useShowAdvancedSettings.ts | 8 ++ frontend/src/Quality/QualityDefinition.ts | 11 ++ .../Definition/QualityDefinitionConnector.js | 1 + .../Definition/QualityDefinitionLimits.js | 40 ------ .../Definition/QualityDefinitionLimits.tsx | 30 +++++ .../Quality/Definition/QualityDefinitions.js | 80 ----------- .../Quality/Definition/QualityDefinitions.tsx | 124 ++++++++++++++++++ .../Definition/QualityDefinitionsConnector.js | 90 ------------- frontend/src/Settings/Quality/Quality.js | 105 --------------- frontend/src/Settings/Quality/Quality.tsx | 95 ++++++++++++++ .../src/Settings/Quality/QualityConnector.js | 38 ------ .../Reset/ResetQualityDefinitionsModal.js | 33 ----- .../Reset/ResetQualityDefinitionsModal.tsx | 22 ++++ .../ResetQualityDefinitionsModalContent.js | 106 --------------- .../ResetQualityDefinitionsModalContent.tsx | 92 +++++++++++++ ...QualityDefinitionsModalContentConnector.js | 54 -------- frontend/src/Utilities/Object/isEmpty.ts | 11 ++ .../src/typings/Settings/SettingsState.ts | 10 ++ 20 files changed, 415 insertions(+), 548 deletions(-) create mode 100644 frontend/src/Helpers/Hooks/useShowAdvancedSettings.ts create mode 100644 frontend/src/Quality/QualityDefinition.ts delete mode 100644 frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.js create mode 100644 frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.tsx delete mode 100644 frontend/src/Settings/Quality/Definition/QualityDefinitions.js create mode 100644 frontend/src/Settings/Quality/Definition/QualityDefinitions.tsx delete mode 100644 frontend/src/Settings/Quality/Definition/QualityDefinitionsConnector.js delete mode 100644 frontend/src/Settings/Quality/Quality.js create mode 100644 frontend/src/Settings/Quality/Quality.tsx delete mode 100644 frontend/src/Settings/Quality/QualityConnector.js delete mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js create mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.tsx delete mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js create mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.tsx delete mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js create mode 100644 frontend/src/Utilities/Object/isEmpty.ts create mode 100644 frontend/src/typings/Settings/SettingsState.ts diff --git a/frontend/src/App/AppRoutes.tsx b/frontend/src/App/AppRoutes.tsx index cbf224c77..853bd4590 100644 --- a/frontend/src/App/AppRoutes.tsx +++ b/frontend/src/App/AppRoutes.tsx @@ -20,7 +20,7 @@ import MetadataSettings from 'Settings/Metadata/MetadataSettings'; import MetadataSourceSettings from 'Settings/MetadataSource/MetadataSourceSettings'; import NotificationSettings from 'Settings/Notifications/NotificationSettings'; import Profiles from 'Settings/Profiles/Profiles'; -import QualityConnector from 'Settings/Quality/QualityConnector'; +import Quality from 'Settings/Quality/Quality'; import Settings from 'Settings/Settings'; import TagSettings from 'Settings/Tags/TagSettings'; import UISettings from 'Settings/UI/UISettings'; @@ -105,7 +105,7 @@ function AppRoutes() { - + , AppSectionDeleteState {} +export interface QualityDefinitionsAppState + extends AppSectionState, + AppSectionSaveState { + pendingChanges: { + [key: number]: Partial; + }; +} + export interface QualityProfilesAppState extends AppSectionState, AppSectionItemSchemaState {} @@ -123,6 +131,7 @@ interface SettingsAppState { naming: NamingAppState; namingExamples: NamingExamplesAppState; notifications: NotificationAppState; + qualityDefinitions: QualityDefinitionsAppState; qualityProfiles: QualityProfilesAppState; releaseProfiles: ReleaseProfilesAppState; ui: UiSettingsAppState; diff --git a/frontend/src/Helpers/Hooks/useShowAdvancedSettings.ts b/frontend/src/Helpers/Hooks/useShowAdvancedSettings.ts new file mode 100644 index 000000000..754c989cc --- /dev/null +++ b/frontend/src/Helpers/Hooks/useShowAdvancedSettings.ts @@ -0,0 +1,8 @@ +import { useSelector } from 'react-redux'; +import AppState from 'App/State/AppState'; + +function useShowAdvancedSettings() { + return useSelector((state: AppState) => state.settings.advancedSettings); +} + +export default useShowAdvancedSettings; diff --git a/frontend/src/Quality/QualityDefinition.ts b/frontend/src/Quality/QualityDefinition.ts new file mode 100644 index 000000000..47b134cf3 --- /dev/null +++ b/frontend/src/Quality/QualityDefinition.ts @@ -0,0 +1,11 @@ +import Quality from './Quality'; + +export default interface QualityDefinition { + quality: Quality; + title: string; + weight: number; + minSize: number; + maxSize: number; + preferredSize: number; + id: number; +} diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js b/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js index eee0558f1..cbd9c5d49 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js +++ b/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js @@ -60,6 +60,7 @@ class QualityDefinitionConnector extends Component { QualityDefinitionConnector.propTypes = { id: PropTypes.number.isRequired, + advancedSettings: PropTypes.bool.isRequired, minSize: PropTypes.number, maxSize: PropTypes.number, preferredSize: PropTypes.number, diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.js b/frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.js deleted file mode 100644 index 9f738edc6..000000000 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.js +++ /dev/null @@ -1,40 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import formatBytes from 'Utilities/Number/formatBytes'; -import translate from 'Utilities/String/translate'; - -function QualityDefinitionLimits(props) { - const { - bytes, - message - } = props; - - if (!bytes) { - return
{message}
; - } - - const thirty = formatBytes(bytes * 30); - const fortyFive = formatBytes(bytes * 45); - const sixty = formatBytes(bytes * 60); - - return ( -
-
- {translate('MinutesThirty', { thirty })} -
-
- {translate('MinutesFortyFive', { fortyFive })} -
-
- {translate('MinutesSixty', { sixty })} -
-
- ); -} - -QualityDefinitionLimits.propTypes = { - bytes: PropTypes.number, - message: PropTypes.string.isRequired -}; - -export default QualityDefinitionLimits; diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.tsx b/frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.tsx new file mode 100644 index 000000000..9ae5d48e1 --- /dev/null +++ b/frontend/src/Settings/Quality/Definition/QualityDefinitionLimits.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import formatBytes from 'Utilities/Number/formatBytes'; +import translate from 'Utilities/String/translate'; + +interface QualityDefinitionLimitsProps { + bytes?: number; + message: string; +} + +function QualityDefinitionLimits(props: QualityDefinitionLimitsProps) { + const { bytes, message } = props; + + if (!bytes) { + return
{message}
; + } + + const thirty = formatBytes(bytes * 30); + const fortyFive = formatBytes(bytes * 45); + const sixty = formatBytes(bytes * 60); + + return ( +
+
{translate('MinutesThirty', { thirty })}
+
{translate('MinutesFortyFive', { fortyFive })}
+
{translate('MinutesSixty', { sixty })}
+
+ ); +} + +export default QualityDefinitionLimits; diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitions.js b/frontend/src/Settings/Quality/Definition/QualityDefinitions.js deleted file mode 100644 index 76b7ca383..000000000 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitions.js +++ /dev/null @@ -1,80 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import FieldSet from 'Components/FieldSet'; -import PageSectionContent from 'Components/Page/PageSectionContent'; -import translate from 'Utilities/String/translate'; -import QualityDefinitionConnector from './QualityDefinitionConnector'; -import styles from './QualityDefinitions.css'; - -class QualityDefinitions extends Component { - - // - // Render - - render() { - const { - items, - advancedSettings, - ...otherProps - } = this.props; - - return ( -
- -
-
- {translate('Quality')} -
-
- {translate('Title')} -
-
- {translate('SizeLimit')} -
- - { - advancedSettings ? -
- {translate('MegabytesPerMinute')} -
: - null - } -
- -
- { - items.map((item) => { - return ( - - ); - }) - } -
- -
-
- {translate('QualityLimitsSeriesRuntimeHelpText')} -
-
-
-
- ); - } -} - -QualityDefinitions.propTypes = { - isFetching: PropTypes.bool.isRequired, - error: PropTypes.object, - defaultProfile: PropTypes.object, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - advancedSettings: PropTypes.bool.isRequired -}; - -export default QualityDefinitions; diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitions.tsx b/frontend/src/Settings/Quality/Definition/QualityDefinitions.tsx new file mode 100644 index 000000000..7baefd3a6 --- /dev/null +++ b/frontend/src/Settings/Quality/Definition/QualityDefinitions.tsx @@ -0,0 +1,124 @@ +import { isEmpty } from 'lodash'; +import React, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; +import FieldSet from 'Components/FieldSet'; +import PageSectionContent from 'Components/Page/PageSectionContent'; +import usePrevious from 'Helpers/Hooks/usePrevious'; +import useShowAdvancedSettings from 'Helpers/Hooks/useShowAdvancedSettings'; +import { + fetchQualityDefinitions, + saveQualityDefinitions, +} from 'Store/Actions/settingsActions'; +import { + OnChildStateChange, + SetChildSave, +} from 'typings/Settings/SettingsState'; +import translate from 'Utilities/String/translate'; +import QualityDefinitionConnector from './QualityDefinitionConnector'; +import styles from './QualityDefinitions.css'; + +function createQualityDefinitionsSelector() { + return createSelector( + (state: AppState) => state.settings.qualityDefinitions, + (qualityDefinitions) => { + const items = qualityDefinitions.items.map((item) => { + const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {}; + + return Object.assign({}, item, pendingChanges); + }); + + return { + ...qualityDefinitions, + items, + hasPendingChanges: !isEmpty(qualityDefinitions.pendingChanges), + }; + } + ); +} + +interface QualityDefinitionsProps { + isResettingQualityDefinitions: boolean; + setChildSave: SetChildSave; + onChildStateChange: OnChildStateChange; +} + +function QualityDefinitions({ + isResettingQualityDefinitions, + setChildSave, + onChildStateChange, +}: QualityDefinitionsProps) { + const dispatch = useDispatch(); + const showAdvancedSettings = useShowAdvancedSettings(); + const { items, isFetching, isPopulated, isSaving, error, hasPendingChanges } = + useSelector(createQualityDefinitionsSelector()); + + const wasResettingQualityDefinitions = usePrevious( + isResettingQualityDefinitions + ); + + useEffect(() => { + dispatch(fetchQualityDefinitions()); + + setChildSave(() => { + dispatch(saveQualityDefinitions()); + }); + }, [dispatch, setChildSave]); + + useEffect(() => { + onChildStateChange({ + isSaving, + hasPendingChanges, + }); + }, [hasPendingChanges, isSaving, onChildStateChange]); + + useEffect(() => { + if (wasResettingQualityDefinitions && !isResettingQualityDefinitions) { + dispatch(fetchQualityDefinitions()); + } + }, [isResettingQualityDefinitions, wasResettingQualityDefinitions, dispatch]); + + return ( +
+ +
+
{translate('Quality')}
+
{translate('Title')}
+
{translate('SizeLimit')}
+ + {showAdvancedSettings ? ( +
+ {translate('MegabytesPerMinute')} +
+ ) : null} +
+ +
+ {items.map((item) => { + return ( + + ); + })} +
+ +
+
+ {translate('QualityLimitsSeriesRuntimeHelpText')} +
+
+
+
+ ); +} + +export default QualityDefinitions; diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitionsConnector.js b/frontend/src/Settings/Quality/Definition/QualityDefinitionsConnector.js deleted file mode 100644 index 4b1fc72a6..000000000 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitionsConnector.js +++ /dev/null @@ -1,90 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { fetchQualityDefinitions, saveQualityDefinitions } from 'Store/Actions/settingsActions'; -import QualityDefinitions from './QualityDefinitions'; - -function createMapStateToProps() { - return createSelector( - (state) => state.settings.qualityDefinitions, - (state) => state.settings.advancedSettings, - (qualityDefinitions, advancedSettings) => { - const items = qualityDefinitions.items.map((item) => { - const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {}; - - return Object.assign({}, item, pendingChanges); - }); - - return { - ...qualityDefinitions, - items, - hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges), - advancedSettings - }; - } - ); -} - -const mapDispatchToProps = { - dispatchFetchQualityDefinitions: fetchQualityDefinitions, - dispatchSaveQualityDefinitions: saveQualityDefinitions -}; - -class QualityDefinitionsConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - const { - dispatchFetchQualityDefinitions, - dispatchSaveQualityDefinitions, - onChildMounted - } = this.props; - - dispatchFetchQualityDefinitions(); - onChildMounted(dispatchSaveQualityDefinitions); - } - - componentDidUpdate(prevProps) { - const { - hasPendingChanges, - isSaving, - onChildStateChange - } = this.props; - - if ( - prevProps.isSaving !== isSaving || - prevProps.hasPendingChanges !== hasPendingChanges - ) { - onChildStateChange({ - isSaving, - hasPendingChanges - }); - } - } - - // - // Render - - render() { - return ( - - ); - } -} - -QualityDefinitionsConnector.propTypes = { - isSaving: PropTypes.bool.isRequired, - hasPendingChanges: PropTypes.bool.isRequired, - dispatchFetchQualityDefinitions: PropTypes.func.isRequired, - dispatchSaveQualityDefinitions: PropTypes.func.isRequired, - onChildMounted: PropTypes.func.isRequired, - onChildStateChange: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps, null)(QualityDefinitionsConnector); diff --git a/frontend/src/Settings/Quality/Quality.js b/frontend/src/Settings/Quality/Quality.js deleted file mode 100644 index dbab2d363..000000000 --- a/frontend/src/Settings/Quality/Quality.js +++ /dev/null @@ -1,105 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; -import PageContent from 'Components/Page/PageContent'; -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 SettingsToolbar from 'Settings/SettingsToolbar'; -import translate from 'Utilities/String/translate'; -import QualityDefinitionsConnector from './Definition/QualityDefinitionsConnector'; -import ResetQualityDefinitionsModal from './Reset/ResetQualityDefinitionsModal'; - -class Quality extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this._saveCallback = null; - - this.state = { - isSaving: false, - hasPendingChanges: false, - isConfirmQualityDefinitionResetModalOpen: false - }; - } - - // - // Listeners - - onChildMounted = (saveCallback) => { - this._saveCallback = saveCallback; - }; - - onChildStateChange = (payload) => { - this.setState(payload); - }; - - onResetQualityDefinitionsPress = () => { - this.setState({ isConfirmQualityDefinitionResetModalOpen: true }); - }; - - onCloseResetQualityDefinitionsModal = () => { - this.setState({ isConfirmQualityDefinitionResetModalOpen: false }); - }; - - onSavePress = () => { - if (this._saveCallback) { - this._saveCallback(); - } - }; - - // - // Render - - render() { - const { - isSaving, - isResettingQualityDefinitions, - hasPendingChanges - } = this.state; - - return ( - - - - - - - } - onSavePress={this.onSavePress} - /> - - - - - - - - ); - } -} - -Quality.propTypes = { - isResettingQualityDefinitions: PropTypes.bool.isRequired -}; - -export default Quality; diff --git a/frontend/src/Settings/Quality/Quality.tsx b/frontend/src/Settings/Quality/Quality.tsx new file mode 100644 index 000000000..f0ce86f70 --- /dev/null +++ b/frontend/src/Settings/Quality/Quality.tsx @@ -0,0 +1,95 @@ +import React, { useCallback, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import * as commandNames from 'Commands/commandNames'; +import PageContent from 'Components/Page/PageContent'; +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 SettingsToolbar from 'Settings/SettingsToolbar'; +import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; +import { + SaveCallback, + SettingsStateChange, +} from 'typings/Settings/SettingsState'; +import translate from 'Utilities/String/translate'; +import QualityDefinitions from './Definition/QualityDefinitions'; +import ResetQualityDefinitionsModal from './Reset/ResetQualityDefinitionsModal'; + +function Quality() { + const isResettingQualityDefinitions = useSelector( + createCommandExecutingSelector(commandNames.RESET_QUALITY_DEFINITIONS) + ); + + const saveDefinitions = useRef<() => void>(); + + const [isSaving, setIsSaving] = useState(false); + + const [ + isConfirmQualityDefinitionResetModalOpen, + setIsConfirmQualityDefinitionResetModalOpen, + ] = useState(false); + + const [hasPendingChanges, setHasPendingChanges] = useState(false); + + const handleSetChildSave = useCallback((saveCallback: SaveCallback) => { + saveDefinitions.current = saveCallback; + }, []); + + const handleChildStateChange = useCallback( + ({ isSaving, hasPendingChanges }: SettingsStateChange) => { + setIsSaving(isSaving); + setHasPendingChanges(hasPendingChanges); + }, + [] + ); + + const handleResetQualityDefinitionsPress = useCallback(() => { + setIsConfirmQualityDefinitionResetModalOpen(true); + }, []); + + const handleCloseResetQualityDefinitionsModal = useCallback(() => { + setIsConfirmQualityDefinitionResetModalOpen(false); + }, []); + + const handleSavePress = useCallback(() => { + saveDefinitions.current?.(); + }, []); + + return ( + + + + + + + } + onSavePress={handleSavePress} + /> + + + + + + + + ); +} + +export default Quality; diff --git a/frontend/src/Settings/Quality/QualityConnector.js b/frontend/src/Settings/Quality/QualityConnector.js deleted file mode 100644 index 8cc9219cb..000000000 --- a/frontend/src/Settings/Quality/QualityConnector.js +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import * as commandNames from 'Commands/commandNames'; -import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; -import Quality from './Quality'; - -function createMapStateToProps() { - return createSelector( - createCommandExecutingSelector(commandNames.RESET_QUALITY_DEFINITIONS), - (isResettingQualityDefinitions) => { - return { - isResettingQualityDefinitions - }; - } - ); -} - -class QualityConnector extends Component { - - // - // Render - - render() { - return ( - - ); - } -} - -QualityConnector.propTypes = { - isResettingQualityDefinitions: PropTypes.bool.isRequired -}; - -export default connect(createMapStateToProps)(QualityConnector); diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js deleted file mode 100644 index ee9caa260..000000000 --- a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js +++ /dev/null @@ -1,33 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import Modal from 'Components/Modal/Modal'; -import { sizes } from 'Helpers/Props'; -import ResetQualityDefinitionsModalContentConnector from './ResetQualityDefinitionsModalContentConnector'; - -function ResetQualityDefinitionsModal(props) { - const { - isOpen, - onModalClose, - ...otherProps - } = props; - - return ( - - - - ); -} - -ResetQualityDefinitionsModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default ResetQualityDefinitionsModal; diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.tsx b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.tsx new file mode 100644 index 000000000..311506086 --- /dev/null +++ b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import Modal from 'Components/Modal/Modal'; +import { sizes } from 'Helpers/Props'; +import ResetQualityDefinitionsModalContent from './ResetQualityDefinitionsModalContent'; + +interface ResetQualityDefinitionsModalProps { + isOpen: boolean; + onModalClose: () => void; +} + +function ResetQualityDefinitionsModal({ + isOpen, + onModalClose, +}: ResetQualityDefinitionsModalProps) { + return ( + + + + ); +} + +export default ResetQualityDefinitionsModal; diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js deleted file mode 100644 index 11f9dfa2f..000000000 --- a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js +++ /dev/null @@ -1,106 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import FormGroup from 'Components/Form/FormGroup'; -import FormInputGroup from 'Components/Form/FormInputGroup'; -import FormLabel from 'Components/Form/FormLabel'; -import Button from 'Components/Link/Button'; -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 { inputTypes, kinds } from 'Helpers/Props'; -import translate from 'Utilities/String/translate'; -import styles from './ResetQualityDefinitionsModalContent.css'; - -class ResetQualityDefinitionsModalContent extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - resetDefinitionTitles: false - }; - } - - // - // Listeners - - onResetDefinitionTitlesChange = ({ value }) => { - this.setState({ resetDefinitionTitles: value }); - }; - - onResetQualityDefinitionsConfirmed = () => { - const resetDefinitionTitles = this.state.resetDefinitionTitles; - - this.setState({ resetDefinitionTitles: false }); - this.props.onResetQualityDefinitions(resetDefinitionTitles); - }; - - // - // Render - - render() { - const { - onModalClose, - isResettingQualityDefinitions - } = this.props; - - const resetDefinitionTitles = this.state.resetDefinitionTitles; - - return ( - - - {translate('ResetQualityDefinitions')} - - - -
- {translate('ResetQualityDefinitionsMessageText')} -
- - - - {translate('ResetTitles')} - - - - - -
- - - - - - -
- ); - } -} - -ResetQualityDefinitionsModalContent.propTypes = { - onResetQualityDefinitions: PropTypes.func.isRequired, - isResettingQualityDefinitions: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired -}; - -export default ResetQualityDefinitionsModalContent; diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.tsx b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.tsx new file mode 100644 index 000000000..bb007f989 --- /dev/null +++ b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import * as commandNames from 'Commands/commandNames'; +import FormGroup from 'Components/Form/FormGroup'; +import FormInputGroup from 'Components/Form/FormInputGroup'; +import FormLabel from 'Components/Form/FormLabel'; +import Button from 'Components/Link/Button'; +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 { inputTypes, kinds } from 'Helpers/Props'; +import { executeCommand } from 'Store/Actions/commandActions'; +import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; +import { InputChanged } from 'typings/inputs'; +import translate from 'Utilities/String/translate'; +import styles from './ResetQualityDefinitionsModalContent.css'; + +interface ResetQualityDefinitionsModalContentProps { + onModalClose: () => void; +} + +function ResetQualityDefinitionsModalContent({ + onModalClose, +}: ResetQualityDefinitionsModalContentProps) { + const dispatch = useDispatch(); + + const isResettingQualityDefinitions = useSelector( + createCommandExecutingSelector(commandNames.RESET_QUALITY_DEFINITIONS) + ); + + const [resetDefinitionTitles, setResetDefinitionTitles] = useState(false); + + const handleResetDefinitionTitlesChange = useCallback( + ({ value }: InputChanged) => { + setResetDefinitionTitles(value); + }, + [] + ); + + const handleResetQualityDefinitionsConfirmed = useCallback(() => { + const resetTitles = resetDefinitionTitles; + + setResetDefinitionTitles(false); + + dispatch( + executeCommand({ + name: commandNames.RESET_QUALITY_DEFINITIONS, + resetTitles, + }) + ); + onModalClose(); + }, [resetDefinitionTitles, dispatch, onModalClose]); + + return ( + + {translate('ResetQualityDefinitions')} + + +
+ {translate('ResetQualityDefinitionsMessageText')} +
+ + + {translate('ResetTitles')} + + + +
+ + + + + + +
+ ); +} + +export default ResetQualityDefinitionsModalContent; diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js deleted file mode 100644 index 645cac1e1..000000000 --- a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import * as commandNames from 'Commands/commandNames'; -import { executeCommand } from 'Store/Actions/commandActions'; -import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; -import ResetQualityDefinitionsModalContent from './ResetQualityDefinitionsModalContent'; - -function createMapStateToProps() { - return createSelector( - createCommandExecutingSelector(commandNames.RESET_QUALITY_DEFINITIONS), - (isResettingQualityDefinitions) => { - return { - isResettingQualityDefinitions - }; - } - ); -} - -const mapDispatchToProps = { - executeCommand -}; - -class ResetQualityDefinitionsModalContentConnector extends Component { - - // - // Listeners - - onResetQualityDefinitions = (resetTitles) => { - this.props.executeCommand({ name: commandNames.RESET_QUALITY_DEFINITIONS, resetTitles }); - this.props.onModalClose(true); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -ResetQualityDefinitionsModalContentConnector.propTypes = { - onModalClose: PropTypes.func.isRequired, - isResettingQualityDefinitions: PropTypes.bool.isRequired, - executeCommand: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(ResetQualityDefinitionsModalContentConnector); diff --git a/frontend/src/Utilities/Object/isEmpty.ts b/frontend/src/Utilities/Object/isEmpty.ts new file mode 100644 index 000000000..08d71f090 --- /dev/null +++ b/frontend/src/Utilities/Object/isEmpty.ts @@ -0,0 +1,11 @@ +function isEmpty(obj: T) { + for (const prop in obj) { + if (Object.hasOwn(obj, prop)) { + return false; + } + } + + return true; +} + +export default isEmpty; diff --git a/frontend/src/typings/Settings/SettingsState.ts b/frontend/src/typings/Settings/SettingsState.ts new file mode 100644 index 000000000..b83317f10 --- /dev/null +++ b/frontend/src/typings/Settings/SettingsState.ts @@ -0,0 +1,10 @@ +export type SaveCallback = () => void; + +export interface SettingsStateChange { + isSaving: boolean; + hasPendingChanges: boolean; +} + +export type SetChildSave = (SaveCallback: SaveCallback) => void; + +export type OnChildStateChange = (change: SettingsStateChange) => void;