mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-25 22:46:31 -04:00
Use react-query for Quality Definitions
This commit is contained in:
@@ -1,31 +1,29 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Quality from 'Quality/Quality';
|
||||
import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
|
||||
import { useManageQualityDefinitions } from './useQualityDefinitions';
|
||||
import styles from './QualityDefinition.css';
|
||||
|
||||
interface QualityDefinitionProps {
|
||||
id: number;
|
||||
quality: Quality;
|
||||
title: string;
|
||||
updateDefinition: ReturnType<
|
||||
typeof useManageQualityDefinitions
|
||||
>['updateDefinition'];
|
||||
}
|
||||
|
||||
function QualityDefinition(props: QualityDefinitionProps) {
|
||||
const { id, quality, title } = props;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
function QualityDefinition({
|
||||
id,
|
||||
quality,
|
||||
title,
|
||||
updateDefinition,
|
||||
}: QualityDefinitionProps) {
|
||||
const handleTitleChange = useCallback(
|
||||
({ value }: { value: string }) => {
|
||||
dispatch(
|
||||
setQualityDefinitionValue({
|
||||
id,
|
||||
name: 'title',
|
||||
value,
|
||||
})
|
||||
);
|
||||
updateDefinition(id, 'title', value);
|
||||
},
|
||||
[id, dispatch]
|
||||
[id, updateDefinition]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,42 +1,17 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
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 {
|
||||
fetchQualityDefinitions,
|
||||
saveQualityDefinitions,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import {
|
||||
OnChildStateChange,
|
||||
SetChildSave,
|
||||
} from 'typings/Settings/SettingsState';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityDefinition from './QualityDefinition';
|
||||
import { useManageQualityDefinitions } from './useQualityDefinitions';
|
||||
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;
|
||||
@@ -48,21 +23,27 @@ function QualityDefinitions({
|
||||
setChildSave,
|
||||
onChildStateChange,
|
||||
}: QualityDefinitionsProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { items, isFetching, isPopulated, isSaving, error, hasPendingChanges } =
|
||||
useSelector(createQualityDefinitionsSelector());
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
items,
|
||||
isFetching,
|
||||
isFetched,
|
||||
isSaving,
|
||||
error,
|
||||
hasPendingChanges,
|
||||
updateDefinition,
|
||||
saveQualityDefinitions,
|
||||
} = useManageQualityDefinitions();
|
||||
|
||||
const wasResettingQualityDefinitions = usePrevious(
|
||||
isResettingQualityDefinitions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchQualityDefinitions());
|
||||
|
||||
setChildSave(() => {
|
||||
dispatch(saveQualityDefinitions());
|
||||
saveQualityDefinitions();
|
||||
});
|
||||
}, [dispatch, setChildSave]);
|
||||
}, [saveQualityDefinitions, setChildSave]);
|
||||
|
||||
useEffect(() => {
|
||||
onChildStateChange({
|
||||
@@ -73,16 +54,20 @@ function QualityDefinitions({
|
||||
|
||||
useEffect(() => {
|
||||
if (wasResettingQualityDefinitions && !isResettingQualityDefinitions) {
|
||||
dispatch(fetchQualityDefinitions());
|
||||
queryClient.invalidateQueries({ queryKey: ['/qualitydefinition'] });
|
||||
}
|
||||
}, [isResettingQualityDefinitions, wasResettingQualityDefinitions, dispatch]);
|
||||
}, [
|
||||
isResettingQualityDefinitions,
|
||||
wasResettingQualityDefinitions,
|
||||
queryClient,
|
||||
]);
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('QualityDefinitions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('QualityDefinitionsLoadError')}
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
isPopulated={isFetched}
|
||||
error={error}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
@@ -92,7 +77,13 @@ function QualityDefinitions({
|
||||
|
||||
<div className={styles.definitions}>
|
||||
{items.map((item) => {
|
||||
return <QualityDefinition key={item.id} {...item} />;
|
||||
return (
|
||||
<QualityDefinition
|
||||
key={item.id}
|
||||
{...item}
|
||||
updateDefinition={updateDefinition}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PageSectionContent>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import { usePendingItemsStore } from 'Helpers/Hooks/usePendingItemsStore';
|
||||
import QualityDefinitionModel from 'Quality/QualityDefinitionModel';
|
||||
import { useSaveSettings } from 'Settings/useSettings';
|
||||
|
||||
const PATH = '/qualitydefinition';
|
||||
const DEFAULT_QUALITY_DEFINITIONS: QualityDefinitionModel[] = [];
|
||||
|
||||
export const useQualityDefinitions = () => {
|
||||
const result = useApiQuery<QualityDefinitionModel[]>({
|
||||
path: PATH,
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
data: result.data ?? DEFAULT_QUALITY_DEFINITIONS,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSaveQualityDefinitions = (onSuccess?: () => void) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending, error } = useApiMutation<
|
||||
QualityDefinitionModel[],
|
||||
QualityDefinitionModel[]
|
||||
>({
|
||||
path: PATH,
|
||||
method: 'PUT',
|
||||
mutationOptions: {
|
||||
onSuccess: (updatedSettings: QualityDefinitionModel[]) => {
|
||||
queryClient.setQueryData<QualityDefinitionModel[]>(
|
||||
[PATH],
|
||||
updatedSettings
|
||||
);
|
||||
onSuccess?.();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
save: mutate,
|
||||
isSaving: isPending,
|
||||
saveError: error,
|
||||
};
|
||||
};
|
||||
|
||||
export const useManageQualityDefinitions = () => {
|
||||
const { data, isFetching, isFetched, error } = useQualityDefinitions();
|
||||
const {
|
||||
setPendingItem,
|
||||
clearPendingItems,
|
||||
getItemsWithPendingChanges,
|
||||
getPendingChangesForSave,
|
||||
hasPendingChanges,
|
||||
} = usePendingItemsStore<QualityDefinitionModel>();
|
||||
|
||||
const { save, isSaving, saveError } = useSaveSettings(
|
||||
PATH,
|
||||
clearPendingItems
|
||||
);
|
||||
|
||||
const settings = useMemo(() => {
|
||||
return {
|
||||
items: getItemsWithPendingChanges(data),
|
||||
hasPendingChanges,
|
||||
};
|
||||
}, [data, getItemsWithPendingChanges, hasPendingChanges]);
|
||||
|
||||
const saveQualityDefinitions = useCallback(() => {
|
||||
const updatedSettings = getPendingChangesForSave(data);
|
||||
save(updatedSettings);
|
||||
}, [data, getPendingChangesForSave, save]);
|
||||
|
||||
const updateDefinition = useCallback(
|
||||
<K extends keyof QualityDefinitionModel>(
|
||||
id: number,
|
||||
key: keyof QualityDefinitionModel,
|
||||
value: QualityDefinitionModel[K]
|
||||
) => {
|
||||
const originalItem = data.find((def) => def.id === id);
|
||||
setPendingItem(id, key, value, originalItem);
|
||||
},
|
||||
[data, setPendingItem]
|
||||
);
|
||||
|
||||
return {
|
||||
...settings,
|
||||
updateDefinition,
|
||||
saveQualityDefinitions,
|
||||
isFetching,
|
||||
isFetched,
|
||||
isSaving,
|
||||
error,
|
||||
saveError,
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import CommandNames from 'Commands/CommandNames';
|
||||
import { useCommandExecuting } from 'Commands/useCommands';
|
||||
import { useCommandExecuting, useExecuteCommand } from 'Commands/useCommands';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
@@ -13,9 +14,9 @@ import {
|
||||
} from 'typings/Settings/SettingsState';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityDefinitions from './Definition/QualityDefinitions';
|
||||
import ResetQualityDefinitionsModal from './Reset/ResetQualityDefinitionsModal';
|
||||
|
||||
function Quality() {
|
||||
const executeCommand = useExecuteCommand();
|
||||
const isResettingQualityDefinitions = useCommandExecuting(
|
||||
CommandNames.ResetQualityDefinitions
|
||||
);
|
||||
@@ -51,6 +52,15 @@ function Quality() {
|
||||
setIsConfirmQualityDefinitionResetModalOpen(false);
|
||||
}, []);
|
||||
|
||||
const handleResetQualityDefinitionsConfirmed = useCallback(() => {
|
||||
executeCommand({
|
||||
name: CommandNames.ResetQualityDefinitions,
|
||||
resetTitles: true,
|
||||
});
|
||||
|
||||
setIsConfirmQualityDefinitionResetModalOpen(false);
|
||||
}, [executeCommand]);
|
||||
|
||||
const handleSavePress = useCallback(() => {
|
||||
saveDefinitions.current?.();
|
||||
}, []);
|
||||
@@ -68,13 +78,13 @@ function Quality() {
|
||||
label={translate('ResetDefinitions')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isResettingQualityDefinitions}
|
||||
isDisabled={isResettingQualityDefinitions}
|
||||
onPress={handleResetQualityDefinitionsPress}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onSavePress={handleSavePress}
|
||||
/>
|
||||
|
||||
<PageContentBody>
|
||||
<QualityDefinitions
|
||||
isResettingQualityDefinitions={isResettingQualityDefinitions}
|
||||
@@ -83,9 +93,14 @@ function Quality() {
|
||||
/>
|
||||
</PageContentBody>
|
||||
|
||||
<ResetQualityDefinitionsModal
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmQualityDefinitionResetModalOpen}
|
||||
onModalClose={handleCloseResetQualityDefinitionsModal}
|
||||
kind="danger"
|
||||
title={translate('ResetQualityDefinitions')}
|
||||
message={translate('ResetQualityDefinitionsMessageText')}
|
||||
confirmLabel={translate('Reset')}
|
||||
onConfirm={handleResetQualityDefinitionsConfirmed}
|
||||
onCancel={handleCloseResetQualityDefinitionsModal}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
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,3 +0,0 @@
|
||||
.messageContainer {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'messageContainer': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
@@ -1,87 +0,0 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import CommandNames from 'Commands/CommandNames';
|
||||
import { useCommandExecuting, useExecuteCommand } from 'Commands/useCommands';
|
||||
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 { InputChanged } from 'typings/inputs';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ResetQualityDefinitionsModalContent.css';
|
||||
|
||||
interface ResetQualityDefinitionsModalContentProps {
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function ResetQualityDefinitionsModalContent({
|
||||
onModalClose,
|
||||
}: ResetQualityDefinitionsModalContentProps) {
|
||||
const executeCommand = useExecuteCommand();
|
||||
const isResettingQualityDefinitions = useCommandExecuting(
|
||||
CommandNames.ResetQualityDefinitions
|
||||
);
|
||||
|
||||
const [resetDefinitionTitles, setResetDefinitionTitles] = useState(false);
|
||||
|
||||
const handleResetDefinitionTitlesChange = useCallback(
|
||||
({ value }: InputChanged<boolean>) => {
|
||||
setResetDefinitionTitles(value);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleResetQualityDefinitionsConfirmed = useCallback(() => {
|
||||
const resetTitles = resetDefinitionTitles;
|
||||
|
||||
setResetDefinitionTitles(false);
|
||||
|
||||
executeCommand({
|
||||
name: CommandNames.ResetQualityDefinitions,
|
||||
resetTitles,
|
||||
});
|
||||
onModalClose();
|
||||
}, [resetDefinitionTitles, executeCommand, 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;
|
||||
Reference in New Issue
Block a user