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

Use react-query for Languages

This commit is contained in:
Mark McDowall
2026-02-14 12:25:49 -08:00
parent 236978a9b1
commit 5bac016f0c
10 changed files with 107 additions and 132 deletions
@@ -6,7 +6,6 @@ import AppSectionState, {
AppSectionSchemaState,
PagedAppSectionState,
} from 'App/State/AppSectionState';
import Language from 'Language/Language';
import AutoTagging, { AutoTaggingSpecification } from 'typings/AutoTagging';
import CustomFormat from 'typings/CustomFormat';
import CustomFormatSpecification from 'typings/CustomFormatSpecification';
@@ -101,7 +100,6 @@ export interface ImportListExclusionsSettingsAppState
}
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
export type LanguageSettingsAppState = AppSectionState<Language>;
interface SettingsAppState {
autoTaggings: AutoTaggingAppState;
@@ -118,7 +116,6 @@ interface SettingsAppState {
indexerFlags: IndexerFlagSettingsAppState;
indexerOptions: IndexerOptionsAppState;
indexers: IndexerAppState;
languages: LanguageSettingsAppState;
}
export default SettingsAppState;
@@ -1,6 +1,5 @@
import React from 'react';
import { useSelector } from 'react-redux';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import { useLanguages } from 'Language/useLanguages';
import FilterBuilderRowValue, {
FilterBuilderRowValueProps,
} from './FilterBuilderRowValue';
@@ -13,7 +12,7 @@ type LanguageFilterBuilderRowValueProps<T> = Omit<
function LanguageFilterBuilderRowValue<T>(
props: LanguageFilterBuilderRowValueProps<T>
) {
const { items } = useSelector(createLanguagesSelector());
const { data: items = [] } = useLanguages();
return <FilterBuilderRowValue {...props} tagList={items} />;
}
@@ -1,7 +1,6 @@
import React, { useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import Language from 'Language/Language';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import { useFilteredLanguages } from 'Language/useLanguages';
import translate from 'Utilities/String/translate';
import EnhancedSelectInput, {
EnhancedSelectInputValue,
@@ -31,13 +30,11 @@ export default function LanguageSelectInput({
onChange,
...otherProps
}: LanguageSelectInputProps) {
const { items } = useSelector(
createLanguagesSelector({
Any: true,
Original: true,
Unknown: true,
})
);
const { data: items = [] } = useFilteredLanguages({
includeAny: true,
includeOriginal: true,
includeUnknown: true,
});
const values = useMemo(() => {
const result: EnhancedSelectInputValue<number | string>[] = items.map(
+11 -7
View File
@@ -6,6 +6,7 @@ import { useTranslations } from 'App/useTranslations';
import useCommands from 'Commands/useCommands';
import useCustomFilters from 'Filters/useCustomFilters';
import { useInitializeLanguage } from 'Language/useLanguageName';
import { useLanguages } from 'Language/useLanguages';
import useSeries from 'Series/useSeries';
import { useQualityProfiles } from 'Settings/Profiles/Quality/useQualityProfiles';
import { useUiSettings } from 'Settings/UI/useUiSettings';
@@ -13,7 +14,6 @@ import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import {
fetchImportLists,
fetchIndexerFlags,
fetchLanguages,
} from 'Store/Actions/settingsActions';
import useSystemStatus from 'System/Status/useSystemStatus';
import useTags from 'Tags/useTags';
@@ -27,6 +27,7 @@ const createErrorsSelector = ({
uiSettingsError,
seriesError,
qualityProfilesError,
languagesError,
}: {
customFiltersError: ApiError | null;
systemStatusError: ApiError | null;
@@ -35,12 +36,12 @@ const createErrorsSelector = ({
uiSettingsError: ApiError | null;
seriesError: ApiError | null;
qualityProfilesError: ApiError | null;
languagesError: ApiError | null;
}) =>
createSelector(
(state: AppState) => state.settings.languages.error,
(state: AppState) => state.settings.importLists.error,
(state: AppState) => state.settings.indexerFlags.error,
(languagesError, importListsError, indexerFlagsError) => {
(importListsError, indexerFlagsError) => {
const hasError = !!(
customFiltersError ||
seriesError ||
@@ -82,7 +83,7 @@ const useAppPage = () => {
const { isFetched: isCustomFiltersFetched, error: customFiltersError } =
useCustomFilters();
const { isSuccess: isSeriesFetched, error: seriesError } = useSeries();
const { isFetched: isSeriesFetched, error: seriesError } = useSeries();
const { isFetched: isSystemStatusFetched, error: systemStatusError } =
useSystemStatus();
@@ -98,9 +99,11 @@ const useAppPage = () => {
const { isFetched: isQualityProfilesFetched, error: qualityProfilesError } =
useQualityProfiles();
const { isFetched: isLanguagesFetched, error: languagesError } =
useLanguages();
const isAppStatePopulated = useSelector(
(state: AppState) =>
state.settings.languages.isPopulated &&
state.settings.importLists.isPopulated &&
state.settings.indexerFlags.isPopulated
);
@@ -113,7 +116,8 @@ const useAppPage = () => {
isTagsFetched &&
isTranslationsFetched &&
isUiSettingsFetched &&
isQualityProfilesFetched;
isQualityProfilesFetched &&
isLanguagesFetched;
const { hasError, errors } = useSelector(
createErrorsSelector({
@@ -124,6 +128,7 @@ const useAppPage = () => {
translationsError,
uiSettingsError,
qualityProfilesError,
languagesError,
})
);
@@ -142,7 +147,6 @@ const useAppPage = () => {
useEffect(() => {
dispatch(fetchCustomFilters());
dispatch(fetchLanguages());
dispatch(fetchImportLists());
dispatch(fetchIndexerFlags());
}, [dispatch]);
@@ -1,5 +1,4 @@
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -13,7 +12,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import Language from 'Language/Language';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import { useFilteredLanguages } from 'Language/useLanguages';
import translate from 'Utilities/String/translate';
import styles from './SelectLanguageModalContent.css';
@@ -27,12 +26,15 @@ interface SelectLanguageModalContentProps {
function SelectLanguageModalContent(props: SelectLanguageModalContentProps) {
const { modalTitle, onLanguagesSelect, onModalClose } = props;
const { isFetching, isPopulated, error, items } = useSelector(
createLanguagesSelector({
Any: true,
Original: true,
})
);
const {
data: items = [],
isFetching,
isFetched: isPopulated,
error,
} = useFilteredLanguages({
includeAny: true,
includeOriginal: true,
});
const [languageIds, setLanguageIds] = useState(props.languageIds);
+65
View File
@@ -0,0 +1,65 @@
import { useMemo } from 'react';
import useApiQuery from 'Helpers/Hooks/useApiQuery';
import Language from 'Language/Language';
interface LanguageFilter {
[key: string]: boolean | undefined;
includeAny: boolean;
includeOriginal?: boolean;
includeUnknown?: boolean;
}
const PATH = '/language';
export const useLanguages = () => {
return useApiQuery<Language[]>({
path: PATH,
queryOptions: {
gcTime: Infinity,
staleTime: Infinity,
},
});
};
export const useFilteredLanguages = (
excludeLanguages: LanguageFilter = { includeAny: true }
) => {
const { data, isFetching, isFetched, error } = useLanguages();
const filteredItems = useMemo(() => {
if (!data) return [];
return data.filter((lang) => !excludeLanguages[lang.name]);
}, [data, excludeLanguages]);
return {
data: filteredItems,
isFetching,
isFetched,
error,
};
};
export const useLanguageById = (id: number | undefined) => {
const { data } = useLanguages();
return useMemo(() => {
if (id === undefined || !data) {
return undefined;
}
return data.find((language) => language.id === id);
}, [data, id]);
};
export const useLanguageByName = (name: string | undefined) => {
const { data } = useLanguages();
return useMemo(() => {
if (!name || !data) {
return undefined;
}
return data.find((language) => language.name === name);
}, [data, name]);
};
+10 -13
View File
@@ -1,5 +1,4 @@
import React, { useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
@@ -11,8 +10,8 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { inputTypes, kinds } from 'Helpers/Props';
import { useFilteredLanguages } from 'Language/useLanguages';
import SettingsToolbar from 'Settings/SettingsToolbar';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import themes from 'Styles/Themes';
import { InputChanged } from 'typings/inputs';
import timeZoneOptions from 'Utilities/Date/timeZoneOptions';
@@ -63,17 +62,15 @@ export const timeFormatOptions: EnhancedSelectInputValue<string>[] = [
function UISettings() {
const {
items,
data: languageItems = [],
isFetching: isLanguagesFetching,
isPopulated: isLanguagesPopulated,
isFetched: isLanguagesPopulated,
error: languagesError,
} = useSelector(
createLanguagesSelector({
Any: true,
Original: true,
Unknown: true,
})
);
} = useFilteredLanguages({
includeAny: true,
includeOriginal: true,
includeUnknown: true,
});
const {
isFetching: isSettingsFetching,
@@ -94,13 +91,13 @@ function UISettings() {
const error = languagesError || settingsError;
const languages = useMemo(() => {
return items.map((item) => {
return languageItems.map((item) => {
return {
key: item.id,
value: item.name,
};
});
}, [items]);
}, [languageItems]);
const themeOptions = Object.keys(themes).map((theme) => ({
key: theme,
@@ -1,48 +0,0 @@
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import { createThunk } from 'Store/thunks';
//
// Variables
const section = 'settings.languages';
//
// Actions Types
export const FETCH_LANGUAGES = 'settings/languages/fetchLanguages';
//
// Action Creators
export const fetchLanguages = createThunk(FETCH_LANGUAGES);
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
//
// Action Handlers
actionHandlers: {
[FETCH_LANGUAGES]: createFetchHandler(section, '/language')
},
//
// Reducers
reducers: {
}
};
@@ -14,7 +14,6 @@ import importLists from './Settings/importLists';
import indexerFlags from './Settings/indexerFlags';
import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import languages from './Settings/languages';
export * from './Settings/autoTaggingSpecifications';
export * from './Settings/autoTaggings';
@@ -30,7 +29,6 @@ export * from './Settings/importListExclusions';
export * from './Settings/indexerFlags';
export * from './Settings/indexerOptions';
export * from './Settings/indexers';
export * from './Settings/languages';
//
// Variables
@@ -55,8 +53,7 @@ export const defaultState = {
importListOptions: importListOptions.defaultState,
indexerFlags: indexerFlags.defaultState,
indexerOptions: indexerOptions.defaultState,
indexers: indexers.defaultState,
languages: languages.defaultState
indexers: indexers.defaultState
};
export const persistState = [
@@ -80,8 +77,7 @@ export const actionHandlers = handleThunks({
...importListOptions.actionHandlers,
...indexerFlags.actionHandlers,
...indexerOptions.actionHandlers,
...indexers.actionHandlers,
...languages.actionHandlers
...indexers.actionHandlers
});
//
@@ -101,7 +97,6 @@ export const reducers = createHandleActions({
...importListOptions.reducers,
...indexerFlags.reducers,
...indexerOptions.reducers,
...indexers.reducers,
...languages.reducers
...indexers.reducers
}, defaultState, section);
@@ -1,33 +0,0 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
interface LanguageFilter {
[key: string]: boolean | undefined;
Any: boolean;
Original?: boolean;
Unknown?: boolean;
}
function createLanguagesSelector(
excludeLanguages: LanguageFilter = { Any: true }
) {
return createSelector(
(state: AppState) => state.settings.languages,
(languages) => {
const { isFetching, isPopulated, error, items } = languages;
const filteredLanguages = items.filter(
(lang) => !excludeLanguages[lang.name]
);
return {
isFetching,
isPopulated,
error,
items: filteredLanguages,
};
}
);
}
export default createLanguagesSelector;