mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-18 21:35:27 -04:00
Use react-query for Release Profiles
This commit is contained in:
@@ -27,7 +27,6 @@ import IndexerOptions from 'typings/Settings/IndexerOptions';
|
||||
import MediaManagement from 'typings/Settings/MediaManagement';
|
||||
import NamingConfig from 'typings/Settings/NamingConfig';
|
||||
import NamingExample from 'typings/Settings/NamingExample';
|
||||
import ReleaseProfile from 'typings/Settings/ReleaseProfile';
|
||||
import MetadataAppState from './MetadataAppState';
|
||||
|
||||
type Presets<T> = T & {
|
||||
@@ -116,12 +115,6 @@ export interface QualityProfilesAppState
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState {}
|
||||
|
||||
export interface ReleaseProfilesAppState
|
||||
extends AppSectionState<ReleaseProfile>,
|
||||
AppSectionSaveState {
|
||||
pendingChanges: Partial<ReleaseProfile>;
|
||||
}
|
||||
|
||||
export interface CustomFormatAppState
|
||||
extends AppSectionState<CustomFormat>,
|
||||
AppSectionDeleteState,
|
||||
@@ -171,7 +164,6 @@ interface SettingsAppState {
|
||||
notifications: NotificationAppState;
|
||||
qualityDefinitions: QualityDefinitionsAppState;
|
||||
qualityProfiles: QualityProfilesAppState;
|
||||
releaseProfiles: ReleaseProfilesAppState;
|
||||
}
|
||||
|
||||
export default SettingsAppState;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -14,55 +11,13 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import {
|
||||
saveReleaseProfile,
|
||||
setReleaseProfileValue,
|
||||
} from 'Store/Actions/Settings/releaseProfiles';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import { InputChanged } from 'typings/inputs';
|
||||
import ReleaseProfile from 'typings/Settings/ReleaseProfile';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { useManageReleaseProfile } from './useReleaseProfiles';
|
||||
import styles from './EditReleaseProfileModalContent.css';
|
||||
|
||||
const tagInputDelimiters = ['Tab', 'Enter'];
|
||||
|
||||
const newReleaseProfile: ReleaseProfile = {
|
||||
id: 0,
|
||||
name: '',
|
||||
enabled: true,
|
||||
required: [],
|
||||
ignored: [],
|
||||
tags: [],
|
||||
excludedTags: [],
|
||||
indexerId: 0,
|
||||
};
|
||||
|
||||
function createReleaseProfileSelector(id?: number) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.releaseProfiles,
|
||||
(releaseProfiles) => {
|
||||
const { items, isFetching, error, isSaving, saveError, pendingChanges } =
|
||||
releaseProfiles;
|
||||
|
||||
const mapping = id ? items.find((i) => i.id === id)! : newReleaseProfile;
|
||||
const settings = selectSettings<ReleaseProfile>(
|
||||
mapping,
|
||||
pendingChanges,
|
||||
saveError
|
||||
);
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
error,
|
||||
isSaving,
|
||||
saveError,
|
||||
item: settings.settings,
|
||||
...settings,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface EditReleaseProfileModalContentProps {
|
||||
id?: number;
|
||||
onModalClose: () => void;
|
||||
@@ -74,43 +29,39 @@ function EditReleaseProfileModalContent({
|
||||
onModalClose,
|
||||
onDeleteReleaseProfilePress,
|
||||
}: EditReleaseProfileModalContentProps) {
|
||||
const { item, isFetching, isSaving, error, saveError, ...otherProps } =
|
||||
useSelector(createReleaseProfileSelector(id));
|
||||
const {
|
||||
item,
|
||||
isSaving,
|
||||
saveError,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
updateValue,
|
||||
saveProvider,
|
||||
} = useManageReleaseProfile(id ?? 0);
|
||||
|
||||
const { name, enabled, required, ignored, tags, excludedTags, indexerId } =
|
||||
item;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const previousIsSaving = usePrevious(isSaving);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
Object.entries(newReleaseProfile).forEach(([name, value]) => {
|
||||
// @ts-expect-error 'setReleaseProfileValue' isn't typed yet
|
||||
dispatch(setReleaseProfileValue({ name, value }));
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (previousIsSaving && !isSaving && !saveError) {
|
||||
onModalClose();
|
||||
}
|
||||
}, [previousIsSaving, isSaving, saveError, onModalClose]);
|
||||
|
||||
const handleSavePress = useCallback(() => {
|
||||
dispatch(saveReleaseProfile({ id }));
|
||||
}, [dispatch, id]);
|
||||
const wasSaving = usePrevious(isSaving);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(change: InputChanged) => {
|
||||
// @ts-expect-error 'setReleaseProfileValue' isn't typed yet
|
||||
dispatch(setReleaseProfileValue(change));
|
||||
// @ts-expect-error - change is not yet typed
|
||||
updateValue(change.name, change.value);
|
||||
},
|
||||
[dispatch]
|
||||
[updateValue]
|
||||
);
|
||||
|
||||
const handleSavePress = useCallback(() => {
|
||||
saveProvider();
|
||||
}, [saveProvider]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasSaving && !isSaving && !saveError) {
|
||||
onModalClose();
|
||||
}
|
||||
}, [isSaving, wasSaving, saveError, onModalClose]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
@@ -118,7 +69,10 @@ function EditReleaseProfileModalContent({
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form {...otherProps}>
|
||||
<Form
|
||||
validationErrors={validationErrors}
|
||||
validationWarnings={validationWarnings}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Name')}</FormLabel>
|
||||
|
||||
@@ -219,6 +173,7 @@ function EditReleaseProfileModalContent({
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{id ? (
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import MiddleTruncate from 'Components/MiddleTruncate';
|
||||
@@ -7,15 +6,17 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TagList from 'Components/TagList';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { deleteReleaseProfile } from 'Store/Actions/Settings/releaseProfiles';
|
||||
import { Tag } from 'Tags/useTags';
|
||||
import Indexer from 'typings/Indexer';
|
||||
import ReleaseProfile from 'typings/Settings/ReleaseProfile';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditReleaseProfileModal from './EditReleaseProfileModal';
|
||||
import {
|
||||
ReleaseProfileModel,
|
||||
useDeleteReleaseProfile,
|
||||
} from './useReleaseProfiles';
|
||||
import styles from './ReleaseProfileItem.css';
|
||||
|
||||
interface ReleaseProfileProps extends ReleaseProfile {
|
||||
interface ReleaseProfileProps extends ReleaseProfileModel {
|
||||
tagList: Tag[];
|
||||
indexerList: Indexer[];
|
||||
}
|
||||
@@ -34,7 +35,7 @@ function ReleaseProfileItem(props: ReleaseProfileProps) {
|
||||
indexerList,
|
||||
} = props;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { deleteReleaseProfile } = useDeleteReleaseProfile(id);
|
||||
|
||||
const [
|
||||
isEditReleaseProfileModalOpen,
|
||||
@@ -49,8 +50,8 @@ function ReleaseProfileItem(props: ReleaseProfileProps) {
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const handleDeletePress = useCallback(() => {
|
||||
dispatch(deleteReleaseProfile({ id }));
|
||||
}, [id, dispatch]);
|
||||
deleteReleaseProfile();
|
||||
}, [deleteReleaseProfile]);
|
||||
|
||||
const indexer =
|
||||
indexerId !== 0 && indexerList.find((i) => i.id === indexerId);
|
||||
|
||||
@@ -1,50 +1,39 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import AppState from 'App/State/AppState';
|
||||
import { ReleaseProfilesAppState } from 'App/State/SettingsAppState';
|
||||
import Card from 'Components/Card';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { fetchIndexers } from 'Store/Actions/Settings/indexers';
|
||||
import { fetchReleaseProfiles } from 'Store/Actions/Settings/releaseProfiles';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { useTagList } from 'Tags/useTags';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditReleaseProfileModal from './EditReleaseProfileModal';
|
||||
import ReleaseProfileItem from './ReleaseProfileItem';
|
||||
import { useReleaseProfiles } from './useReleaseProfiles';
|
||||
import styles from './ReleaseProfiles.css';
|
||||
|
||||
function ReleaseProfiles() {
|
||||
const { items, isFetching, isPopulated, error }: ReleaseProfilesAppState =
|
||||
useSelector(createClientSideCollectionSelector('settings.releaseProfiles'));
|
||||
const { data, isFetching, isFetched, error } = useReleaseProfiles();
|
||||
|
||||
const tagList = useTagList();
|
||||
const indexerList = useSelector(
|
||||
(state: AppState) => state.settings.indexers.items
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [
|
||||
isAddReleaseProfileModalOpen,
|
||||
setAddReleaseProfileModalOpen,
|
||||
setAddReleaseProfileModalClosed,
|
||||
] = useModalOpenState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchReleaseProfiles());
|
||||
dispatch(fetchIndexers());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('ReleaseProfiles')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('ReleaseProfilesLoadError')}
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
isPopulated={isFetched}
|
||||
error={error}
|
||||
>
|
||||
<div className={styles.releaseProfiles}>
|
||||
@@ -57,7 +46,7 @@ function ReleaseProfiles() {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{items.map((item) => {
|
||||
{data.map((item) => {
|
||||
return (
|
||||
<ReleaseProfileItem
|
||||
key={item.id}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import {
|
||||
useDeleteProvider,
|
||||
useManageProviderSettings,
|
||||
useProviderSettings,
|
||||
} from 'Settings/useProviderSettings';
|
||||
|
||||
export interface ReleaseProfileModel extends ModelBase {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
required: string[];
|
||||
ignored: string[];
|
||||
indexerId: number;
|
||||
tags: number[];
|
||||
excludedTags: number[];
|
||||
}
|
||||
|
||||
const PATH = '/releaseprofile';
|
||||
|
||||
const NEW_RELEASE_PROFILE: ReleaseProfileModel = {
|
||||
id: 0,
|
||||
name: '',
|
||||
enabled: true,
|
||||
required: [],
|
||||
ignored: [],
|
||||
indexerId: 0,
|
||||
tags: [],
|
||||
excludedTags: [],
|
||||
};
|
||||
|
||||
export const useReleaseProfilesWithIds = (ids: number[]) => {
|
||||
const allReleaseProfiles = useReleaseProfiles();
|
||||
|
||||
return allReleaseProfiles.data.filter((releaseProfiles) =>
|
||||
ids.includes(releaseProfiles.id)
|
||||
);
|
||||
};
|
||||
|
||||
export const useReleaseProfiles = () => {
|
||||
return useProviderSettings<ReleaseProfileModel>(PATH);
|
||||
};
|
||||
|
||||
export const useManageReleaseProfile = (id: number) => {
|
||||
return useManageProviderSettings<ReleaseProfileModel>(
|
||||
id,
|
||||
NEW_RELEASE_PROFILE,
|
||||
PATH
|
||||
);
|
||||
};
|
||||
|
||||
export const useDeleteReleaseProfile = (id: number) => {
|
||||
const result = useDeleteProvider<ReleaseProfileModel>(id, PATH);
|
||||
|
||||
return {
|
||||
...result,
|
||||
deleteReleaseProfile: result.deleteProvider,
|
||||
};
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import useSeries from 'Series/useSeries';
|
||||
import { useReleaseProfilesWithIds } from 'Settings/Profiles/Release/useReleaseProfiles';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import TagDetailsDelayProfile from './TagDetailsDelayProfile';
|
||||
import styles from './TagDetailsModalContent.css';
|
||||
@@ -102,12 +103,7 @@ function TagDetailsModalContent({
|
||||
)
|
||||
);
|
||||
|
||||
const releaseProfiles = useSelector(
|
||||
createMatchingItemSelector(
|
||||
releaseProfileIds,
|
||||
(state: AppState) => state.settings.releaseProfiles.items
|
||||
)
|
||||
);
|
||||
const releaseProfiles = useReleaseProfilesWithIds(releaseProfileIds);
|
||||
|
||||
const indexers = useSelector(
|
||||
createMatchingItemSelector(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Alert from 'Components/Alert';
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
fetchImportLists,
|
||||
fetchIndexers,
|
||||
fetchNotifications,
|
||||
fetchReleaseProfiles,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import useTagDetails from 'Tags/useTagDetails';
|
||||
import useTags, { useSortedTagList } from 'Tags/useTags';
|
||||
@@ -20,6 +20,7 @@ import styles from './Tags.css';
|
||||
|
||||
function Tags() {
|
||||
const dispatch = useDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { isFetching, isFetched, error } = useTags();
|
||||
const items = useSortedTagList();
|
||||
@@ -33,10 +34,11 @@ function Tags() {
|
||||
dispatch(fetchDelayProfiles());
|
||||
dispatch(fetchImportLists());
|
||||
dispatch(fetchNotifications());
|
||||
dispatch(fetchReleaseProfiles());
|
||||
dispatch(fetchIndexers());
|
||||
dispatch(fetchDownloadClients());
|
||||
}, [dispatch]);
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['releaseprofile'] });
|
||||
}, [dispatch, queryClient]);
|
||||
|
||||
if (!items.length) {
|
||||
return (
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
const section = 'settings.releaseProfiles';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_RELEASE_PROFILES = 'settings/releaseProfiles/fetchReleaseProfiles';
|
||||
export const SAVE_RELEASE_PROFILE = 'settings/releaseProfiles/saveReleaseProfile';
|
||||
export const DELETE_RELEASE_PROFILE = 'settings/releaseProfiles/deleteReleaseProfile';
|
||||
export const SET_RELEASE_PROFILE_VALUE = 'settings/releaseProfiles/setReleaseProfileValue';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchReleaseProfiles = createThunk(FETCH_RELEASE_PROFILES);
|
||||
export const saveReleaseProfile = createThunk(SAVE_RELEASE_PROFILE);
|
||||
export const deleteReleaseProfile = createThunk(DELETE_RELEASE_PROFILE);
|
||||
|
||||
export const setReleaseProfileValue = createAction(SET_RELEASE_PROFILE_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
items: [],
|
||||
pendingChanges: {}
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: {
|
||||
[FETCH_RELEASE_PROFILES]: createFetchHandler(section, '/releaseprofile'),
|
||||
|
||||
[SAVE_RELEASE_PROFILE]: createSaveProviderHandler(section, '/releaseprofile'),
|
||||
|
||||
[DELETE_RELEASE_PROFILE]: createRemoveItemHandler(section, '/releaseprofile')
|
||||
},
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
[SET_RELEASE_PROFILE_VALUE]: createSetSettingValueReducer(section)
|
||||
}
|
||||
|
||||
};
|
||||
@@ -22,7 +22,6 @@ import namingExamples from './Settings/namingExamples';
|
||||
import notifications from './Settings/notifications';
|
||||
import qualityDefinitions from './Settings/qualityDefinitions';
|
||||
import qualityProfiles from './Settings/qualityProfiles';
|
||||
import releaseProfiles from './Settings/releaseProfiles';
|
||||
|
||||
export * from './Settings/autoTaggingSpecifications';
|
||||
export * from './Settings/autoTaggings';
|
||||
@@ -46,7 +45,6 @@ export * from './Settings/namingExamples';
|
||||
export * from './Settings/notifications';
|
||||
export * from './Settings/qualityDefinitions';
|
||||
export * from './Settings/qualityProfiles';
|
||||
export * from './Settings/releaseProfiles';
|
||||
|
||||
//
|
||||
// Variables
|
||||
@@ -79,8 +77,7 @@ export const defaultState = {
|
||||
namingExamples: namingExamples.defaultState,
|
||||
notifications: notifications.defaultState,
|
||||
qualityDefinitions: qualityDefinitions.defaultState,
|
||||
qualityProfiles: qualityProfiles.defaultState,
|
||||
releaseProfiles: releaseProfiles.defaultState
|
||||
qualityProfiles: qualityProfiles.defaultState
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
@@ -112,8 +109,7 @@ export const actionHandlers = handleThunks({
|
||||
...namingExamples.actionHandlers,
|
||||
...notifications.actionHandlers,
|
||||
...qualityDefinitions.actionHandlers,
|
||||
...qualityProfiles.actionHandlers,
|
||||
...releaseProfiles.actionHandlers
|
||||
...qualityProfiles.actionHandlers
|
||||
});
|
||||
|
||||
//
|
||||
@@ -141,7 +137,6 @@ export const reducers = createHandleActions({
|
||||
...namingExamples.reducers,
|
||||
...notifications.reducers,
|
||||
...qualityDefinitions.reducers,
|
||||
...qualityProfiles.reducers,
|
||||
...releaseProfiles.reducers
|
||||
...qualityProfiles.reducers
|
||||
|
||||
}, defaultState, section);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
|
||||
interface ReleaseProfile extends ModelBase {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
required: string[];
|
||||
ignored: string[];
|
||||
indexerId: number;
|
||||
tags: number[];
|
||||
excludedTags: number[];
|
||||
}
|
||||
|
||||
export default ReleaseProfile;
|
||||
Reference in New Issue
Block a user