1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-18 21:35:27 -04:00

Use react-query for Metadata

This commit is contained in:
Mark McDowall
2026-01-03 13:13:11 -08:00
parent 8b8cd14834
commit c0a565861e
7 changed files with 130 additions and 148 deletions
@@ -1,6 +0,0 @@
import { AppSectionProviderState } from 'App/State/AppSectionState';
import Metadata from 'typings/Metadata';
type MetadataAppState = AppSectionProviderState<Metadata>;
export default MetadataAppState;
@@ -25,7 +25,6 @@ import DownloadClientOptions from 'typings/Settings/DownloadClientOptions';
import General from 'typings/Settings/General';
import IndexerOptions from 'typings/Settings/IndexerOptions';
import MediaManagement from 'typings/Settings/MediaManagement';
import MetadataAppState from './MetadataAppState';
type Presets<T> = T & {
presets: T[];
@@ -136,7 +135,6 @@ interface SettingsAppState {
indexers: IndexerAppState;
languages: LanguageSettingsAppState;
mediaManagement: MediaManagementAppState;
metadata: MetadataAppState;
naming: NamingAppState;
namingExamples: NamingExamplesAppState;
}
@@ -1,6 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import React, { useCallback, useEffect } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -15,14 +13,10 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { inputTypes } from 'Helpers/Props';
import {
saveMetadata,
setMetadataFieldValue,
setMetadataValue,
} from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings';
import { InputChanged } from 'typings/inputs';
import Metadata from 'typings/Metadata';
import translate from 'Utilities/String/translate';
import { useManageMetadata } from '../useMetadata';
import styles from './EditMetadataModalContent.css';
export interface EditMetadataModalContentProps {
@@ -36,41 +30,39 @@ function EditMetadataModalContent({
advancedSettings,
onModalClose,
}: EditMetadataModalContentProps) {
const dispatch = useDispatch();
const { isSaving, saveError, pendingChanges, items } = useSelector(
(state: AppState) => state.settings.metadata
);
const {
item,
updateValue,
updateFieldValue,
saveProvider,
isSaving,
saveError,
...otherSettings
} = useManageMetadata(id);
const wasSaving = usePrevious(isSaving);
const { settings, ...otherSettings } = useMemo(() => {
const item = items.find((item) => item.id === id)!;
return selectSettings(item, pendingChanges, saveError);
}, [id, items, pendingChanges, saveError]);
const { name, enable, fields, message } = settings;
const { name, enable, fields, message } = item;
const handleInputChange = useCallback(
({ name, value }: InputChanged) => {
// @ts-expect-error not typed
dispatch(setMetadataValue({ name, value }));
const key = name as keyof Metadata;
updateValue(key, value as Metadata[typeof key]);
},
[dispatch]
[updateValue]
);
const handleFieldChange = useCallback(
({ name, value }: InputChanged) => {
// @ts-expect-error not typed
dispatch(setMetadataFieldValue({ name, value }));
updateFieldValue?.({ [name]: value });
},
[dispatch]
[updateFieldValue]
);
const handleSavePress = useCallback(() => {
dispatch(saveMetadata({ id }));
}, [id, dispatch]);
saveProvider();
}, [saveProvider]);
useEffect(() => {
if (wasSaving && !isSaving && !saveError) {
@@ -1,43 +1,21 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import MetadataAppState from 'App/State/MetadataAppState';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { fetchMetadata } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import MetadataType from 'typings/Metadata';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import { useSortedMetadata } from '../useMetadata';
import Metadata from './Metadata';
import styles from './Metadatas.css';
function createMetadatasSelector() {
return createSelector(
createSortedSectionSelector<MetadataType, MetadataAppState>(
'settings.metadata',
sortByProp('name')
),
(metadata: MetadataAppState) => metadata
);
}
function Metadatas() {
const dispatch = useDispatch();
const { isFetching, error, items, ...otherProps } = useSelector(
createMetadatasSelector()
);
useEffect(() => {
dispatch(fetchMetadata());
}, [dispatch]);
const { data: items, isFetching, isFetched, error } = useSortedMetadata();
return (
<FieldSet legend={translate('Metadata')}>
<PageSectionContent
isFetching={isFetching}
error={error}
errorMessage={translate('MetadataLoadError')}
{...otherProps}
isFetching={isFetching}
isPopulated={isFetched}
>
<div className={styles.metadatas}>
{items.map((item) => {
@@ -0,0 +1,100 @@
import { useMemo } from 'react';
import {
SelectedSchema,
useProviderSchema,
useSelectedSchema,
} from 'Settings/useProviderSchema';
import {
useDeleteProvider,
useManageProviderSettings,
useProviderSettings,
} from 'Settings/useProviderSettings';
import Provider from 'typings/Provider';
import { sortByProp } from 'Utilities/Array/sortByProp';
export interface MetadataModel extends Provider {
enable: boolean;
tags: number[];
}
const PATH = '/metadata';
export const useMetadataWithIds = (ids: number[]) => {
const allMetadata = useMetadataData();
return allMetadata.filter((metadata) => ids.includes(metadata.id));
};
export const useMetadataItem = (id: number | undefined) => {
const { data } = useMetadata();
if (id === undefined) {
return undefined;
}
return data.find((metadata) => metadata.id === id);
};
export const useMetadataData = () => {
const { data } = useMetadata();
return data;
};
export const useSortedMetadata = () => {
const result = useMetadata();
const sortedData = useMemo(
() => result.data.sort(sortByProp('name')),
[result.data]
);
return {
...result,
data: sortedData,
};
};
export const useMetadata = () => {
return useProviderSettings<MetadataModel>({
path: PATH,
});
};
export const useManageMetadata = (
id: number | undefined,
selectedSchema?: SelectedSchema
) => {
const schema = useSelectedSchema<MetadataModel>(PATH, selectedSchema);
if (selectedSchema && !schema) {
throw new Error('A selected schema is required to manage metadata');
}
const manage = useManageProviderSettings<MetadataModel>(
id,
selectedSchema && schema
? {
...schema,
name: schema.implementationName || '',
enable: true,
}
: ({} as MetadataModel),
PATH
);
return manage;
};
export const useDeleteMetadata = (id: number) => {
const result = useDeleteProvider<MetadataModel>(id, PATH);
return {
...result,
deleteMetadata: result.deleteProvider,
};
};
export const useMetadataSchema = (enabled: boolean = true) => {
return useProviderSchema<MetadataModel>(PATH, enabled);
};
@@ -1,75 +0,0 @@
import { createAction } from 'redux-actions';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
//
// Variables
const section = 'settings.metadata';
//
// Actions Types
export const FETCH_METADATA = 'settings/metadata/fetchMetadata';
export const SET_METADATA_VALUE = 'settings/metadata/setMetadataValue';
export const SET_METADATA_FIELD_VALUE = 'settings/metadata/setMetadataFieldValue';
export const SAVE_METADATA = 'settings/metadata/saveMetadata';
//
// Action Creators
export const fetchMetadata = createThunk(FETCH_METADATA);
export const saveMetadata = createThunk(SAVE_METADATA);
export const setMetadataValue = createAction(SET_METADATA_VALUE, (payload) => {
return {
section,
...payload
};
});
export const setMetadataFieldValue = createAction(SET_METADATA_FIELD_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_METADATA]: createFetchHandler(section, '/metadata'),
[SAVE_METADATA]: createSaveProviderHandler(section, '/metadata')
},
//
// Reducers
reducers: {
[SET_METADATA_VALUE]: createSetSettingValueReducer(section),
[SET_METADATA_FIELD_VALUE]: createSetProviderFieldValueReducer(section)
}
};
@@ -16,7 +16,6 @@ import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import languages from './Settings/languages';
import mediaManagement from './Settings/mediaManagement';
import metadata from './Settings/metadata';
export * from './Settings/autoTaggingSpecifications';
export * from './Settings/autoTaggings';
@@ -34,7 +33,6 @@ export * from './Settings/indexerOptions';
export * from './Settings/indexers';
export * from './Settings/languages';
export * from './Settings/mediaManagement';
export * from './Settings/metadata';
//
// Variables
@@ -61,8 +59,7 @@ export const defaultState = {
indexerOptions: indexerOptions.defaultState,
indexers: indexers.defaultState,
languages: languages.defaultState,
mediaManagement: mediaManagement.defaultState,
metadata: metadata.defaultState
mediaManagement: mediaManagement.defaultState
};
export const persistState = [
@@ -88,8 +85,7 @@ export const actionHandlers = handleThunks({
...indexerOptions.actionHandlers,
...indexers.actionHandlers,
...languages.actionHandlers,
...mediaManagement.actionHandlers,
...metadata.actionHandlers
...mediaManagement.actionHandlers
});
//
@@ -111,7 +107,6 @@ export const reducers = createHandleActions({
...indexerOptions.reducers,
...indexers.reducers,
...languages.reducers,
...mediaManagement.reducers,
...metadata.reducers
...mediaManagement.reducers
}, defaultState, section);