mirror of
https://github.com/Sonarr/Sonarr
synced 2025-03-10 06:03:32 +00:00
Convert Quality Settings to TypeScript
This commit is contained in:
parent
89f584d1b3
commit
9ce473d9bb
20 changed files with 415 additions and 548 deletions
|
@ -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() {
|
|||
|
||||
<Route path="/settings/profiles" component={Profiles} />
|
||||
|
||||
<Route path="/settings/quality" component={QualityConnector} />
|
||||
<Route path="/settings/quality" component={Quality} />
|
||||
|
||||
<Route
|
||||
path="/settings/customformats"
|
||||
|
|
|
@ -74,6 +74,14 @@ export interface NotificationAppState
|
|||
extends AppSectionState<Notification>,
|
||||
AppSectionDeleteState {}
|
||||
|
||||
export interface QualityDefinitionsAppState
|
||||
extends AppSectionState<QualityProfile>,
|
||||
AppSectionSaveState {
|
||||
pendingChanges: {
|
||||
[key: number]: Partial<QualityProfile>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface QualityProfilesAppState
|
||||
extends AppSectionState<QualityProfile>,
|
||||
AppSectionItemSchemaState<QualityProfile> {}
|
||||
|
@ -123,6 +131,7 @@ interface SettingsAppState {
|
|||
naming: NamingAppState;
|
||||
namingExamples: NamingExamplesAppState;
|
||||
notifications: NotificationAppState;
|
||||
qualityDefinitions: QualityDefinitionsAppState;
|
||||
qualityProfiles: QualityProfilesAppState;
|
||||
releaseProfiles: ReleaseProfilesAppState;
|
||||
ui: UiSettingsAppState;
|
||||
|
|
8
frontend/src/Helpers/Hooks/useShowAdvancedSettings.ts
Normal file
8
frontend/src/Helpers/Hooks/useShowAdvancedSettings.ts
Normal file
|
@ -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;
|
11
frontend/src/Quality/QualityDefinition.ts
Normal file
11
frontend/src/Quality/QualityDefinition.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 <div>{message}</div>;
|
||||
}
|
||||
|
||||
const thirty = formatBytes(bytes * 30);
|
||||
const fortyFive = formatBytes(bytes * 45);
|
||||
const sixty = formatBytes(bytes * 60);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{translate('MinutesThirty', { thirty })}
|
||||
</div>
|
||||
<div>
|
||||
{translate('MinutesFortyFive', { fortyFive })}
|
||||
</div>
|
||||
<div>
|
||||
{translate('MinutesSixty', { sixty })}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
QualityDefinitionLimits.propTypes = {
|
||||
bytes: PropTypes.number,
|
||||
message: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default QualityDefinitionLimits;
|
|
@ -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 <div>{message}</div>;
|
||||
}
|
||||
|
||||
const thirty = formatBytes(bytes * 30);
|
||||
const fortyFive = formatBytes(bytes * 45);
|
||||
const sixty = formatBytes(bytes * 60);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{translate('MinutesThirty', { thirty })}</div>
|
||||
<div>{translate('MinutesFortyFive', { fortyFive })}</div>
|
||||
<div>{translate('MinutesSixty', { sixty })}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default QualityDefinitionLimits;
|
|
@ -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 (
|
||||
<FieldSet legend={translate('QualityDefinitions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('QualityDefinitionsLoadError')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.quality}>
|
||||
{translate('Quality')}
|
||||
</div>
|
||||
<div className={styles.title}>
|
||||
{translate('Title')}
|
||||
</div>
|
||||
<div className={styles.sizeLimit}>
|
||||
{translate('SizeLimit')}
|
||||
</div>
|
||||
|
||||
{
|
||||
advancedSettings ?
|
||||
<div className={styles.megabytesPerMinute}>
|
||||
{translate('MegabytesPerMinute')}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.definitions}>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<QualityDefinitionConnector
|
||||
key={item.id}
|
||||
{...item}
|
||||
advancedSettings={advancedSettings}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.sizeLimitHelpTextContainer}>
|
||||
<div className={styles.sizeLimitHelpText}>
|
||||
{translate('QualityLimitsSeriesRuntimeHelpText')}
|
||||
</div>
|
||||
</div>
|
||||
</PageSectionContent>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
124
frontend/src/Settings/Quality/Definition/QualityDefinitions.tsx
Normal file
124
frontend/src/Settings/Quality/Definition/QualityDefinitions.tsx
Normal file
|
@ -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 (
|
||||
<FieldSet legend={translate('QualityDefinitions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('QualityDefinitionsLoadError')}
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
error={error}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.quality}>{translate('Quality')}</div>
|
||||
<div className={styles.title}>{translate('Title')}</div>
|
||||
<div className={styles.sizeLimit}>{translate('SizeLimit')}</div>
|
||||
|
||||
{showAdvancedSettings ? (
|
||||
<div className={styles.megabytesPerMinute}>
|
||||
{translate('MegabytesPerMinute')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className={styles.definitions}>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<QualityDefinitionConnector
|
||||
key={item.id}
|
||||
{...item}
|
||||
advancedSettings={showAdvancedSettings}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className={styles.sizeLimitHelpTextContainer}>
|
||||
<div className={styles.sizeLimitHelpText}>
|
||||
{translate('QualityLimitsSeriesRuntimeHelpText')}
|
||||
</div>
|
||||
</div>
|
||||
</PageSectionContent>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
|
||||
export default QualityDefinitions;
|
|
@ -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 (
|
||||
<QualityDefinitions
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
|
@ -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 (
|
||||
<PageContent title={translate('QualitySettings')}>
|
||||
<SettingsToolbar
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
additionalButtons={
|
||||
<Fragment>
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('ResetDefinitions')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isResettingQualityDefinitions}
|
||||
onPress={this.onResetQualityDefinitionsPress}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
onSavePress={this.onSavePress}
|
||||
/>
|
||||
|
||||
<PageContentBody>
|
||||
<QualityDefinitionsConnector
|
||||
onChildMounted={this.onChildMounted}
|
||||
onChildStateChange={this.onChildStateChange}
|
||||
/>
|
||||
</PageContentBody>
|
||||
|
||||
<ResetQualityDefinitionsModal
|
||||
isOpen={this.state.isConfirmQualityDefinitionResetModalOpen}
|
||||
onModalClose={this.onCloseResetQualityDefinitionsModal}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Quality.propTypes = {
|
||||
isResettingQualityDefinitions: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default Quality;
|
95
frontend/src/Settings/Quality/Quality.tsx
Normal file
95
frontend/src/Settings/Quality/Quality.tsx
Normal file
|
@ -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 (
|
||||
<PageContent title={translate('QualitySettings')}>
|
||||
<SettingsToolbar
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
additionalButtons={
|
||||
<>
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('ResetDefinitions')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isResettingQualityDefinitions}
|
||||
onPress={handleResetQualityDefinitionsPress}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onSavePress={handleSavePress}
|
||||
/>
|
||||
|
||||
<PageContentBody>
|
||||
<QualityDefinitions
|
||||
isResettingQualityDefinitions={isResettingQualityDefinitions}
|
||||
setChildSave={handleSetChildSave}
|
||||
onChildStateChange={handleChildStateChange}
|
||||
/>
|
||||
</PageContentBody>
|
||||
|
||||
<ResetQualityDefinitionsModal
|
||||
isOpen={isConfirmQualityDefinitionResetModalOpen}
|
||||
onModalClose={handleCloseResetQualityDefinitionsModal}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default Quality;
|
|
@ -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 (
|
||||
<Quality
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QualityConnector.propTypes = {
|
||||
isResettingQualityDefinitions: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(QualityConnector);
|
|
@ -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 (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
size={sizes.MEDIUM}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ResetQualityDefinitionsModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
ResetQualityDefinitionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ResetQualityDefinitionsModal;
|
|
@ -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 (
|
||||
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
|
||||
<ResetQualityDefinitionsModalContent onModalClose={onModalClose} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetQualityDefinitionsModal;
|
|
@ -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 (
|
||||
<ModalContent
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
{translate('ResetQualityDefinitions')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.messageContainer}>
|
||||
{translate('ResetQualityDefinitionsMessageText')}
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{translate('ResetTitles')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="resetDefinitionTitles"
|
||||
value={resetDefinitionTitles}
|
||||
helpText={translate('ResetDefinitionTitlesHelpText')}
|
||||
onChange={this.onResetDefinitionTitlesChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onResetQualityDefinitionsConfirmed}
|
||||
isDisabled={isResettingQualityDefinitions}
|
||||
>
|
||||
{translate('Reset')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ResetQualityDefinitionsModalContent.propTypes = {
|
||||
onResetQualityDefinitions: PropTypes.func.isRequired,
|
||||
isResettingQualityDefinitions: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ResetQualityDefinitionsModalContent;
|
|
@ -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<boolean>) => {
|
||||
setResetDefinitionTitles(value);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleResetQualityDefinitionsConfirmed = useCallback(() => {
|
||||
const resetTitles = resetDefinitionTitles;
|
||||
|
||||
setResetDefinitionTitles(false);
|
||||
|
||||
dispatch(
|
||||
executeCommand({
|
||||
name: commandNames.RESET_QUALITY_DEFINITIONS,
|
||||
resetTitles,
|
||||
})
|
||||
);
|
||||
onModalClose();
|
||||
}, [resetDefinitionTitles, dispatch, onModalClose]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('ResetQualityDefinitions')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.messageContainer}>
|
||||
{translate('ResetQualityDefinitionsMessageText')}
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ResetTitles')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="resetDefinitionTitles"
|
||||
value={resetDefinitionTitles}
|
||||
helpText={translate('ResetDefinitionTitlesHelpText')}
|
||||
onChange={handleResetDefinitionTitlesChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
isDisabled={isResettingQualityDefinitions}
|
||||
onPress={handleResetQualityDefinitionsConfirmed}
|
||||
>
|
||||
{translate('Reset')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetQualityDefinitionsModalContent;
|
|
@ -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 (
|
||||
<ResetQualityDefinitionsModalContent
|
||||
{...this.props}
|
||||
onResetQualityDefinitions={this.onResetQualityDefinitions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ResetQualityDefinitionsModalContentConnector.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
isResettingQualityDefinitions: PropTypes.bool.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ResetQualityDefinitionsModalContentConnector);
|
11
frontend/src/Utilities/Object/isEmpty.ts
Normal file
11
frontend/src/Utilities/Object/isEmpty.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
function isEmpty<T extends object>(obj: T) {
|
||||
for (const prop in obj) {
|
||||
if (Object.hasOwn(obj, prop)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default isEmpty;
|
10
frontend/src/typings/Settings/SettingsState.ts
Normal file
10
frontend/src/typings/Settings/SettingsState.ts
Normal file
|
@ -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;
|
Loading…
Add table
Reference in a new issue