mirror of
https://github.com/Sonarr/Sonarr
synced 2026-05-08 13:01:10 +02:00
Use react-query for Quality Profiles
This commit is contained in:
parent
f4b9b30978
commit
21ca65a015
30 changed files with 318 additions and 403 deletions
|
|
@ -1,18 +1,17 @@
|
|||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router';
|
||||
import {
|
||||
setAddSeriesOption,
|
||||
useAddSeriesOption,
|
||||
} from 'AddSeries/addSeriesOptionsStore';
|
||||
import { SelectProvider } from 'App/Select/SelectContext';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import useRootFolders, { useRootFolder } from 'RootFolder/useRootFolders';
|
||||
import { useQualityProfilesData } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import ImportSeriesFooter from './ImportSeriesFooter';
|
||||
import { clearImportSeries } from './importSeriesStore';
|
||||
|
|
@ -48,9 +47,7 @@ function ImportSeries() {
|
|||
};
|
||||
}, [rootFolders, rootFolderId]);
|
||||
|
||||
const qualityProfiles = useSelector(
|
||||
(state: AppState) => state.settings.qualityProfiles.items
|
||||
);
|
||||
const qualityProfiles = useQualityProfilesData();
|
||||
|
||||
const defaultQualityProfileId = useAddSeriesOption('qualityProfileId');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import AppSectionState, {
|
||||
AppSectionDeleteState,
|
||||
AppSectionItemSchemaState,
|
||||
AppSectionItemState,
|
||||
AppSectionListState,
|
||||
AppSectionSaveState,
|
||||
|
|
@ -8,6 +7,7 @@ import AppSectionState, {
|
|||
PagedAppSectionState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityProfileModel } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import AutoTagging, { AutoTaggingSpecification } from 'typings/AutoTagging';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import CustomFormatSpecification from 'typings/CustomFormatSpecification';
|
||||
|
|
@ -20,7 +20,6 @@ import Indexer from 'typings/Indexer';
|
|||
import IndexerFlag from 'typings/IndexerFlag';
|
||||
import Notification from 'typings/Notification';
|
||||
import QualityDefinition from 'typings/QualityDefinition';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import DownloadClientOptions from 'typings/Settings/DownloadClientOptions';
|
||||
import General from 'typings/Settings/General';
|
||||
import IndexerOptions from 'typings/Settings/IndexerOptions';
|
||||
|
|
@ -105,16 +104,10 @@ export interface QualityDefinitionsAppState
|
|||
extends AppSectionState<QualityDefinition>,
|
||||
AppSectionSaveState {
|
||||
pendingChanges: {
|
||||
[key: number]: Partial<QualityProfile>;
|
||||
[key: number]: Partial<QualityProfileModel>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface QualityProfilesAppState
|
||||
extends AppSectionState<QualityProfile>,
|
||||
AppSectionItemSchemaState<QualityProfile>,
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState {}
|
||||
|
||||
export interface CustomFormatAppState
|
||||
extends AppSectionState<CustomFormat>,
|
||||
AppSectionDeleteState,
|
||||
|
|
@ -163,7 +156,6 @@ interface SettingsAppState {
|
|||
namingExamples: NamingExamplesAppState;
|
||||
notifications: NotificationAppState;
|
||||
qualityDefinitions: QualityDefinitionsAppState;
|
||||
qualityProfiles: QualityProfilesAppState;
|
||||
}
|
||||
|
||||
export default SettingsAppState;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AppState from 'App/State/AppState';
|
||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useQualityProfileSchema } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import getQualities from 'Utilities/Quality/getQualities';
|
||||
import FilterBuilderRowValue, {
|
||||
FilterBuilderRowValueProps,
|
||||
|
|
@ -15,22 +13,12 @@ type QualityFilterBuilderRowValueProps<T> = Omit<
|
|||
function QualityFilterBuilderRowValue<T>(
|
||||
props: QualityFilterBuilderRowValueProps<T>
|
||||
) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { isSchemaPopulated, schema } = useSelector(
|
||||
(state: AppState) => state.settings.qualityProfiles
|
||||
);
|
||||
const { schema } = useQualityProfileSchema(true);
|
||||
|
||||
const tagList = useMemo(() => {
|
||||
return getQualities(schema.items);
|
||||
}, [schema]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSchemaPopulated) {
|
||||
dispatch(fetchQualityProfileSchema());
|
||||
}
|
||||
}, [isSchemaPopulated, dispatch]);
|
||||
|
||||
return <FilterBuilderRowValue {...props} tagList={tagList} />;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,10 @@
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import { useQualityProfilesData } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import FilterBuilderRowValue, {
|
||||
FilterBuilderRowValueProps,
|
||||
} from './FilterBuilderRowValue';
|
||||
|
||||
function createQualityProfilesSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.qualityProfiles.items,
|
||||
(qualityProfiles) => {
|
||||
return qualityProfiles;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
type QualityProfileFilterBuilderRowValueProps<T> = Omit<
|
||||
FilterBuilderRowValueProps<T, number, string>,
|
||||
'tagList'
|
||||
|
|
@ -24,7 +13,7 @@ type QualityProfileFilterBuilderRowValueProps<T> = Omit<
|
|||
function QualityProfileFilterBuilderRowValue<T>(
|
||||
props: QualityProfileFilterBuilderRowValueProps<T>
|
||||
) {
|
||||
const qualityProfiles = useSelector(createQualityProfilesSelector());
|
||||
const qualityProfiles = useQualityProfilesData();
|
||||
|
||||
const tagList = qualityProfiles
|
||||
.map(({ id, name }) => ({ id, name }))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { QualityProfilesAppState } from 'App/State/SettingsAppState';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useQualityProfilesData } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import { EnhancedSelectInputChanged } from 'typings/inputs';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EnhancedSelectInput, {
|
||||
|
|
@ -12,49 +8,46 @@ import EnhancedSelectInput, {
|
|||
EnhancedSelectInputValue,
|
||||
} from './EnhancedSelectInput';
|
||||
|
||||
function createQualityProfilesSelector(
|
||||
const useValues = (
|
||||
includeNoChange: boolean,
|
||||
includeNoChangeDisabled: boolean,
|
||||
includeMixed: boolean
|
||||
) {
|
||||
return createSelector(
|
||||
createSortedSectionSelector<QualityProfile, QualityProfilesAppState>(
|
||||
'settings.qualityProfiles',
|
||||
sortByProp<QualityProfile, 'name'>('name')
|
||||
),
|
||||
(qualityProfiles: QualityProfilesAppState) => {
|
||||
const values: EnhancedSelectInputValue<number | string>[] =
|
||||
qualityProfiles.items.map((qualityProfile) => {
|
||||
return {
|
||||
key: qualityProfile.id,
|
||||
value: qualityProfile.name,
|
||||
};
|
||||
});
|
||||
) => {
|
||||
const qualityProfiles = useQualityProfilesData();
|
||||
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
isDisabled: includeNoChangeDisabled,
|
||||
});
|
||||
}
|
||||
return useMemo(() => {
|
||||
const values: EnhancedSelectInputValue<number | string>[] = qualityProfiles
|
||||
.sort(sortByProp('name'))
|
||||
.map((qualityProfile) => {
|
||||
return {
|
||||
key: qualityProfile.id,
|
||||
value: qualityProfile.name,
|
||||
};
|
||||
});
|
||||
|
||||
if (includeMixed) {
|
||||
values.unshift({
|
||||
key: 'mixed',
|
||||
get value() {
|
||||
return `(${translate('Mixed')})`;
|
||||
},
|
||||
isDisabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
return values;
|
||||
if (includeNoChange) {
|
||||
values.unshift({
|
||||
key: 'noChange',
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
isDisabled: includeNoChangeDisabled,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (includeMixed) {
|
||||
values.unshift({
|
||||
key: 'mixed',
|
||||
get value() {
|
||||
return `(${translate('Mixed')})`;
|
||||
},
|
||||
isDisabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
return values;
|
||||
}, [qualityProfiles, includeNoChange, includeNoChangeDisabled, includeMixed]);
|
||||
};
|
||||
|
||||
export interface QualityProfileSelectInputProps
|
||||
extends Omit<
|
||||
|
|
@ -79,12 +72,10 @@ function QualityProfileSelectInput({
|
|||
onChange,
|
||||
...otherProps
|
||||
}: QualityProfileSelectInputProps) {
|
||||
const values = useSelector(
|
||||
createQualityProfilesSelector(
|
||||
includeNoChange,
|
||||
includeNoChangeDisabled,
|
||||
includeMixed
|
||||
)
|
||||
const values = useValues(
|
||||
includeNoChange,
|
||||
includeNoChangeDisabled,
|
||||
includeMixed
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import { Error } from 'App/State/AppSectionState';
|
||||
import { ApiError } from 'Utilities/Fetch/fetchJson';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
|
@ -12,7 +11,7 @@ interface ErrorPageProps {
|
|||
seriesError: ApiError | null;
|
||||
customFiltersError: ApiError | null;
|
||||
tagsError: ApiError | null;
|
||||
qualityProfilesError?: Error;
|
||||
qualityProfilesError: ApiError | null;
|
||||
uiSettingsError: ApiError | null;
|
||||
systemStatusError: ApiError | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { useTranslations } from 'App/useTranslations';
|
|||
import useCommands from 'Commands/useCommands';
|
||||
import useCustomFilters from 'Filters/useCustomFilters';
|
||||
import useSeries from 'Series/useSeries';
|
||||
import { useQualityProfiles } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import { useUiSettings } from 'Settings/UI/useUiSettings';
|
||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||
import {
|
||||
fetchImportLists,
|
||||
fetchIndexerFlags,
|
||||
fetchLanguages,
|
||||
fetchQualityProfiles,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import useSystemStatus from 'System/Status/useSystemStatus';
|
||||
import useTags from 'Tags/useTags';
|
||||
|
|
@ -25,6 +25,7 @@ const createErrorsSelector = ({
|
|||
translationsError,
|
||||
uiSettingsError,
|
||||
seriesError,
|
||||
qualityProfilesError,
|
||||
}: {
|
||||
customFiltersError: ApiError | null;
|
||||
systemStatusError: ApiError | null;
|
||||
|
|
@ -32,18 +33,13 @@ const createErrorsSelector = ({
|
|||
translationsError: ApiError | null;
|
||||
uiSettingsError: ApiError | null;
|
||||
seriesError: ApiError | null;
|
||||
qualityProfilesError: ApiError | null;
|
||||
}) =>
|
||||
createSelector(
|
||||
(state: AppState) => state.settings.qualityProfiles.error,
|
||||
(state: AppState) => state.settings.languages.error,
|
||||
(state: AppState) => state.settings.importLists.error,
|
||||
(state: AppState) => state.settings.indexerFlags.error,
|
||||
(
|
||||
qualityProfilesError,
|
||||
languagesError,
|
||||
importListsError,
|
||||
indexerFlagsError
|
||||
) => {
|
||||
(languagesError, importListsError, indexerFlagsError) => {
|
||||
const hasError = !!(
|
||||
customFiltersError ||
|
||||
seriesError ||
|
||||
|
|
@ -97,9 +93,11 @@ const useAppPage = () => {
|
|||
const { isFetched: isUiSettingsFetched, error: uiSettingsError } =
|
||||
useUiSettings();
|
||||
|
||||
const { isFetched: isQualityProfilesFetched, error: qualityProfilesError } =
|
||||
useQualityProfiles();
|
||||
|
||||
const isAppStatePopulated = useSelector(
|
||||
(state: AppState) =>
|
||||
state.settings.qualityProfiles.isPopulated &&
|
||||
state.settings.languages.isPopulated &&
|
||||
state.settings.importLists.isPopulated &&
|
||||
state.settings.indexerFlags.isPopulated
|
||||
|
|
@ -112,7 +110,8 @@ const useAppPage = () => {
|
|||
isSystemStatusFetched &&
|
||||
isTagsFetched &&
|
||||
isTranslationsFetched &&
|
||||
isUiSettingsFetched;
|
||||
isUiSettingsFetched &&
|
||||
isQualityProfilesFetched;
|
||||
|
||||
const { hasError, errors } = useSelector(
|
||||
createErrorsSelector({
|
||||
|
|
@ -122,6 +121,7 @@ const useAppPage = () => {
|
|||
tagsError,
|
||||
translationsError,
|
||||
uiSettingsError,
|
||||
qualityProfilesError,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -140,7 +140,6 @@ const useAppPage = () => {
|
|||
|
||||
useEffect(() => {
|
||||
dispatch(fetchCustomFilters());
|
||||
dispatch(fetchQualityProfiles());
|
||||
dispatch(fetchLanguages());
|
||||
dispatch(fetchImportLists());
|
||||
dispatch(fetchIndexerFlags());
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
|
|
@ -16,30 +13,11 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
|||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import Quality, { QualityModel } from 'Quality/Quality';
|
||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import { useQualityProfileSchema } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import { InputChanged } from 'typings/inputs';
|
||||
import getQualities from 'Utilities/Quality/getQualities';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function createQualitySchemaSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.qualityProfiles,
|
||||
(qualityProfiles) => {
|
||||
const { isSchemaFetching, isSchemaPopulated, schemaError, schema } =
|
||||
qualityProfiles;
|
||||
|
||||
const items = getQualities(schema.items);
|
||||
|
||||
return {
|
||||
isFetching: isSchemaFetching,
|
||||
isPopulated: isSchemaPopulated,
|
||||
error: schemaError,
|
||||
items,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface SelectQualityModalContentProps {
|
||||
qualityId: number;
|
||||
proper: boolean;
|
||||
|
|
@ -56,18 +34,12 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
|
|||
const [proper, setProper] = useState(props.proper);
|
||||
const [real, setReal] = useState(props.real);
|
||||
|
||||
const { isFetching, isPopulated, error, items } = useSelector(
|
||||
createQualitySchemaSelector()
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const { schema, isSchemaFetching, isSchemaFetched, schemaError } =
|
||||
useQualityProfileSchema(true);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
dispatch(fetchQualityProfileSchema());
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
const items = useMemo(() => {
|
||||
return getQualities(schema.items);
|
||||
}, [schema]);
|
||||
|
||||
const qualityOptions = useMemo(() => {
|
||||
return items.map(({ id, name }): EnhancedSelectInputValue<number> => {
|
||||
|
|
@ -119,13 +91,13 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
|
|||
<ModalHeader>{modalTitle} - Select Quality</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{isFetching ? <LoadingIndicator /> : null}
|
||||
{isSchemaFetching ? <LoadingIndicator /> : null}
|
||||
|
||||
{!isFetching && error ? (
|
||||
{!isSchemaFetching && schemaError ? (
|
||||
<Alert kind={kinds.DANGER}>{translate('QualitiesLoadError')}</Alert>
|
||||
) : null}
|
||||
|
||||
{isPopulated && !error ? (
|
||||
{isSchemaFetched && !schemaError ? (
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Quality')}</FormLabel>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { IconName } from 'Components/Icon';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { QualityProfileModel } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import {
|
||||
UiSettingsModel,
|
||||
useUiSettingsValues,
|
||||
} from 'Settings/UI/useUiSettings';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
|
|
@ -39,7 +39,7 @@ interface SeriesIndexOverviewInfoProps {
|
|||
monitored: boolean;
|
||||
nextAiring?: string;
|
||||
network?: string;
|
||||
qualityProfile?: QualityProfile;
|
||||
qualityProfile?: QualityProfileModel;
|
||||
previousAiring?: string;
|
||||
added?: string;
|
||||
seasonCount: number;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import SeriesTagList from 'Components/SeriesTagList';
|
||||
import Language from 'Language/Language';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import { QualityProfileModel } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
|
|
@ -12,7 +12,7 @@ interface SeriesIndexPosterInfoProps {
|
|||
originalLanguage?: Language;
|
||||
network?: string;
|
||||
showQualityProfile: boolean;
|
||||
qualityProfile?: QualityProfile;
|
||||
qualityProfile?: QualityProfileModel;
|
||||
previousAiring?: string;
|
||||
added?: string;
|
||||
seasonCount: number;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
import { maxBy } from 'lodash';
|
||||
import { useSelector } from 'react-redux';
|
||||
import CommandNames from 'Commands/CommandNames';
|
||||
import { useCommandExecuting } from 'Commands/useCommands';
|
||||
import { Season } from 'Series/Series';
|
||||
import { useSingleSeries } from 'Series/useSeries';
|
||||
import createSeriesQualityProfileSelector from 'Store/Selectors/createSeriesQualityProfileSelector';
|
||||
import useSeriesQualityProfile from 'Series/useSeriesQualityProfile';
|
||||
|
||||
export function useSeriesIndexItem(seriesId: number) {
|
||||
const series = useSingleSeries(seriesId);
|
||||
const qualityProfile = useSelector(
|
||||
createSeriesQualityProfileSelector(series)
|
||||
);
|
||||
const qualityProfile = useSeriesQualityProfile(series);
|
||||
|
||||
const isRefreshingSeries = useCommandExecuting(CommandNames.RefreshSeries, {
|
||||
seriesIds: [seriesId],
|
||||
|
|
|
|||
8
frontend/src/Series/useSeriesQualityProfile.ts
Normal file
8
frontend/src/Series/useSeriesQualityProfile.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { useQualityProfile } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import Series from './Series';
|
||||
|
||||
const useSeriesQualityProfile = (series: Series | undefined) => {
|
||||
return useQualityProfile(series?.qualityProfileId);
|
||||
};
|
||||
|
||||
export default useSeriesQualityProfile;
|
||||
|
|
@ -23,7 +23,7 @@ const NEW_REMOTE_PATH_MAPPING: RemotePathMappingModel = {
|
|||
export const useRemotePathMapping = () => {};
|
||||
|
||||
export const useRemotePathMappings = () => {
|
||||
return useProviderSettings<RemotePathMappingModel>(PATH);
|
||||
return useProviderSettings<RemotePathMappingModel>({ path: PATH });
|
||||
};
|
||||
|
||||
export const useManageRemotePathMappings = (id: number) => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSelect } from 'App/Select/SelectContext';
|
||||
import SeriesTagList from 'Components/SeriesTagList';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import Column from 'Components/Table/Column';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
||||
import { useQualityProfile } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import ImportList from 'typings/ImportList';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
|
@ -37,9 +36,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
|
|||
const { toggleSelected, useIsSelected } = useSelect<ImportList>();
|
||||
const isSelected = useIsSelected(id);
|
||||
|
||||
const qualityProfile = useSelector(
|
||||
createQualityProfileSelectorForHook(qualityProfileId)
|
||||
);
|
||||
const qualityProfile = useQualityProfile(qualityProfileId);
|
||||
|
||||
const onSelectedChangeWrapper = useCallback(
|
||||
({ id, value, shiftKey }: SelectStateInputProps) => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import EditQualityProfileModalContent from './EditQualityProfileModalContent';
|
|||
|
||||
interface EditQualityProfileModalProps {
|
||||
id?: number;
|
||||
cloneId?: number;
|
||||
isOpen: boolean;
|
||||
onDeleteQualityProfilePress?: () => void;
|
||||
onModalClose: () => void;
|
||||
|
|
@ -14,6 +15,7 @@ interface EditQualityProfileModalProps {
|
|||
|
||||
function EditQualityProfileModal({
|
||||
id,
|
||||
cloneId,
|
||||
isOpen,
|
||||
onDeleteQualityProfilePress,
|
||||
onModalClose,
|
||||
|
|
@ -44,6 +46,7 @@ function EditQualityProfileModal({
|
|||
>
|
||||
<EditQualityProfileModalContent
|
||||
id={id}
|
||||
cloneId={cloneId}
|
||||
onContentHeightChange={handleContentHeightChange}
|
||||
onDeleteQualityProfilePress={onDeleteQualityProfilePress}
|
||||
onModalClose={handleOnModalClose}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { QualityProfilesAppState } from 'App/State/SettingsAppState';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
|
|
@ -17,18 +15,8 @@ import useMeasure from 'Helpers/Hooks/useMeasure';
|
|||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import useQualityProfileInUse from 'Settings/Profiles/Quality/useQualityProfileInUse';
|
||||
import {
|
||||
fetchQualityProfileSchema,
|
||||
saveQualityProfile,
|
||||
setQualityProfileValue,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import { createProviderSettingsSelectorHook } from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import { InputChanged } from 'typings/inputs';
|
||||
import QualityProfile, {
|
||||
QualityProfileGroup,
|
||||
QualityProfileQualityItem,
|
||||
} from 'typings/QualityProfile';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityProfileFormatItems from './QualityProfileFormatItems';
|
||||
import { DragMoveState } from './QualityProfileItemDragSource';
|
||||
|
|
@ -36,6 +24,11 @@ import QualityProfileItems, {
|
|||
EditQualityProfileMode,
|
||||
} from './QualityProfileItems';
|
||||
import { SizeChanged } from './QualityProfileItemSize';
|
||||
import {
|
||||
QualityProfileGroup,
|
||||
QualityProfileQualityItem,
|
||||
useManageQualityProfile,
|
||||
} from './useQualityProfiles';
|
||||
import styles from './EditQualityProfileModalContent.css';
|
||||
|
||||
const MODAL_BODY_PADDING = parseInt(dimensions.modalBodyPadding);
|
||||
|
|
@ -52,6 +45,7 @@ function parseIndex(index: string): [number | null, number] {
|
|||
|
||||
interface EditQualityProfileModalContentProps {
|
||||
id?: number;
|
||||
cloneId?: number;
|
||||
onContentHeightChange: (height: number) => void;
|
||||
onDeleteQualityProfilePress?: () => void;
|
||||
onModalClose: () => void;
|
||||
|
|
@ -59,19 +53,21 @@ interface EditQualityProfileModalContentProps {
|
|||
|
||||
function EditQualityProfileModalContent({
|
||||
id,
|
||||
cloneId,
|
||||
onContentHeightChange,
|
||||
onDeleteQualityProfilePress,
|
||||
onModalClose,
|
||||
}: EditQualityProfileModalContentProps) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { error, isFetching, isPopulated, isSaving, saveError, item } =
|
||||
useSelector(
|
||||
createProviderSettingsSelectorHook<
|
||||
QualityProfile,
|
||||
QualityProfilesAppState
|
||||
>('qualityProfiles', id)
|
||||
);
|
||||
const {
|
||||
item,
|
||||
isSaving,
|
||||
saveError,
|
||||
isSchemaFetching,
|
||||
isSchemaFetched,
|
||||
schemaError,
|
||||
updateValue,
|
||||
saveProvider,
|
||||
} = useManageQualityProfile(id, cloneId);
|
||||
|
||||
const isInUse = useQualityProfileInUse(id);
|
||||
|
||||
|
|
@ -132,15 +128,15 @@ function EditQualityProfileModalContent({
|
|||
|
||||
const handleInputChange = useCallback(
|
||||
({ name, value }: InputChanged) => {
|
||||
// @ts-expect-error - actions are not typed
|
||||
dispatch(setQualityProfileValue({ name, value }));
|
||||
// @ts-expect-error - change is not yet typed
|
||||
updateValue(name, value);
|
||||
},
|
||||
[dispatch]
|
||||
[updateValue]
|
||||
);
|
||||
|
||||
const handleSavePress = useCallback(() => {
|
||||
dispatch(saveQualityProfile({ id }));
|
||||
}, [id, dispatch]);
|
||||
saveProvider();
|
||||
}, [saveProvider]);
|
||||
|
||||
const handleCutoffChange = useCallback(
|
||||
({ name, value }: InputChanged<number>) => {
|
||||
|
|
@ -153,10 +149,10 @@ function EditQualityProfileModalContent({
|
|||
'id' in cutoffItem ? cutoffItem.id : cutoffItem.quality.id;
|
||||
|
||||
// @ts-expect-error - actions are not typed
|
||||
dispatch(setQualityProfileValue({ name, value: cutoffId }));
|
||||
updateValue(name, cutoffId);
|
||||
}
|
||||
},
|
||||
[items, dispatch]
|
||||
[items, updateValue]
|
||||
);
|
||||
|
||||
const handleItemAllowedChange = useCallback(
|
||||
|
|
@ -172,15 +168,9 @@ function EditQualityProfileModalContent({
|
|||
return item;
|
||||
});
|
||||
|
||||
dispatch(
|
||||
// @ts-expect-error - actions are not typed
|
||||
setQualityProfileValue({
|
||||
name: 'items',
|
||||
value: newItems,
|
||||
})
|
||||
);
|
||||
updateValue('items', newItems);
|
||||
},
|
||||
[items, dispatch]
|
||||
[items, updateValue]
|
||||
);
|
||||
|
||||
const handleGroupAllowedChange = useCallback(
|
||||
|
|
@ -196,15 +186,9 @@ function EditQualityProfileModalContent({
|
|||
return item;
|
||||
});
|
||||
|
||||
dispatch(
|
||||
// @ts-expect-error - actions are not typed
|
||||
setQualityProfileValue({
|
||||
name: 'items',
|
||||
value: newItems,
|
||||
})
|
||||
);
|
||||
updateValue('items', newItems);
|
||||
},
|
||||
[items, dispatch]
|
||||
[items, updateValue]
|
||||
);
|
||||
|
||||
const handleGroupNameChange = useCallback(
|
||||
|
|
@ -220,10 +204,9 @@ function EditQualityProfileModalContent({
|
|||
return item;
|
||||
});
|
||||
|
||||
// @ts-expect-error - actions are not typed
|
||||
dispatch(setQualityProfileValue({ name: 'items', value: newItems }));
|
||||
updateValue('items', newItems);
|
||||
},
|
||||
[items, dispatch]
|
||||
[items, updateValue]
|
||||
);
|
||||
|
||||
const handleSizeChange = useCallback(
|
||||
|
|
@ -253,15 +236,9 @@ function EditQualityProfileModalContent({
|
|||
};
|
||||
});
|
||||
|
||||
dispatch(
|
||||
// @ts-expect-error - actions are not typed
|
||||
setQualityProfileValue({
|
||||
name: 'items',
|
||||
value: newItems,
|
||||
})
|
||||
);
|
||||
updateValue('items', newItems);
|
||||
},
|
||||
[items, dispatch]
|
||||
[items, updateValue]
|
||||
);
|
||||
|
||||
const handleCreateGroupPress = useCallback(
|
||||
|
|
@ -288,10 +265,9 @@ function EditQualityProfileModalContent({
|
|||
return item;
|
||||
});
|
||||
|
||||
// @ts-expect-error - actions are not typed
|
||||
dispatch(setQualityProfileValue({ name: 'items', value: newItems }));
|
||||
updateValue('items', newItems);
|
||||
},
|
||||
[items, dispatch]
|
||||
[items, updateValue]
|
||||
);
|
||||
|
||||
const handleDeleteGroupPress = useCallback(
|
||||
|
|
@ -308,10 +284,9 @@ function EditQualityProfileModalContent({
|
|||
[]
|
||||
);
|
||||
|
||||
// @ts-expect-error - actions are not typed
|
||||
dispatch(setQualityProfileValue({ name: 'items', value: newItems }));
|
||||
updateValue('items', newItems);
|
||||
},
|
||||
[items, dispatch]
|
||||
[items, updateValue]
|
||||
);
|
||||
|
||||
const handleDragMove = useCallback((options: DragMoveState) => {
|
||||
|
|
@ -443,13 +418,7 @@ function EditQualityProfileModalContent({
|
|||
dropGroup.items.splice(dropItemIndex, 0, item);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
// @ts-expect-error - actions are not typed
|
||||
setQualityProfileValue({
|
||||
name: 'items',
|
||||
value: newItems,
|
||||
})
|
||||
);
|
||||
updateValue('items', newItems);
|
||||
}
|
||||
|
||||
setDndState({
|
||||
|
|
@ -458,7 +427,7 @@ function EditQualityProfileModalContent({
|
|||
dropPosition: null,
|
||||
});
|
||||
},
|
||||
[dragQualityIndex, dropQualityIndex, items, dispatch]
|
||||
[dragQualityIndex, dropQualityIndex, items, updateValue]
|
||||
);
|
||||
|
||||
const handleChangeMode = useCallback((newMode: EditQualityProfileMode) => {
|
||||
|
|
@ -478,15 +447,9 @@ function EditQualityProfileModalContent({
|
|||
return formatItem;
|
||||
});
|
||||
|
||||
dispatch(
|
||||
// @ts-expect-error - actions are not typed
|
||||
setQualityProfileValue({
|
||||
name: 'formatItems',
|
||||
value: newFormatItems,
|
||||
})
|
||||
);
|
||||
updateValue('formatItems', newFormatItems);
|
||||
},
|
||||
[formatItems, dispatch]
|
||||
[formatItems, updateValue]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -523,12 +486,6 @@ function EditQualityProfileModalContent({
|
|||
}
|
||||
}, [bodyHeight, mode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id && !isPopulated) {
|
||||
dispatch(fetchQualityProfileSchema());
|
||||
}
|
||||
}, [id, isPopulated, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasSaving && !isSaving && !saveError) {
|
||||
onModalClose();
|
||||
|
|
@ -554,11 +511,10 @@ function EditQualityProfileModalContent({
|
|||
cutoffId =
|
||||
'id' in firstAllowed ? firstAllowed.id : firstAllowed.quality.id;
|
||||
|
||||
// @ts-expect-error - actions are not typed
|
||||
dispatch(setQualityProfileValue({ name: 'cutoff', value: cutoffId }));
|
||||
updateValue('cutoff', cutoffId);
|
||||
}
|
||||
}
|
||||
}, [cutoff, items, dispatch]);
|
||||
}, [cutoff, items, updateValue]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
|
|
@ -568,15 +524,15 @@ function EditQualityProfileModalContent({
|
|||
|
||||
<ModalBody>
|
||||
<div ref={measureBodyRef}>
|
||||
{isPopulated ? null : <LoadingIndicator />}
|
||||
{isSchemaFetched ? null : <LoadingIndicator />}
|
||||
|
||||
{!isFetching && error ? (
|
||||
{!isSchemaFetching && schemaError ? (
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddQualityProfileError')}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{isPopulated && !error ? (
|
||||
{isSchemaFetched && !schemaError ? (
|
||||
<Form>
|
||||
<div className={styles.formGroupsContainer}>
|
||||
<div className={styles.formGroupWrapper}>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import { deleteQualityProfile } from 'Store/Actions/settingsActions';
|
||||
import { QualityProfileItems } from 'typings/QualityProfile';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditQualityProfileModal from './EditQualityProfileModal';
|
||||
import {
|
||||
QualityProfileItems,
|
||||
useDeleteQualityProfile,
|
||||
} from './useQualityProfiles';
|
||||
import styles from './QualityProfile.css';
|
||||
|
||||
interface QualityProfileProps {
|
||||
|
|
@ -18,7 +19,6 @@ interface QualityProfileProps {
|
|||
upgradeAllowed: boolean;
|
||||
cutoff: number;
|
||||
items: QualityProfileItems;
|
||||
|
||||
isDeleting: boolean;
|
||||
onCloneQualityProfilePress: (id: number) => void;
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ function QualityProfile({
|
|||
isDeleting,
|
||||
onCloneQualityProfilePress,
|
||||
}: QualityProfileProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { deleteQualityProfile } = useDeleteQualityProfile(id);
|
||||
|
||||
const [isEditQualityProfileModalOpen, setIsEditQualityProfileModalOpen] =
|
||||
useState(false);
|
||||
|
|
@ -57,8 +57,8 @@ function QualityProfile({
|
|||
}, []);
|
||||
|
||||
const handleConfirmDeleteQualityProfile = useCallback(() => {
|
||||
dispatch(deleteQualityProfile({ id }));
|
||||
}, [id, dispatch]);
|
||||
deleteQualityProfile();
|
||||
}, [deleteQualityProfile]);
|
||||
|
||||
const handleCloneQualityProfilePress = useCallback(() => {
|
||||
onCloneQualityProfilePress(id);
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import { DragSourceMonitor, useDrag, useDrop, XYCoord } from 'react-dnd';
|
|||
import DragType from 'Helpers/DragType';
|
||||
import useMeasure from 'Helpers/Hooks/useMeasure';
|
||||
import { qualityProfileItemHeight } from 'Styles/Variables/dimensions';
|
||||
import { QualityProfileQualityItem } from 'typings/QualityProfile';
|
||||
import QualityProfileItem from './QualityProfileItem';
|
||||
import QualityProfileItemGroup from './QualityProfileItemGroup';
|
||||
import { SizeChanged } from './QualityProfileItemSize';
|
||||
import { QualityProfileQualityItem } from './useQualityProfiles';
|
||||
import styles from './QualityProfileItemDragSource.css';
|
||||
|
||||
export interface DragMoveState {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import Label from 'Components/Label';
|
|||
import IconButton from 'Components/Link/IconButton';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { InputChanged } from 'typings/inputs';
|
||||
import { QualityProfileQualityItem } from 'typings/QualityProfile';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityProfileItemDragSource, {
|
||||
DragMoveState,
|
||||
} from './QualityProfileItemDragSource';
|
||||
import { SizeChanged } from './QualityProfileItemSize';
|
||||
import { QualityProfileQualityItem } from './useQualityProfiles';
|
||||
import styles from './QualityProfileItemGroup.css';
|
||||
|
||||
interface QualityProfileItemGroupProps {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import Icon from 'Components/Icon';
|
|||
import Button from 'Components/Link/Button';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import { Failure } from 'typings/pending';
|
||||
import { QualityProfileItems as Items } from 'typings/QualityProfile';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityProfileItemDragSource, {
|
||||
QualityProfileItemDragSourceActionProps,
|
||||
} from './QualityProfileItemDragSource';
|
||||
import { QualityProfileItems as Items } from './useQualityProfiles';
|
||||
import styles from './QualityProfileItems.css';
|
||||
|
||||
export type EditQualityProfileMode = 'default' | 'editGroups' | 'editSizes';
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { useQualityProfile } from './useQualityProfiles';
|
||||
|
||||
interface QualityProfileNameProps {
|
||||
qualityProfileId: number;
|
||||
}
|
||||
|
||||
function QualityProfileName({ qualityProfileId }: QualityProfileNameProps) {
|
||||
const qualityProfile = useSelector(
|
||||
createQualityProfileSelectorForHook(qualityProfileId)
|
||||
);
|
||||
const qualityProfile = useQualityProfile(qualityProfileId);
|
||||
|
||||
return <span>{qualityProfile?.name ?? translate('Unknown')}</span>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,40 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { QualityProfilesAppState } from 'App/State/SettingsAppState';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import Card from 'Components/Card';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import {
|
||||
cloneQualityProfile,
|
||||
fetchQualityProfiles,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import QualityProfileModel from 'typings/QualityProfile';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditQualityProfileModal from './EditQualityProfileModal';
|
||||
import QualityProfile from './QualityProfile';
|
||||
import { useQualityProfiles } from './useQualityProfiles';
|
||||
import styles from './QualityProfiles.css';
|
||||
|
||||
function QualityProfiles() {
|
||||
const dispatch = useDispatch();
|
||||
const { data, error, isFetching, isFetched } = useQualityProfiles();
|
||||
|
||||
const { error, isFetching, isPopulated, isDeleting, items } = useSelector(
|
||||
createSortedSectionSelector<QualityProfileModel, QualityProfilesAppState>(
|
||||
'settings.qualityProfiles',
|
||||
sortByProp('name')
|
||||
)
|
||||
) as QualityProfilesAppState;
|
||||
// Sort the data by name
|
||||
const sortedItems = data ? data.sort(sortByProp('name')) : [];
|
||||
|
||||
const [isQualityProfileModalOpen, setIsQualityProfileModalOpen] =
|
||||
useState(false);
|
||||
const [cloneProfileId, setCloneProfileId] = useState<number | null>(null);
|
||||
|
||||
const handleEditQualityProfilePress = useCallback(() => {
|
||||
const handleAddQualityProfilePress = useCallback(() => {
|
||||
setCloneProfileId(null);
|
||||
setIsQualityProfileModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleEditQualityProfileClosePress = useCallback(() => {
|
||||
const handleAddQualityProfileClosePress = useCallback(() => {
|
||||
setCloneProfileId(null);
|
||||
setIsQualityProfileModalOpen(false);
|
||||
}, []);
|
||||
|
||||
const handleCloneQualityProfilePress = useCallback(
|
||||
(id: number) => {
|
||||
dispatch(cloneQualityProfile({ id }));
|
||||
setIsQualityProfileModalOpen(true);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchQualityProfiles());
|
||||
}, [dispatch]);
|
||||
const handleCloneQualityProfilePress = useCallback((id: number) => {
|
||||
setCloneProfileId(id);
|
||||
setIsQualityProfileModalOpen(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('QualityProfiles')}>
|
||||
|
|
@ -57,15 +42,15 @@ function QualityProfiles() {
|
|||
errorMessage={translate('QualityProfilesLoadError')}
|
||||
error={error}
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
isPopulated={isFetched}
|
||||
>
|
||||
<div className={styles.qualityProfiles}>
|
||||
{items.map((item) => {
|
||||
{sortedItems.map((item) => {
|
||||
return (
|
||||
<QualityProfile
|
||||
key={item.id}
|
||||
{...item}
|
||||
isDeleting={isDeleting}
|
||||
isDeleting={false}
|
||||
onCloneQualityProfilePress={handleCloneQualityProfilePress}
|
||||
/>
|
||||
);
|
||||
|
|
@ -73,7 +58,7 @@ function QualityProfiles() {
|
|||
|
||||
<Card
|
||||
className={styles.addQualityProfile}
|
||||
onPress={handleEditQualityProfilePress}
|
||||
onPress={handleAddQualityProfilePress}
|
||||
>
|
||||
<div className={styles.center}>
|
||||
<Icon name={icons.ADD} size={45} />
|
||||
|
|
@ -83,7 +68,8 @@ function QualityProfiles() {
|
|||
|
||||
<EditQualityProfileModal
|
||||
isOpen={isQualityProfileModalOpen}
|
||||
onModalClose={handleEditQualityProfileClosePress}
|
||||
cloneId={cloneProfileId ?? undefined}
|
||||
onModalClose={handleAddQualityProfileClosePress}
|
||||
/>
|
||||
</PageSectionContent>
|
||||
</FieldSet>
|
||||
|
|
|
|||
130
frontend/src/Settings/Profiles/Quality/useQualityProfiles.ts
Normal file
130
frontend/src/Settings/Profiles/Quality/useQualityProfiles.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import ModelBase from 'App/ModelBase';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import Quality from 'Quality/Quality';
|
||||
import {
|
||||
useDeleteProvider,
|
||||
useManageProviderSettings,
|
||||
useProviderSettings,
|
||||
} from 'Settings/useProviderSettings';
|
||||
import { QualityProfileFormatItem } from 'typings/CustomFormat';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
export interface QualityProfileQualityItem {
|
||||
quality: Quality;
|
||||
allowed: boolean;
|
||||
minSize: number | null;
|
||||
maxSize: number | null;
|
||||
preferredSize: number | null;
|
||||
}
|
||||
|
||||
export interface QualityProfileGroup {
|
||||
id: number;
|
||||
items: QualityProfileQualityItem[];
|
||||
allowed: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type QualityProfileItems = (
|
||||
| QualityProfileQualityItem
|
||||
| QualityProfileGroup
|
||||
)[];
|
||||
|
||||
export interface QualityProfileModel extends ModelBase {
|
||||
name: string;
|
||||
upgradeAllowed: boolean;
|
||||
cutoff: number;
|
||||
items: QualityProfileItems;
|
||||
minFormatScore: number;
|
||||
cutoffFormatScore: number;
|
||||
minUpgradeFormatScore: number;
|
||||
formatItems: QualityProfileFormatItem[];
|
||||
}
|
||||
|
||||
const PATH = '/qualityprofile';
|
||||
|
||||
export const useQualityProfile = (id: number | undefined) => {
|
||||
const { data } = useQualityProfiles();
|
||||
|
||||
if (id === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return data.find((profile) => profile.id === id);
|
||||
};
|
||||
|
||||
export const useQualityProfilesData = () => {
|
||||
const { data } = useQualityProfiles();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useQualityProfiles = () => {
|
||||
return useProviderSettings<QualityProfileModel>({
|
||||
path: PATH,
|
||||
queryOptions: {
|
||||
gcTime: Infinity,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useManageQualityProfile = (
|
||||
id: number | undefined,
|
||||
cloneId: number | undefined
|
||||
) => {
|
||||
const { schema, isSchemaFetching, isSchemaFetched, schemaError } =
|
||||
useQualityProfileSchema(cloneId == null);
|
||||
|
||||
const profile = useQualityProfile(cloneId);
|
||||
|
||||
if (cloneId && !profile) {
|
||||
throw new Error(`Quality Profile with ID ${cloneId} not found`);
|
||||
}
|
||||
|
||||
const manage = useManageProviderSettings<QualityProfileModel>(
|
||||
id,
|
||||
cloneId && profile
|
||||
? {
|
||||
...profile,
|
||||
id: 0,
|
||||
name: translate('DefaultNameCopiedProfile', {
|
||||
name: profile.name,
|
||||
}),
|
||||
}
|
||||
: schema,
|
||||
PATH
|
||||
);
|
||||
|
||||
return {
|
||||
...manage,
|
||||
isSchemaFetching: cloneId ? false : isSchemaFetching,
|
||||
isSchemaFetched: cloneId ? true : isSchemaFetched,
|
||||
schemaError: cloneId ? undefined : schemaError,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteQualityProfile = (id: number) => {
|
||||
const result = useDeleteProvider<QualityProfileModel>(id, PATH);
|
||||
|
||||
return {
|
||||
...result,
|
||||
deleteQualityProfile: result.deleteProvider,
|
||||
};
|
||||
};
|
||||
|
||||
export const useQualityProfileSchema = (enabled: boolean) => {
|
||||
const { isFetching, isFetched, error, data } =
|
||||
useApiQuery<QualityProfileModel>({
|
||||
path: `${PATH}/schema`,
|
||||
queryOptions: {
|
||||
enabled,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
isSchemaFetching: isFetching,
|
||||
isSchemaFetched: isFetched,
|
||||
schemaError: error,
|
||||
schema: data ?? ({} as QualityProfileModel),
|
||||
};
|
||||
};
|
||||
|
|
@ -37,7 +37,7 @@ export const useReleaseProfilesWithIds = (ids: number[]) => {
|
|||
};
|
||||
|
||||
export const useReleaseProfiles = () => {
|
||||
return useProviderSettings<ReleaseProfileModel>(PATH);
|
||||
return useProviderSettings<ReleaseProfileModel>({ path: PATH });
|
||||
};
|
||||
|
||||
export const useManageReleaseProfile = (id: number) => {
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@ import { useQueryClient } from '@tanstack/react-query';
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import useApiQuery, { QueryOptions } from 'Helpers/Hooks/useApiQuery';
|
||||
import { usePendingChangesStore } from 'Helpers/Hooks/usePendingChangesStore';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
|
||||
export const useProvider = <T extends ModelBase>(
|
||||
id: number,
|
||||
id: number | undefined,
|
||||
defaultProvider: T,
|
||||
path: string
|
||||
) => {
|
||||
const { data } = useProviderSettings<T>(path);
|
||||
const { data } = useProviderSettings<T>({ path });
|
||||
|
||||
return useMemo(() => {
|
||||
if (id === 0) {
|
||||
if (!id) {
|
||||
return defaultProvider;
|
||||
}
|
||||
|
||||
|
|
@ -28,10 +28,10 @@ export const useProvider = <T extends ModelBase>(
|
|||
}, [data, defaultProvider, id]);
|
||||
};
|
||||
|
||||
export const useProviderSettings = <T extends ModelBase>(path: string) => {
|
||||
const result = useApiQuery<T[]>({
|
||||
path,
|
||||
});
|
||||
export const useProviderSettings = <T extends ModelBase>(
|
||||
options: QueryOptions<T[]>
|
||||
) => {
|
||||
const result = useApiQuery<T[]>(options);
|
||||
|
||||
return {
|
||||
...result,
|
||||
|
|
@ -47,18 +47,18 @@ export const useSaveProviderSettings = <T extends ModelBase>(
|
|||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending, error } = useApiMutation<T, T>({
|
||||
path: id === 0 ? path : `${path}/${id}`,
|
||||
method: id === 0 ? 'POST' : 'PUT',
|
||||
path: id ? `${path}/${id}` : path,
|
||||
method: id ? 'PUT' : 'POST',
|
||||
mutationOptions: {
|
||||
onSuccess: (updatedSettings: T) => {
|
||||
queryClient.setQueryData<T[]>([path], (oldData = []) => {
|
||||
if (id === 0) {
|
||||
return [...oldData, updatedSettings];
|
||||
if (id) {
|
||||
return oldData.map((item) =>
|
||||
item.id === updatedSettings.id ? updatedSettings : item
|
||||
);
|
||||
}
|
||||
|
||||
return oldData.map((item) =>
|
||||
item.id === updatedSettings.id ? updatedSettings : item
|
||||
);
|
||||
return [...oldData, updatedSettings];
|
||||
});
|
||||
onSuccess?.();
|
||||
},
|
||||
|
|
@ -73,7 +73,7 @@ export const useSaveProviderSettings = <T extends ModelBase>(
|
|||
};
|
||||
|
||||
export const useManageProviderSettings = <T extends ModelBase>(
|
||||
id: number,
|
||||
id: number | undefined,
|
||||
defaultProvider: T,
|
||||
path: string
|
||||
) => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import naming from './Settings/naming';
|
|||
import namingExamples from './Settings/namingExamples';
|
||||
import notifications from './Settings/notifications';
|
||||
import qualityDefinitions from './Settings/qualityDefinitions';
|
||||
import qualityProfiles from './Settings/qualityProfiles';
|
||||
|
||||
export * from './Settings/autoTaggingSpecifications';
|
||||
export * from './Settings/autoTaggings';
|
||||
|
|
@ -44,7 +43,6 @@ export * from './Settings/naming';
|
|||
export * from './Settings/namingExamples';
|
||||
export * from './Settings/notifications';
|
||||
export * from './Settings/qualityDefinitions';
|
||||
export * from './Settings/qualityProfiles';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
|
@ -76,8 +74,7 @@ export const defaultState = {
|
|||
naming: naming.defaultState,
|
||||
namingExamples: namingExamples.defaultState,
|
||||
notifications: notifications.defaultState,
|
||||
qualityDefinitions: qualityDefinitions.defaultState,
|
||||
qualityProfiles: qualityProfiles.defaultState
|
||||
qualityDefinitions: qualityDefinitions.defaultState
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
|
@ -108,8 +105,7 @@ export const actionHandlers = handleThunks({
|
|||
...naming.actionHandlers,
|
||||
...namingExamples.actionHandlers,
|
||||
...notifications.actionHandlers,
|
||||
...qualityDefinitions.actionHandlers,
|
||||
...qualityProfiles.actionHandlers
|
||||
...qualityDefinitions.actionHandlers
|
||||
});
|
||||
|
||||
//
|
||||
|
|
@ -136,7 +132,6 @@ export const reducers = createHandleActions({
|
|||
...naming.reducers,
|
||||
...namingExamples.reducers,
|
||||
...notifications.reducers,
|
||||
...qualityDefinitions.reducers,
|
||||
...qualityProfiles.reducers
|
||||
...qualityDefinitions.reducers
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
|
||||
export function createQualityProfileSelectorForHook(qualityProfileId: number) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.qualityProfiles.items,
|
||||
(qualityProfiles) => {
|
||||
return qualityProfiles.find((profile) => profile.id === qualityProfileId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createQualityProfileSelector() {
|
||||
return createSelector(
|
||||
(_: AppState, { qualityProfileId }: { qualityProfileId: number }) =>
|
||||
qualityProfileId,
|
||||
(state: AppState) => state.settings.qualityProfiles.items,
|
||||
(qualityProfileId, qualityProfiles) => {
|
||||
return qualityProfiles.find((profile) => profile.id === qualityProfileId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createQualityProfileSelector;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Series from 'Series/Series';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
|
||||
function createSeriesQualityProfileSelector(series?: Series) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.qualityProfiles.items,
|
||||
(qualityProfiles: QualityProfile[]) => {
|
||||
if (!series) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return qualityProfiles.find(
|
||||
(profile) => profile.id === series.qualityProfileId
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createSeriesQualityProfileSelector;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import Quality from 'Quality/Quality';
|
||||
import { QualityProfileItems } from 'typings/QualityProfile';
|
||||
import { QualityProfileItems } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
|
||||
export default function getQualities(qualities?: QualityProfileItems) {
|
||||
if (!qualities) {
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
import Quality from 'Quality/Quality';
|
||||
import { QualityProfileFormatItem } from './CustomFormat';
|
||||
|
||||
export interface QualityProfileQualityItem {
|
||||
quality: Quality;
|
||||
allowed: boolean;
|
||||
minSize: number | null;
|
||||
maxSize: number | null;
|
||||
preferredSize: number | null;
|
||||
}
|
||||
|
||||
export interface QualityProfileGroup {
|
||||
id: number;
|
||||
items: QualityProfileQualityItem[];
|
||||
allowed: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type QualityProfileItems = (
|
||||
| QualityProfileQualityItem
|
||||
| QualityProfileGroup
|
||||
)[];
|
||||
|
||||
interface QualityProfile {
|
||||
name: string;
|
||||
upgradeAllowed: boolean;
|
||||
cutoff: number;
|
||||
items: QualityProfileItems;
|
||||
minFormatScore: number;
|
||||
cutoffFormatScore: number;
|
||||
minUpgradeFormatScore: number;
|
||||
formatItems: QualityProfileFormatItem[];
|
||||
id: number;
|
||||
}
|
||||
|
||||
export default QualityProfile;
|
||||
Loading…
Reference in a new issue